fix(graph): Set the full CS3 user id in the Create Share request

Up to now we only set the OpaqueId attribute, which breaks sharing as soon as
multi-tenancy is enabled. We need the full UserId (including the
tenantId and the idp value).

Related Issue: #1194
This commit is contained in:
Ralf Haferkamp
2025-09-04 17:13:50 +02:00
committed by Ralf Haferkamp
parent f36cf7832b
commit a5e0c1ec4b
3 changed files with 51 additions and 40 deletions

View File

@@ -121,7 +121,7 @@ func CreateUserModelFromCS3(u *cs3user.User) *libregraph.User {
if u.GetId() == nil {
u.Id = &cs3user.UserId{}
}
userType := cs3UserTypeToGraph(u.GetId().GetType())
userType := CS3UserTypeToGraph(u.GetId().GetType())
user := &libregraph.User{
Identities: []libregraph.ObjectIdentity{{
Issuer: &u.GetId().Idp,
@@ -136,7 +136,7 @@ func CreateUserModelFromCS3(u *cs3user.User) *libregraph.User {
return user
}
func cs3UserTypeToGraph(cs3type cs3user.UserType) string {
func CS3UserTypeToGraph(cs3type cs3user.UserType) string {
switch cs3type {
case cs3user.UserType_USER_TYPE_PRIMARY:
return UserTypeMember

View File

@@ -7,6 +7,7 @@ import (
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"
@@ -17,7 +18,7 @@ import (
// IdentityCache implements a simple ttl based cache for looking up users and groups by ID
type IdentityCache struct {
users *ttlcache.Cache[string, libregraph.User]
users *ttlcache.Cache[string, *cs3User.User]
groups *ttlcache.Cache[string, libregraph.Group]
gatewaySelector pool.Selectable[gateway.GatewayAPIClient]
}
@@ -67,8 +68,8 @@ func NewIdentityCache(opts ...IdentityCacheOption) IdentityCache {
var cache IdentityCache
cache.users = ttlcache.New(
ttlcache.WithTTL[string, libregraph.User](opt.usersTTL),
ttlcache.WithDisableTouchOnHit[string, libregraph.User](),
ttlcache.WithTTL[string, *cs3user.User](opt.usersTTL),
ttlcache.WithDisableTouchOnHit[string, *cs3user.User](),
)
go cache.users.Start()
@@ -85,23 +86,30 @@ func NewIdentityCache(opts ...IdentityCacheOption) IdentityCache {
// 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, userid string) (libregraph.User, error) {
var user libregraph.User
u, err := cache.GetCS3User(ctx, userid)
if err != nil {
return libregraph.User{}, err
}
return *CreateUserModelFromCS3(u), nil
}
func (cache IdentityCache) GetCS3User(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 libregraph.User{}, errorcode.New(errorcode.GeneralException, err.Error())
return nil, errorcode.New(errorcode.GeneralException, err.Error())
}
cs3UserID := &cs3User.UserId{
OpaqueId: userid,
}
u, err := revautils.GetUserNoGroups(ctx, cs3UserID, gatewayClient)
user, err = revautils.GetUserNoGroups(ctx, cs3UserID, gatewayClient)
if err != nil {
if revautils.IsErrNotFound(err) {
return libregraph.User{}, ErrNotFound
return nil, ErrNotFound
}
return libregraph.User{}, errorcode.New(errorcode.GeneralException, err.Error())
return nil, errorcode.New(errorcode.GeneralException, err.Error())
}
user = *CreateUserModelFromCS3(u)
cache.users.Set(userid, user, ttlcache.DefaultTTL)
} else {
@@ -112,25 +120,31 @@ func (cache IdentityCache) GetUser(ctx context.Context, userid string) (libregra
// 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) {
var user libregraph.User
u, err := cache.GetAcceptedCS3User(ctx, userid)
if err != nil {
return libregraph.User{}, err
}
return *CreateUserModelFromCS3(u), 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 libregraph.User{}, errorcode.New(errorcode.GeneralException, err.Error())
return nil, errorcode.New(errorcode.GeneralException, err.Error())
}
cs3UserID := &cs3User.UserId{
OpaqueId: userid,
}
u, err := revautils.GetAcceptedUserWithContext(ctx, cs3UserID, gatewayClient)
user, err = revautils.GetAcceptedUserWithContext(ctx, cs3UserID, gatewayClient)
if err != nil {
if revautils.IsErrNotFound(err) {
return libregraph.User{}, ErrNotFound
return nil, ErrNotFound
}
return libregraph.User{}, errorcode.New(errorcode.GeneralException, err.Error())
return nil, errorcode.New(errorcode.GeneralException, err.Error())
}
user = *CreateUserModelFromCS3(u)
cache.users.Set(userid, user, ttlcache.DefaultTTL)
} else {
user = item.Value()
}

View File

@@ -184,9 +184,9 @@ func (s DriveItemPermissionsService) Invite(ctx context.Context, resourceId *sto
cTime = createShareResponse.GetShare().GetCtime()
expiration = createShareResponse.GetShare().GetExpiration()
default:
user, err := s.identityCache.GetUser(ctx, objectID)
user, err := s.identityCache.GetCS3User(ctx, objectID)
if errors.Is(err, identity.ErrNotFound) && s.config.IncludeOCMSharees {
user, err = s.identityCache.GetAcceptedUser(ctx, objectID)
user, err = s.identityCache.GetAcceptedCS3User(ctx, objectID)
if err == nil && IsSpaceRoot(statResponse.GetInfo().GetId()) {
return libregraph.Permission{}, errorcode.New(errorcode.InvalidRequest, "federated user can not become a space member")
}
@@ -198,19 +198,16 @@ func (s DriveItemPermissionsService) Invite(ctx context.Context, resourceId *sto
permission.GrantedToV2 = &libregraph.SharePointIdentitySet{
User: &libregraph.Identity{
DisplayName: user.GetDisplayName(),
Id: conversions.ToPointer(user.GetId()),
LibreGraphUserType: conversions.ToPointer(user.GetUserType()),
Id: conversions.ToPointer(user.GetId().GetOpaqueId()),
LibreGraphUserType: conversions.ToPointer(identity.CS3UserTypeToGraph(user.GetId().GetType())),
},
}
if user.GetUserType() == identity.UserTypeFederated {
if len(user.Identities) < 1 {
return libregraph.Permission{}, errorcode.New(errorcode.InvalidRequest, "user has no federated identity")
}
if user.GetId().GetType() == userpb.UserType_USER_TYPE_FEDERATED {
providerInfoResp, err := gatewayClient.GetInfoByDomain(ctx, &ocmprovider.GetInfoByDomainRequest{
Domain: *user.Identities[0].Issuer,
Domain: user.GetId().GetIdp(),
})
if err := errorcode.FromCS3Status(providerInfoResp.GetStatus(), err); err != nil {
if err = errorcode.FromCS3Status(providerInfoResp.GetStatus(), err); err != nil {
s.logger.Error().Err(err).Msg("getting provider info failed")
return libregraph.Permission{}, err
}
@@ -220,7 +217,7 @@ func (s DriveItemPermissionsService) Invite(ctx context.Context, resourceId *sto
createShareRequest.Expiration = utils.TimeToTS(*invite.ExpirationDateTime)
}
createShareResponse, err := gatewayClient.CreateOCMShare(ctx, createShareRequest)
if err := errorcode.FromCS3Status(createShareResponse.GetStatus(), err); err != nil {
if err = errorcode.FromCS3Status(createShareResponse.GetStatus(), err); err != nil {
s.logger.Error().Err(err).Msg("share creation failed")
return libregraph.Permission{}, err
}
@@ -233,7 +230,7 @@ func (s DriveItemPermissionsService) Invite(ctx context.Context, resourceId *sto
createShareRequest.GetGrant().Expiration = utils.TimeToTS(*invite.ExpirationDateTime)
}
createShareResponse, err := gatewayClient.CreateShare(ctx, createShareRequest)
if err := errorcode.FromCS3Status(createShareResponse.GetStatus(), err); err != nil {
if err = errorcode.FromCS3Status(createShareResponse.GetStatus(), err); err != nil {
s.logger.Error().Err(err).Msg("share creation failed")
return libregraph.Permission{}, err
}
@@ -293,15 +290,16 @@ func createShareRequestToGroup(group libregraph.Group, info *storageprovider.Res
},
}
}
func createShareRequestToUser(user libregraph.User, info *storageprovider.ResourceInfo, cs3ResourcePermissions *storageprovider.ResourcePermissions) *collaboration.CreateShareRequest {
func createShareRequestToUser(user *userpb.User, info *storageprovider.ResourceInfo, cs3ResourcePermissions *storageprovider.ResourcePermissions) *collaboration.CreateShareRequest {
return &collaboration.CreateShareRequest{
ResourceInfo: info,
Grant: &collaboration.ShareGrant{
Grantee: &storageprovider.Grantee{
Type: storageprovider.GranteeType_GRANTEE_TYPE_USER,
Id: &storageprovider.Grantee_UserId{UserId: &userpb.UserId{
OpaqueId: user.GetId(),
}},
Id: &storageprovider.Grantee_UserId{
UserId: user.GetId(),
},
},
Permissions: &collaboration.SharePermissions{
Permissions: cs3ResourcePermissions,
@@ -309,16 +307,15 @@ func createShareRequestToUser(user libregraph.User, info *storageprovider.Resour
},
}
}
func createShareRequestToFederatedUser(user libregraph.User, resourceId *storageprovider.ResourceId, providerInfo *ocmprovider.ProviderInfo, cs3ResourcePermissions *storageprovider.ResourcePermissions) *ocm.CreateOCMShareRequest {
func createShareRequestToFederatedUser(user *userpb.User, resourceId *storageprovider.ResourceId, providerInfo *ocmprovider.ProviderInfo, cs3ResourcePermissions *storageprovider.ResourcePermissions) *ocm.CreateOCMShareRequest {
return &ocm.CreateOCMShareRequest{
ResourceId: resourceId,
Grantee: &storageprovider.Grantee{
Type: storageprovider.GranteeType_GRANTEE_TYPE_USER,
Id: &storageprovider.Grantee_UserId{UserId: &userpb.UserId{
Type: userpb.UserType_USER_TYPE_FEDERATED,
OpaqueId: user.GetId(),
Idp: *user.GetIdentities()[0].Issuer, // the domain is persisted in the grant as u:{opaqueid}:{domain}
}},
Id: &storageprovider.Grantee_UserId{
UserId: user.GetId(),
},
},
RecipientMeshProvider: providerInfo,
AccessMethods: []*ocm.AccessMethod{