From ae53a97cc0926575c5f0cf1fa953ebc0a7919093 Mon Sep 17 00:00:00 2001 From: Ralf Haferkamp Date: Tue, 26 Mar 2024 16:44:27 +0100 Subject: [PATCH] refactor(graph): create BaseGraphService to provide common fields and methods BaseGraphService is a struct to hold common fields and methods that can be share between different service implementations. E.g. for converting CS3 objects to their libregraph equivalents. --- .../service/v0/api_driveitem_permissions.go | 22 +- .../v0/api_driveitem_permissions_test.go | 4 +- services/graph/pkg/service/v0/base.go | 393 ++++++++++++++++++ services/graph/pkg/service/v0/driveitems.go | 31 -- services/graph/pkg/service/v0/drives.go | 112 ----- services/graph/pkg/service/v0/graph.go | 8 +- services/graph/pkg/service/v0/links.go | 33 -- services/graph/pkg/service/v0/service.go | 12 +- services/graph/pkg/service/v0/sharedbyme.go | 190 --------- 9 files changed, 415 insertions(+), 390 deletions(-) create mode 100644 services/graph/pkg/service/v0/base.go diff --git a/services/graph/pkg/service/v0/api_driveitem_permissions.go b/services/graph/pkg/service/v0/api_driveitem_permissions.go index f0d707b897..2f4c87445b 100644 --- a/services/graph/pkg/service/v0/api_driveitem_permissions.go +++ b/services/graph/pkg/service/v0/api_driveitem_permissions.go @@ -16,6 +16,7 @@ import ( libregraph "github.com/owncloud/libre-graph-api-go" "github.com/owncloud/ocis/v2/ocis-pkg/conversions" "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/graph/pkg/config" "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" "github.com/owncloud/ocis/v2/services/graph/pkg/identity" "github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole" @@ -29,19 +30,18 @@ type DriveItemPermissionsProvider interface { // DriveItemPermissionsService contains the production business logic for everything that relates to permissions on drive items. type DriveItemPermissionsService struct { - logger log.Logger - gatewaySelector pool.Selectable[gateway.GatewayAPIClient] - identityCache identity.IdentityCache - resharingEnabled bool + BaseGraphService } // NewDriveItemPermissionsService creates a new DriveItemPermissionsService -func NewDriveItemPermissionsService(logger log.Logger, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], identityCache identity.IdentityCache, resharing bool) (DriveItemPermissionsService, error) { +func NewDriveItemPermissionsService(logger log.Logger, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], identityCache identity.IdentityCache, config *config.Config) (DriveItemPermissionsService, error) { return DriveItemPermissionsService{ - logger: log.Logger{Logger: logger.With().Str("graph api", "DrivesDriveItemService").Logger()}, - gatewaySelector: gatewaySelector, - identityCache: identityCache, - resharingEnabled: resharing, + BaseGraphService: BaseGraphService{ + logger: &log.Logger{Logger: logger.With().Str("graph api", "DrivesDriveItemService").Logger()}, + gatewaySelector: gatewaySelector, + identityCache: identityCache, + config: config, + }, }, nil } @@ -66,7 +66,7 @@ func (s DriveItemPermissionsService) Invite(ctx context.Context, resourceId stor unifiedRolePermissions := []*libregraph.UnifiedRolePermission{{AllowedResourceActions: invite.LibreGraphPermissionsActions}} for _, roleID := range invite.GetRoles() { - role, err := unifiedrole.NewUnifiedRoleFromID(roleID, s.resharingEnabled) + role, err := unifiedrole.NewUnifiedRoleFromID(roleID, s.config.FilesSharing.EnableResharing) if err != nil { s.logger.Debug().Err(err).Interface("role", invite.GetRoles()[0]).Msg("unable to convert requested role") return libregraph.Permission{}, err @@ -95,7 +95,7 @@ func (s DriveItemPermissionsService) Invite(ctx context.Context, resourceId stor } permission := &libregraph.Permission{} - if role := unifiedrole.CS3ResourcePermissionsToUnifiedRole(*cs3ResourcePermissions, condition, s.resharingEnabled); role != nil { + if role := unifiedrole.CS3ResourcePermissionsToUnifiedRole(*cs3ResourcePermissions, condition, s.config.FilesSharing.EnableResharing); role != nil { permission.Roles = []string{role.GetId()} } diff --git a/services/graph/pkg/service/v0/api_driveitem_permissions_test.go b/services/graph/pkg/service/v0/api_driveitem_permissions_test.go index 7e5da750e2..452ecb2a8a 100644 --- a/services/graph/pkg/service/v0/api_driveitem_permissions_test.go +++ b/services/graph/pkg/service/v0/api_driveitem_permissions_test.go @@ -26,6 +26,7 @@ import ( libregraph "github.com/owncloud/libre-graph-api-go" "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/services/graph/mocks" + "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" "github.com/owncloud/ocis/v2/services/graph/pkg/identity" svc "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0" @@ -55,7 +56,8 @@ var _ = Describe("DriveItemPermissionsService", func() { cache := identity.NewIdentityCache(identity.IdentityCacheWithGatewaySelector(gatewaySelector)) - service, err := svc.NewDriveItemPermissionsService(logger, gatewaySelector, cache, false) + cfg := defaults.FullDefaultConfig() + service, err := svc.NewDriveItemPermissionsService(logger, gatewaySelector, cache, cfg) Expect(err).ToNot(HaveOccurred()) driveItemPermissionsService = service }) diff --git a/services/graph/pkg/service/v0/base.go b/services/graph/pkg/service/v0/base.go new file mode 100644 index 0000000000..05ab324664 --- /dev/null +++ b/services/graph/pkg/service/v0/base.go @@ -0,0 +1,393 @@ +package svc + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/url" + "path" + "time" + + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" + storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" + "github.com/cs3org/reva/v2/pkg/share" + "github.com/cs3org/reva/v2/pkg/storagespace" + "github.com/cs3org/reva/v2/pkg/utils" + libregraph "github.com/owncloud/libre-graph-api-go" + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/graph/pkg/config" + "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/identity" + "github.com/owncloud/ocis/v2/services/graph/pkg/linktype" + "github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole" +) + +// BaseGraphService implements a couple of helper functions that are +// shared between the different graph services +type BaseGraphService struct { + logger *log.Logger + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] + identityCache identity.IdentityCache + config *config.Config +} + +func (g BaseGraphService) getSpaceRootPermissions(ctx context.Context, spaceID *storageprovider.StorageSpaceId) ([]libregraph.Permission, error) { + gatewayClient, err := g.gatewaySelector.Next() + + if err != nil { + g.logger.Debug().Err(err).Msg("selecting gatewaySelector failed") + return nil, err + } + space, err := utils.GetSpace(ctx, spaceID.GetOpaqueId(), gatewayClient) + if err != nil { + return nil, err + } + + return g.cs3SpacePermissionsToLibreGraph(ctx, space, APIVersion_1_Beta_1), nil +} + +func (g BaseGraphService) getDriveItem(ctx context.Context, ref storageprovider.Reference) (*libregraph.DriveItem, error) { + gatewayClient, err := g.gatewaySelector.Next() + if err != nil { + return nil, err + } + + res, err := gatewayClient.Stat(ctx, &storageprovider.StatRequest{Ref: &ref}) + if err != nil { + return nil, err + } + if res.GetStatus().GetCode() != cs3rpc.Code_CODE_OK { + refStr, _ := storagespace.FormatReference(&ref) + return nil, fmt.Errorf("could not stat %s: %s", refStr, res.GetStatus().GetMessage()) + } + return cs3ResourceToDriveItem(g.logger, res.GetInfo()) +} + +func (g BaseGraphService) cs3SpacePermissionsToLibreGraph(ctx context.Context, space *storageprovider.StorageSpace, apiVersion APIVersion) []libregraph.Permission { + if space.Opaque == nil { + return nil + } + logger := g.logger.SubloggerWithRequestID(ctx) + + var permissionsMap map[string]*storageprovider.ResourcePermissions + opaqueGrants, ok := space.Opaque.Map["grants"] + if ok { + err := json.Unmarshal(opaqueGrants.Value, &permissionsMap) + if err != nil { + logger.Debug(). + Err(err). + Interface("space", space.Root). + Bytes("grants", opaqueGrants.Value). + Msg("unable to parse space: failed to read spaces grants") + } + } + if len(permissionsMap) == 0 { + return nil + } + + var permissionsExpirations map[string]*types.Timestamp + opaqueGrantsExpirations, ok := space.Opaque.Map["grants_expirations"] + if ok { + err := json.Unmarshal(opaqueGrantsExpirations.Value, &permissionsExpirations) + if err != nil { + logger.Debug(). + Err(err). + Interface("space", space.Root). + Bytes("grants_expirations", opaqueGrantsExpirations.Value). + Msg("unable to parse space: failed to read spaces grants expirations") + } + } + + var groupsMap map[string]struct{} + opaqueGroups, ok := space.Opaque.Map["groups"] + if ok { + err := json.Unmarshal(opaqueGroups.Value, &groupsMap) + if err != nil { + logger.Debug(). + Err(err). + Interface("space", space.Root). + Bytes("groups", opaqueGroups.Value). + Msg("unable to parse space: failed to read spaces groups") + } + } + + permissions := make([]libregraph.Permission, 0, len(permissionsMap)) + for id, perm := range permissionsMap { + // This temporary variable is necessary since we need to pass a pointer to the + // libregraph.Identity and if we pass the pointer from the loop every identity + // will have the same id. + tmp := id + isGroup := false + var identity libregraph.Identity + var err error + var p libregraph.Permission + if _, ok := groupsMap[id]; ok { + identity, err = groupIdToIdentity(ctx, g.identityCache, tmp) + if err != nil { + g.logger.Warn().Str("groupid", tmp).Msg("Group not found by id") + } + isGroup = true + } else { + identity, err = userIdToIdentity(ctx, g.identityCache, tmp) + if err != nil { + g.logger.Warn().Str("userid", tmp).Msg("User not found by id") + } + } + switch apiVersion { + case APIVersion_1: + var identitySet libregraph.IdentitySet + if isGroup { + identitySet.SetGroup(identity) + } else { + identitySet.SetUser(identity) + } + p.SetGrantedToIdentities([]libregraph.IdentitySet{identitySet}) + case APIVersion_1_Beta_1: + var identitySet libregraph.SharePointIdentitySet + if isGroup { + identitySet.SetGroup(identity) + } else { + identitySet.SetUser(identity) + } + p.SetId(identitySetToSpacePermissionID(identitySet)) + p.SetGrantedToV2(identitySet) + } + + if exp := permissionsExpirations[id]; exp != nil { + p.SetExpirationDateTime(time.Unix(int64(exp.GetSeconds()), int64(exp.GetNanos()))) + } + + if role := unifiedrole.CS3ResourcePermissionsToUnifiedRole(*perm, unifiedrole.UnifiedRoleConditionOwner, false); role != nil { + switch apiVersion { + case APIVersion_1: + if r := unifiedrole.GetLegacyName(*role); r != "" { + p.SetRoles([]string{r}) + } + case APIVersion_1_Beta_1: + p.SetRoles([]string{role.GetId()}) + } + } + + permissions = append(permissions, p) + } + return permissions +} + +func (g BaseGraphService) libreGraphPermissionFromCS3PublicShare(createdLink *link.PublicShare) (*libregraph.Permission, error) { + webURL, err := url.Parse(g.config.Spaces.WebDavBase) + if err != nil { + g.logger.Error(). + Err(err). + Str("url", g.config.Spaces.WebDavBase). + Msg("failed to parse webURL base url") + return nil, err + } + lt, actions := linktype.SharingLinkTypeFromCS3Permissions(createdLink.GetPermissions()) + perm := libregraph.NewPermission() + perm.Id = libregraph.PtrString(createdLink.GetId().GetOpaqueId()) + perm.Link = &libregraph.SharingLink{ + Type: lt, + PreventsDownload: libregraph.PtrBool(false), + LibreGraphDisplayName: libregraph.PtrString(createdLink.GetDisplayName()), + LibreGraphQuickLink: libregraph.PtrBool(createdLink.GetQuicklink()), + } + perm.LibreGraphPermissionsActions = actions + webURL.Path = path.Join(webURL.Path, "s", createdLink.GetToken()) + perm.Link.SetWebUrl(webURL.String()) + + // set expiration date + if createdLink.GetExpiration() != nil { + perm.SetExpirationDateTime(cs3TimestampToTime(createdLink.GetExpiration()).UTC()) + } + + perm.SetHasPassword(createdLink.GetPasswordProtected()) + + return perm, nil +} + +func (g BaseGraphService) listUserShares(ctx context.Context, filters []*collaboration.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 := []*collaboration.Filter{ + share.UserGranteeFilter(), + share.GroupGranteeFilter(), + } + concreteFilters = append(concreteFilters, filters...) + + lsUserSharesRequest := collaboration.ListSharesRequest{ + Filters: concreteFilters, + } + + lsUserSharesResponse, err := gatewayClient.ListShares(ctx, &lsUserSharesRequest) + if err != nil { + return driveItems, errorcode.New(errorcode.GeneralException, err.Error()) + } + if statusCode := lsUserSharesResponse.GetStatus().GetCode(); statusCode != rpc.Code_CODE_OK { + return driveItems, errorcode.New(cs3StatusToErrCode(statusCode), lsUserSharesResponse.Status.Message) + } + driveItems, err = g.cs3UserSharesToDriveItems(ctx, lsUserSharesResponse.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() + if err != nil { + g.logger.Error().Err(err).Msg("could not select next gateway client") + return driveItems, errorcode.New(errorcode.GeneralException, err.Error()) + } + + var concreteFilters []*link.ListPublicSharesRequest_Filter + concreteFilters = append(concreteFilters, filters...) + + req := link.ListPublicSharesRequest{ + Filters: concreteFilters, + } + + lsPublicSharesResponse, err := gatewayClient.ListPublicShares(ctx, &req) + if err != nil { + return driveItems, errorcode.New(errorcode.GeneralException, err.Error()) + } + if statusCode := lsPublicSharesResponse.GetStatus().GetCode(); statusCode != rpc.Code_CODE_OK { + return driveItems, errorcode.New(cs3StatusToErrCode(statusCode), lsPublicSharesResponse.Status.Message) + } + driveItems, err = g.cs3PublicSharesToDriveItems(ctx, lsPublicSharesResponse.Share, driveItems) + if err != nil { + return driveItems, errorcode.New(errorcode.GeneralException, err.Error()) + } + return driveItems, nil + +} + +func (g BaseGraphService) cs3UserSharesToDriveItems(ctx context.Context, shares []*collaboration.Share, driveItems driveItemsByResourceID) (driveItemsByResourceID, error) { + for _, s := range shares { + g.logger.Debug().Interface("CS3 UserShare", 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 share, skipping") + continue + } + item = *itemptr + } + perm, err := g.cs3UserShareToPermission(ctx, s, false) + + 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, isSpacePermission bool) (*libregraph.Permission, error) { + perm := libregraph.Permission{} + perm.SetRoles([]string{}) + if !isSpacePermission { + perm.SetId(share.GetId().GetOpaqueId()) + } + grantedTo := libregraph.SharePointIdentitySet{} + 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 isSpacePermission { + 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 isSpacePermission { + perm.SetId("g:" + group.GetId()) + } + } + } + + // set expiration date + if share.GetExpiration() != nil { + perm.SetExpirationDateTime(cs3TimestampToTime(share.GetExpiration())) + } + condition := unifiedrole.UnifiedRoleConditionGrantee + if isSpacePermission { + condition = unifiedrole.UnifiedRoleConditionOwner + } + role := unifiedrole.CS3ResourcePermissionsToUnifiedRole( + *share.GetPermissions().GetPermissions(), + condition, + g.config.FilesSharing.EnableResharing, + ) + if role != nil { + perm.SetRoles([]string{role.GetId()}) + } else { + actions := unifiedrole.CS3ResourcePermissionsToLibregraphActions(*share.GetPermissions().GetPermissions()) + perm.SetLibreGraphPermissionsActions(actions) + perm.SetRoles(nil) + } + perm.SetGrantedToV2(grantedTo) + return &perm, nil +} + +func (g BaseGraphService) cs3PublicSharesToDriveItems(ctx context.Context, shares []*link.PublicShare, driveItems driveItemsByResourceID) (driveItemsByResourceID, error) { + for _, s := range shares { + g.logger.Debug().Interface("CS3 PublicShare", 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 share, skipping") + continue + } + item = *itemptr + } + perm, err := g.libreGraphPermissionFromCS3PublicShare(s) + if err != nil { + g.logger.Error().Err(err).Interface("Link", s.ResourceId).Msg("could not convert link to libregraph") + return driveItems, err + } + + item.Permissions = append(item.Permissions, *perm) + driveItems[resIDStr] = item + } + + return driveItems, nil +} diff --git a/services/graph/pkg/service/v0/driveitems.go b/services/graph/pkg/service/v0/driveitems.go index ce91964bd2..3c1031b844 100644 --- a/services/graph/pkg/service/v0/driveitems.go +++ b/services/graph/pkg/service/v0/driveitems.go @@ -640,20 +640,6 @@ func (g Graph) getPermissionByID(ctx context.Context, permissionID string, itemI } -func (g Graph) getSpaceRootPermissions(ctx context.Context, spaceID *storageprovider.StorageSpaceId) ([]libregraph.Permission, error) { - gatewayClient, err := g.gatewaySelector.Next() - if err != nil { - g.logger.Debug().Err(err).Msg("selecting gatewaySelector failed") - return nil, err - } - space, err := utils.GetSpace(ctx, spaceID.GetOpaqueId(), gatewayClient) - if err != nil { - return nil, err - } - - return g.cs3SpacePermissionsToLibreGraph(ctx, space, APIVersion_1_Beta_1), nil -} - func (g Graph) getUserPermissionResourceID(ctx context.Context, permissionID string) (*storageprovider.ResourceId, error) { shareByID, err := g.getCS3UserShareByID(ctx, permissionID) if err != nil { @@ -916,23 +902,6 @@ func (g Graph) removePublicShare(ctx context.Context, permissionID string) error return nil } -func (g Graph) getDriveItem(ctx context.Context, ref storageprovider.Reference) (*libregraph.DriveItem, error) { - gatewayClient, err := g.gatewaySelector.Next() - if err != nil { - return nil, err - } - - res, err := gatewayClient.Stat(ctx, &storageprovider.StatRequest{Ref: &ref}) - if err != nil { - return nil, err - } - if res.GetStatus().GetCode() != cs3rpc.Code_CODE_OK { - refStr, _ := storagespace.FormatReference(&ref) - return nil, fmt.Errorf("could not stat %s: %s", refStr, res.GetStatus().GetMessage()) - } - return cs3ResourceToDriveItem(g.logger, res.GetInfo()) -} - func (g Graph) getRemoteItem(ctx context.Context, root *storageprovider.ResourceId, baseURL *url.URL) (*libregraph.RemoteItem, error) { gatewayClient, err := g.gatewaySelector.Next() if err != nil { diff --git a/services/graph/pkg/service/v0/drives.go b/services/graph/pkg/service/v0/drives.go index 464cd1c702..b841621060 100644 --- a/services/graph/pkg/service/v0/drives.go +++ b/services/graph/pkg/service/v0/drives.go @@ -11,7 +11,6 @@ import ( "sort" "strconv" "strings" - "time" "github.com/CiscoM31/godata" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" @@ -34,7 +33,6 @@ import ( v0 "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" - "github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole" settingsServiceExt "github.com/owncloud/ocis/v2/services/settings/pkg/store/defaults" ) @@ -845,116 +843,6 @@ func (g Graph) cs3StorageSpaceToDrive(ctx context.Context, baseURL *url.URL, spa return drive, nil } -func (g Graph) cs3SpacePermissionsToLibreGraph(ctx context.Context, space *storageprovider.StorageSpace, apiVersion APIVersion) []libregraph.Permission { - if space.Opaque == nil { - return nil - } - logger := g.logger.SubloggerWithRequestID(ctx) - - var permissionsMap map[string]*storageprovider.ResourcePermissions - opaqueGrants, ok := space.Opaque.Map["grants"] - if ok { - err := json.Unmarshal(opaqueGrants.Value, &permissionsMap) - if err != nil { - logger.Debug(). - Err(err). - Interface("space", space.Root). - Bytes("grants", opaqueGrants.Value). - Msg("unable to parse space: failed to read spaces grants") - } - } - if len(permissionsMap) == 0 { - return nil - } - - var permissionsExpirations map[string]*types.Timestamp - opaqueGrantsExpirations, ok := space.Opaque.Map["grants_expirations"] - if ok { - err := json.Unmarshal(opaqueGrantsExpirations.Value, &permissionsExpirations) - if err != nil { - logger.Debug(). - Err(err). - Interface("space", space.Root). - Bytes("grants_expirations", opaqueGrantsExpirations.Value). - Msg("unable to parse space: failed to read spaces grants expirations") - } - } - - var groupsMap map[string]struct{} - opaqueGroups, ok := space.Opaque.Map["groups"] - if ok { - err := json.Unmarshal(opaqueGroups.Value, &groupsMap) - if err != nil { - logger.Debug(). - Err(err). - Interface("space", space.Root). - Bytes("groups", opaqueGroups.Value). - Msg("unable to parse space: failed to read spaces groups") - } - } - - permissions := make([]libregraph.Permission, 0, len(permissionsMap)) - for id, perm := range permissionsMap { - // This temporary variable is necessary since we need to pass a pointer to the - // libregraph.Identity and if we pass the pointer from the loop every identity - // will have the same id. - tmp := id - isGroup := false - var identity libregraph.Identity - var err error - var p libregraph.Permission - if _, ok := groupsMap[id]; ok { - identity, err = groupIdToIdentity(ctx, g.identityCache, tmp) - if err != nil { - g.logger.Warn().Str("groupid", tmp).Msg("Group not found by id") - } - isGroup = true - } else { - identity, err = userIdToIdentity(ctx, g.identityCache, tmp) - if err != nil { - g.logger.Warn().Str("userid", tmp).Msg("User not found by id") - } - } - switch apiVersion { - case APIVersion_1: - var identitySet libregraph.IdentitySet - if isGroup { - identitySet.SetGroup(identity) - } else { - identitySet.SetUser(identity) - } - p.SetGrantedToIdentities([]libregraph.IdentitySet{identitySet}) - case APIVersion_1_Beta_1: - var identitySet libregraph.SharePointIdentitySet - if isGroup { - identitySet.SetGroup(identity) - } else { - identitySet.SetUser(identity) - } - p.SetId(identitySetToSpacePermissionID(identitySet)) - p.SetGrantedToV2(identitySet) - } - - if exp := permissionsExpirations[id]; exp != nil { - p.SetExpirationDateTime(time.Unix(int64(exp.GetSeconds()), int64(exp.GetNanos()))) - } - - if role := unifiedrole.CS3ResourcePermissionsToUnifiedRole(*perm, unifiedrole.UnifiedRoleConditionOwner, false); role != nil { - switch apiVersion { - case APIVersion_1: - if r := unifiedrole.GetLegacyName(*role); r != "" { - p.SetRoles([]string{r}) - } - case APIVersion_1_Beta_1: - p.SetRoles([]string{role.GetId()}) - } - } - - permissions = append(permissions, p) - } - return permissions -} - func (g Graph) getDriveQuota(ctx context.Context, space *storageprovider.StorageSpace) (libregraph.Quota, error) { logger := g.logger.SubloggerWithRequestID(ctx) diff --git a/services/graph/pkg/service/v0/graph.go b/services/graph/pkg/service/v0/graph.go index 33893e18e2..8c562fd64e 100644 --- a/services/graph/pkg/service/v0/graph.go +++ b/services/graph/pkg/service/v0/graph.go @@ -18,15 +18,12 @@ import ( "google.golang.org/protobuf/types/known/emptypb" "github.com/cs3org/reva/v2/pkg/events" - "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/owncloud/ocis/v2/ocis-pkg/keycloak" - "github.com/owncloud/ocis/v2/ocis-pkg/log" ehsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/eventhistory/v0" searchsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/search/v0" settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" - "github.com/owncloud/ocis/v2/services/graph/pkg/config" "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" "github.com/owncloud/ocis/v2/services/graph/pkg/identity" ) @@ -61,17 +58,14 @@ type RoleService interface { // Graph defines implements the business logic for Service. type Graph struct { - config *config.Config + BaseGraphService mux *chi.Mux - logger *log.Logger identityBackend identity.Backend identityEducationBackend identity.EducationBackend - gatewaySelector pool.Selectable[gateway.GatewayAPIClient] roleService RoleService permissionsService Permissions valueService settingssvc.ValueService specialDriveItemsCache *ttlcache.Cache[string, interface{}] - identityCache identity.IdentityCache eventsPublisher events.Publisher searchService searchsvc.SearchProviderService keycloakClient keycloak.Client diff --git a/services/graph/pkg/service/v0/links.go b/services/graph/pkg/service/v0/links.go index 8c8dbc3022..b4e5763cad 100644 --- a/services/graph/pkg/service/v0/links.go +++ b/services/graph/pkg/service/v0/links.go @@ -6,7 +6,6 @@ import ( "io" "net/http" "net/url" - "path" "strconv" "time" @@ -173,38 +172,6 @@ func (g Graph) createLink(ctx context.Context, driveItemID *providerv1beta1.Reso return createResp.GetShare(), nil } -func (g Graph) libreGraphPermissionFromCS3PublicShare(createdLink *link.PublicShare) (*libregraph.Permission, error) { - webURL, err := url.Parse(g.config.Spaces.WebDavBase) - if err != nil { - g.logger.Error(). - Err(err). - Str("url", g.config.Spaces.WebDavBase). - Msg("failed to parse webURL base url") - return nil, err - } - lt, actions := linktype.SharingLinkTypeFromCS3Permissions(createdLink.GetPermissions()) - perm := libregraph.NewPermission() - perm.Id = libregraph.PtrString(createdLink.GetId().GetOpaqueId()) - perm.Link = &libregraph.SharingLink{ - Type: lt, - PreventsDownload: libregraph.PtrBool(false), - LibreGraphDisplayName: libregraph.PtrString(createdLink.GetDisplayName()), - LibreGraphQuickLink: libregraph.PtrBool(createdLink.GetQuicklink()), - } - perm.LibreGraphPermissionsActions = actions - webURL.Path = path.Join(webURL.Path, "s", createdLink.GetToken()) - perm.Link.SetWebUrl(webURL.String()) - - // set expiration date - if createdLink.GetExpiration() != nil { - perm.SetExpirationDateTime(cs3TimestampToTime(createdLink.GetExpiration()).UTC()) - } - - perm.SetHasPassword(createdLink.GetPasswordProtected()) - - return perm, nil -} - func parseAndFillUpTime(t *time.Time) *types.Timestamp { if t == nil || t.IsZero() { return nil diff --git a/services/graph/pkg/service/v0/service.go b/services/graph/pkg/service/v0/service.go index ea7a90b820..9a8b89d25e 100644 --- a/services/graph/pkg/service/v0/service.go +++ b/services/graph/pkg/service/v0/service.go @@ -145,13 +145,15 @@ func NewService(opts ...Option) (Graph, error) { ) svc := Graph{ - config: options.Config, + BaseGraphService: BaseGraphService{ + logger: &options.Logger, + identityCache: identityCache, + gatewaySelector: options.GatewaySelector, + config: options.Config, + }, mux: m, - logger: &options.Logger, specialDriveItemsCache: spacePropertiesCache, - identityCache: identityCache, eventsPublisher: options.EventsPublisher, - gatewaySelector: options.GatewaySelector, searchService: options.SearchService, identityEducationBackend: options.IdentityEducationBackend, keycloakClient: options.KeycloakClient, @@ -213,7 +215,7 @@ func NewService(opts ...Option) (Graph, error) { return svc, err } - driveItemPermissionsService, err := NewDriveItemPermissionsService(options.Logger, options.GatewaySelector, identityCache, options.Config.FilesSharing.EnableResharing) + driveItemPermissionsService, err := NewDriveItemPermissionsService(options.Logger, options.GatewaySelector, identityCache, options.Config) if err != nil { return svc, err } diff --git a/services/graph/pkg/service/v0/sharedbyme.go b/services/graph/pkg/service/v0/sharedbyme.go index c21dff8985..286476dc1e 100644 --- a/services/graph/pkg/service/v0/sharedbyme.go +++ b/services/graph/pkg/service/v0/sharedbyme.go @@ -1,23 +1,13 @@ package svc import ( - "context" - "errors" "net/http" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" - collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" - link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" - storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/go-chi/render" libregraph "github.com/owncloud/libre-graph-api-go" - "github.com/cs3org/reva/v2/pkg/share" - "github.com/cs3org/reva/v2/pkg/storagespace" - "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" - "github.com/owncloud/ocis/v2/services/graph/pkg/identity" - "github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole" ) type driveItemsByResourceID map[string]libregraph.DriveItem @@ -50,186 +40,6 @@ func (g Graph) GetSharedByMe(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, &ListResponse{Value: res}) } -func (g Graph) listUserShares(ctx context.Context, filters []*collaboration.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 := []*collaboration.Filter{ - share.UserGranteeFilter(), - share.GroupGranteeFilter(), - } - concreteFilters = append(concreteFilters, filters...) - - lsUserSharesRequest := collaboration.ListSharesRequest{ - Filters: concreteFilters, - } - - lsUserSharesResponse, err := gatewayClient.ListShares(ctx, &lsUserSharesRequest) - if err != nil { - return driveItems, errorcode.New(errorcode.GeneralException, err.Error()) - } - if statusCode := lsUserSharesResponse.GetStatus().GetCode(); statusCode != rpc.Code_CODE_OK { - return driveItems, errorcode.New(cs3StatusToErrCode(statusCode), lsUserSharesResponse.Status.Message) - } - driveItems, err = g.cs3UserSharesToDriveItems(ctx, lsUserSharesResponse.Shares, driveItems) - if err != nil { - return driveItems, errorcode.New(errorcode.GeneralException, err.Error()) - } - return driveItems, nil -} - -func (g Graph) listPublicShares(ctx context.Context, filters []*link.ListPublicSharesRequest_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()) - } - - var concreteFilters []*link.ListPublicSharesRequest_Filter - concreteFilters = append(concreteFilters, filters...) - - req := link.ListPublicSharesRequest{ - Filters: concreteFilters, - } - - lsPublicSharesResponse, err := gatewayClient.ListPublicShares(ctx, &req) - if err != nil { - return driveItems, errorcode.New(errorcode.GeneralException, err.Error()) - } - if statusCode := lsPublicSharesResponse.GetStatus().GetCode(); statusCode != rpc.Code_CODE_OK { - return driveItems, errorcode.New(cs3StatusToErrCode(statusCode), lsPublicSharesResponse.Status.Message) - } - driveItems, err = g.cs3PublicSharesToDriveItems(ctx, lsPublicSharesResponse.Share, driveItems) - if err != nil { - return driveItems, errorcode.New(errorcode.GeneralException, err.Error()) - } - return driveItems, nil - -} - -func (g Graph) cs3UserSharesToDriveItems(ctx context.Context, shares []*collaboration.Share, driveItems driveItemsByResourceID) (driveItemsByResourceID, error) { - for _, s := range shares { - g.logger.Debug().Interface("CS3 UserShare", 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 share, skipping") - continue - } - item = *itemptr - } - perm, err := g.cs3UserShareToPermission(ctx, s, false) - - 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 Graph) cs3UserShareToPermission(ctx context.Context, share *collaboration.Share, isSpacePermission bool) (*libregraph.Permission, error) { - perm := libregraph.Permission{} - perm.SetRoles([]string{}) - if !isSpacePermission { - perm.SetId(share.GetId().GetOpaqueId()) - } - grantedTo := libregraph.SharePointIdentitySet{} - 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 isSpacePermission { - 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 isSpacePermission { - perm.SetId("g:" + group.GetId()) - } - } - } - - // set expiration date - if share.GetExpiration() != nil { - perm.SetExpirationDateTime(cs3TimestampToTime(share.GetExpiration())) - } - condition := unifiedrole.UnifiedRoleConditionGrantee - if isSpacePermission { - condition = unifiedrole.UnifiedRoleConditionOwner - } - role := unifiedrole.CS3ResourcePermissionsToUnifiedRole( - *share.GetPermissions().GetPermissions(), - condition, - g.config.FilesSharing.EnableResharing, - ) - if role != nil { - perm.SetRoles([]string{role.GetId()}) - } else { - actions := unifiedrole.CS3ResourcePermissionsToLibregraphActions(*share.GetPermissions().GetPermissions()) - perm.SetLibreGraphPermissionsActions(actions) - perm.SetRoles(nil) - } - perm.SetGrantedToV2(grantedTo) - return &perm, nil -} - -func (g Graph) cs3PublicSharesToDriveItems(ctx context.Context, shares []*link.PublicShare, driveItems driveItemsByResourceID) (driveItemsByResourceID, error) { - for _, s := range shares { - g.logger.Debug().Interface("CS3 PublicShare", 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 share, skipping") - continue - } - item = *itemptr - } - perm, err := g.libreGraphPermissionFromCS3PublicShare(s) - if err != nil { - g.logger.Error().Err(err).Interface("Link", s.ResourceId).Msg("could not convert link to libregraph") - return driveItems, err - } - - item.Permissions = append(item.Permissions, *perm) - driveItems[resIDStr] = item - } - - return driveItems, nil -} - func cs3StatusToErrCode(code rpc.Code) (errcode errorcode.ErrorCode) { switch code { case rpc.Code_CODE_UNAUTHENTICATED: