diff --git a/services/graph/pkg/service/v0/rolemanagement.go b/services/graph/pkg/service/v0/rolemanagement.go index ee29d0f876..a7b479c926 100644 --- a/services/graph/pkg/service/v0/rolemanagement.go +++ b/services/graph/pkg/service/v0/rolemanagement.go @@ -15,7 +15,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, getRoleDefinitionList(g.config.FilesSharing.EnableResharing)) + render.JSON(w, r, unifiedrole.GetBuiltinRoleDefinitionList(g.config.FilesSharing.EnableResharing)) } // GetRoleDefinition a permission role than can be used when sharing with users or groups @@ -37,21 +37,8 @@ func (g Graph) GetRoleDefinition(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, role) } -func getRoleDefinitionList(resharing bool) []*libregraph.UnifiedRoleDefinition { - return []*libregraph.UnifiedRoleDefinition{ - unifiedrole.NewViewerUnifiedRole(resharing), - unifiedrole.NewSpaceViewerUnifiedRole(), - unifiedrole.NewEditorUnifiedRole(resharing), - unifiedrole.NewSpaceEditorUnifiedRole(), - unifiedrole.NewFileEditorUnifiedRole(resharing), - unifiedrole.NewCoownerUnifiedRole(), - unifiedrole.NewUploaderUnifiedRole(), - unifiedrole.NewManagerUnifiedRole(), - } -} - func getRoleDefinition(roleID string, resharing bool) (*libregraph.UnifiedRoleDefinition, error) { - roleList := getRoleDefinitionList(resharing) + roleList := unifiedrole.GetBuiltinRoleDefinitionList(resharing) for _, role := range roleList { if role != nil && role.Id != nil && *role.Id == roleID { return role, nil diff --git a/services/graph/pkg/service/v0/sharedbyme.go b/services/graph/pkg/service/v0/sharedbyme.go index 18621d3f52..26ebf1dacd 100644 --- a/services/graph/pkg/service/v0/sharedbyme.go +++ b/services/graph/pkg/service/v0/sharedbyme.go @@ -17,6 +17,7 @@ import ( libregraph "github.com/owncloud/libre-graph-api-go" "github.com/owncloud/ocis/v2/services/graph/pkg/identity" "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole" ) type driveItemsByResourceID map[string]libregraph.DriveItem @@ -160,7 +161,18 @@ func (g Graph) cs3UserSharesToDriveItems(ctx context.Context, shares []*collabor if s.GetExpiration() != nil { perm.SetExpirationDateTime(cs3TimestampToTime(s.GetExpiration())) } - + role := unifiedrole.CS3ResourcePermissionsToUnifiedRole( + *s.GetPermissions().GetPermissions(), + unifiedrole.UnifiedRoleConditionGrantee, + g.config.FilesSharing.EnableResharing, + ) + if role != nil { + perm.SetRoles([]string{role.GetId()}) + } else { + actions := unifiedrole.CS3ResourcePermissionsToLibregraphActions(*s.GetPermissions().GetPermissions()) + perm.SetLibreGraphPermissionsActions(actions) + perm.SetRoles(nil) + } perm.SetGrantedToV2(grantedTo) item.Permissions = append(item.Permissions, perm) driveItems[resIDStr] = item diff --git a/services/graph/pkg/service/v0/sharedbyme_test.go b/services/graph/pkg/service/v0/sharedbyme_test.go index edfe78cfe9..ac128364ab 100644 --- a/services/graph/pkg/service/v0/sharedbyme_test.go +++ b/services/graph/pkg/service/v0/sharedbyme_test.go @@ -15,6 +15,7 @@ import ( collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/v2/pkg/conversions" "github.com/cs3org/reva/v2/pkg/rgrpc/status" "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/storagespace" @@ -28,6 +29,7 @@ import ( "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" identitymocks "github.com/owncloud/ocis/v2/services/graph/pkg/identity/mocks" service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0" + "github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole" "github.com/stretchr/testify/mock" "google.golang.org/grpc" ) @@ -45,6 +47,8 @@ var _ = Describe("sharedbyme", func() { rr *httptest.ResponseRecorder ) expiration := time.Now() + + editorResourcePermissions := conversions.NewEditorRole(true).CS3ResourcePermissions() userShare := collaboration.Share{ Id: &collaboration.ShareId{ OpaqueId: "share-id", @@ -62,6 +66,9 @@ var _ = Describe("sharedbyme", func() { }, }, }, + Permissions: &collaboration.SharePermissions{ + Permissions: editorResourcePermissions, + }, } groupShare := collaboration.Share{ Id: &collaboration.ShareId{ @@ -80,6 +87,9 @@ var _ = Describe("sharedbyme", func() { }, }, }, + Permissions: &collaboration.SharePermissions{ + Permissions: editorResourcePermissions, + }, } userShareWithExpiration := collaboration.Share{ Id: &collaboration.ShareId{ @@ -98,6 +108,9 @@ var _ = Describe("sharedbyme", func() { }, }, }, + Permissions: &collaboration.SharePermissions{ + Permissions: editorResourcePermissions, + }, Expiration: utils.TimeToTS(expiration), } @@ -218,6 +231,7 @@ var _ = Describe("sharedbyme", func() { cfg.TokenManager.JWTSecret = "loremipsum" cfg.Commons = &shared.Commons{} cfg.GRPCClientTLS = &shared.GRPCClientTLS{} + cfg.FilesSharing.EnableResharing = true svc, _ = service.NewService( service.Config(cfg), @@ -323,6 +337,10 @@ var _ = Describe("sharedbyme", func() { Expect(user.GetId()).To(Equal(userShare.GetGrantee().GetUserId().GetOpaqueId())) _, ok = perm[0].GetLinkOk() Expect(ok).To(BeFalse()) + roles, ok := perm[0].GetRolesOk() + Expect(ok).To(BeTrue()) + Expect(len(roles)).To(Equal(1)) + Expect(roles[0]).To(Equal(unifiedrole.UnifiedRoleEditorID)) }) It("returns a proper driveItem, when a single group share is returned", func() { @@ -363,6 +381,10 @@ var _ = Describe("sharedbyme", func() { Expect(group.GetId()).To(Equal(groupShare.GetGrantee().GetGroupId().GetOpaqueId())) _, ok = perm[0].GetLinkOk() Expect(ok).To(BeFalse()) + roles, ok := perm[0].GetRolesOk() + Expect(ok).To(BeTrue()) + Expect(len(roles)).To(Equal(1)) + Expect(roles[0]).To(Equal(unifiedrole.UnifiedRoleEditorID)) }) It("returns a single driveItem, when a mulitple shares for the same resource are returned", func() { diff --git a/services/graph/pkg/unifiedrole/unifiedrole.go b/services/graph/pkg/unifiedrole/unifiedrole.go index b6d007bd6b..1f782e1338 100644 --- a/services/graph/pkg/unifiedrole/unifiedrole.go +++ b/services/graph/pkg/unifiedrole/unifiedrole.go @@ -1,6 +1,7 @@ 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" "google.golang.org/protobuf/proto" @@ -24,12 +25,32 @@ const ( // UnifiedRoleManagerID Unified role manager id. UnifiedRoleManagerID = "312c0871-5ef7-4b3a-85b6-0e4074c64049" - // UnifiedRoleConditionSelf TODO defines constraints - UnifiedRoleConditionSelf = "Self: @Subject.objectId == @Resource.objectId" + // UnifiedRoleConditionSelf defines constraint where the principal matches the target resource + UnifiedRoleConditionSelf = "@Subject.objectId == @Resource.objectId" // UnifiedRoleConditionOwner defines constraints when the principal is the owner of the target resource - UnifiedRoleConditionOwner = "Owner: @Subject.objectId Any_of @Resource.owners" + UnifiedRoleConditionOwner = "@Subject.objectId Any_of @Resource.owners" // UnifiedRoleConditionGrantee does not exist in MS Graph, but we use it to express permissions on shared resources - UnifiedRoleConditionGrantee = "Grantee: @Subject.objectId Any_of @Resource.grantee" + UnifiedRoleConditionGrantee = "@Subject.objectId Any_of @Resource.grantee" + + 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" ) // NewViewerUnifiedRole creates a viewer role. `sharing` indicates if sharing permission should be added @@ -168,6 +189,126 @@ func NewManagerUnifiedRole() *libregraph.UnifiedRoleDefinition { } } +func GetBuiltinRoleDefinitionList(resharing bool) []*libregraph.UnifiedRoleDefinition { + return []*libregraph.UnifiedRoleDefinition{ + NewViewerUnifiedRole(resharing), + NewSpaceViewerUnifiedRole(), + NewEditorUnifiedRole(resharing), + NewSpaceEditorUnifiedRole(), + NewFileEditorUnifiedRole(resharing), + NewCoownerUnifiedRole(), + NewUploaderUnifiedRole(), + NewManagerUnifiedRole(), + } +} + +// CS3ResourcePermissionsToLibregraphActions converts the provided cs3 ResourcePermissions to a list of +// libregraph actions +func CS3ResourcePermissionsToLibregraphActions(p provider.ResourcePermissions) (actions []string) { + if p.AddGrant { + actions = append(actions, DriveItemPermissionsCreate) + } + if p.CreateContainer { + actions = append(actions, DriveItemChildrenCreate) + } + if p.Delete { + actions = append(actions, DriveItemStandardDelete) + } + if p.GetPath { + actions = append(actions, DriveItemPathRead) + } + if p.GetQuota { + actions = append(actions, DriveItemQuotaRead) + } + if p.InitiateFileDownload { + actions = append(actions, DriveItemContentRead) + } + if p.InitiateFileUpload { + actions = append(actions, DriveItemUploadCreate) + } + if p.ListGrants { + actions = append(actions, DriveItemPermissionsRead) + } + if p.ListContainer { + actions = append(actions, DriveItemChildrenRead) + } + if p.ListFileVersions { + actions = append(actions, DriveItemVersionsRead) + } + if p.ListRecycle { + actions = append(actions, DriveItemDeletedRead) + } + if p.Move { + actions = append(actions, DriveItemPathUpdate) + } + if p.RemoveGrant { + actions = append(actions, DriveItemPermissionsDelete) + } + if p.PurgeRecycle { + actions = append(actions, DriveItemDeletedDelete) + } + if p.RestoreFileVersion { + actions = append(actions, DriveItemVersionsUpdate) + } + if p.RestoreRecycleItem { + actions = append(actions, DriveItemDeletedUpdate) + } + if p.Stat { + actions = append(actions, DriveItemBasicRead) + } + if p.UpdateGrant { + actions = append(actions, DriveItemPermissionsUpdate) + } + if p.DenyGrant { + actions = append(actions, DriveItemPermissionsDeny) + } + return actions +} + +// CS3ResourcePermissionsToUnifiedRole tries to find the UnifiedRoleDefinition that matches the supplied +// CS3 ResourcePermissions and constraints. +func CS3ResourcePermissionsToUnifiedRole(p provider.ResourcePermissions, constraints string, resharing bool) *libregraph.UnifiedRoleDefinition { + actionSet := map[string]struct{}{} + for _, action := range CS3ResourcePermissionsToLibregraphActions(p) { + actionSet[action] = struct{}{} + } + + var res *libregraph.UnifiedRoleDefinition + for _, uRole := range GetBuiltinRoleDefinitionList(resharing) { + 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 @@ -201,63 +342,5 @@ func convert(role *conversions.Role) []string { if role == nil && role.CS3ResourcePermissions() == nil { return actions } - p := role.CS3ResourcePermissions() - if p.AddGrant { - actions = append(actions, "libre.graph/driveItem/permissions/create") - } - if p.CreateContainer { - actions = append(actions, "libre.graph/driveItem/children/create") - } - if p.Delete { - actions = append(actions, "libre.graph/driveItem/standard/delete") - } - if p.GetPath { - actions = append(actions, "libre.graph/driveItem/path/read") - } - if p.GetQuota { - actions = append(actions, "libre.graph/driveItem/quota/read") - } - if p.InitiateFileDownload { - actions = append(actions, "libre.graph/driveItem/content/read") - } - if p.InitiateFileUpload { - actions = append(actions, "libre.graph/driveItem/upload/create") - } - if p.ListGrants { - actions = append(actions, "libre.graph/driveItem/permissions/read") - } - if p.ListContainer { - actions = append(actions, "libre.graph/driveItem/children/read") - } - if p.ListFileVersions { - actions = append(actions, "libre.graph/driveItem/versions/read") - } - if p.ListRecycle { - actions = append(actions, "libre.graph/driveItem/deleted/read") - } - if p.Move { - actions = append(actions, "libre.graph/driveItem/path/update") - } - if p.RemoveGrant { - actions = append(actions, "libre.graph/driveItem/permissions/delete") - } - if p.PurgeRecycle { - actions = append(actions, "libre.graph/driveItem/deleted/delete") - } - if p.RestoreFileVersion { - actions = append(actions, "libre.graph/driveItem/versions/update") - } - if p.RestoreRecycleItem { - actions = append(actions, "libre.graph/driveItem/deleted/update") - } - if p.Stat { - actions = append(actions, "libre.graph/driveItem/basic/read") - } - if p.UpdateGrant { - actions = append(actions, "libre.graph/driveItem/permissions/update") - } - if p.DenyGrant { - actions = append(actions, "libre.graph/driveItem/permissions/deny") - } - return actions + return CS3ResourcePermissionsToLibregraphActions(*role.CS3ResourcePermissions()) } diff --git a/services/graph/pkg/unifiedrole/unifiedrole_suite_test.go b/services/graph/pkg/unifiedrole/unifiedrole_suite_test.go new file mode 100644 index 0000000000..2e9a378086 --- /dev/null +++ b/services/graph/pkg/unifiedrole/unifiedrole_suite_test.go @@ -0,0 +1,13 @@ +package unifiedrole_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestUnifiedrole(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Unifiedrole Suite") +} diff --git a/services/graph/pkg/unifiedrole/unifiedrole_test.go b/services/graph/pkg/unifiedrole/unifiedrole_test.go new file mode 100644 index 0000000000..6334efb587 --- /dev/null +++ b/services/graph/pkg/unifiedrole/unifiedrole_test.go @@ -0,0 +1,26 @@ +package unifiedrole_test + +import ( + "github.com/cs3org/reva/v2/pkg/conversions" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + 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 *conversions.Role, unifiedRole *libregraph.UnifiedRoleDefinition) { + cs3perm := legacyRole.CS3ResourcePermissions() + + r := unifiedrole.CS3ResourcePermissionsToUnifiedRole(*cs3perm, unifiedrole.UnifiedRoleConditionGrantee, true) + Expect(r.GetId()).To(Equal(unifiedRole.GetId())) + + }, + Entry(conversions.RoleViewer, conversions.NewViewerRole(true), unifiedrole.NewViewerUnifiedRole(true)), + Entry(conversions.RoleEditor, conversions.NewEditorRole(true), unifiedrole.NewEditorUnifiedRole(true)), + Entry(conversions.RoleFileEditor, conversions.NewFileEditorRole(true), unifiedrole.NewFileEditorUnifiedRole(true)), + Entry(conversions.RoleCoowner, conversions.NewCoownerRole(), unifiedrole.NewCoownerUnifiedRole()), + Entry(conversions.RoleManager, conversions.NewManagerRole(), unifiedrole.NewManagerUnifiedRole()), + ) +})