enhancement: only use allowed roles for the graph service

This commit is contained in:
Florian Schade
2024-08-21 14:08:39 +02:00
parent 196c988b8c
commit e348ac290e
22 changed files with 161 additions and 244 deletions
+5 -16
View File
@@ -142,17 +142,15 @@ func CS3ResourcePermissionsToLibregraphActions(p *provider.ResourcePermissions)
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)
// CS3ResourcePermissionsToRole converts the provided cs3 ResourcePermissions to a libregraph UnifiedRoleDefinition
func CS3ResourcePermissionsToRole(roleSet []*libregraph.UnifiedRoleDefinition, p *provider.ResourcePermissions, constraints string) *libregraph.UnifiedRoleDefinition {
actionSet := map[string]struct{}{}
for _, action := range a {
for _, action := range CS3ResourcePermissionsToLibregraphActions(p) {
actionSet[action] = struct{}{}
}
var res *libregraph.UnifiedRoleDefinition
for _, uRole := range GetDefinitions(RoleFilterAll()) {
for _, uRole := range roleSet {
matchFound := false
for _, uPerm := range uRole.GetRolePermissions() {
if uPerm.GetCondition() != constraints {
@@ -174,6 +172,7 @@ func _CS3ResourcePermissionsToDefinition(p *provider.ResourcePermissions, constr
return res
}
// resourceActionsEqual checks if the provided actions are equal to the actions defined for a resource
func resourceActionsEqual(targetActionSet map[string]struct{}, actions []string) bool {
if len(targetActionSet) != len(actions) {
return false
@@ -187,16 +186,6 @@ func resourceActionsEqual(targetActionSet map[string]struct{}, actions []string)
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 {
@@ -48,7 +48,7 @@ func TestPermissionsToCS3ResourcePermissions(t *testing.T) {
}
}
func TestCS3ResourcePermissionsToDefinition(t *testing.T) {
func TestCS3ResourcePermissionsToRole(t *testing.T) {
tests := map[string]struct {
cs3ResourcePermissions *provider.ResourcePermissions
unifiedRoleDefinition *libregraph.UnifiedRoleDefinition
@@ -69,7 +69,7 @@ func TestCS3ResourcePermissionsToDefinition(t *testing.T) {
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
NewWithT(t).Expect(
unifiedrole.CS3ResourcePermissionsToDefinition(tc.cs3ResourcePermissions, tc.constraints),
unifiedrole.CS3ResourcePermissionsToRole(unifiedrole.BuildInRoles, tc.cs3ResourcePermissions, tc.constraints),
).To(Equal(tc.unifiedRoleDefinition))
})
}
+2 -5
View File
@@ -5,9 +5,6 @@ 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")
// ErrUnknownRole is returned when an unknown unified role is requested.
ErrUnknownRole = errors.New("unknown role, check if the role is enabled")
)
@@ -1,7 +1,6 @@
package unifiedrole
var (
// roles
RoleViewer = roleViewer
RoleSpaceViewer = roleSpaceViewer
RoleEditor = roleEditor
@@ -11,6 +10,7 @@ var (
RoleManager = roleManager
RoleSecureViewer = roleSecureViewer
// functions
WeightDefinitions = weightDefinitions
BuildInRoles = buildInRoles
WeightDefinitions = weightRoles
)
+3 -46
View File
@@ -8,18 +8,7 @@ import (
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
RoleFilter func(*libregraph.UnifiedRoleDefinition) bool
)
// RoleFilterInvert inverts the provided role filter
@@ -44,44 +33,12 @@ func RoleFilterIDs(ids ...string) RoleFilter {
}
}
// 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 {
func filterRoles(roles []*libregraph.UnifiedRoleDefinition, f RoleFilter) []*libregraph.UnifiedRoleDefinition {
return slices.DeleteFunc(
slices.Clone(roles),
func(r *libregraph.UnifiedRoleDefinition) bool {
return !filter(r)
return !f(r)
},
)
}
+3 -97
View File
@@ -5,7 +5,6 @@ import (
. "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"
)
@@ -22,7 +21,9 @@ func TestRoleFilterIDs(t *testing.T) {
func TestRoleFilterInvert(t *testing.T) {
NewWithT(t).Expect(
unifiedrole.RoleFilterInvert(
unifiedrole.RoleFilterAll(),
func(_ *libregraph.UnifiedRoleDefinition) bool {
return true
},
)(unifiedrole.RoleEditorLite),
).To(BeFalse())
}
@@ -32,98 +33,3 @@ func TestRoleFilterAll(t *testing.T) {
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))
})
}
}
+49 -22
View File
@@ -278,41 +278,68 @@ var (
}()
)
// GetDefinitions returns a role filter that matches the provided resources
func GetDefinitions(filter RoleFilter) []*libregraph.UnifiedRoleDefinition {
return filterRoles(buildInRoles, filter)
// GetRoles returns a role filter that matches the provided resources
func GetRoles(f RoleFilter) []*libregraph.UnifiedRoleDefinition {
return filterRoles(buildInRoles, f)
}
// 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
// GetRole returns a role filter that matches the provided resources
func GetRole(f RoleFilter) (*libregraph.UnifiedRoleDefinition, error) {
roles := filterRoles(buildInRoles, f)
if len(roles) == 0 {
return nil, ErrUnknownRole
}
return definitions[0], nil
return roles[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)
func GetRolesByPermissions(roleSet []*libregraph.UnifiedRoleDefinition, actions []string, constraints string, descending bool) []*libregraph.UnifiedRoleDefinition {
roles := make([]*libregraph.UnifiedRoleDefinition, 0, len(roleSet))
return roles
for _, role := range roleSet {
var match bool
for _, permission := range role.GetRolePermissions() {
if permission.GetCondition() != constraints {
continue
}
for i, action := range permission.GetAllowedResourceActions() {
if !slices.Contains(actions, action) {
break
}
if i == len(permission.GetAllowedResourceActions())-1 {
match = true
}
}
if match {
break
}
}
if match {
roles = append(roles, role)
}
}
return weightRoles(roles, constraints, descending)
}
// GetLegacyDefinitionName returns the legacy role name for the provided role
func GetLegacyDefinitionName(definition libregraph.UnifiedRoleDefinition) string {
return legacyNames[definition.GetId()]
// GetLegacyRoleName returns the legacy role name for the provided role
func GetLegacyRoleName(role libregraph.UnifiedRoleDefinition) string {
return legacyNames[role.GetId()]
}
// weightDefinitions sorts the provided role definitions by the number of permissions[n].actions they grant,
// weightRoles 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 {
func weightRoles(roleSet []*libregraph.UnifiedRoleDefinition, constraints string, descending bool) []*libregraph.UnifiedRoleDefinition {
slices.SortFunc(roleSet, func(i, j *libregraph.UnifiedRoleDefinition) int {
var ia []string
for _, rp := range i.GetRolePermissions() {
if rp.GetCondition() == constraints {
@@ -335,12 +362,12 @@ func weightDefinitions(definitions []*libregraph.UnifiedRoleDefinition, constrai
}
})
for i, definition := range definitions {
definition.LibreGraphWeight = libregraph.PtrInt32(int32(i) + 1)
for i, role := range roleSet {
role.LibreGraphWeight = libregraph.PtrInt32(int32(i) + 1)
}
// return for the sake of consistency, optional because the slice is modified in place
return definitions
return roleSet
}
// GetAllowedResourceActions returns the allowed resource actions for the provided role by condition
+12 -12
View File
@@ -27,14 +27,14 @@ func TestGetDefinition(t *testing.T) {
},
"fail unknown": {
ids: []string{"unknown"},
expectError: unifiedrole.ErrUnknownUnifiedRole,
expectError: unifiedrole.ErrUnknownRole,
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
g := NewWithT(t)
definition, err := unifiedrole.GetDefinition(unifiedrole.RoleFilterIDs(tc.ids...))
definition, err := unifiedrole.GetRole(unifiedrole.RoleFilterIDs(tc.ids...))
if tc.expectError != nil {
g.Expect(err).To(MatchError(tc.expectError))
@@ -96,7 +96,7 @@ func TestGetRolesByPermissions(t *testing.T) {
unifiedRoleDefinition []*libregraph.UnifiedRoleDefinition
}{
"ViewerUnifiedRole": {
givenActions: rolesToAction(unifiedrole.RoleViewer),
givenActions: getRoleActions(unifiedrole.RoleViewer),
constraints: unifiedrole.UnifiedRoleConditionFolder,
unifiedRoleDefinition: []*libregraph.UnifiedRoleDefinition{
unifiedrole.RoleSecureViewer,
@@ -104,7 +104,7 @@ func TestGetRolesByPermissions(t *testing.T) {
},
},
"ViewerUnifiedRole | share": {
givenActions: rolesToAction(unifiedrole.RoleViewer),
givenActions: getRoleActions(unifiedrole.RoleViewer),
constraints: unifiedrole.UnifiedRoleConditionFile,
unifiedRoleDefinition: []*libregraph.UnifiedRoleDefinition{
unifiedrole.RoleSecureViewer,
@@ -112,7 +112,7 @@ func TestGetRolesByPermissions(t *testing.T) {
},
},
"NewFileEditorUnifiedRole": {
givenActions: rolesToAction(unifiedrole.RoleFileEditor),
givenActions: getRoleActions(unifiedrole.RoleFileEditor),
constraints: unifiedrole.UnifiedRoleConditionFile,
unifiedRoleDefinition: []*libregraph.UnifiedRoleDefinition{
unifiedrole.RoleSecureViewer,
@@ -121,7 +121,7 @@ func TestGetRolesByPermissions(t *testing.T) {
},
},
"NewEditorUnifiedRole": {
givenActions: rolesToAction(unifiedrole.RoleEditor),
givenActions: getRoleActions(unifiedrole.RoleEditor),
constraints: unifiedrole.UnifiedRoleConditionFolder,
unifiedRoleDefinition: []*libregraph.UnifiedRoleDefinition{
unifiedrole.RoleSecureViewer,
@@ -131,7 +131,7 @@ func TestGetRolesByPermissions(t *testing.T) {
},
},
"GetRoles 1": {
givenActions: rolesToAction(unifiedrole.GetDefinitions(unifiedrole.RoleFilterAll())...),
givenActions: getRoleActions(unifiedrole.BuildInRoles...),
constraints: unifiedrole.UnifiedRoleConditionFile,
unifiedRoleDefinition: []*libregraph.UnifiedRoleDefinition{
unifiedrole.RoleSecureViewer,
@@ -140,7 +140,7 @@ func TestGetRolesByPermissions(t *testing.T) {
},
},
"GetRoles 2": {
givenActions: rolesToAction(unifiedrole.GetDefinitions(unifiedrole.RoleFilterAll())...),
givenActions: getRoleActions(unifiedrole.BuildInRoles...),
constraints: unifiedrole.UnifiedRoleConditionFolder,
unifiedRoleDefinition: []*libregraph.UnifiedRoleDefinition{
unifiedrole.RoleSecureViewer,
@@ -150,7 +150,7 @@ func TestGetRolesByPermissions(t *testing.T) {
},
},
"GetRoles 3": {
givenActions: rolesToAction(unifiedrole.GetDefinitions(unifiedrole.RoleFilterAll())...),
givenActions: getRoleActions(unifiedrole.BuildInRoles...),
constraints: unifiedrole.UnifiedRoleConditionDrive,
unifiedRoleDefinition: []*libregraph.UnifiedRoleDefinition{
unifiedrole.RoleSpaceViewer,
@@ -164,7 +164,7 @@ func TestGetRolesByPermissions(t *testing.T) {
unifiedRoleDefinition: []*libregraph.UnifiedRoleDefinition{},
},
"mixed": {
givenActions: append(rolesToAction(unifiedrole.RoleEditorLite), unifiedrole.DriveItemQuotaRead),
givenActions: append(getRoleActions(unifiedrole.RoleEditorLite), unifiedrole.DriveItemQuotaRead),
constraints: unifiedrole.UnifiedRoleConditionFolder,
unifiedRoleDefinition: []*libregraph.UnifiedRoleDefinition{
unifiedrole.RoleSecureViewer,
@@ -176,7 +176,7 @@ func TestGetRolesByPermissions(t *testing.T) {
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
g := NewWithT(t)
generatedDefinitions := unifiedrole.GetRolesByPermissions(tc.givenActions, tc.constraints, false)
generatedDefinitions := unifiedrole.GetRolesByPermissions(unifiedrole.BuildInRoles, tc.givenActions, tc.constraints, false)
g.Expect(len(generatedDefinitions)).To(Equal(len(tc.unifiedRoleDefinition)))
@@ -185,7 +185,7 @@ func TestGetRolesByPermissions(t *testing.T) {
g.Expect(*generatedDefinition.LibreGraphWeight).To(Equal(int32(i + 1)))
}
generatedActions := rolesToAction(generatedDefinitions...)
generatedActions := getRoleActions(generatedDefinitions...)
g.Expect(len(tc.givenActions) >= len(generatedActions)).To(BeTrue())
for _, generatedAction := range generatedActions {
@@ -6,7 +6,7 @@ import (
libregraph "github.com/owncloud/libre-graph-api-go"
)
func rolesToAction(definitions ...*libregraph.UnifiedRoleDefinition) []string {
func getRoleActions(definitions ...*libregraph.UnifiedRoleDefinition) []string {
var actions []string
for _, definition := range definitions {