From 6d465575f4883b913935c38b5983f10d3cc4d3e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= <jfd@butonic.de> Date: Fri, 23 Aug 2024 11:34:40 +0200 Subject: [PATCH] List OCM permissions as graph drive item permissions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de> --- changelog/unreleased/list-ocm-permissions.md | 6 + .../service/v0/api_driveitem_permissions.go | 11 ++ services/graph/pkg/service/v0/base.go | 147 ++++++++++++++++++ services/graph/pkg/service/v0/utils.go | 19 +++ 4 files changed, 183 insertions(+) create mode 100644 changelog/unreleased/list-ocm-permissions.md diff --git a/changelog/unreleased/list-ocm-permissions.md b/changelog/unreleased/list-ocm-permissions.md new file mode 100644 index 00000000000..5f5afa7f1c5 --- /dev/null +++ b/changelog/unreleased/list-ocm-permissions.md @@ -0,0 +1,6 @@ +Bugfix: List OCM permissions as graph drive item permissions + +The libre graph API now returns OCM shares when listing driveItem permissions. + +https://github.com/owncloud/ocis/pull/9905 +https://github.com/owncloud/ocis/issues/9898 diff --git a/services/graph/pkg/service/v0/api_driveitem_permissions.go b/services/graph/pkg/service/v0/api_driveitem_permissions.go index 0c571cbec3f..292a098b516 100644 --- a/services/graph/pkg/service/v0/api_driveitem_permissions.go +++ b/services/graph/pkg/service/v0/api_driveitem_permissions.go @@ -382,6 +382,17 @@ func (s DriveItemPermissionsService) ListPermissions(ctx context.Context, itemID if err != nil { return collectionOfPermissions, err } + if s.config.IncludeOCMSharees { + driveItems, err = s.listOCMShares(ctx, []*ocm.ListOCMSharesRequest_Filter{ + { + Type: ocm.ListOCMSharesRequest_Filter_TYPE_RESOURCE_ID, + Term: &ocm.ListOCMSharesRequest_Filter_ResourceId{ResourceId: itemID}, + }, + }, driveItems) + if err != nil { + return collectionOfPermissions, err + } + } } // finally get public shares, which are possible for spaceroots and "normal" resources driveItems, err = s.listPublicShares(ctx, []*link.ListPublicSharesRequest_Filter{ diff --git a/services/graph/pkg/service/v0/base.go b/services/graph/pkg/service/v0/base.go index dff462afdb9..6c8a58ecd6f 100644 --- a/services/graph/pkg/service/v0/base.go +++ b/services/graph/pkg/service/v0/base.go @@ -275,6 +275,34 @@ func (g BaseGraphService) listUserShares(ctx context.Context, filters []*collabo return driveItems, nil } +func (g BaseGraphService) listOCMShares(ctx context.Context, filters []*ocm.ListOCMSharesRequest_Filter, driveItems driveItemsByResourceID) (driveItemsByResourceID, error) { + gatewayClient, err := g.gatewaySelector.Next() + if err != nil { + g.logger.Error().Err(err).Msg("could not select next gateway client") + return driveItems, errorcode.New(errorcode.GeneralException, err.Error()) + } + + concreteFilters := []*ocm.ListOCMSharesRequest_Filter{} + concreteFilters = append(concreteFilters, filters...) + + lsOCMSharesRequest := ocm.ListOCMSharesRequest{ + Filters: concreteFilters, + } + + lsOCMSharesResponse, err := gatewayClient.ListOCMShares(ctx, &lsOCMSharesRequest) + if err != nil { + return driveItems, errorcode.New(errorcode.GeneralException, err.Error()) + } + if statusCode := lsOCMSharesResponse.GetStatus().GetCode(); statusCode != rpc.Code_CODE_OK { + return driveItems, errorcode.New(cs3StatusToErrCode(statusCode), lsOCMSharesResponse.Status.Message) + } + driveItems, err = g.cs3OCMSharesToDriveItems(ctx, lsOCMSharesResponse.Shares, driveItems) + if err != nil { + return driveItems, errorcode.New(errorcode.GeneralException, err.Error()) + } + return driveItems, nil +} + func (g BaseGraphService) listPublicShares(ctx context.Context, filters []*link.ListPublicSharesRequest_Filter, driveItems driveItemsByResourceID) (driveItemsByResourceID, error) { gatewayClient, err := g.gatewaySelector.Next() @@ -343,6 +371,42 @@ func (g BaseGraphService) cs3UserSharesToDriveItems(ctx context.Context, shares } return driveItems, nil } +func (g BaseGraphService) cs3OCMSharesToDriveItems(ctx context.Context, shares []*ocm.Share, driveItems driveItemsByResourceID) (driveItemsByResourceID, error) { + for _, s := range shares { + g.logger.Debug().Interface("CS3 OCMShare", s).Msg("Got Share") + resIDStr := storagespace.FormatResourceID(s.ResourceId) + item, ok := driveItems[resIDStr] + if !ok { + itemptr, err := g.getDriveItem(ctx, &storageprovider.Reference{ResourceId: s.ResourceId}) + if err != nil { + g.logger.Debug().Err(err).Interface("Share", s.ResourceId).Msg("could not stat ocm share, skipping") + continue + } + item = *itemptr + } + + var condition string + switch { + case item.Folder != nil: + condition = unifiedrole.UnifiedRoleConditionFolderFederatedUser + case item.File != nil: + condition = unifiedrole.UnifiedRoleConditionFileFederatedUser + } + perm, err := g.cs3OCMShareToPermission(ctx, s, condition) + + var errcode errorcode.Error + switch { + case errors.As(err, &errcode) && errcode.GetCode() == errorcode.ItemNotFound: + // The Grantee couldn't be found (user/group does not exist anymore) + continue + case err != nil: + return driveItems, err + } + item.Permissions = append(item.Permissions, *perm) + driveItems[resIDStr] = item + } + return driveItems, nil +} func (g BaseGraphService) cs3UserShareToPermission(ctx context.Context, share *collaboration.Share, roleCondition string) (*libregraph.Permission, error) { perm := libregraph.Permission{} @@ -419,6 +483,89 @@ func (g BaseGraphService) cs3UserShareToPermission(ctx context.Context, share *c } return &perm, nil } +func (g BaseGraphService) cs3OCMShareToPermission(ctx context.Context, share *ocm.Share, roleCondition string) (*libregraph.Permission, error) { + perm := libregraph.Permission{} + perm.SetRoles([]string{}) + if roleCondition != unifiedrole.UnifiedRoleConditionDrive { + perm.SetId(share.GetId().GetOpaqueId()) + } + grantedTo := libregraph.SharePointIdentitySet{} + // hm or use share.GetShareType() to determine the type of share??? + switch share.GetGrantee().GetType() { + case storageprovider.GranteeType_GRANTEE_TYPE_USER: + user, err := cs3UserIdToIdentity(ctx, g.identityCache, share.Grantee.GetUserId()) + switch { + case errors.Is(err, identity.ErrNotFound): + g.logger.Warn().Str("userid", share.Grantee.GetUserId().GetOpaqueId()).Msg("User not found by id") + // User does not seem to exist anymore, don't add a permission for this + return nil, errorcode.New(errorcode.ItemNotFound, "grantee does not exist") + case err != nil: + return nil, errorcode.New(errorcode.GeneralException, err.Error()) + default: + grantedTo.SetUser(user) + if roleCondition == unifiedrole.UnifiedRoleConditionDrive { + perm.SetId("u:" + user.GetId()) + } + } + case storageprovider.GranteeType_GRANTEE_TYPE_GROUP: + group, err := groupIdToIdentity(ctx, g.identityCache, share.Grantee.GetGroupId().GetOpaqueId()) + switch { + case errors.Is(err, identity.ErrNotFound): + g.logger.Warn().Str("groupid", share.Grantee.GetGroupId().GetOpaqueId()).Msg("Group not found by id") + // Group not seem to exist anymore, don't add a permission for this + return nil, errorcode.New(errorcode.ItemNotFound, "grantee does not exist") + case err != nil: + return nil, errorcode.New(errorcode.GeneralException, err.Error()) + default: + grantedTo.SetGroup(group) + if roleCondition == unifiedrole.UnifiedRoleConditionDrive { + perm.SetId("g:" + group.GetId()) + } + } + } + + // set expiration date + if share.GetExpiration() != nil { + perm.SetExpirationDateTime(cs3TimestampToTime(share.GetExpiration())) + } + // set cTime + if share.GetCtime() != nil { + perm.SetCreatedDateTime(cs3TimestampToTime(share.GetCtime())) + } + var permissions *storageprovider.ResourcePermissions + for _, role := range share.GetAccessMethods() { + if role.GetWebdavOptions().GetPermissions() != nil { + permissions = role.GetWebdavOptions().GetPermissions() + } + } + + role := unifiedrole.CS3ResourcePermissionsToUnifiedRole( + permissions, + roleCondition, + ) + if role != nil { + perm.SetRoles([]string{role.GetId()}) + } else { + actions := unifiedrole.CS3ResourcePermissionsToLibregraphActions(permissions) + perm.SetLibreGraphPermissionsActions(actions) + perm.SetRoles(nil) + } + perm.SetGrantedToV2(grantedTo) + if share.GetCreator() != nil { + identity, err := cs3UserIdToIdentity(ctx, g.identityCache, share.GetCreator()) + if err != nil { + return nil, errorcode.New(errorcode.GeneralException, err.Error()) + } + perm.SetInvitation( + libregraph.SharingInvitation{ + InvitedBy: &libregraph.IdentitySet{ + User: &identity, + }, + }, + ) + } + return &perm, nil +} func (g BaseGraphService) cs3PublicSharesToDriveItems(ctx context.Context, shares []*link.PublicShare, driveItems driveItemsByResourceID) (driveItemsByResourceID, error) { for _, s := range shares { diff --git a/services/graph/pkg/service/v0/utils.go b/services/graph/pkg/service/v0/utils.go index 9a1cd60ee93..d08a05567c4 100644 --- a/services/graph/pkg/service/v0/utils.go +++ b/services/graph/pkg/service/v0/utils.go @@ -108,6 +108,22 @@ func userIdToIdentity(ctx context.Context, cache identity.IdentityCache, userID user, err := cache.GetUser(ctx, userID) if err == nil { identity.SetDisplayName(user.GetDisplayName()) + identity.SetLibreGraphUserType(user.GetUserType()) + } + return identity, err +} + +// federatedIdToIdentity looks the user for the supplied id using the cache and returns it +// as a libregraph.Identity +func federatedIdToIdentity(ctx context.Context, cache identity.IdentityCache, userID string) (libregraph.Identity, error) { + identity := libregraph.Identity{ + Id: libregraph.PtrString(userID), + LibreGraphUserType: libregraph.PtrString("Federated"), + } + user, err := cache.GetAcceptedUser(ctx, userID) + if err == nil { + identity.SetDisplayName(user.GetDisplayName()) + identity.SetLibreGraphUserType(user.GetUserType()) } return identity, err } @@ -115,6 +131,9 @@ func userIdToIdentity(ctx context.Context, cache identity.IdentityCache, userID // cs3UserIdToIdentity looks up the user for the supplied cs3 userid using the cache and returns it // as a libregraph.Identity. Skips the user lookup if the id type is USER_TYPE_SPACE_OWNER func cs3UserIdToIdentity(ctx context.Context, cache identity.IdentityCache, cs3UserID *cs3User.UserId) (libregraph.Identity, error) { + if cs3UserID.GetType() == cs3User.UserType_USER_TYPE_FEDERATED { + return federatedIdToIdentity(ctx, cache, cs3UserID.GetOpaqueId()) + } if cs3UserID.GetType() != cs3User.UserType_USER_TYPE_SPACE_OWNER { return userIdToIdentity(ctx, cache, cs3UserID.GetOpaqueId()) }