Initial QSfera import
This commit is contained in:
+215
@@ -0,0 +1,215 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
cs3Group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
|
||||
cs3User "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
cs3user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
"github.com/jellydator/ttlcache/v3"
|
||||
libregraph "github.com/opencloud-eu/libre-graph-api-go"
|
||||
"github.com/qsfera/server/services/graph/pkg/errorcode"
|
||||
"github.com/qsfera/server/services/graph/pkg/identity"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
revautils "github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
)
|
||||
|
||||
// IdentityCache implements a simple ttl based cache for looking up users and groups by ID
|
||||
type IdentityCache struct {
|
||||
users *ttlcache.Cache[string, *cs3User.User]
|
||||
groups *ttlcache.Cache[string, libregraph.Group]
|
||||
gatewaySelector pool.Selectable[gateway.GatewayAPIClient]
|
||||
}
|
||||
|
||||
type identityCacheOptions struct {
|
||||
gatewaySelector pool.Selectable[gateway.GatewayAPIClient]
|
||||
usersTTL time.Duration
|
||||
groupsTTL time.Duration
|
||||
}
|
||||
|
||||
// IdentityCacheOption defines a single option function.
|
||||
type IdentityCacheOption func(o *identityCacheOptions)
|
||||
|
||||
// IdentityCacheWithGatewaySelector set the gatewaySelector for the Identity Cache
|
||||
func IdentityCacheWithGatewaySelector(gatewaySelector pool.Selectable[gateway.GatewayAPIClient]) IdentityCacheOption {
|
||||
return func(o *identityCacheOptions) {
|
||||
o.gatewaySelector = gatewaySelector
|
||||
}
|
||||
}
|
||||
|
||||
// IdentityCacheWithUsersTTL sets the TTL for the users cache
|
||||
func IdentityCacheWithUsersTTL(ttl time.Duration) IdentityCacheOption {
|
||||
return func(o *identityCacheOptions) {
|
||||
o.usersTTL = ttl
|
||||
}
|
||||
}
|
||||
|
||||
// IdentityCacheWithGroupsTTL sets the TTL for the groups cache
|
||||
func IdentityCacheWithGroupsTTL(ttl time.Duration) IdentityCacheOption {
|
||||
return func(o *identityCacheOptions) {
|
||||
o.groupsTTL = ttl
|
||||
}
|
||||
}
|
||||
|
||||
func newOptions(opts ...IdentityCacheOption) identityCacheOptions {
|
||||
opt := identityCacheOptions{}
|
||||
for _, o := range opts {
|
||||
o(&opt)
|
||||
}
|
||||
return opt
|
||||
}
|
||||
|
||||
// NewIdentityCache instantiates a new IdentityCache and sets the supplied options
|
||||
func NewIdentityCache(opts ...IdentityCacheOption) IdentityCache {
|
||||
opt := newOptions(opts...)
|
||||
|
||||
var cache IdentityCache
|
||||
|
||||
cache.users = ttlcache.New(
|
||||
ttlcache.WithTTL[string, *cs3user.User](opt.usersTTL),
|
||||
ttlcache.WithDisableTouchOnHit[string, *cs3user.User](),
|
||||
)
|
||||
go cache.users.Start()
|
||||
|
||||
cache.groups = ttlcache.New(
|
||||
ttlcache.WithTTL[string, libregraph.Group](opt.groupsTTL),
|
||||
ttlcache.WithDisableTouchOnHit[string, libregraph.Group](),
|
||||
)
|
||||
go cache.groups.Start()
|
||||
|
||||
cache.gatewaySelector = opt.gatewaySelector
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
// GetUser looks up a user by id, if the user is not cached, yet it will do a lookup via the CS3 API
|
||||
func (cache IdentityCache) GetUser(ctx context.Context, tenantId, userid string) (libregraph.User, error) {
|
||||
// can we get the tenant from the context or do we have to pass it?
|
||||
u, err := cache.GetCS3User(ctx, tenantId, userid)
|
||||
if err != nil {
|
||||
return libregraph.User{}, err
|
||||
}
|
||||
if tenantId != u.GetId().GetTenantId() {
|
||||
return libregraph.User{}, identity.ErrNotFound
|
||||
}
|
||||
return *identity.CreateUserModelFromCS3(u), nil
|
||||
}
|
||||
|
||||
func (cache IdentityCache) GetCS3User(ctx context.Context, tenantId, userid string) (*cs3User.User, error) {
|
||||
var user *cs3User.User
|
||||
if item := cache.users.Get(tenantId + "|" + userid); item == nil {
|
||||
gatewayClient, err := cache.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
return nil, errorcode.New(errorcode.GeneralException, err.Error())
|
||||
}
|
||||
cs3UserID := &cs3User.UserId{
|
||||
OpaqueId: userid,
|
||||
TenantId: tenantId,
|
||||
}
|
||||
user, err = revautils.GetUserNoGroups(ctx, cs3UserID, gatewayClient)
|
||||
if err != nil {
|
||||
if revautils.IsErrNotFound(err) {
|
||||
return nil, identity.ErrNotFound
|
||||
}
|
||||
return nil, errorcode.New(errorcode.GeneralException, err.Error())
|
||||
}
|
||||
|
||||
cache.users.Set(tenantId+"|"+userid, user, ttlcache.DefaultTTL)
|
||||
} else {
|
||||
user = item.Value()
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// GetAcceptedUser looks up a user by id, if the user is not cached, yet it will do a lookup via the CS3 API
|
||||
func (cache IdentityCache) GetAcceptedUser(ctx context.Context, userid string) (libregraph.User, error) {
|
||||
u, err := cache.GetAcceptedCS3User(ctx, userid)
|
||||
if err != nil {
|
||||
return libregraph.User{}, err
|
||||
}
|
||||
return *identity.CreateUserModelFromCS3(u), nil
|
||||
}
|
||||
|
||||
func getIDAndMeshProvider(user string) (id, provider string, err error) {
|
||||
last := strings.LastIndex(user, "@")
|
||||
if last == -1 {
|
||||
return "", "", errors.New("not in the form <id>@<provider>")
|
||||
}
|
||||
if len(user[:last]) == 0 {
|
||||
return "", "", errors.New("empty id")
|
||||
}
|
||||
if len(user[last+1:]) == 0 {
|
||||
return "", "", errors.New("empty provider")
|
||||
}
|
||||
return user[:last], user[last+1:], nil
|
||||
}
|
||||
|
||||
func (cache IdentityCache) GetAcceptedCS3User(ctx context.Context, userid string) (*cs3User.User, error) {
|
||||
var user *cs3user.User
|
||||
if item := cache.users.Get(userid); item == nil {
|
||||
gatewayClient, err := cache.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
return nil, errorcode.New(errorcode.GeneralException, err.Error())
|
||||
}
|
||||
id, provider, err := getIDAndMeshProvider(userid)
|
||||
if err != nil {
|
||||
return nil, errorcode.New(errorcode.InvalidRequest, err.Error())
|
||||
}
|
||||
cs3UserID := &cs3User.UserId{
|
||||
Idp: provider,
|
||||
OpaqueId: id,
|
||||
Type: cs3User.UserType_USER_TYPE_FEDERATED,
|
||||
}
|
||||
user, err = revautils.GetAcceptedUserWithContext(ctx, cs3UserID, gatewayClient)
|
||||
if err != nil {
|
||||
if revautils.IsErrNotFound(err) {
|
||||
return nil, identity.ErrNotFound
|
||||
}
|
||||
return nil, errorcode.New(errorcode.GeneralException, err.Error())
|
||||
}
|
||||
cache.users.Set(userid, user, ttlcache.DefaultTTL)
|
||||
} else {
|
||||
user = item.Value()
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// GetGroup looks up a group by id, if the group is not cached, yet it will do a lookup via the CS3 API
|
||||
func (cache IdentityCache) GetGroup(ctx context.Context, groupID string) (libregraph.Group, error) {
|
||||
var group libregraph.Group
|
||||
if item := cache.groups.Get(groupID); item == nil {
|
||||
gatewayClient, err := cache.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
return group, errorcode.New(errorcode.GeneralException, err.Error())
|
||||
}
|
||||
cs3GroupID := &cs3Group.GroupId{
|
||||
OpaqueId: groupID,
|
||||
}
|
||||
req := cs3Group.GetGroupRequest{
|
||||
GroupId: cs3GroupID,
|
||||
SkipFetchingMembers: true,
|
||||
}
|
||||
res, err := gatewayClient.GetGroup(ctx, &req)
|
||||
if err != nil {
|
||||
return group, errorcode.New(errorcode.GeneralException, err.Error())
|
||||
}
|
||||
switch res.Status.Code {
|
||||
case rpc.Code_CODE_OK:
|
||||
g := res.GetGroup()
|
||||
group = *identity.CreateGroupModelFromCS3(g)
|
||||
cache.groups.Set(groupID, group, ttlcache.DefaultTTL)
|
||||
case rpc.Code_CODE_NOT_FOUND:
|
||||
return group, identity.ErrNotFound
|
||||
default:
|
||||
return group, errorcode.New(errorcode.GeneralException, res.Status.Message)
|
||||
}
|
||||
} else {
|
||||
group = item.Value()
|
||||
}
|
||||
return group, nil
|
||||
}
|
||||
Reference in New Issue
Block a user