enhancement: add unified roles filters

This commit is contained in:
Florian Schade
2024-08-06 17:16:14 +02:00
parent 4638280d21
commit 196c988b8c
25 changed files with 1187 additions and 960 deletions

View File

@@ -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 == "":

View File

@@ -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
```

View File

@@ -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()})
}

View File

@@ -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...)),
) {

View File

@@ -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))
}

View File

@@ -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"
)

View File

@@ -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,

View File

@@ -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},
}
})

View File

@@ -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

View File

@@ -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
}

View File

@@ -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()})

View File

@@ -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 ""
}
}

View File

@@ -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))
})
}
}

View File

@@ -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")
)

View File

@@ -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
)

View File

@@ -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)
},
)
}

View File

@@ -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))
})
}
}

View File

@@ -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{}
}

View File

@@ -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))
})
}
}

View File

@@ -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{}
}

View File

@@ -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
}

View File

@@ -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,
)
}
})

View File

@@ -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 {

View File

@@ -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
}),

View File

@@ -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")
}