diff --git a/.drone.star b/.drone.star index e278eae0d..2bb508382 100644 --- a/.drone.star +++ b/.drone.star @@ -2104,6 +2104,7 @@ def ocisServer(storage, accounts_hash_difficulty = 4, volumes = [], depends_on = "NATS_NATS_PORT": 9233, "OCIS_JWT_SECRET": "some-ocis-jwt-secret", "EVENTHISTORY_STORE": "memory", + "UNIFIED_ROLES_AVAILABLE_ROLES": "b1e2218d-eef8-4d4c-b82d-0f1a1b48f3b5,a8d5fe5e-96e3-418d-825b-534dbdf22b99,fb6c3e19-e378-47e5-b277-9732f9de6e21,58c63c02-1d89-4572-916a-870abc5a1b7d,2d00ce52-1fc2-4dbc-8b95-a73b73395f5a,1c996275-f1c9-4e71-abdf-a42f6495e960,312c0871-5ef7-4b3a-85b6-0e4074c64049,aa97fe03-7980-45ac-9e50-b325749fd7e6", } if deploy_type == "": diff --git a/changelog/unreleased/enhancement-unified-roles-management.md b/changelog/unreleased/enhancement-unified-roles-management.md index 06bd9216f..5763a580c 100644 --- a/changelog/unreleased/enhancement-unified-roles-management.md +++ b/changelog/unreleased/enhancement-unified-roles-management.md @@ -25,7 +25,7 @@ To enable a role, include the UID of the role in the list of available roles. A new command has been introduced to simplify the process of finding out which UID belongs to which role. The command is: -```bash +``` $ ocis graph list-unified-roles ``` diff --git a/services/graph/pkg/command/unified_roles.go b/services/graph/pkg/command/unified_roles.go index 5c2759564..df1b1d7ef 100644 --- a/services/graph/pkg/command/unified_roles.go +++ b/services/graph/pkg/command/unified_roles.go @@ -44,7 +44,7 @@ func unifiedRolesStatus(cfg *config.Config) *cli.Command { var data [][]string - for _, definition := range unifiedrole.GetBuiltinRoleDefinitionList() { + for _, definition := range unifiedrole.GetDefinitions(unifiedrole.RoleFilterAll()) { data = append(data, []string{"", definition.GetId(), definition.GetDescription()}) } diff --git a/services/graph/pkg/config/defaults/defaultconfig.go b/services/graph/pkg/config/defaults/defaultconfig.go index 1d13d88c9..77e5183c1 100644 --- a/services/graph/pkg/config/defaults/defaultconfig.go +++ b/services/graph/pkg/config/defaults/defaultconfig.go @@ -177,7 +177,7 @@ func EnsureDefaults(cfg *config.Config) { // set default roles, if no roles are defined, we need to take care and provide all the default roles if len(cfg.UnifiedRoles.AvailableRoles) == 0 { - for _, definition := range unifiedrole.GetBuiltinRoleDefinitionList( + for _, definition := range unifiedrole.GetDefinitions( // filter out the roles that are disabled by default unifiedrole.RoleFilterInvert(unifiedrole.RoleFilterIDs(_disabledByDefaultUnifiedRoleRoleIDs...)), ) { diff --git a/services/graph/pkg/config/parser/parse.go b/services/graph/pkg/config/parser/parse.go index ca311550b..2638ba2f8 100644 --- a/services/graph/pkg/config/parser/parse.go +++ b/services/graph/pkg/config/parser/parse.go @@ -8,12 +8,11 @@ import ( ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" defaults2 "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" "github.com/owncloud/ocis/v2/ocis-pkg/shared" "github.com/owncloud/ocis/v2/services/graph/pkg/config" "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" "github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" ) // ParseConfig loads configuration from known paths. @@ -80,7 +79,7 @@ func Validate(cfg *config.Config) error { for _, uid := range cfg.UnifiedRoles.AvailableRoles { // check if the role is known - if len(unifiedrole.GetBuiltinRoleDefinitionList(unifiedrole.RoleFilterIDs(uid))) == 0 { + if len(unifiedrole.GetDefinitions(unifiedrole.RoleFilterIDs(uid))) == 0 { // collect all possible errors to return them all at once err = errors.Join(err, fmt.Errorf("%w: %s", unifiedrole.ErrUnknownUnifiedRole, uid)) } diff --git a/services/graph/pkg/linktype/linktype.go b/services/graph/pkg/linktype/linktype.go index a2b6557c1..e049701d2 100644 --- a/services/graph/pkg/linktype/linktype.go +++ b/services/graph/pkg/linktype/linktype.go @@ -7,6 +7,7 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/v2/pkg/storage/utils/grants" libregraph "github.com/owncloud/libre-graph-api-go" + "github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole" ) diff --git a/services/graph/pkg/service/v0/api_driveitem_permissions.go b/services/graph/pkg/service/v0/api_driveitem_permissions.go index 6497b7f7b..0774ef0f8 100644 --- a/services/graph/pkg/service/v0/api_driveitem_permissions.go +++ b/services/graph/pkg/service/v0/api_driveitem_permissions.go @@ -109,7 +109,7 @@ func (s DriveItemPermissionsService) Invite(ctx context.Context, resourceId *sto return libregraph.Permission{}, unifiedrole.ErrUnknownUnifiedRole } - role, err := unifiedrole.NewUnifiedRoleFromID(roleID) + role, err := unifiedrole.GetDefinition(unifiedrole.RoleFilterIDs(roleID)) if err != nil { s.logger.Debug().Err(err).Interface("role", invite.GetRoles()[0]).Msg("unable to convert requested role") return libregraph.Permission{}, err @@ -129,7 +129,7 @@ func (s DriveItemPermissionsService) Invite(ctx context.Context, resourceId *sto cs3ResourcePermissions := unifiedrole.PermissionsToCS3ResourcePermissions(unifiedRolePermissions) permission := &libregraph.Permission{} - if role := unifiedrole.CS3ResourcePermissionsToUnifiedRole(cs3ResourcePermissions, condition); role != nil { + if role := unifiedrole.CS3ResourcePermissionsToDefinition(cs3ResourcePermissions, condition); role != nil { permission.Roles = []string{role.GetId()} } @@ -350,7 +350,7 @@ func (s DriveItemPermissionsService) ListPermissions(ctx context.Context, itemID collectionOfPermissions = libregraph.CollectionOfPermissionsWithAllowedValues{ LibreGraphPermissionsActionsAllowedValues: allowedActions, LibreGraphPermissionsRolesAllowedValues: conversions.ToValueSlice( - unifiedrole.GetApplicableRoleDefinitionsForActions( + unifiedrole.GetRolesByPermissions( allowedActions, condition, listFederatedRoles, 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 c3eca5d55..b7a430408 100644 --- a/services/graph/pkg/service/v0/api_driveitem_permissions_test.go +++ b/services/graph/pkg/service/v0/api_driveitem_permissions_test.go @@ -169,13 +169,13 @@ var _ = Describe("DriveItemPermissionsService", func() { driveItemInvite.Recipients = []libregraph.DriveRecipient{ {ObjectId: libregraph.PtrString("1"), LibreGraphRecipientType: libregraph.PtrString("user")}, } - driveItemInvite.Roles = []string{unifiedrole.NewViewerUnifiedRole().GetId()} + driveItemInvite.Roles = []string{unifiedrole.UnifiedRoleViewerID} permission, err := driveItemPermissionsService.Invite(context.Background(), driveItemId, driveItemInvite) Expect(err).ToNot(HaveOccurred()) Expect(permission.GetRoles()).To(HaveLen(1)) - Expect(permission.GetRoles()[0]).To(Equal(unifiedrole.NewViewerUnifiedRole().GetId())) + Expect(permission.GetRoles()[0]).To(Equal(unifiedrole.UnifiedRoleViewerID)) }) It("succeeds with folder roles (happy path)", func() { @@ -185,20 +185,20 @@ var _ = Describe("DriveItemPermissionsService", func() { driveItemInvite.Recipients = []libregraph.DriveRecipient{ {ObjectId: libregraph.PtrString("1"), LibreGraphRecipientType: libregraph.PtrString("user")}, } - driveItemInvite.Roles = []string{unifiedrole.NewEditorUnifiedRole().GetId()} + driveItemInvite.Roles = []string{unifiedrole.UnifiedRoleEditorID} permission, err := driveItemPermissionsService.Invite(context.Background(), driveItemId, driveItemInvite) Expect(err).ToNot(HaveOccurred()) Expect(permission.GetRoles()).To(HaveLen(1)) - Expect(permission.GetRoles()[0]).To(Equal(unifiedrole.NewEditorUnifiedRole().GetId())) + Expect(permission.GetRoles()[0]).To(Equal(unifiedrole.UnifiedRoleEditorID)) }) It("fails with when trying to set a space role on a file", func() { driveItemInvite.Recipients = []libregraph.DriveRecipient{ {ObjectId: libregraph.PtrString("1"), LibreGraphRecipientType: libregraph.PtrString("user")}, } - driveItemInvite.Roles = []string{unifiedrole.NewManagerUnifiedRole().GetId()} + driveItemInvite.Roles = []string{unifiedrole.UnifiedRoleManagerID} permission, err := driveItemPermissionsService.Invite(context.Background(), driveItemId, driveItemInvite) Expect(err).To(MatchError(errorcode.New(errorcode.InvalidRequest, "role not applicable to this resource"))) @@ -209,7 +209,7 @@ var _ = Describe("DriveItemPermissionsService", func() { driveItemInvite.Recipients = []libregraph.DriveRecipient{ {ObjectId: libregraph.PtrString("1"), LibreGraphRecipientType: libregraph.PtrString("user")}, } - driveItemInvite.Roles = []string{unifiedrole.NewEditorUnifiedRole().GetId()} + driveItemInvite.Roles = []string{unifiedrole.UnifiedRoleEditorID} permission, err := driveItemPermissionsService.Invite(context.Background(), driveItemId, driveItemInvite) Expect(err).To(MatchError(errorcode.New(errorcode.InvalidRequest, "role not applicable to this resource"))) @@ -221,7 +221,7 @@ var _ = Describe("DriveItemPermissionsService", func() { driveItemInvite.Recipients = []libregraph.DriveRecipient{ {ObjectId: libregraph.PtrString("1"), LibreGraphRecipientType: libregraph.PtrString("user")}, } - driveItemInvite.Roles = []string{unifiedrole.NewFileEditorUnifiedRole().GetId()} + driveItemInvite.Roles = []string{unifiedrole.UnifiedRoleFileEditorID} permission, err := driveItemPermissionsService.Invite(context.Background(), driveItemId, driveItemInvite) Expect(err).To(MatchError(errorcode.New(errorcode.InvalidRequest, "role not applicable to this resource"))) @@ -836,7 +836,7 @@ var _ = Describe("DriveItemPermissionsService", func() { gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) - driveItemPermission.SetRoles([]string{unifiedrole.NewSpaceViewerUnifiedRole().GetId()}) + driveItemPermission.SetRoles([]string{unifiedrole.UnifiedRoleSpaceViewerID}) res, err := driveItemPermissionsService.UpdatePermission(context.Background(), driveItemId, "permissionid", driveItemPermission) Expect(err).To(MatchError(errorcode.New(errorcode.InvalidRequest, "role not applicable to this resource"))) Expect(res).To(BeZero()) @@ -853,7 +853,7 @@ var _ = Describe("DriveItemPermissionsService", func() { gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil) gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) - driveItemPermission.SetRoles([]string{unifiedrole.NewFileEditorUnifiedRole().GetId()}) + driveItemPermission.SetRoles([]string{unifiedrole.UnifiedRoleFileEditorID}) spaceId := &provider.ResourceId{ StorageId: "1", SpaceId: "2", @@ -1036,7 +1036,7 @@ var _ = Describe("DriveItemPermissionsApi", func() { ObjectId: libregraph.PtrString("1"), LibreGraphRecipientType: libregraph.PtrString("user")}, }, - Roles: []string{unifiedrole.NewViewerUnifiedRole().GetId()}, + Roles: []string{unifiedrole.UnifiedRoleViewerID}, } }) diff --git a/services/graph/pkg/service/v0/base.go b/services/graph/pkg/service/v0/base.go index dff462afd..6d7dabbd1 100644 --- a/services/graph/pkg/service/v0/base.go +++ b/services/graph/pkg/service/v0/base.go @@ -191,10 +191,10 @@ func (g BaseGraphService) cs3SpacePermissionsToLibreGraph(ctx context.Context, s p.SetExpirationDateTime(time.Unix(int64(exp.GetSeconds()), int64(exp.GetNanos()))) } - if role := unifiedrole.CS3ResourcePermissionsToUnifiedRole(perm, unifiedrole.UnifiedRoleConditionDrive); role != nil { + if role := unifiedrole.CS3ResourcePermissionsToDefinition(perm, unifiedrole.UnifiedRoleConditionDrive); role != nil { switch apiVersion { case APIVersion_1: - if r := unifiedrole.GetLegacyName(*role); r != "" { + if r := unifiedrole.GetLegacyDefinitionName(*role); r != "" { p.SetRoles([]string{r}) } case APIVersion_1_Beta_1: @@ -392,7 +392,7 @@ func (g BaseGraphService) cs3UserShareToPermission(ctx context.Context, share *c if share.GetCtime() != nil { perm.SetCreatedDateTime(cs3TimestampToTime(share.GetCtime())) } - role := unifiedrole.CS3ResourcePermissionsToUnifiedRole( + role := unifiedrole.CS3ResourcePermissionsToDefinition( share.GetPermissions().GetPermissions(), roleCondition, ) @@ -695,7 +695,7 @@ func (g BaseGraphService) updateUserShare(ctx context.Context, permissionID stri var permissionsUpdated, ok bool if roles, ok = newPermission.GetRolesOk(); ok && len(roles) > 0 { for _, roleID := range roles { - role, err := unifiedrole.NewUnifiedRoleFromID(roleID) + role, err := unifiedrole.GetDefinition(unifiedrole.RoleFilterIDs(roleID)) if err != nil { g.logger.Debug().Err(err).Interface("role", role).Msg("unable to convert requested role") return nil, err diff --git a/services/graph/pkg/service/v0/rolemanagement.go b/services/graph/pkg/service/v0/rolemanagement.go index 3fdfd4ef3..d1eb42999 100644 --- a/services/graph/pkg/service/v0/rolemanagement.go +++ b/services/graph/pkg/service/v0/rolemanagement.go @@ -6,7 +6,6 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/render" - libregraph "github.com/owncloud/libre-graph-api-go" "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" "github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole" @@ -15,7 +14,7 @@ import ( // GetRoleDefinitions a list of permission roles than can be used when sharing with users or groups func (g Graph) GetRoleDefinitions(w http.ResponseWriter, r *http.Request) { render.Status(r, http.StatusOK) - render.JSON(w, r, unifiedrole.GetBuiltinRoleDefinitionList(unifiedrole.RoleFilterIDs(g.config.UnifiedRoles.AvailableRoles...))) + render.JSON(w, r, unifiedrole.GetDefinitions(unifiedrole.RoleFilterIDs(g.config.UnifiedRoles.AvailableRoles...))) } // GetRoleDefinition a permission role than can be used when sharing with users or groups @@ -27,7 +26,7 @@ func (g Graph) GetRoleDefinition(w http.ResponseWriter, r *http.Request) { errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping role id failed") return } - role, err := getRoleDefinition(roleID, g.config.UnifiedRoles.AvailableRoles) + role, err := unifiedrole.GetDefinition(unifiedrole.RoleFilterIDs(roleID)) if err != nil { logger.Debug().Str("roleID", roleID).Msg("could not get role: not found") errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, err.Error()) @@ -36,12 +35,3 @@ func (g Graph) GetRoleDefinition(w http.ResponseWriter, r *http.Request) { render.Status(r, http.StatusOK) render.JSON(w, r, role) } - -func getRoleDefinition(roleID string, availableRoles []string) (*libregraph.UnifiedRoleDefinition, error) { - for _, role := range unifiedrole.GetBuiltinRoleDefinitionList(unifiedrole.RoleFilterIDs(availableRoles...)) { - if role != nil && role.Id != nil && *role.Id == roleID { - return role, nil - } - } - return nil, unifiedrole.ErrUnknownUnifiedRole -} diff --git a/services/graph/pkg/service/v0/utils.go b/services/graph/pkg/service/v0/utils.go index 9a1cd60ee..755e45c29 100644 --- a/services/graph/pkg/service/v0/utils.go +++ b/services/graph/pkg/service/v0/utils.go @@ -16,11 +16,12 @@ import ( "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" libregraph "github.com/owncloud/libre-graph-api-go" + "golang.org/x/sync/errgroup" + "github.com/owncloud/ocis/v2/ocis-pkg/log" "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" - "golang.org/x/sync/errgroup" ) // StrictJSONUnmarshal is a wrapper around json.Unmarshal that returns an error if the json contains unknown fields. @@ -426,7 +427,7 @@ func cs3ReceivedShareToLibreGraphPermissions(ctx context.Context, logger *log.Lo if err != nil { return nil, err } - role := unifiedrole.CS3ResourcePermissionsToUnifiedRole(permissionSet, condition) + role := unifiedrole.CS3ResourcePermissionsToDefinition(permissionSet, condition) if role != nil { permission.SetRoles([]string{role.GetId()}) diff --git a/services/graph/pkg/unifiedrole/conversion.go b/services/graph/pkg/unifiedrole/conversion.go new file mode 100644 index 000000000..c5bb764f2 --- /dev/null +++ b/services/graph/pkg/unifiedrole/conversion.go @@ -0,0 +1,226 @@ +package unifiedrole + +import ( + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/v2/pkg/conversions" + libregraph "github.com/owncloud/libre-graph-api-go" +) + +// PermissionsToCS3ResourcePermissions converts the provided libregraph UnifiedRolePermissions to a cs3 ResourcePermissions +func PermissionsToCS3ResourcePermissions(unifiedRolePermissions []*libregraph.UnifiedRolePermission) *provider.ResourcePermissions { + p := &provider.ResourcePermissions{} + + for _, permission := range unifiedRolePermissions { + for _, allowedResourceAction := range permission.AllowedResourceActions { + switch allowedResourceAction { + case DriveItemPermissionsCreate: + p.AddGrant = true + case DriveItemChildrenCreate: + p.CreateContainer = true + case DriveItemStandardDelete: + p.Delete = true + case DriveItemPathRead: + p.GetPath = true + case DriveItemQuotaRead: + p.GetQuota = true + case DriveItemContentRead: + p.InitiateFileDownload = true + case DriveItemUploadCreate: + p.InitiateFileUpload = true + case DriveItemPermissionsRead: + p.ListGrants = true + case DriveItemChildrenRead: + p.ListContainer = true + case DriveItemVersionsRead: + p.ListFileVersions = true + case DriveItemDeletedRead: + p.ListRecycle = true + case DriveItemPathUpdate: + p.Move = true + case DriveItemPermissionsDelete: + p.RemoveGrant = true + case DriveItemDeletedDelete: + p.PurgeRecycle = true + case DriveItemVersionsUpdate: + p.RestoreFileVersion = true + case DriveItemDeletedUpdate: + p.RestoreRecycleItem = true + case DriveItemBasicRead: + p.Stat = true + case DriveItemPermissionsUpdate: + p.UpdateGrant = true + case DriveItemPermissionsDeny: + p.DenyGrant = true + } + } + } + + return p +} + +// CS3ResourcePermissionsToLibregraphActions converts the provided cs3 ResourcePermissions to a list of +// libregraph actions +func CS3ResourcePermissionsToLibregraphActions(p *provider.ResourcePermissions) []string { + var actions []string + + if p.GetAddGrant() { + actions = append(actions, DriveItemPermissionsCreate) + } + + if p.GetCreateContainer() { + actions = append(actions, DriveItemChildrenCreate) + } + + if p.GetDelete() { + actions = append(actions, DriveItemStandardDelete) + } + + if p.GetGetPath() { + actions = append(actions, DriveItemPathRead) + } + + if p.GetGetQuota() { + actions = append(actions, DriveItemQuotaRead) + } + + if p.GetInitiateFileDownload() { + actions = append(actions, DriveItemContentRead) + } + + if p.GetInitiateFileUpload() { + actions = append(actions, DriveItemUploadCreate) + } + + if p.GetListGrants() { + actions = append(actions, DriveItemPermissionsRead) + } + + if p.GetListContainer() { + actions = append(actions, DriveItemChildrenRead) + } + + if p.GetListFileVersions() { + actions = append(actions, DriveItemVersionsRead) + } + + if p.GetListRecycle() { + actions = append(actions, DriveItemDeletedRead) + } + + if p.GetMove() { + actions = append(actions, DriveItemPathUpdate) + } + + if p.GetRemoveGrant() { + actions = append(actions, DriveItemPermissionsDelete) + } + + if p.GetPurgeRecycle() { + actions = append(actions, DriveItemDeletedDelete) + } + + if p.GetRestoreFileVersion() { + actions = append(actions, DriveItemVersionsUpdate) + } + + if p.GetRestoreRecycleItem() { + actions = append(actions, DriveItemDeletedUpdate) + } + + if p.GetStat() { + actions = append(actions, DriveItemBasicRead) + } + + if p.GetUpdateGrant() { + actions = append(actions, DriveItemPermissionsUpdate) + } + + if p.GetDenyGrant() { + actions = append(actions, DriveItemPermissionsDeny) + } + + return actions +} + +// CS3ResourcePermissionsToDefinition tries to find the UnifiedRoleDefinition that matches the supplied +// CS3 ResourcePermissions and constraints. +func _CS3ResourcePermissionsToDefinition(p *provider.ResourcePermissions, constraints string) *libregraph.UnifiedRoleDefinition { + a := CS3ResourcePermissionsToLibregraphActions(p) + actionSet := map[string]struct{}{} + for _, action := range a { + actionSet[action] = struct{}{} + } + + var res *libregraph.UnifiedRoleDefinition + for _, uRole := range GetDefinitions(RoleFilterAll()) { + matchFound := false + for _, uPerm := range uRole.GetRolePermissions() { + if uPerm.GetCondition() != constraints { + // the requested constraints don't match, this isn't our role + continue + } + + // if the actions converted from the ResourcePermissions equal the action the defined for the role, we have match + if resourceActionsEqual(actionSet, uPerm.GetAllowedResourceActions()) { + matchFound = true + break + } + } + if matchFound { + res = uRole + break + } + } + return res +} + +func resourceActionsEqual(targetActionSet map[string]struct{}, actions []string) bool { + if len(targetActionSet) != len(actions) { + return false + } + + for _, action := range actions { + if _, ok := targetActionSet[action]; !ok { + return false + } + } + return true +} + +func CS3ResourcePermissionsToDefinition(p *provider.ResourcePermissions, constraints string) *libregraph.UnifiedRoleDefinition { + var res *libregraph.UnifiedRoleDefinition + matches := GetDefinitions(RoleFilterPermission(RoleFilterMatchExact, constraints, CS3ResourcePermissionsToLibregraphActions(p)...)) + if len(matches) >= 1 { + res = matches[0] + } + + return res +} + +// cs3RoleToDisplayName converts a CS3 role to a human-readable display name +func cs3RoleToDisplayName(role *conversions.Role) string { + if role == nil { + return "" + } + + switch role.Name { + case conversions.RoleViewer: + return _viewerUnifiedRoleDisplayName + case conversions.RoleSpaceViewer: + return _spaceViewerUnifiedRoleDisplayName + case conversions.RoleEditor: + return _editorUnifiedRoleDisplayName + case conversions.RoleSpaceEditor: + return _spaceEditorUnifiedRoleDisplayName + case conversions.RoleFileEditor: + return _fileEditorUnifiedRoleDisplayName + case conversions.RoleEditorLite: + return _editorLiteUnifiedRoleDisplayName + case conversions.RoleManager: + return _managerUnifiedRoleDisplayName + case conversions.RoleSecureViewer: + return _secureViewerUnifiedRoleDisplayName + default: + return "" + } +} diff --git a/services/graph/pkg/unifiedrole/conversion_test.go b/services/graph/pkg/unifiedrole/conversion_test.go new file mode 100644 index 000000000..eec08b204 --- /dev/null +++ b/services/graph/pkg/unifiedrole/conversion_test.go @@ -0,0 +1,76 @@ +package unifiedrole_test + +import ( + "testing" + + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + cs3Conversions "github.com/cs3org/reva/v2/pkg/conversions" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/types" + libregraph "github.com/owncloud/libre-graph-api-go" + + "github.com/owncloud/ocis/v2/ocis-pkg/conversions" + "github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole" +) + +func TestPermissionsToCS3ResourcePermissions(t *testing.T) { + tests := map[string]struct { + cs3Role *cs3Conversions.Role + unifiedRoleDefinition *libregraph.UnifiedRoleDefinition + match bool + }{ + cs3Conversions.RoleViewer: {cs3Conversions.NewViewerRole(), unifiedrole.RoleViewer, true}, + cs3Conversions.RoleEditor: {cs3Conversions.NewEditorRole(), unifiedrole.RoleEditor, true}, + cs3Conversions.RoleFileEditor: {cs3Conversions.NewFileEditorRole(), unifiedrole.RoleFileEditor, true}, + cs3Conversions.RoleManager: {cs3Conversions.NewManagerRole(), unifiedrole.RoleManager, true}, + cs3Conversions.RoleSecureViewer: {cs3Conversions.NewSecureViewerRole(), unifiedrole.RoleSecureViewer, true}, + "no match": {cs3Conversions.NewFileEditorRole(), unifiedrole.RoleManager, false}, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + g := NewWithT(t) + permsFromCS3 := tc.cs3Role.CS3ResourcePermissions() + permsFromUnifiedRole := unifiedrole.PermissionsToCS3ResourcePermissions( + conversions.ToPointerSlice(tc.unifiedRoleDefinition.RolePermissions), + ) + + var matcher types.GomegaMatcher + + if tc.match { + matcher = Equal(permsFromUnifiedRole) + } else { + matcher = Not(Equal(permsFromUnifiedRole)) + } + + g.Expect(permsFromCS3).To(matcher) + }) + } +} + +func TestCS3ResourcePermissionsToDefinition(t *testing.T) { + tests := map[string]struct { + cs3ResourcePermissions *provider.ResourcePermissions + unifiedRoleDefinition *libregraph.UnifiedRoleDefinition + constraints string + }{ + cs3Conversions.RoleViewer + "1": {cs3Conversions.NewViewerRole().CS3ResourcePermissions(), unifiedrole.RoleViewer, unifiedrole.UnifiedRoleConditionFile}, + cs3Conversions.RoleViewer + "2": {cs3Conversions.NewViewerRole().CS3ResourcePermissions(), unifiedrole.RoleViewer, unifiedrole.UnifiedRoleConditionFolder}, + cs3Conversions.RoleEditor: {cs3Conversions.NewEditorRole().CS3ResourcePermissions(), unifiedrole.RoleEditor, unifiedrole.UnifiedRoleConditionFolder}, + cs3Conversions.RoleFileEditor: {cs3Conversions.NewFileEditorRole().CS3ResourcePermissions(), unifiedrole.RoleFileEditor, unifiedrole.UnifiedRoleConditionFile}, + cs3Conversions.RoleManager: {cs3Conversions.NewManagerRole().CS3ResourcePermissions(), unifiedrole.RoleManager, unifiedrole.UnifiedRoleConditionDrive}, + cs3Conversions.RoleSpaceViewer: {cs3Conversions.NewSpaceViewerRole().CS3ResourcePermissions(), unifiedrole.RoleSpaceViewer, unifiedrole.UnifiedRoleConditionDrive}, + cs3Conversions.RoleSpaceEditor: {cs3Conversions.NewSpaceEditorRole().CS3ResourcePermissions(), unifiedrole.RoleSpaceEditor, unifiedrole.UnifiedRoleConditionDrive}, + cs3Conversions.RoleSecureViewer + "1": {cs3Conversions.NewSecureViewerRole().CS3ResourcePermissions(), unifiedrole.RoleSecureViewer, unifiedrole.UnifiedRoleConditionFile}, + cs3Conversions.RoleSecureViewer + "2": {cs3Conversions.NewSecureViewerRole().CS3ResourcePermissions(), unifiedrole.RoleSecureViewer, unifiedrole.UnifiedRoleConditionFolder}, + "custom 1": {&provider.ResourcePermissions{GetPath: true}, nil, unifiedrole.UnifiedRoleConditionFolder}, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + NewWithT(t).Expect( + unifiedrole.CS3ResourcePermissionsToDefinition(tc.cs3ResourcePermissions, tc.constraints), + ).To(Equal(tc.unifiedRoleDefinition)) + }) + } +} diff --git a/services/graph/pkg/unifiedrole/errors.go b/services/graph/pkg/unifiedrole/errors.go index 04b007ba4..e4f095e7a 100644 --- a/services/graph/pkg/unifiedrole/errors.go +++ b/services/graph/pkg/unifiedrole/errors.go @@ -7,4 +7,7 @@ import ( var ( // ErrUnknownUnifiedRole is returned when an unknown unified role is requested. ErrUnknownUnifiedRole = errors.New("unknown unified role, check if the role is enabled") + + // ErrTooManyResults is returned when a filter returns too many results. + ErrTooManyResults = errors.New("too many results, consider using a more specific filter") ) diff --git a/services/graph/pkg/unifiedrole/export_test.go b/services/graph/pkg/unifiedrole/export_test.go new file mode 100644 index 000000000..d402ddc44 --- /dev/null +++ b/services/graph/pkg/unifiedrole/export_test.go @@ -0,0 +1,16 @@ +package unifiedrole + +var ( + // roles + RoleViewer = roleViewer + RoleSpaceViewer = roleSpaceViewer + RoleEditor = roleEditor + RoleSpaceEditor = roleSpaceEditor + RoleFileEditor = roleFileEditor + RoleEditorLite = roleEditorLite + RoleManager = roleManager + RoleSecureViewer = roleSecureViewer + + // functions + WeightDefinitions = weightDefinitions +) diff --git a/services/graph/pkg/unifiedrole/filter.go b/services/graph/pkg/unifiedrole/filter.go new file mode 100644 index 000000000..7e7f020cb --- /dev/null +++ b/services/graph/pkg/unifiedrole/filter.go @@ -0,0 +1,87 @@ +package unifiedrole + +import ( + "slices" + + libregraph "github.com/owncloud/libre-graph-api-go" +) + +type ( + // RoleFilter is used to filter role collections + RoleFilter func(r *libregraph.UnifiedRoleDefinition) bool + + // RoleFilterMatch defines the match behavior of a role filter + RoleFilterMatch int +) + +const ( + // RoleFilterMatchExact is the behavior for role filters that require an exact match + RoleFilterMatchExact RoleFilterMatch = iota + + // RoleFilterMatchSome is the behavior for role filters that require some match + RoleFilterMatchSome +) + +// RoleFilterInvert inverts the provided role filter +func RoleFilterInvert(f RoleFilter) RoleFilter { + return func(r *libregraph.UnifiedRoleDefinition) bool { + return !f(r) + } +} + +// RoleFilterAll returns a role filter that matches all roles +func RoleFilterAll() RoleFilter { + return func(r *libregraph.UnifiedRoleDefinition) bool { + return true + } +} + +// RoleFilterIDs returns a role filter that matches the provided ids +// the filter is always OR! +func RoleFilterIDs(ids ...string) RoleFilter { + return func(r *libregraph.UnifiedRoleDefinition) bool { + return slices.Contains(ids, r.GetId()) + } +} + +// RoleFilterPermission returns a role filter that matches the provided condition and actions pair +func RoleFilterPermission(matchBehavior RoleFilterMatch, condition string, wantActions ...string) RoleFilter { + return func(r *libregraph.UnifiedRoleDefinition) bool { + for _, permission := range r.GetRolePermissions() { + if permission.GetCondition() != condition { + continue + } + + givenActions := permission.GetAllowedResourceActions() + + switch { + case matchBehavior == RoleFilterMatchExact && slices.Equal(givenActions, wantActions): + return true + case matchBehavior == RoleFilterMatchSome: + matches := 0 + + for _, action := range givenActions { + if !slices.Contains(wantActions, action) { + break + } + + matches++ + } + + return len(givenActions) == matches + } + } + + return false + } +} + +// filterRoles filters the provided roles by the provided filter +func filterRoles(roles []*libregraph.UnifiedRoleDefinition, filter RoleFilter) []*libregraph.UnifiedRoleDefinition { + return slices.DeleteFunc( + slices.Clone(roles), + func(r *libregraph.UnifiedRoleDefinition) bool { + return !filter(r) + }, + ) +} diff --git a/services/graph/pkg/unifiedrole/filter_test.go b/services/graph/pkg/unifiedrole/filter_test.go new file mode 100644 index 000000000..c2b28a639 --- /dev/null +++ b/services/graph/pkg/unifiedrole/filter_test.go @@ -0,0 +1,129 @@ +package unifiedrole_test + +import ( + "testing" + + . "github.com/onsi/gomega" + libregraph "github.com/owncloud/libre-graph-api-go" + "google.golang.org/protobuf/proto" + + "github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole" +) + +func TestRoleFilterIDs(t *testing.T) { + NewWithT(t).Expect( + unifiedrole.RoleFilterIDs( + unifiedrole.UnifiedRoleEditorLiteID, + unifiedrole.UnifiedRoleSpaceEditorID, + )(unifiedrole.RoleEditorLite), + ).To(BeTrue()) +} + +func TestRoleFilterInvert(t *testing.T) { + NewWithT(t).Expect( + unifiedrole.RoleFilterInvert( + unifiedrole.RoleFilterAll(), + )(unifiedrole.RoleEditorLite), + ).To(BeFalse()) +} + +func TestRoleFilterAll(t *testing.T) { + NewWithT(t).Expect( + unifiedrole.RoleFilterAll()(unifiedrole.RoleEditorLite), + ).To(BeTrue()) +} + +func TestRoleFilterPermissions(t *testing.T) { + tests := map[string]struct { + unifiedRolePermission []libregraph.UnifiedRolePermission + filterCondition string + filterActions []string + filterMatch bool + }{ + "true | single": { + unifiedRolePermission: []libregraph.UnifiedRolePermission{ + { + Condition: proto.String(unifiedrole.UnifiedRoleConditionDrive), + AllowedResourceActions: []string{ + unifiedrole.DriveItemPermissionsCreate, + }, + }, + }, + filterCondition: unifiedrole.UnifiedRoleConditionDrive, + filterActions: []string{ + unifiedrole.DriveItemPermissionsCreate, + }, + filterMatch: true, + }, + "true | multiple": { + unifiedRolePermission: []libregraph.UnifiedRolePermission{ + { + Condition: proto.String(unifiedrole.UnifiedRoleConditionFolder), + AllowedResourceActions: []string{ + unifiedrole.DriveItemDeletedRead, + }, + }, + { + Condition: proto.String(unifiedrole.UnifiedRoleConditionDrive), + AllowedResourceActions: []string{ + unifiedrole.DriveItemPermissionsCreate, + }, + }, + }, + filterCondition: unifiedrole.UnifiedRoleConditionDrive, + filterActions: []string{ + unifiedrole.DriveItemPermissionsCreate, + }, + filterMatch: true, + }, + "false | cross match": { + unifiedRolePermission: []libregraph.UnifiedRolePermission{ + { + Condition: proto.String(unifiedrole.UnifiedRoleConditionDrive), + AllowedResourceActions: []string{ + unifiedrole.DriveItemDeletedRead, + }, + }, + { + Condition: proto.String(unifiedrole.UnifiedRoleConditionFolder), + AllowedResourceActions: []string{ + unifiedrole.DriveItemPermissionsCreate, + }, + }, + }, + filterCondition: unifiedrole.UnifiedRoleConditionDrive, + filterActions: []string{unifiedrole.DriveItemPermissionsCreate}, + filterMatch: false, + }, + "false | too many actions": { + unifiedRolePermission: []libregraph.UnifiedRolePermission{ + { + Condition: proto.String(unifiedrole.UnifiedRoleConditionDrive), + AllowedResourceActions: []string{ + unifiedrole.DriveItemDeletedRead, + unifiedrole.DriveItemPermissionsCreate, + }, + }, + }, + filterCondition: unifiedrole.UnifiedRoleConditionDrive, + filterActions: []string{ + unifiedrole.DriveItemPermissionsCreate, + }, + filterMatch: false, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + NewWithT(t).Expect( + unifiedrole.RoleFilterPermission( + unifiedrole.RoleFilterMatchExact, + tc.filterCondition, + tc.filterActions..., + )(&libregraph.UnifiedRoleDefinition{ + RolePermissions: tc.unifiedRolePermission, + }), + ).To(Equal(tc.filterMatch)) + }) + } +} diff --git a/services/graph/pkg/unifiedrole/roles.go b/services/graph/pkg/unifiedrole/roles.go new file mode 100644 index 000000000..d1c5dc199 --- /dev/null +++ b/services/graph/pkg/unifiedrole/roles.go @@ -0,0 +1,359 @@ +package unifiedrole + +import ( + "cmp" + "slices" + + libregraph "github.com/owncloud/libre-graph-api-go" + "google.golang.org/protobuf/proto" + + "github.com/cs3org/reva/v2/pkg/conversions" + + "github.com/owncloud/ocis/v2/ocis-pkg/l10n" +) + +const ( + // UnifiedRoleViewerID Unified role viewer id. + UnifiedRoleViewerID = "b1e2218d-eef8-4d4c-b82d-0f1a1b48f3b5" + // UnifiedRoleSpaceViewerID Unified role space viewer id. + UnifiedRoleSpaceViewerID = "a8d5fe5e-96e3-418d-825b-534dbdf22b99" + // UnifiedRoleEditorID Unified role editor id. + UnifiedRoleEditorID = "fb6c3e19-e378-47e5-b277-9732f9de6e21" + // UnifiedRoleSpaceEditorID Unified role space editor id. + UnifiedRoleSpaceEditorID = "58c63c02-1d89-4572-916a-870abc5a1b7d" + // UnifiedRoleFileEditorID Unified role file editor id. + UnifiedRoleFileEditorID = "2d00ce52-1fc2-4dbc-8b95-a73b73395f5a" + // UnifiedRoleEditorLiteID Unified role editor-lite id. + UnifiedRoleEditorLiteID = "1c996275-f1c9-4e71-abdf-a42f6495e960" + // UnifiedRoleManagerID Unified role manager id. + UnifiedRoleManagerID = "312c0871-5ef7-4b3a-85b6-0e4074c64049" + // UnifiedRoleSecureViewerID Unified role secure viewer id. + UnifiedRoleSecureViewerID = "aa97fe03-7980-45ac-9e50-b325749fd7e6" + + // UnifiedRoleConditionDrive defines constraint that matches a Driveroot/Spaceroot + UnifiedRoleConditionDrive = "exists @Resource.Root" + // UnifiedRoleConditionFolder defines constraints that matches a DriveItem representing a Folder + UnifiedRoleConditionFolder = "exists @Resource.Folder" + // UnifiedRoleConditionFile defines a constraint that matches a DriveItem representing a File + UnifiedRoleConditionFile = "exists @Resource.File" + + DriveItemPermissionsCreate = "libre.graph/driveItem/permissions/create" + DriveItemChildrenCreate = "libre.graph/driveItem/children/create" + DriveItemStandardDelete = "libre.graph/driveItem/standard/delete" + DriveItemPathRead = "libre.graph/driveItem/path/read" + DriveItemQuotaRead = "libre.graph/driveItem/quota/read" + DriveItemContentRead = "libre.graph/driveItem/content/read" + DriveItemUploadCreate = "libre.graph/driveItem/upload/create" + DriveItemPermissionsRead = "libre.graph/driveItem/permissions/read" + DriveItemChildrenRead = "libre.graph/driveItem/children/read" + DriveItemVersionsRead = "libre.graph/driveItem/versions/read" + DriveItemDeletedRead = "libre.graph/driveItem/deleted/read" + DriveItemPathUpdate = "libre.graph/driveItem/path/update" + DriveItemPermissionsDelete = "libre.graph/driveItem/permissions/delete" + DriveItemDeletedDelete = "libre.graph/driveItem/deleted/delete" + DriveItemVersionsUpdate = "libre.graph/driveItem/versions/update" + DriveItemDeletedUpdate = "libre.graph/driveItem/deleted/update" + DriveItemBasicRead = "libre.graph/driveItem/basic/read" + DriveItemPermissionsUpdate = "libre.graph/driveItem/permissions/update" + DriveItemPermissionsDeny = "libre.graph/driveItem/permissions/deny" +) + +var ( + // UnifiedRole Viewer, Role Description (resolves directly) + _viewerUnifiedRoleDescription = l10n.Template("View and download.") + + // UnifiedRole Viewer, Role DisplayName (resolves directly) + _viewerUnifiedRoleDisplayName = l10n.Template("Can view") + + // UnifiedRole SpaceViewer, Role Description (resolves directly) + _spaceViewerUnifiedRoleDescription = l10n.Template("View and download.") + + // UnifiedRole SpaseViewer, Role DisplayName (resolves directly) + _spaceViewerUnifiedRoleDisplayName = l10n.Template("Can view") + + // UnifiedRole Editor, Role Description (resolves directly) + _editorUnifiedRoleDescription = l10n.Template("View, download, upload, edit, add and delete.") + + // UnifiedRole Editor, Role DisplayName (resolves directly) + _editorUnifiedRoleDisplayName = l10n.Template("Can edit") + + // UnifiedRole SpaseEditor, Role Description (resolves directly) + _spaceEditorUnifiedRoleDescription = l10n.Template("View, download, upload, edit, add and delete.") + + // UnifiedRole SpaseEditor, Role DisplayName (resolves directly) + _spaceEditorUnifiedRoleDisplayName = l10n.Template("Can edit") + + // UnifiedRole FileEditor, Role Description (resolves directly) + _fileEditorUnifiedRoleDescription = l10n.Template("View, download and edit.") + + // UnifiedRole FileEditor, Role DisplayName (resolves directly) + _fileEditorUnifiedRoleDisplayName = l10n.Template("Can edit") + + // UnifiedRole EditorLite, Role Description (resolves directly) + _editorLiteUnifiedRoleDescription = l10n.Template("View, download and upload.") + + // UnifiedRole EditorLite, Role DisplayName (resolves directly) + _editorLiteUnifiedRoleDisplayName = l10n.Template("Can upload") + + // UnifiedRole Manager, Role Description (resolves directly) + _managerUnifiedRoleDescription = l10n.Template("View, download, upload, edit, add, delete and manage members.") + + // UnifiedRole Manager, Role DisplayName (resolves directly) + _managerUnifiedRoleDisplayName = l10n.Template("Can manage") + + // UnifiedRole SecureViewer, Role Description (resolves directly) + _secureViewerUnifiedRoleDescription = l10n.Template("View only documents, images and PDFs. Watermarks will be applied.") + + // UnifiedRole SecureViewer, Role DisplayName (resolves directly) + _secureViewerUnifiedRoleDisplayName = l10n.Template("Can view (secure)") + + // legacyNames contains the legacy role names. + legacyNames = map[string]string{ + UnifiedRoleViewerID: conversions.RoleViewer, + // one V1 api the "spaceviewer" role was call "viewer" and the "spaceeditor" was "editor", + // we need to stay compatible with that + UnifiedRoleSpaceViewerID: "viewer", + UnifiedRoleSpaceEditorID: "editor", + UnifiedRoleEditorID: conversions.RoleEditor, + UnifiedRoleFileEditorID: conversions.RoleFileEditor, + UnifiedRoleEditorLiteID: conversions.RoleEditorLite, + UnifiedRoleManagerID: conversions.RoleManager, + UnifiedRoleSecureViewerID: conversions.RoleSecureViewer, + } + + // buildInRoles contains the built-in roles. + buildInRoles = []*libregraph.UnifiedRoleDefinition{ + roleViewer, + roleSpaceViewer, + roleEditor, + roleSpaceEditor, + roleFileEditor, + roleEditorLite, + roleManager, + roleSecureViewer, + } + + // roleViewer creates a viewer role. + roleViewer = func() *libregraph.UnifiedRoleDefinition { + r := conversions.NewViewerRole() + return &libregraph.UnifiedRoleDefinition{ + Id: proto.String(UnifiedRoleViewerID), + Description: proto.String(_viewerUnifiedRoleDescription), + DisplayName: proto.String(cs3RoleToDisplayName(r)), + RolePermissions: []libregraph.UnifiedRolePermission{ + { + AllowedResourceActions: CS3ResourcePermissionsToLibregraphActions(r.CS3ResourcePermissions()), + Condition: proto.String(UnifiedRoleConditionFile), + }, + { + AllowedResourceActions: CS3ResourcePermissionsToLibregraphActions(r.CS3ResourcePermissions()), + Condition: proto.String(UnifiedRoleConditionFolder), + }, + }, + LibreGraphWeight: proto.Int32(0), + } + }() + + // roleSpaceViewer creates a spaceviewer role + roleSpaceViewer = func() *libregraph.UnifiedRoleDefinition { + r := conversions.NewSpaceViewerRole() + return &libregraph.UnifiedRoleDefinition{ + Id: proto.String(UnifiedRoleSpaceViewerID), + Description: proto.String(_spaceViewerUnifiedRoleDescription), + DisplayName: proto.String(cs3RoleToDisplayName(r)), + RolePermissions: []libregraph.UnifiedRolePermission{ + { + AllowedResourceActions: CS3ResourcePermissionsToLibregraphActions(r.CS3ResourcePermissions()), + Condition: proto.String(UnifiedRoleConditionDrive), + }, + }, + LibreGraphWeight: proto.Int32(0), + } + }() + + // roleEditor creates an editor role. + roleEditor = func() *libregraph.UnifiedRoleDefinition { + r := conversions.NewEditorRole() + return &libregraph.UnifiedRoleDefinition{ + Id: proto.String(UnifiedRoleEditorID), + Description: proto.String(_editorUnifiedRoleDescription), + DisplayName: proto.String(cs3RoleToDisplayName(r)), + RolePermissions: []libregraph.UnifiedRolePermission{ + { + AllowedResourceActions: CS3ResourcePermissionsToLibregraphActions(r.CS3ResourcePermissions()), + Condition: proto.String(UnifiedRoleConditionFolder), + }, + }, + LibreGraphWeight: proto.Int32(0), + } + }() + + // roleSpaceEditor creates an editor role + roleSpaceEditor = func() *libregraph.UnifiedRoleDefinition { + r := conversions.NewSpaceEditorRole() + return &libregraph.UnifiedRoleDefinition{ + Id: proto.String(UnifiedRoleSpaceEditorID), + Description: proto.String(_spaceEditorUnifiedRoleDescription), + DisplayName: proto.String(cs3RoleToDisplayName(r)), + RolePermissions: []libregraph.UnifiedRolePermission{ + { + AllowedResourceActions: CS3ResourcePermissionsToLibregraphActions(r.CS3ResourcePermissions()), + Condition: proto.String(UnifiedRoleConditionDrive), + }, + }, + LibreGraphWeight: proto.Int32(0), + } + }() + + // roleFileEditor creates a file-editor role + roleFileEditor = func() *libregraph.UnifiedRoleDefinition { + r := conversions.NewFileEditorRole() + return &libregraph.UnifiedRoleDefinition{ + Id: proto.String(UnifiedRoleFileEditorID), + Description: proto.String(_fileEditorUnifiedRoleDescription), + DisplayName: proto.String(cs3RoleToDisplayName(r)), + RolePermissions: []libregraph.UnifiedRolePermission{ + { + AllowedResourceActions: CS3ResourcePermissionsToLibregraphActions(r.CS3ResourcePermissions()), + Condition: proto.String(UnifiedRoleConditionFile), + }, + }, + LibreGraphWeight: proto.Int32(0), + } + }() + + // roleEditorLite creates an editor-lite role + roleEditorLite = func() *libregraph.UnifiedRoleDefinition { + r := conversions.NewEditorLiteRole() + return &libregraph.UnifiedRoleDefinition{ + Id: proto.String(UnifiedRoleEditorLiteID), + Description: proto.String(_editorLiteUnifiedRoleDescription), + DisplayName: proto.String(cs3RoleToDisplayName(r)), + RolePermissions: []libregraph.UnifiedRolePermission{ + { + AllowedResourceActions: CS3ResourcePermissionsToLibregraphActions(r.CS3ResourcePermissions()), + Condition: proto.String(UnifiedRoleConditionFolder), + }, + }, + LibreGraphWeight: proto.Int32(0), + } + }() + + // roleManager creates a manager role + roleManager = func() *libregraph.UnifiedRoleDefinition { + r := conversions.NewManagerRole() + return &libregraph.UnifiedRoleDefinition{ + Id: proto.String(UnifiedRoleManagerID), + Description: proto.String(_managerUnifiedRoleDescription), + DisplayName: proto.String(cs3RoleToDisplayName(r)), + RolePermissions: []libregraph.UnifiedRolePermission{ + { + AllowedResourceActions: CS3ResourcePermissionsToLibregraphActions(r.CS3ResourcePermissions()), + Condition: proto.String(UnifiedRoleConditionDrive), + }, + }, + LibreGraphWeight: proto.Int32(0), + } + }() + + // roleSecureViewer creates a secure viewer role + roleSecureViewer = func() *libregraph.UnifiedRoleDefinition { + r := conversions.NewSecureViewerRole() + return &libregraph.UnifiedRoleDefinition{ + Id: proto.String(UnifiedRoleSecureViewerID), + Description: proto.String(_secureViewerUnifiedRoleDescription), + DisplayName: proto.String(cs3RoleToDisplayName(r)), + RolePermissions: []libregraph.UnifiedRolePermission{ + { + AllowedResourceActions: CS3ResourcePermissionsToLibregraphActions(r.CS3ResourcePermissions()), + Condition: proto.String(UnifiedRoleConditionFile), + }, + { + AllowedResourceActions: CS3ResourcePermissionsToLibregraphActions(r.CS3ResourcePermissions()), + Condition: proto.String(UnifiedRoleConditionFolder), + }, + }, + LibreGraphWeight: proto.Int32(0), + } + }() +) + +// GetDefinitions returns a role filter that matches the provided resources +func GetDefinitions(filter RoleFilter) []*libregraph.UnifiedRoleDefinition { + return filterRoles(buildInRoles, filter) +} + +// GetDefinition returns a role filter that matches the provided resources +func GetDefinition(filter RoleFilter) (*libregraph.UnifiedRoleDefinition, error) { + definitions := filterRoles(buildInRoles, filter) + if len(definitions) == 0 { + return nil, ErrUnknownUnifiedRole + } + + return definitions[0], nil +} + +// GetRolesByPermissions returns a list of role definitions +// that match the provided actions and constraints +func GetRolesByPermissions(actions []string, constraints string, descending bool) []*libregraph.UnifiedRoleDefinition { + roles := GetDefinitions(RoleFilterPermission(RoleFilterMatchSome, constraints, actions...)) + roles = weightDefinitions(roles, constraints, descending) + + return roles +} + +// GetLegacyDefinitionName returns the legacy role name for the provided role +func GetLegacyDefinitionName(definition libregraph.UnifiedRoleDefinition) string { + return legacyNames[definition.GetId()] +} + +// weightDefinitions sorts the provided role definitions by the number of permissions[n].actions they grant, +// the implementation is optimistic and assumes that the weight relies on the number of available actions. +// descending - false - sorts the roles from least to most permissions +// descending - true - sorts the roles from most to least permissions +func weightDefinitions(definitions []*libregraph.UnifiedRoleDefinition, constraints string, descending bool) []*libregraph.UnifiedRoleDefinition { + slices.SortFunc(definitions, func(i, j *libregraph.UnifiedRoleDefinition) int { + var ia []string + for _, rp := range i.GetRolePermissions() { + if rp.GetCondition() == constraints { + ia = append(ia, rp.GetAllowedResourceActions()...) + } + } + + var ja []string + for _, rp := range j.GetRolePermissions() { + if rp.GetCondition() == constraints { + ja = append(ja, rp.GetAllowedResourceActions()...) + } + } + + switch descending { + case true: + return cmp.Compare(len(ja), len(ia)) + default: + return cmp.Compare(len(ia), len(ja)) + } + }) + + for i, definition := range definitions { + definition.LibreGraphWeight = libregraph.PtrInt32(int32(i) + 1) + } + + // return for the sake of consistency, optional because the slice is modified in place + return definitions +} + +// GetAllowedResourceActions returns the allowed resource actions for the provided role by condition +func GetAllowedResourceActions(role *libregraph.UnifiedRoleDefinition, condition string) []string { + if role == nil { + return []string{} + } + + for _, p := range role.GetRolePermissions() { + if p.GetCondition() == condition { + return p.GetAllowedResourceActions() + } + } + + return []string{} +} diff --git a/services/graph/pkg/unifiedrole/roles_test.go b/services/graph/pkg/unifiedrole/roles_test.go new file mode 100644 index 000000000..b51fb24ff --- /dev/null +++ b/services/graph/pkg/unifiedrole/roles_test.go @@ -0,0 +1,236 @@ +package unifiedrole_test + +import ( + "slices" + "testing" + + . "github.com/onsi/gomega" + libregraph "github.com/owncloud/libre-graph-api-go" + "google.golang.org/protobuf/proto" + + "github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole" +) + +func TestGetDefinition(t *testing.T) { + tests := map[string]struct { + ids []string + unifiedRoleDefinition *libregraph.UnifiedRoleDefinition + expectError error + }{ + "pass single": { + ids: []string{unifiedrole.UnifiedRoleViewerID}, + unifiedRoleDefinition: unifiedrole.RoleViewer, + }, + "pass many": { + ids: []string{unifiedrole.UnifiedRoleViewerID, unifiedrole.UnifiedRoleEditorID}, + unifiedRoleDefinition: unifiedrole.RoleViewer, + }, + "fail unknown": { + ids: []string{"unknown"}, + expectError: unifiedrole.ErrUnknownUnifiedRole, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + g := NewWithT(t) + definition, err := unifiedrole.GetDefinition(unifiedrole.RoleFilterIDs(tc.ids...)) + + if tc.expectError != nil { + g.Expect(err).To(MatchError(tc.expectError)) + } else { + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(definition).To(Equal(tc.unifiedRoleDefinition)) + } + }) + } +} + +func TestWeightDefinitions(t *testing.T) { + tests := map[string]struct { + unifiedRoleDefinition []*libregraph.UnifiedRoleDefinition + constraint string + descending bool + expectedDefinitions []*libregraph.UnifiedRoleDefinition + }{ + "ascending": { + []*libregraph.UnifiedRoleDefinition{ + unifiedrole.RoleViewer, + unifiedrole.RoleFileEditor, + }, + unifiedrole.UnifiedRoleConditionFile, + false, + []*libregraph.UnifiedRoleDefinition{ + unifiedrole.RoleViewer, + unifiedrole.RoleFileEditor, + }, + }, + "descending": { + []*libregraph.UnifiedRoleDefinition{ + unifiedrole.RoleViewer, + unifiedrole.RoleFileEditor, + }, + unifiedrole.UnifiedRoleConditionFile, + true, + []*libregraph.UnifiedRoleDefinition{ + unifiedrole.RoleFileEditor, + unifiedrole.RoleViewer, + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + g := NewWithT(t) + for i, generatedDefinition := range unifiedrole.WeightDefinitions(tc.unifiedRoleDefinition, tc.constraint, tc.descending) { + g.Expect(generatedDefinition.Id).To(Equal(tc.expectedDefinitions[i].Id)) + } + }) + } +} + +func TestGetRolesByPermissions(t *testing.T) { + tests := map[string]struct { + givenActions []string + constraints string + unifiedRoleDefinition []*libregraph.UnifiedRoleDefinition + }{ + "ViewerUnifiedRole": { + givenActions: rolesToAction(unifiedrole.RoleViewer), + constraints: unifiedrole.UnifiedRoleConditionFolder, + unifiedRoleDefinition: []*libregraph.UnifiedRoleDefinition{ + unifiedrole.RoleSecureViewer, + unifiedrole.RoleViewer, + }, + }, + "ViewerUnifiedRole | share": { + givenActions: rolesToAction(unifiedrole.RoleViewer), + constraints: unifiedrole.UnifiedRoleConditionFile, + unifiedRoleDefinition: []*libregraph.UnifiedRoleDefinition{ + unifiedrole.RoleSecureViewer, + unifiedrole.RoleViewer, + }, + }, + "NewFileEditorUnifiedRole": { + givenActions: rolesToAction(unifiedrole.RoleFileEditor), + constraints: unifiedrole.UnifiedRoleConditionFile, + unifiedRoleDefinition: []*libregraph.UnifiedRoleDefinition{ + unifiedrole.RoleSecureViewer, + unifiedrole.RoleViewer, + unifiedrole.RoleFileEditor, + }, + }, + "NewEditorUnifiedRole": { + givenActions: rolesToAction(unifiedrole.RoleEditor), + constraints: unifiedrole.UnifiedRoleConditionFolder, + unifiedRoleDefinition: []*libregraph.UnifiedRoleDefinition{ + unifiedrole.RoleSecureViewer, + unifiedrole.RoleViewer, + unifiedrole.RoleEditorLite, + unifiedrole.RoleEditor, + }, + }, + "GetRoles 1": { + givenActions: rolesToAction(unifiedrole.GetDefinitions(unifiedrole.RoleFilterAll())...), + constraints: unifiedrole.UnifiedRoleConditionFile, + unifiedRoleDefinition: []*libregraph.UnifiedRoleDefinition{ + unifiedrole.RoleSecureViewer, + unifiedrole.RoleViewer, + unifiedrole.RoleFileEditor, + }, + }, + "GetRoles 2": { + givenActions: rolesToAction(unifiedrole.GetDefinitions(unifiedrole.RoleFilterAll())...), + constraints: unifiedrole.UnifiedRoleConditionFolder, + unifiedRoleDefinition: []*libregraph.UnifiedRoleDefinition{ + unifiedrole.RoleSecureViewer, + unifiedrole.RoleViewer, + unifiedrole.RoleEditorLite, + unifiedrole.RoleEditor, + }, + }, + "GetRoles 3": { + givenActions: rolesToAction(unifiedrole.GetDefinitions(unifiedrole.RoleFilterAll())...), + constraints: unifiedrole.UnifiedRoleConditionDrive, + unifiedRoleDefinition: []*libregraph.UnifiedRoleDefinition{ + unifiedrole.RoleSpaceViewer, + unifiedrole.RoleSpaceEditor, + unifiedrole.RoleManager, + }, + }, + "single": { + givenActions: []string{unifiedrole.DriveItemQuotaRead}, + constraints: unifiedrole.UnifiedRoleConditionFile, + unifiedRoleDefinition: []*libregraph.UnifiedRoleDefinition{}, + }, + "mixed": { + givenActions: append(rolesToAction(unifiedrole.RoleEditorLite), unifiedrole.DriveItemQuotaRead), + constraints: unifiedrole.UnifiedRoleConditionFolder, + unifiedRoleDefinition: []*libregraph.UnifiedRoleDefinition{ + unifiedrole.RoleSecureViewer, + unifiedrole.RoleEditorLite, + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + g := NewWithT(t) + generatedDefinitions := unifiedrole.GetRolesByPermissions(tc.givenActions, tc.constraints, false) + + g.Expect(len(generatedDefinitions)).To(Equal(len(tc.unifiedRoleDefinition))) + + for i, generatedDefinition := range generatedDefinitions { + g.Expect(generatedDefinition.Id).To(Equal(tc.unifiedRoleDefinition[i].Id)) + g.Expect(*generatedDefinition.LibreGraphWeight).To(Equal(int32(i + 1))) + } + + generatedActions := rolesToAction(generatedDefinitions...) + + g.Expect(len(tc.givenActions) >= len(generatedActions)).To(BeTrue()) + for _, generatedAction := range generatedActions { + g.Expect(slices.Contains(tc.givenActions, generatedAction)).To(BeTrue()) + } + }) + } +} + +func TestGetAllowedResourceActions(t *testing.T) { + tests := map[string]struct { + unifiedRoleDefinition *libregraph.UnifiedRoleDefinition + condition string + expectedActions []string + }{ + "no role": { + expectedActions: []string{}, + }, + "no match": { + unifiedRoleDefinition: &libregraph.UnifiedRoleDefinition{ + RolePermissions: []libregraph.UnifiedRolePermission{ + {Condition: proto.String(unifiedrole.UnifiedRoleConditionDrive), AllowedResourceActions: []string{unifiedrole.DriveItemPermissionsCreate}}, + {Condition: proto.String(unifiedrole.UnifiedRoleConditionFolder), AllowedResourceActions: []string{unifiedrole.DriveItemDeletedRead}}, + }, + }, + condition: unifiedrole.UnifiedRoleConditionFile, + expectedActions: []string{}, + }, + "match": { + unifiedRoleDefinition: &libregraph.UnifiedRoleDefinition{ + RolePermissions: []libregraph.UnifiedRolePermission{ + {Condition: proto.String(unifiedrole.UnifiedRoleConditionDrive), AllowedResourceActions: []string{unifiedrole.DriveItemPermissionsCreate}}, + {Condition: proto.String(unifiedrole.UnifiedRoleConditionFolder), AllowedResourceActions: []string{unifiedrole.DriveItemDeletedRead}}, + }, + }, + condition: unifiedrole.UnifiedRoleConditionFolder, + expectedActions: []string{unifiedrole.DriveItemDeletedRead}, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + NewWithT(t). + Expect(unifiedrole.GetAllowedResourceActions(tc.unifiedRoleDefinition, tc.condition)). + To(ContainElements(tc.expectedActions)) + }) + } +} diff --git a/services/graph/pkg/unifiedrole/unifiedrole.go b/services/graph/pkg/unifiedrole/unifiedrole.go deleted file mode 100644 index 5bf34aab6..000000000 --- a/services/graph/pkg/unifiedrole/unifiedrole.go +++ /dev/null @@ -1,603 +0,0 @@ -package unifiedrole - -import ( - "cmp" - "errors" - "slices" - - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - libregraph "github.com/owncloud/libre-graph-api-go" - "github.com/owncloud/ocis/v2/ocis-pkg/l10n" - "google.golang.org/protobuf/proto" - - "github.com/cs3org/reva/v2/pkg/conversions" -) - -// roleFilter is used to filter role collections -type roleFilter func(r *libregraph.UnifiedRoleDefinition) bool - -var ( - // RoleFilterInvert inverts the provided role filter - RoleFilterInvert = func(f roleFilter) roleFilter { - return func(r *libregraph.UnifiedRoleDefinition) bool { - return !f(r) - } - } - - // RoleFilterIDs returns a role filter that matches the provided ids - RoleFilterIDs = func(ids ...string) roleFilter { - return func(r *libregraph.UnifiedRoleDefinition) bool { - for _, id := range ids { - if r.GetId() == id { - return true - } - } - - return false - } - } -) - -const ( - // UnifiedRoleViewerID Unified role viewer id. - UnifiedRoleViewerID = "b1e2218d-eef8-4d4c-b82d-0f1a1b48f3b5" - // UnifiedRoleSpaceViewerID Unified role space viewer id. - UnifiedRoleSpaceViewerID = "a8d5fe5e-96e3-418d-825b-534dbdf22b99" - // UnifiedRoleEditorID Unified role editor id. - UnifiedRoleEditorID = "fb6c3e19-e378-47e5-b277-9732f9de6e21" - // UnifiedRoleSpaceEditorID Unified role space editor id. - UnifiedRoleSpaceEditorID = "58c63c02-1d89-4572-916a-870abc5a1b7d" - // UnifiedRoleFileEditorID Unified role file editor id. - UnifiedRoleFileEditorID = "2d00ce52-1fc2-4dbc-8b95-a73b73395f5a" - // UnifiedRoleEditorLiteID Unified role editor-lite id. - UnifiedRoleEditorLiteID = "1c996275-f1c9-4e71-abdf-a42f6495e960" - // UnifiedRoleManagerID Unified role manager id. - UnifiedRoleManagerID = "312c0871-5ef7-4b3a-85b6-0e4074c64049" - // UnifiedRoleSecureViewerID Unified role secure viewer id. - UnifiedRoleSecureViewerID = "aa97fe03-7980-45ac-9e50-b325749fd7e6" - - // UnifiedRoleConditionDrive defines constraint that matches a Driveroot/Spaceroot - UnifiedRoleConditionDrive = "exists @Resource.Root" - // UnifiedRoleConditionFolder defines constraints that matches a DriveItem representing a Folder - UnifiedRoleConditionFolder = "exists @Resource.Folder" - // UnifiedRoleConditionFile defines a constraint that matches a DriveItem representing a File - UnifiedRoleConditionFile = "exists @Resource.File" - - DriveItemPermissionsCreate = "libre.graph/driveItem/permissions/create" - DriveItemChildrenCreate = "libre.graph/driveItem/children/create" - DriveItemStandardDelete = "libre.graph/driveItem/standard/delete" - DriveItemPathRead = "libre.graph/driveItem/path/read" - DriveItemQuotaRead = "libre.graph/driveItem/quota/read" - DriveItemContentRead = "libre.graph/driveItem/content/read" - DriveItemUploadCreate = "libre.graph/driveItem/upload/create" - DriveItemPermissionsRead = "libre.graph/driveItem/permissions/read" - DriveItemChildrenRead = "libre.graph/driveItem/children/read" - DriveItemVersionsRead = "libre.graph/driveItem/versions/read" - DriveItemDeletedRead = "libre.graph/driveItem/deleted/read" - DriveItemPathUpdate = "libre.graph/driveItem/path/update" - DriveItemPermissionsDelete = "libre.graph/driveItem/permissions/delete" - DriveItemDeletedDelete = "libre.graph/driveItem/deleted/delete" - DriveItemVersionsUpdate = "libre.graph/driveItem/versions/update" - DriveItemDeletedUpdate = "libre.graph/driveItem/deleted/update" - DriveItemBasicRead = "libre.graph/driveItem/basic/read" - DriveItemPermissionsUpdate = "libre.graph/driveItem/permissions/update" - DriveItemPermissionsDeny = "libre.graph/driveItem/permissions/deny" -) - -var legacyNames = map[string]string{ - UnifiedRoleViewerID: conversions.RoleViewer, - // one V1 api the "spaceviewer" role was call "viewer" and the "spaceeditor" was "editor", - // we need to stay compatible with that - UnifiedRoleSpaceViewerID: "viewer", - UnifiedRoleSpaceEditorID: "editor", - UnifiedRoleEditorID: conversions.RoleEditor, - UnifiedRoleFileEditorID: conversions.RoleFileEditor, - UnifiedRoleEditorLiteID: conversions.RoleEditorLite, - UnifiedRoleManagerID: conversions.RoleManager, - UnifiedRoleSecureViewerID: conversions.RoleSecureViewer, -} - -var ( - // UnifiedRole Viewer, Role Description (resolves directly) - _viewerUnifiedRoleDescription = l10n.Template("View and download.") - // UnifiedRole Viewer, Role DisplayName (resolves directly) - _viewerUnifiedRoleDisplayName = l10n.Template("Can view") - - // UnifiedRole SpaceViewer, Role Description (resolves directly) - _spaceViewerUnifiedRoleDescription = l10n.Template("View and download.") - // UnifiedRole SpaseViewer, Role DisplayName (resolves directly) - _spaceViewerUnifiedRoleDisplayName = l10n.Template("Can view") - - // UnifiedRole Editor, Role Description (resolves directly) - _editorUnifiedRoleDescription = l10n.Template("View, download, upload, edit, add and delete.") - // UnifiedRole Editor, Role DisplayName (resolves directly) - _editorUnifiedRoleDisplayName = l10n.Template("Can edit") - - // UnifiedRole SpaseEditor, Role Description (resolves directly) - _spaceEditorUnifiedRoleDescription = l10n.Template("View, download, upload, edit, add and delete.") - // UnifiedRole SpaseEditor, Role DisplayName (resolves directly) - _spaceEditorUnifiedRoleDisplayName = l10n.Template("Can edit") - - // UnifiedRole FileEditor, Role Description (resolves directly) - _fileEditorUnifiedRoleDescription = l10n.Template("View, download and edit.") - // UnifiedRole FileEditor, Role DisplayName (resolves directly) - _fileEditorUnifiedRoleDisplayName = l10n.Template("Can edit") - - // UnifiedRole EditorLite, Role Description (resolves directly) - _editorLiteUnifiedRoleDescription = l10n.Template("View, download and upload.") - // UnifiedRole EditorLite, Role DisplayName (resolves directly) - _editorLiteUnifiedRoleDisplayName = l10n.Template("Can upload") - - // UnifiedRole Manager, Role Description (resolves directly) - _managerUnifiedRoleDescription = l10n.Template("View, download, upload, edit, add, delete and manage members.") - // UnifiedRole Manager, Role DisplayName (resolves directly) - _managerUnifiedRoleDisplayName = l10n.Template("Can manage") - - // UnifiedRole SecureViewer, Role Description (resolves directly) - _secureViewerUnifiedRoleDescription = l10n.Template("View only documents, images and PDFs. Watermarks will be applied.") - // UnifiedRole SecureViewer, Role DisplayName (resolves directly) - _secureViewerUnifiedRoleDisplayName = l10n.Template("Can view (secure)") -) - -// NewViewerUnifiedRole creates a viewer role. -func NewViewerUnifiedRole() *libregraph.UnifiedRoleDefinition { - r := conversions.NewViewerRole() - return &libregraph.UnifiedRoleDefinition{ - Id: proto.String(UnifiedRoleViewerID), - Description: proto.String(_viewerUnifiedRoleDescription), - DisplayName: displayName(r), - RolePermissions: []libregraph.UnifiedRolePermission{ - { - AllowedResourceActions: convert(r), - Condition: proto.String(UnifiedRoleConditionFile), - }, - { - AllowedResourceActions: convert(r), - Condition: proto.String(UnifiedRoleConditionFolder), - }, - }, - LibreGraphWeight: proto.Int32(0), - } -} - -// NewSpaceViewerUnifiedRole creates a spaceviewer role -func NewSpaceViewerUnifiedRole() *libregraph.UnifiedRoleDefinition { - r := conversions.NewSpaceViewerRole() - return &libregraph.UnifiedRoleDefinition{ - Id: proto.String(UnifiedRoleSpaceViewerID), - Description: proto.String(_spaceViewerUnifiedRoleDescription), - DisplayName: displayName(r), - RolePermissions: []libregraph.UnifiedRolePermission{ - { - AllowedResourceActions: convert(r), - Condition: proto.String(UnifiedRoleConditionDrive), - }, - }, - LibreGraphWeight: proto.Int32(0), - } -} - -// NewEditorUnifiedRole creates an editor role. -func NewEditorUnifiedRole() *libregraph.UnifiedRoleDefinition { - r := conversions.NewEditorRole() - return &libregraph.UnifiedRoleDefinition{ - Id: proto.String(UnifiedRoleEditorID), - Description: proto.String(_editorUnifiedRoleDescription), - DisplayName: displayName(r), - RolePermissions: []libregraph.UnifiedRolePermission{ - { - AllowedResourceActions: convert(r), - Condition: proto.String(UnifiedRoleConditionFolder), - }, - }, - LibreGraphWeight: proto.Int32(0), - } -} - -// NewSpaceEditorUnifiedRole creates an editor role -func NewSpaceEditorUnifiedRole() *libregraph.UnifiedRoleDefinition { - r := conversions.NewSpaceEditorRole() - return &libregraph.UnifiedRoleDefinition{ - Id: proto.String(UnifiedRoleSpaceEditorID), - Description: proto.String(_spaceEditorUnifiedRoleDescription), - DisplayName: displayName(r), - RolePermissions: []libregraph.UnifiedRolePermission{ - { - AllowedResourceActions: convert(r), - Condition: proto.String(UnifiedRoleConditionDrive), - }, - }, - LibreGraphWeight: proto.Int32(0), - } -} - -// NewFileEditorUnifiedRole creates a file-editor role -func NewFileEditorUnifiedRole() *libregraph.UnifiedRoleDefinition { - r := conversions.NewFileEditorRole() - return &libregraph.UnifiedRoleDefinition{ - Id: proto.String(UnifiedRoleFileEditorID), - Description: proto.String(_fileEditorUnifiedRoleDescription), - DisplayName: displayName(r), - RolePermissions: []libregraph.UnifiedRolePermission{ - { - AllowedResourceActions: convert(r), - Condition: proto.String(UnifiedRoleConditionFile), - }, - }, - LibreGraphWeight: proto.Int32(0), - } -} - -// NewEditorLiteUnifiedRole creates an editor-lite role -func NewEditorLiteUnifiedRole() *libregraph.UnifiedRoleDefinition { - r := conversions.NewEditorLiteRole() - return &libregraph.UnifiedRoleDefinition{ - Id: proto.String(UnifiedRoleEditorLiteID), - Description: proto.String(_editorLiteUnifiedRoleDescription), - DisplayName: displayName(r), - RolePermissions: []libregraph.UnifiedRolePermission{ - { - AllowedResourceActions: convert(r), - Condition: proto.String(UnifiedRoleConditionFolder), - }, - }, - LibreGraphWeight: proto.Int32(0), - } -} - -// NewManagerUnifiedRole creates a manager role -func NewManagerUnifiedRole() *libregraph.UnifiedRoleDefinition { - r := conversions.NewManagerRole() - return &libregraph.UnifiedRoleDefinition{ - Id: proto.String(UnifiedRoleManagerID), - Description: proto.String(_managerUnifiedRoleDescription), - DisplayName: displayName(r), - RolePermissions: []libregraph.UnifiedRolePermission{ - { - AllowedResourceActions: convert(r), - Condition: proto.String(UnifiedRoleConditionDrive), - }, - }, - LibreGraphWeight: proto.Int32(0), - } -} - -// NewSecureViewerUnifiedRole creates a secure viewer role -func NewSecureViewerUnifiedRole() *libregraph.UnifiedRoleDefinition { - r := conversions.NewSecureViewerRole() - return &libregraph.UnifiedRoleDefinition{ - Id: proto.String(UnifiedRoleSecureViewerID), - Description: proto.String(_secureViewerUnifiedRoleDescription), - DisplayName: displayName(r), - RolePermissions: []libregraph.UnifiedRolePermission{ - { - AllowedResourceActions: convert(r), - Condition: proto.String(UnifiedRoleConditionFile), - }, - { - AllowedResourceActions: convert(r), - Condition: proto.String(UnifiedRoleConditionFolder), - }, - }, - LibreGraphWeight: proto.Int32(0), - } -} - -// NewUnifiedRoleFromID returns a unified role definition from the provided id -func NewUnifiedRoleFromID(id string) (*libregraph.UnifiedRoleDefinition, error) { - // fixMe: should we consider all roles or only the ones that are enabled? - for _, definition := range GetBuiltinRoleDefinitionList() { - if definition.GetId() != id { - continue - } - - return definition, nil - } - - return nil, errors.New("role not found") -} - -func GetBuiltinRoleDefinitionList(filter ...roleFilter) []*libregraph.UnifiedRoleDefinition { - roles := []*libregraph.UnifiedRoleDefinition{ - NewViewerUnifiedRole(), - NewSpaceViewerUnifiedRole(), - NewEditorUnifiedRole(), - NewSpaceEditorUnifiedRole(), - NewFileEditorUnifiedRole(), - NewEditorLiteUnifiedRole(), - NewManagerUnifiedRole(), - NewSecureViewerUnifiedRole(), - } - - for _, f := range filter { - roles = slices.DeleteFunc(roles, func(r *libregraph.UnifiedRoleDefinition) bool { - return !f(r) - }) - } - - return roles -} - -// GetApplicableRoleDefinitionsForActions returns a list of role definitions -// that match the provided actions and constraints -func GetApplicableRoleDefinitionsForActions(actions []string, constraints string, descending bool) []*libregraph.UnifiedRoleDefinition { - // fixMe: should we consider all roles or only the ones that are enabled? - builtin := GetBuiltinRoleDefinitionList() - definitions := make([]*libregraph.UnifiedRoleDefinition, 0, len(builtin)) - - for _, definition := range builtin { - var definitionMatch bool - - for _, permission := range definition.GetRolePermissions() { - if permission.GetCondition() != constraints { - continue - } - - for i, action := range permission.GetAllowedResourceActions() { - if !slices.Contains(actions, action) { - break - } - if i == len(permission.GetAllowedResourceActions())-1 { - definitionMatch = true - } - } - - if definitionMatch { - break - } - } - - if definitionMatch { - definitions = append(definitions, definition) - } - - } - - return WeightRoleDefinitions(definitions, constraints, descending) -} - -// WeightRoleDefinitions sorts the provided role definitions by the number of permissions[n].actions they grant, -// the implementation is optimistic and assumes that the weight relies on the number of available actions. -// descending - false - sorts the roles from least to most permissions -// descending - true - sorts the roles from most to least permissions -func WeightRoleDefinitions(roleDefinitions []*libregraph.UnifiedRoleDefinition, constraints string, descending bool) []*libregraph.UnifiedRoleDefinition { - slices.SortFunc(roleDefinitions, func(i, j *libregraph.UnifiedRoleDefinition) int { - var ia []string - for _, rp := range i.GetRolePermissions() { - if rp.GetCondition() == constraints { - ia = append(ia, rp.GetAllowedResourceActions()...) - } - } - - var ja []string - for _, rp := range j.GetRolePermissions() { - if rp.GetCondition() == constraints { - ja = append(ja, rp.GetAllowedResourceActions()...) - } - } - - switch descending { - case true: - return cmp.Compare(len(ja), len(ia)) - default: - return cmp.Compare(len(ia), len(ja)) - } - }) - - for i, definition := range roleDefinitions { - definition.LibreGraphWeight = libregraph.PtrInt32(int32(i) + 1) - } - - // return for the sage of consistency, optional because the slice is modified in place - return roleDefinitions -} - -// PermissionsToCS3ResourcePermissions converts the provided libregraph UnifiedRolePermissions to a cs3 ResourcePermissions -func PermissionsToCS3ResourcePermissions(unifiedRolePermissions []*libregraph.UnifiedRolePermission) *provider.ResourcePermissions { - p := &provider.ResourcePermissions{} - - for _, permission := range unifiedRolePermissions { - for _, allowedResourceAction := range permission.AllowedResourceActions { - switch allowedResourceAction { - case DriveItemPermissionsCreate: - p.AddGrant = true - case DriveItemChildrenCreate: - p.CreateContainer = true - case DriveItemStandardDelete: - p.Delete = true - case DriveItemPathRead: - p.GetPath = true - case DriveItemQuotaRead: - p.GetQuota = true - case DriveItemContentRead: - p.InitiateFileDownload = true - case DriveItemUploadCreate: - p.InitiateFileUpload = true - case DriveItemPermissionsRead: - p.ListGrants = true - case DriveItemChildrenRead: - p.ListContainer = true - case DriveItemVersionsRead: - p.ListFileVersions = true - case DriveItemDeletedRead: - p.ListRecycle = true - case DriveItemPathUpdate: - p.Move = true - case DriveItemPermissionsDelete: - p.RemoveGrant = true - case DriveItemDeletedDelete: - p.PurgeRecycle = true - case DriveItemVersionsUpdate: - p.RestoreFileVersion = true - case DriveItemDeletedUpdate: - p.RestoreRecycleItem = true - case DriveItemBasicRead: - p.Stat = true - case DriveItemPermissionsUpdate: - p.UpdateGrant = true - case DriveItemPermissionsDeny: - p.DenyGrant = true - } - } - } - - return p -} - -// CS3ResourcePermissionsToLibregraphActions converts the provided cs3 ResourcePermissions to a list of -// libregraph actions -func CS3ResourcePermissionsToLibregraphActions(p *provider.ResourcePermissions) (actions []string) { - if p.GetAddGrant() { - actions = append(actions, DriveItemPermissionsCreate) - } - if p.GetCreateContainer() { - actions = append(actions, DriveItemChildrenCreate) - } - if p.GetDelete() { - actions = append(actions, DriveItemStandardDelete) - } - if p.GetGetPath() { - actions = append(actions, DriveItemPathRead) - } - if p.GetGetQuota() { - actions = append(actions, DriveItemQuotaRead) - } - if p.GetInitiateFileDownload() { - actions = append(actions, DriveItemContentRead) - } - if p.GetInitiateFileUpload() { - actions = append(actions, DriveItemUploadCreate) - } - if p.GetListGrants() { - actions = append(actions, DriveItemPermissionsRead) - } - if p.GetListContainer() { - actions = append(actions, DriveItemChildrenRead) - } - if p.GetListFileVersions() { - actions = append(actions, DriveItemVersionsRead) - } - if p.GetListRecycle() { - actions = append(actions, DriveItemDeletedRead) - } - if p.GetMove() { - actions = append(actions, DriveItemPathUpdate) - } - if p.GetRemoveGrant() { - actions = append(actions, DriveItemPermissionsDelete) - } - if p.GetPurgeRecycle() { - actions = append(actions, DriveItemDeletedDelete) - } - if p.GetRestoreFileVersion() { - actions = append(actions, DriveItemVersionsUpdate) - } - if p.GetRestoreRecycleItem() { - actions = append(actions, DriveItemDeletedUpdate) - } - if p.GetStat() { - actions = append(actions, DriveItemBasicRead) - } - if p.GetUpdateGrant() { - actions = append(actions, DriveItemPermissionsUpdate) - } - if p.GetDenyGrant() { - actions = append(actions, DriveItemPermissionsDeny) - } - return actions -} - -func GetLegacyName(role libregraph.UnifiedRoleDefinition) string { - return legacyNames[role.GetId()] -} - -// CS3ResourcePermissionsToUnifiedRole tries to find the UnifiedRoleDefinition that matches the supplied -// CS3 ResourcePermissions and constraints. -func CS3ResourcePermissionsToUnifiedRole(p *provider.ResourcePermissions, constraints string) *libregraph.UnifiedRoleDefinition { - actionSet := map[string]struct{}{} - for _, action := range CS3ResourcePermissionsToLibregraphActions(p) { - actionSet[action] = struct{}{} - } - - var res *libregraph.UnifiedRoleDefinition - // fixMe: should we consider all roles or only the ones that are enabled? - for _, uRole := range GetBuiltinRoleDefinitionList() { - matchFound := false - for _, uPerm := range uRole.GetRolePermissions() { - if uPerm.GetCondition() != constraints { - // the requested constraints don't match, this isn't our role - continue - } - - // if the actions converted from the ResourcePermissions equal the action the defined for the role, we have match - if resourceActionsEqual(actionSet, uPerm.GetAllowedResourceActions()) { - matchFound = true - break - } - } - if matchFound { - res = uRole - break - } - } - return res -} - -func resourceActionsEqual(targetActionSet map[string]struct{}, actions []string) bool { - if len(targetActionSet) != len(actions) { - return false - } - - for _, action := range actions { - if _, ok := targetActionSet[action]; !ok { - return false - } - } - return true -} - -func displayName(role *conversions.Role) *string { - if role == nil { - return nil - } - - var displayName string - switch role.Name { - case conversions.RoleViewer: - displayName = _viewerUnifiedRoleDisplayName - case conversions.RoleSpaceViewer: - displayName = _spaceViewerUnifiedRoleDisplayName - case conversions.RoleEditor: - displayName = _editorUnifiedRoleDisplayName - case conversions.RoleSpaceEditor: - displayName = _spaceEditorUnifiedRoleDisplayName - case conversions.RoleFileEditor: - displayName = _fileEditorUnifiedRoleDisplayName - case conversions.RoleEditorLite: - displayName = _editorLiteUnifiedRoleDisplayName - case conversions.RoleManager: - displayName = _managerUnifiedRoleDisplayName - case conversions.RoleSecureViewer: - displayName = _secureViewerUnifiedRoleDisplayName - default: - return nil - } - return proto.String(displayName) -} - -func convert(role *conversions.Role) []string { - actions := make([]string, 0, 8) - if role == nil && role.CS3ResourcePermissions() == nil { - return actions - } - return CS3ResourcePermissionsToLibregraphActions(role.CS3ResourcePermissions()) -} - -func GetAllowedResourceActions(role *libregraph.UnifiedRoleDefinition, condition string) []string { - for _, p := range role.GetRolePermissions() { - if p.GetCondition() == condition { - return p.GetAllowedResourceActions() - } - } - return []string{} -} diff --git a/services/graph/pkg/unifiedrole/unifiedrole_suite_test.go b/services/graph/pkg/unifiedrole/unifiedrole_suite_test.go index 2e9a37808..098870b3c 100644 --- a/services/graph/pkg/unifiedrole/unifiedrole_suite_test.go +++ b/services/graph/pkg/unifiedrole/unifiedrole_suite_test.go @@ -1,13 +1,24 @@ package unifiedrole_test import ( - "testing" + "slices" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" + libregraph "github.com/owncloud/libre-graph-api-go" ) -func TestUnifiedrole(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Unifiedrole Suite") +func rolesToAction(definitions ...*libregraph.UnifiedRoleDefinition) []string { + var actions []string + + for _, definition := range definitions { + for _, permission := range definition.GetRolePermissions() { + for _, action := range permission.GetAllowedResourceActions() { + if slices.Contains(actions, action) { + continue + } + actions = append(actions, action) + } + } + } + + return actions } diff --git a/services/graph/pkg/unifiedrole/unifiedrole_test.go b/services/graph/pkg/unifiedrole/unifiedrole_test.go deleted file mode 100644 index 145dd66c6..000000000 --- a/services/graph/pkg/unifiedrole/unifiedrole_test.go +++ /dev/null @@ -1,305 +0,0 @@ -package unifiedrole_test - -import ( - "slices" - - rConversions "github.com/cs3org/reva/v2/pkg/conversions" - "github.com/owncloud/ocis/v2/ocis-pkg/conversions" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/onsi/gomega/types" - libregraph "github.com/owncloud/libre-graph-api-go" - - "github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole" -) - -var _ = Describe("unifiedroles", func() { - DescribeTable("CS3ResourcePermissionsToUnifiedRole", - func(legacyRole *rConversions.Role, unifiedRole *libregraph.UnifiedRoleDefinition, constraints string) { - cs3perm := legacyRole.CS3ResourcePermissions() - - r := unifiedrole.CS3ResourcePermissionsToUnifiedRole(cs3perm, constraints) - Expect(r.GetId()).To(Equal(unifiedRole.GetId())) - - }, - Entry(rConversions.RoleViewer, rConversions.NewViewerRole(), unifiedrole.NewViewerUnifiedRole(), unifiedrole.UnifiedRoleConditionFile), - Entry(rConversions.RoleViewer, rConversions.NewViewerRole(), unifiedrole.NewViewerUnifiedRole(), unifiedrole.UnifiedRoleConditionFolder), - Entry(rConversions.RoleEditor, rConversions.NewEditorRole(), unifiedrole.NewEditorUnifiedRole(), unifiedrole.UnifiedRoleConditionFolder), - Entry(rConversions.RoleFileEditor, rConversions.NewFileEditorRole(), unifiedrole.NewFileEditorUnifiedRole(), unifiedrole.UnifiedRoleConditionFile), - Entry(rConversions.RoleManager, rConversions.NewManagerRole(), unifiedrole.NewManagerUnifiedRole(), unifiedrole.UnifiedRoleConditionDrive), - Entry(rConversions.RoleSpaceViewer, rConversions.NewSpaceViewerRole(), unifiedrole.NewSpaceViewerUnifiedRole(), unifiedrole.UnifiedRoleConditionDrive), - Entry(rConversions.RoleSpaceEditor, rConversions.NewSpaceEditorRole(), unifiedrole.NewSpaceEditorUnifiedRole(), unifiedrole.UnifiedRoleConditionDrive), - Entry(rConversions.RoleSecureViewer, rConversions.NewSecureViewerRole(), unifiedrole.NewSecureViewerUnifiedRole(), unifiedrole.UnifiedRoleConditionFile), - Entry(rConversions.RoleSecureViewer, rConversions.NewSecureViewerRole(), unifiedrole.NewSecureViewerUnifiedRole(), unifiedrole.UnifiedRoleConditionFolder), - ) - - DescribeTable("UnifiedRolePermissionsToCS3ResourcePermissions", - func(cs3Role *rConversions.Role, libregraphRole *libregraph.UnifiedRoleDefinition, match bool) { - permsFromCS3 := cs3Role.CS3ResourcePermissions() - permsFromUnifiedRole := unifiedrole.PermissionsToCS3ResourcePermissions( - conversions.ToPointerSlice(libregraphRole.RolePermissions), - ) - - var matcher types.GomegaMatcher - - if match { - matcher = Equal(permsFromUnifiedRole) - } else { - matcher = Not(Equal(permsFromUnifiedRole)) - } - - Expect(permsFromCS3).To(matcher) - }, - Entry(rConversions.RoleViewer, rConversions.NewViewerRole(), unifiedrole.NewViewerUnifiedRole(), true), - Entry(rConversions.RoleEditor, rConversions.NewEditorRole(), unifiedrole.NewEditorUnifiedRole(), true), - Entry(rConversions.RoleFileEditor, rConversions.NewFileEditorRole(), unifiedrole.NewFileEditorUnifiedRole(), true), - Entry(rConversions.RoleManager, rConversions.NewManagerRole(), unifiedrole.NewManagerUnifiedRole(), true), - Entry(rConversions.RoleSecureViewer, rConversions.NewSecureViewerRole(), unifiedrole.NewSecureViewerUnifiedRole(), true), - Entry("no match", rConversions.NewFileEditorRole(), unifiedrole.NewManagerUnifiedRole(), false), - ) - - DescribeTable("WeightRoleDefinitions", - func(roleDefinitions []*libregraph.UnifiedRoleDefinition, constraint string, descending bool, expectedDefinitions []*libregraph.UnifiedRoleDefinition) { - - for i, generatedDefinition := range unifiedrole.WeightRoleDefinitions(roleDefinitions, constraint, descending) { - Expect(generatedDefinition.Id).To(Equal(expectedDefinitions[i].Id)) - } - }, - - Entry("ascending", - []*libregraph.UnifiedRoleDefinition{ - unifiedrole.NewViewerUnifiedRole(), - unifiedrole.NewFileEditorUnifiedRole(), - }, - unifiedrole.UnifiedRoleConditionFile, - false, - []*libregraph.UnifiedRoleDefinition{ - unifiedrole.NewViewerUnifiedRole(), - unifiedrole.NewFileEditorUnifiedRole(), - }, - ), - - Entry("descending", - []*libregraph.UnifiedRoleDefinition{ - unifiedrole.NewViewerUnifiedRole(), - unifiedrole.NewFileEditorUnifiedRole(), - }, - unifiedrole.UnifiedRoleConditionFile, - true, - []*libregraph.UnifiedRoleDefinition{ - unifiedrole.NewFileEditorUnifiedRole(), - unifiedrole.NewViewerUnifiedRole(), - }, - ), - ) - - { - rolesToAction := func(definitions ...*libregraph.UnifiedRoleDefinition) []string { - var actions []string - - for _, definition := range definitions { - for _, permission := range definition.GetRolePermissions() { - for _, action := range permission.GetAllowedResourceActions() { - if slices.Contains(actions, action) { - continue - } - actions = append(actions, action) - } - } - } - - return actions - } - - DescribeTable("GetApplicableRoleDefinitionsForActions", - func(givenActions []string, constraints string, listFederatedRoles bool, expectedDefinitions []*libregraph.UnifiedRoleDefinition) { - - generatedDefinitions := unifiedrole.GetApplicableRoleDefinitionsForActions(givenActions, constraints, listFederatedRoles, false) - - Expect(len(generatedDefinitions)).To(Equal(len(expectedDefinitions))) - - for i, generatedDefinition := range generatedDefinitions { - Expect(generatedDefinition.Id).To(Equal(expectedDefinitions[i].Id)) - Expect(*generatedDefinition.LibreGraphWeight).To(Equal(int32(i + 1))) - } - - generatedActions := rolesToAction(generatedDefinitions...) - Expect(len(givenActions) >= len(generatedActions)).To(BeTrue()) - - for _, generatedAction := range generatedActions { - Expect(slices.Contains(givenActions, generatedAction)).To(BeTrue()) - } - }, - - Entry( - "ViewerUnifiedRole", - rolesToAction(unifiedrole.NewViewerUnifiedRole()), - unifiedrole.UnifiedRoleConditionFolder, - false, - []*libregraph.UnifiedRoleDefinition{ - unifiedrole.NewSecureViewerUnifiedRole(), - unifiedrole.NewViewerUnifiedRole(), - }, - ), - - Entry( - "ViewerUnifiedRole | share", - rolesToAction(unifiedrole.NewViewerUnifiedRole()), - unifiedrole.UnifiedRoleConditionFile, - false, - []*libregraph.UnifiedRoleDefinition{ - unifiedrole.NewSecureViewerUnifiedRole(), - unifiedrole.NewViewerUnifiedRole(), - }, - ), - - Entry( - "ViewerUnifiedRole | share", - rolesToAction(unifiedrole.NewViewerUnifiedRole()), - unifiedrole.UnifiedRoleConditionFile, - true, - []*libregraph.UnifiedRoleDefinition{ - unifiedrole.NewViewerUnifiedRole(), - }, - ), - - Entry( - "EditorUnifiedRole | share folder", - rolesToAction(unifiedrole.NewEditorUnifiedRole()), - unifiedrole.UnifiedRoleConditionFolder, - true, - []*libregraph.UnifiedRoleDefinition{ - unifiedrole.NewViewerUnifiedRole(), - unifiedrole.NewEditorUnifiedRole(), - }, - ), - - Entry( - "EditorUnifiedRole | share file", - rolesToAction(unifiedrole.NewEditorUnifiedRole()), - unifiedrole.UnifiedRoleConditionFile, - true, - []*libregraph.UnifiedRoleDefinition{ - unifiedrole.NewViewerUnifiedRole(), - unifiedrole.NewFileEditorUnifiedRole(), - }, - ), - - Entry( - "NewFileEditorUnifiedRole", - rolesToAction(unifiedrole.NewFileEditorUnifiedRole()), - unifiedrole.UnifiedRoleConditionFile, - false, - []*libregraph.UnifiedRoleDefinition{ - unifiedrole.NewSecureViewerUnifiedRole(), - unifiedrole.NewViewerUnifiedRole(), - unifiedrole.NewFileEditorUnifiedRole(), - }, - ), - - Entry( - "NewEditorUnifiedRole", - rolesToAction(unifiedrole.NewEditorUnifiedRole()), - unifiedrole.UnifiedRoleConditionFolder, - false, - []*libregraph.UnifiedRoleDefinition{ - unifiedrole.NewSecureViewerUnifiedRole(), - unifiedrole.NewViewerUnifiedRole(), - unifiedrole.NewEditorLiteUnifiedRole(), - unifiedrole.NewEditorUnifiedRole(), - }, - ), - - Entry( - "GetBuiltinRoleDefinitionList", - rolesToAction(unifiedrole.GetBuiltinRoleDefinitionList()...), - unifiedrole.UnifiedRoleConditionFile, - false, - []*libregraph.UnifiedRoleDefinition{ - unifiedrole.NewSecureViewerUnifiedRole(), - unifiedrole.NewViewerUnifiedRole(), - unifiedrole.NewFileEditorUnifiedRole(), - }, - ), - - Entry( - "GetBuiltinRoleDefinitionList", - rolesToAction(unifiedrole.GetBuiltinRoleDefinitionList()...), - unifiedrole.UnifiedRoleConditionFolder, - false, - []*libregraph.UnifiedRoleDefinition{ - unifiedrole.NewSecureViewerUnifiedRole(), - unifiedrole.NewViewerUnifiedRole(), - unifiedrole.NewEditorLiteUnifiedRole(), - unifiedrole.NewEditorUnifiedRole(), - }, - ), - - Entry( - "GetBuiltinRoleDefinitionList", - rolesToAction(unifiedrole.GetBuiltinRoleDefinitionList()...), - unifiedrole.UnifiedRoleConditionDrive, - false, - []*libregraph.UnifiedRoleDefinition{ - unifiedrole.NewSpaceViewerUnifiedRole(), - unifiedrole.NewSpaceEditorUnifiedRole(), - unifiedrole.NewManagerUnifiedRole(), - }, - ), - - Entry( - "single", - []string{unifiedrole.DriveItemQuotaRead}, - unifiedrole.UnifiedRoleConditionFile, - false, - []*libregraph.UnifiedRoleDefinition{}, - ), - - Entry( - "mixed", - append(rolesToAction(unifiedrole.NewEditorLiteUnifiedRole()), unifiedrole.DriveItemQuotaRead), - unifiedrole.UnifiedRoleConditionFolder, - false, - []*libregraph.UnifiedRoleDefinition{ - unifiedrole.NewSecureViewerUnifiedRole(), - unifiedrole.NewEditorLiteUnifiedRole(), - }, - ), - ) - } - - { - var newUnifiedRoleFromIDEntries []TableEntry - attachEntry := func(name, id string, definition *libregraph.UnifiedRoleDefinition, errors bool) { - e := Entry( - name, - id, - definition, - errors, - ) - - newUnifiedRoleFromIDEntries = append(newUnifiedRoleFromIDEntries, e) - } - - for _, definition := range unifiedrole.GetBuiltinRoleDefinitionList() { - attachEntry(definition.GetDisplayName(), definition.GetId(), definition, false) - } - - attachEntry("unknown", "123", nil, true) - - DescribeTable("NewUnifiedRoleFromID", - func(id string, expectedRole *libregraph.UnifiedRoleDefinition, expectError bool) { - role, err := unifiedrole.NewUnifiedRoleFromID(id) - - if expectError { - Expect(err).To(HaveOccurred()) - } else { - Expect(err).NotTo(HaveOccurred()) - Expect(role).To(Equal(expectedRole)) - } - }, - newUnifiedRoleFromIDEntries, - ) - } -}) diff --git a/services/graph/pkg/validate/libregraph.go b/services/graph/pkg/validate/libregraph.go index d5007836b..8e863dbfc 100644 --- a/services/graph/pkg/validate/libregraph.go +++ b/services/graph/pkg/validate/libregraph.go @@ -90,9 +90,9 @@ func rolesAndActions(ctx context.Context, sl validator.StructLevel, roles, actio switch roles, ok := ctx.Value(_contextRoleIDsValueKey).([]string); { case ok: - definitions = unifiedrole.GetBuiltinRoleDefinitionList(unifiedrole.RoleFilterIDs(roles...)) + definitions = unifiedrole.GetDefinitions(unifiedrole.RoleFilterIDs(roles...)) default: - definitions = unifiedrole.GetBuiltinRoleDefinitionList() + definitions = unifiedrole.GetDefinitions(unifiedrole.RoleFilterAll()) } for _, definition := range definitions { diff --git a/services/graph/pkg/validate/libregraph_test.go b/services/graph/pkg/validate/libregraph_test.go index 7a71f9910..dfb195b7e 100644 --- a/services/graph/pkg/validate/libregraph_test.go +++ b/services/graph/pkg/validate/libregraph_test.go @@ -27,8 +27,8 @@ var _ = Describe("libregraph", func() { driveItemInvite = libregraph.DriveItemInvite{ Recipients: []libregraph.DriveRecipient{driveRecipient}, - Roles: []string{unifiedrole.UnifiedRoleEditorID}, - LibreGraphPermissionsActions: []string{unifiedrole.DriveItemVersionsUpdate}, + Roles: []string{role.UnifiedRoleEditorID}, + LibreGraphPermissionsActions: []string{role.DriveItemVersionsUpdate}, ExpirationDateTime: libregraph.PtrTime(time.Now().Add(time.Hour)), } @@ -61,8 +61,8 @@ var _ = Describe("libregraph", func() { }), Entry("fail: multiple role assignment", func() (libregraph.DriveItemInvite, bool) { driveItemInvite.Roles = []string{ - unifiedrole.UnifiedRoleEditorID, - unifiedrole.UnifiedRoleManagerID, + role.UnifiedRoleEditorID, + role.UnifiedRoleManagerID, } driveItemInvite.LibreGraphPermissionsActions = nil return driveItemInvite, false @@ -84,8 +84,8 @@ var _ = Describe("libregraph", func() { }), Entry("fail: different number of roles and actions", func() (libregraph.DriveItemInvite, bool) { driveItemInvite.LibreGraphPermissionsActions = []string{ - unifiedrole.DriveItemVersionsUpdate, - unifiedrole.DriveItemChildrenCreate, + role.DriveItemVersionsUpdate, + role.DriveItemChildrenCreate, } return driveItemInvite, false }), diff --git a/services/web/pkg/theme/service_test.go b/services/web/pkg/theme/service_test.go index 9b8efcc03..eab4959c3 100644 --- a/services/web/pkg/theme/service_test.go +++ b/services/web/pkg/theme/service_test.go @@ -70,5 +70,5 @@ func TestService_Get(t *testing.T) { // brandingTheme assert.Equal(t, jsonData.Get("_branding").String(), "_branding") // themeDefaults - assert.Equal(t, jsonData.Get("common.shareRoles."+unifiedrole.UnifiedRoleViewerID+".name").String(), "UnifiedRoleViewer") + assert.Equal(t, jsonData.Get("common.shareRoles."+role.UnifiedRoleViewerID+".name").String(), "UnifiedRoleViewer") }