RBAC: Add and resolve action sets when searching user's permissions (#88694)

* include and resolve action sets when fetching user's permissions

* expand both action and action prefix (returns an empty set for the one that isn't specified)
Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>

* if action is specified, check for exact match; also extend tests
This commit is contained in:
Ieva
2024-06-12 11:20:19 +03:00
committed by GitHub
co-authored by Gabriel MABILLE
parent 12d5251c12
commit 34c40f959f
7 changed files with 189 additions and 11 deletions
+32 -2
View File
@@ -422,7 +422,16 @@ func (s *Service) DeclarePluginRoles(ctx context.Context, ID, name string, regs
return nil
}
// TODO potential changes needed here?
func GetActionFilter(options accesscontrol.SearchOptions) func(action string) bool {
return func(action string) bool {
if options.ActionPrefix != "" {
return strings.HasPrefix(action, options.ActionPrefix)
} else {
return action == options.Action
}
}
}
// SearchUsersPermissions returns all users' permissions filtered by action prefixes
func (s *Service) SearchUsersPermissions(ctx context.Context, usr identity.Requester,
options accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error) {
@@ -437,7 +446,6 @@ func (s *Service) SearchUsersPermissions(ctx context.Context, usr identity.Reque
// Reroute to the user specific implementation of search permissions
// because it leverages the user permission cache.
// TODO
userPerms, err := s.SearchUserPermissions(ctx, usr.GetOrgID(), options)
if err != nil {
return nil, err
@@ -463,6 +471,12 @@ func (s *Service) SearchUsersPermissions(ctx context.Context, usr identity.Reque
return nil, err
}
if s.features.IsEnabled(ctx, featuremgmt.FlagAccessActionSets) {
options.ActionSets = s.actionResolver.ResolveAction(options.Action)
options.ActionSets = append(options.ActionSets,
s.actionResolver.ResolveActionPrefix(options.ActionPrefix)...)
}
// Get managed permissions (DB)
usersPermissions, err := s.store.SearchUsersPermissions(ctx, usr.GetOrgID(), options)
if err != nil {
@@ -522,6 +536,12 @@ func (s *Service) SearchUsersPermissions(ctx context.Context, usr identity.Reque
}
}
if s.features.IsEnabled(ctx, featuremgmt.FlagAccessActionSets) && len(options.ActionSets) > 0 {
for id, perms := range res {
res[id] = s.actionResolver.ExpandActionSetsWithFilter(perms, GetActionFilter(options))
}
}
return res, nil
}
@@ -566,6 +586,12 @@ func (s *Service) searchUserPermissions(ctx context.Context, orgID int64, search
}
}
if s.features.IsEnabled(ctx, featuremgmt.FlagAccessActionSets) {
searchOptions.ActionSets = s.actionResolver.ResolveAction(searchOptions.Action)
searchOptions.ActionSets = append(searchOptions.ActionSets,
s.actionResolver.ResolveActionPrefix(searchOptions.ActionPrefix)...)
}
// Get permissions from the DB
dbPermissions, err := s.store.SearchUsersPermissions(ctx, orgID, searchOptions)
if err != nil {
@@ -573,6 +599,10 @@ func (s *Service) searchUserPermissions(ctx context.Context, orgID int64, search
}
permissions = append(permissions, dbPermissions[userID]...)
if s.features.IsEnabled(ctx, featuremgmt.FlagAccessActionSets) && len(searchOptions.ActionSets) != 0 {
permissions = s.actionResolver.ExpandActionSetsWithFilter(permissions, GetActionFilter(searchOptions))
}
key := accesscontrol.GetSearchPermissionCacheKey(&user.SignedInUser{UserID: userID, OrgID: orgID}, searchOptions)
s.cache.Set(key, permissions, cacheTTL)
@@ -3,6 +3,7 @@ package acimpl
import (
"context"
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/assert"
@@ -599,13 +600,15 @@ func TestService_SearchUsersPermissions(t *testing.T) {
func TestService_SearchUserPermissions(t *testing.T) {
ctx := context.Background()
tests := []struct {
name string
searchOption accesscontrol.SearchOptions
ramRoles map[string]*accesscontrol.RoleDTO // BasicRole => RBAC BasicRole
storedPerms map[int64][]accesscontrol.Permission // UserID => Permissions
storedRoles map[int64][]string // UserID => Roles
want []accesscontrol.Permission
wantErr bool
name string
searchOption accesscontrol.SearchOptions
withActionSets bool
actionSets map[string][]string
ramRoles map[string]*accesscontrol.RoleDTO // BasicRole => RBAC BasicRole
storedPerms map[int64][]accesscontrol.Permission // UserID => Permissions
storedRoles map[int64][]string // UserID => Roles
want []accesscontrol.Permission
wantErr bool
}{
{
name: "ram only",
@@ -726,10 +729,86 @@ func TestService_SearchUserPermissions(t *testing.T) {
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"},
},
},
{
name: "check action sets are correctly included if an action is specified",
searchOption: accesscontrol.SearchOptions{
Action: "dashboards:read",
NamespacedID: fmt.Sprintf("%s:1", identity.NamespaceUser),
},
withActionSets: true,
actionSets: map[string][]string{
"dashboards:view": {"dashboards:read"},
"dashboards:edit": {"dashboards:read", "dashboards:write", "dashboards:read-advanced"},
},
ramRoles: map[string]*accesscontrol.RoleDTO{
string(roletype.RoleEditor): {Permissions: []accesscontrol.Permission{
{Action: "dashboards:read", Scope: "dashboards:uid:ram"},
}},
},
storedRoles: map[int64][]string{
1: {string(roletype.RoleEditor)},
},
storedPerms: map[int64][]accesscontrol.Permission{
1: {
{Action: "dashboards:read", Scope: "dashboards:uid:stored"},
{Action: "dashboards:edit", Scope: "dashboards:uid:stored2"},
{Action: "dashboards:view", Scope: "dashboards:uid:stored3"},
},
},
want: []accesscontrol.Permission{
{Action: "dashboards:read", Scope: "dashboards:uid:ram"},
{Action: "dashboards:read", Scope: "dashboards:uid:stored"},
{Action: "dashboards:read", Scope: "dashboards:uid:stored2"},
{Action: "dashboards:read", Scope: "dashboards:uid:stored3"},
},
},
{
name: "check action sets are correctly included if an action prefix is specified",
searchOption: accesscontrol.SearchOptions{
ActionPrefix: "dashboards",
NamespacedID: fmt.Sprintf("%s:1", identity.NamespaceUser),
},
withActionSets: true,
actionSets: map[string][]string{
"dashboards:view": {"dashboards:read"},
"folders:view": {"dashboards:read", "folders:read"},
"dashboards:edit": {"dashboards:read", "dashboards:write"},
},
ramRoles: map[string]*accesscontrol.RoleDTO{
string(roletype.RoleEditor): {Permissions: []accesscontrol.Permission{
{Action: "dashboards:read", Scope: "dashboards:uid:ram"},
}},
},
storedRoles: map[int64][]string{
1: {string(roletype.RoleEditor)},
},
storedPerms: map[int64][]accesscontrol.Permission{
1: {
{Action: "dashboards:read", Scope: "dashboards:uid:stored"},
{Action: "folders:view", Scope: "folders:uid:stored2"},
{Action: "dashboards:edit", Scope: "dashboards:uid:stored3"},
},
},
want: []accesscontrol.Permission{
{Action: "dashboards:read", Scope: "dashboards:uid:ram"},
{Action: "dashboards:read", Scope: "dashboards:uid:stored"},
{Action: "dashboards:read", Scope: "folders:uid:stored2"},
{Action: "dashboards:read", Scope: "dashboards:uid:stored3"},
{Action: "dashboards:write", Scope: "dashboards:uid:stored3"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ac := setupTestEnv(t)
if tt.withActionSets {
ac.features = featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets)
actionSetSvc := resourcepermissions.NewActionSetService()
for set, actions := range tt.actionSets {
actionSetSvc.StoreActionSet(strings.Split(set, ":")[0], strings.Split(set, ":")[1], actions)
}
ac.actionResolver = actionSetSvc
}
ac.roles = tt.ramRoles
ac.store = actest.FakeStore{