RBAC: Adding action set resolver for RBAC evaluation (#86801)

* add action set resolver

* rename variables

* some fixes and some tests

* more tests

* more tests, and put action set storing behind a feature toggle

* undo change from cfg to feature mgmt - will cover it in a separate PR due to the amount of test changes

* fix dependency cycle, update some tests

* add one more test

* fix for feature toggle check not being set on test configs

* linting fixes

* check that action set name can be split nicely

* clean up tests by turning GetActionSetNames into a function

* undo accidental change

* test fix

* more test fixes
This commit is contained in:
Ieva 2024-05-09 10:18:03 +01:00 committed by GitHub
parent 6380a01543
commit 105313f5c2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 445 additions and 101 deletions

View File

@ -461,11 +461,12 @@ func setupServer(b testing.TB, sc benchScenario, features featuremgmt.FeatureTog
folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, sc.db, features, supportbundlestest.NewFakeBundleService(), nil) folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, sc.db, features, supportbundlestest.NewFakeBundleService(), nil)
cfg := setting.NewCfg() cfg := setting.NewCfg()
actionSets := resourcepermissions.NewActionSetService(ac)
folderPermissions, err := ossaccesscontrol.ProvideFolderPermissions( folderPermissions, err := ossaccesscontrol.ProvideFolderPermissions(
cfg, features, routing.NewRouteRegister(), sc.db, ac, license, &dashboards.FakeDashboardStore{}, folderServiceWithFlagOn, acSvc, sc.teamSvc, sc.userSvc, resourcepermissions.NewActionSetService()) cfg, features, routing.NewRouteRegister(), sc.db, ac, license, &dashboards.FakeDashboardStore{}, folderServiceWithFlagOn, acSvc, sc.teamSvc, sc.userSvc, actionSets)
require.NoError(b, err) require.NoError(b, err)
dashboardPermissions, err := ossaccesscontrol.ProvideDashboardPermissions( dashboardPermissions, err := ossaccesscontrol.ProvideDashboardPermissions(
cfg, features, routing.NewRouteRegister(), sc.db, ac, license, &dashboards.FakeDashboardStore{}, folderServiceWithFlagOn, acSvc, sc.teamSvc, sc.userSvc, resourcepermissions.NewActionSetService()) cfg, features, routing.NewRouteRegister(), sc.db, ac, license, &dashboards.FakeDashboardStore{}, folderServiceWithFlagOn, acSvc, sc.teamSvc, sc.userSvc, actionSets)
require.NoError(b, err) require.NoError(b, err)
dashboardSvc, err := dashboardservice.ProvideDashboardServiceImpl( dashboardSvc, err := dashboardservice.ProvideDashboardServiceImpl(

View File

@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
@ -48,6 +49,11 @@ func (a *AccessControl) Evaluate(ctx context.Context, user identity.Requester, e
return false, nil return false, nil
} }
// TODO update this to use featuremgmt.FeatureToggles instead of checking the config
if a.cfg != nil && a.cfg.IsFeatureToggleEnabled != nil && a.cfg.IsFeatureToggleEnabled(featuremgmt.FlagAccessActionSets) {
evaluator = evaluator.AppendActionSets(ctx, a.resolvers.GetActionSetResolver())
}
a.debug(ctx, user, "Evaluating permissions", evaluator) a.debug(ctx, user, "Evaluating permissions", evaluator)
// Test evaluation without scope resolver first, this will prevent 403 for wildcard scopes when resource does not exist // Test evaluation without scope resolver first, this will prevent 403 for wildcard scopes when resource does not exist
if evaluator.Evaluate(permissions) { if evaluator.Evaluate(permissions) {
@ -70,6 +76,10 @@ func (a *AccessControl) RegisterScopeAttributeResolver(prefix string, resolver a
a.resolvers.AddScopeAttributeResolver(prefix, resolver) a.resolvers.AddScopeAttributeResolver(prefix, resolver)
} }
func (a *AccessControl) RegisterActionResolver(resolver accesscontrol.ActionResolver) {
a.resolvers.SetActionResolver(resolver)
}
func (a *AccessControl) debug(ctx context.Context, ident identity.Requester, msg string, eval accesscontrol.Evaluator) { func (a *AccessControl) debug(ctx context.Context, ident identity.Requester, msg string, eval accesscontrol.Evaluator) {
namespace, id := ident.GetNamespacedID() namespace, id := ident.GetNamespacedID()
a.log.FromContext(ctx).Debug(msg, "namespace", namespace, "id", id, "orgID", ident.GetOrgID(), "permissions", eval.GoString()) a.log.FromContext(ctx).Debug(msg, "namespace", namespace, "id", id, "orgID", ident.GetOrgID(), "permissions", eval.GoString())

View File

@ -1,12 +1,17 @@
package acimpl package acimpl_test
import ( import (
"context" "context"
"strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
@ -19,7 +24,8 @@ func TestAccessControl_Evaluate(t *testing.T) {
resolverPrefix string resolverPrefix string
expected bool expected bool
expectedErr error expectedErr error
resolver accesscontrol.ScopeAttributeResolver scopeResolver accesscontrol.ScopeAttributeResolver
actionSets map[string][]string
} }
tests := []testCase{ tests := []testCase{
@ -55,19 +61,127 @@ func TestAccessControl_Evaluate(t *testing.T) {
}, },
evaluator: accesscontrol.EvalPermission(accesscontrol.ActionTeamsWrite, "teams:id:1"), evaluator: accesscontrol.EvalPermission(accesscontrol.ActionTeamsWrite, "teams:id:1"),
resolverPrefix: "teams:id:", resolverPrefix: "teams:id:",
resolver: accesscontrol.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) { scopeResolver: accesscontrol.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) {
return []string{"another:scope"}, nil return []string{"another:scope"}, nil
}), }),
expected: true, expected: true,
}, },
{
desc: "expect user to have access when resolver translates actions to action sets",
user: user.SignedInUser{
OrgID: 1,
Permissions: map[int64]map[string][]string{
1: {"folders:edit": {"folders:uid:test_folder"}},
},
},
evaluator: accesscontrol.EvalPermission(dashboards.ActionFoldersWrite, "folders:uid:test_folder"),
actionSets: map[string][]string{
"folders:edit": {dashboards.ActionFoldersWrite, dashboards.ActionFoldersRead, dashboards.ActionDashboardsWrite, dashboards.ActionDashboardsRead},
},
expected: true,
},
{
desc: "expect user to have access when resolver translates scopes, as well as expands actions to action sets",
user: user.SignedInUser{
OrgID: 1,
Permissions: map[int64]map[string][]string{
1: {"folders:edit": {"folders:uid:test_folder"}},
},
},
evaluator: accesscontrol.EvalPermission(dashboards.ActionDashboardsRead, "dashboards:uid:test_dashboard"),
actionSets: map[string][]string{
"folders:edit": {dashboards.ActionFoldersWrite, dashboards.ActionFoldersRead, dashboards.ActionDashboardsWrite, dashboards.ActionDashboardsRead},
},
resolverPrefix: "dashboards:uid:",
scopeResolver: accesscontrol.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) {
return []string{"folders:uid:test_folder"}, nil
}),
expected: true,
},
{
desc: "expect user to have access with eval all evaluator when resolver translates scopes, as well as expands actions to action sets",
user: user.SignedInUser{
OrgID: 1,
Permissions: map[int64]map[string][]string{
1: {"folders:edit": {"folders:uid:test_folder"}},
},
},
evaluator: accesscontrol.EvalAll(
accesscontrol.EvalPermission(dashboards.ActionFoldersRead, "folders:uid:test_folder"),
accesscontrol.EvalPermission(dashboards.ActionDashboardsWrite, "dashboards:uid:test_dashboard"),
),
actionSets: map[string][]string{
"folders:edit": {dashboards.ActionFoldersWrite, dashboards.ActionFoldersRead, dashboards.ActionDashboardsWrite, dashboards.ActionDashboardsRead},
},
resolverPrefix: "dashboards:uid:",
scopeResolver: accesscontrol.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) {
return []string{"folders:uid:test_folder"}, nil
}),
expected: true,
},
{
desc: "expect user to not have access with eval all evaluator with resolvers when not all permissions resolve to permissions that the user has",
user: user.SignedInUser{
OrgID: 1,
Permissions: map[int64]map[string][]string{
1: {"folders:edit": {"folders:uid:test_folder"}},
},
},
evaluator: accesscontrol.EvalAll(
accesscontrol.EvalPermission(dashboards.ActionDashboardsWrite, "datasources:uid:test_ds"),
accesscontrol.EvalPermission(dashboards.ActionDashboardsWrite, "dashboards:uid:test_dashboard"),
),
actionSets: map[string][]string{
"folders:edit": {dashboards.ActionFoldersWrite, dashboards.ActionFoldersRead, dashboards.ActionDashboardsWrite, dashboards.ActionDashboardsRead},
},
resolverPrefix: "dashboards:uid:",
scopeResolver: accesscontrol.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) {
return []string{"folders:uid:test_folder"}, nil
}),
expected: false,
},
{
desc: "expect user to have access with eval any evaluator when resolver translates scopes, as well as expands actions to action sets",
user: user.SignedInUser{
OrgID: 1,
Permissions: map[int64]map[string][]string{
1: {"folders:edit": {"folders:uid:test_folder"}},
},
},
evaluator: accesscontrol.EvalAny(
accesscontrol.EvalPermission(dashboards.ActionDashboardsDelete, "dashboards:uid:test_dashboard"),
accesscontrol.EvalPermission(dashboards.ActionDashboardsWrite, "dashboards:uid:test_dashboard"),
),
actionSets: map[string][]string{
"folders:edit": {dashboards.ActionFoldersWrite, dashboards.ActionFoldersRead, dashboards.ActionDashboardsWrite, dashboards.ActionDashboardsRead},
},
resolverPrefix: "dashboards:uid:",
scopeResolver: accesscontrol.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) {
return []string{"folders:uid:test_folder"}, nil
}),
expected: true,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) { t.Run(tt.desc, func(t *testing.T) {
ac := ProvideAccessControl(setting.NewCfg()) cfg := setting.NewCfg()
cfg.IsFeatureToggleEnabled = func(ft string) bool {
return ft == featuremgmt.FlagAccessActionSets
}
ac := acimpl.ProvideAccessControl(cfg)
if tt.resolver != nil { if tt.scopeResolver != nil {
ac.RegisterScopeAttributeResolver(tt.resolverPrefix, tt.resolver) ac.RegisterScopeAttributeResolver(tt.resolverPrefix, tt.scopeResolver)
}
if tt.actionSets != nil {
actionSetResolver := resourcepermissions.NewActionSetService(ac)
for actionSet, actions := range tt.actionSets {
splitActionSet := strings.Split(actionSet, ":")
actionSetResolver.StoreActionSet(splitActionSet[0], splitActionSet[1], actions)
}
ac.RegisterActionResolver(actionSetResolver)
} }
hasAccess, err := ac.Evaluate(context.Background(), &tt.user, tt.evaluator) hasAccess, err := ac.Evaluate(context.Background(), &tt.user, tt.evaluator)

View File

@ -1,4 +1,4 @@
package database package database_test
import ( import (
"context" "context"
@ -13,6 +13,7 @@ import (
"github.com/grafana/grafana/pkg/infra/localcache" "github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/database"
rs "github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions" rs "github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
"github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess" "github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
@ -20,6 +21,7 @@ import (
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/org/orgimpl" "github.com/grafana/grafana/pkg/services/org/orgimpl"
"github.com/grafana/grafana/pkg/services/quota/quotatest" "github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/team" "github.com/grafana/grafana/pkg/services/team"
"github.com/grafana/grafana/pkg/services/team/teamimpl" "github.com/grafana/grafana/pkg/services/team/teamimpl"
@ -91,9 +93,9 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) { t.Run(tt.desc, func(t *testing.T) {
store, permissionStore, usrSvc, teamSvc, _ := setupTestEnv(t) store, permissionStore, usrSvc, teamSvc, _, sql := setupTestEnv(t)
user, team := createUserAndTeam(t, store.sql, usrSvc, teamSvc, tt.orgID) user, team := createUserAndTeam(t, sql, usrSvc, teamSvc, tt.orgID)
for _, id := range tt.userPermissions { for _, id := range tt.userPermissions {
_, err := permissionStore.SetUserResourcePermission(context.Background(), tt.orgID, accesscontrol.User{ID: user.ID}, rs.SetResourcePermissionCommand{ _, err := permissionStore.SetUserResourcePermission(context.Background(), tt.orgID, accesscontrol.User{ID: user.ID}, rs.SetResourcePermissionCommand{
@ -148,7 +150,7 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, permissions, tt.expected) assert.Len(t, permissions, tt.expected)
policies, err := GetAccessPolicies(context.Background(), user.OrgID, store.sql.GetSqlxSession(), policies, err := database.GetAccessPolicies(context.Background(), user.OrgID, sql.GetSqlxSession(),
func(ctx context.Context, orgID int64, scope string) ([]string, error) { func(ctx context.Context, orgID int64, scope string) ([]string, error) {
return strings.Split(scope, ":"), nil return strings.Split(scope, ":"), nil
}) })
@ -195,7 +197,7 @@ func TestAccessControlStore_GetTeamsPermissions(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) { t.Run(tt.desc, func(t *testing.T) {
store, permissionStore, _, teamSvc, _ := setupTestEnv(t) store, permissionStore, _, teamSvc, _, _ := setupTestEnv(t)
teams := make([]team.Team, 0) teams := make([]team.Team, 0)
for i := 0; i < len(tt.teamsPermissions); i++ { for i := 0; i < len(tt.teamsPermissions); i++ {
@ -240,8 +242,8 @@ func TestAccessControlStore_GetTeamsPermissions(t *testing.T) {
func TestAccessControlStore_DeleteUserPermissions(t *testing.T) { func TestAccessControlStore_DeleteUserPermissions(t *testing.T) {
t.Run("expect permissions in all orgs to be deleted", func(t *testing.T) { t.Run("expect permissions in all orgs to be deleted", func(t *testing.T) {
store, permissionsStore, usrSvc, teamSvc, _ := setupTestEnv(t) store, permissionsStore, usrSvc, teamSvc, _, sql := setupTestEnv(t)
user, _ := createUserAndTeam(t, store.sql, usrSvc, teamSvc, 1) user, _ := createUserAndTeam(t, sql, usrSvc, teamSvc, 1)
// generate permissions in org 1 // generate permissions in org 1
_, err := permissionsStore.SetUserResourcePermission(context.Background(), 1, accesscontrol.User{ID: user.ID}, rs.SetResourcePermissionCommand{ _, err := permissionsStore.SetUserResourcePermission(context.Background(), 1, accesscontrol.User{ID: user.ID}, rs.SetResourcePermissionCommand{
@ -280,8 +282,8 @@ func TestAccessControlStore_DeleteUserPermissions(t *testing.T) {
}) })
t.Run("expect permissions in org 1 to be deleted", func(t *testing.T) { t.Run("expect permissions in org 1 to be deleted", func(t *testing.T) {
store, permissionsStore, usrSvc, teamSvc, _ := setupTestEnv(t) store, permissionsStore, usrSvc, teamSvc, _, sql := setupTestEnv(t)
user, _ := createUserAndTeam(t, store.sql, usrSvc, teamSvc, 1) user, _ := createUserAndTeam(t, sql, usrSvc, teamSvc, 1)
// generate permissions in org 1 // generate permissions in org 1
_, err := permissionsStore.SetUserResourcePermission(context.Background(), 1, accesscontrol.User{ID: user.ID}, rs.SetResourcePermissionCommand{ _, err := permissionsStore.SetUserResourcePermission(context.Background(), 1, accesscontrol.User{ID: user.ID}, rs.SetResourcePermissionCommand{
@ -322,8 +324,8 @@ func TestAccessControlStore_DeleteUserPermissions(t *testing.T) {
func TestAccessControlStore_DeleteTeamPermissions(t *testing.T) { func TestAccessControlStore_DeleteTeamPermissions(t *testing.T) {
t.Run("expect permissions related to team to be deleted", func(t *testing.T) { t.Run("expect permissions related to team to be deleted", func(t *testing.T) {
store, permissionsStore, usrSvc, teamSvc, _ := setupTestEnv(t) store, permissionsStore, usrSvc, teamSvc, _, sql := setupTestEnv(t)
user, team := createUserAndTeam(t, store.sql, usrSvc, teamSvc, 1) user, team := createUserAndTeam(t, sql, usrSvc, teamSvc, 1)
// grant permission to the team // grant permission to the team
_, err := permissionsStore.SetTeamResourcePermission(context.Background(), 1, team.ID, rs.SetResourcePermissionCommand{ _, err := permissionsStore.SetTeamResourcePermission(context.Background(), 1, team.ID, rs.SetResourcePermissionCommand{
@ -356,8 +358,8 @@ func TestAccessControlStore_DeleteTeamPermissions(t *testing.T) {
assert.Len(t, permissions, 0) assert.Len(t, permissions, 0)
}) })
t.Run("expect permissions not related to team to be kept", func(t *testing.T) { t.Run("expect permissions not related to team to be kept", func(t *testing.T) {
store, permissionsStore, usrSvc, teamSvc, _ := setupTestEnv(t) store, permissionsStore, usrSvc, teamSvc, _, sql := setupTestEnv(t)
user, team := createUserAndTeam(t, store.sql, usrSvc, teamSvc, 1) user, team := createUserAndTeam(t, sql, usrSvc, teamSvc, 1)
// grant permission to the team // grant permission to the team
_, err := permissionsStore.SetTeamResourcePermission(context.Background(), 1, team.ID, rs.SetResourcePermissionCommand{ _, err := permissionsStore.SetTeamResourcePermission(context.Background(), 1, team.ID, rs.SetResourcePermissionCommand{
@ -468,14 +470,13 @@ func createUsersAndTeams(t *testing.T, store db.DB, svcs helperServices, orgID i
return res return res
} }
func setupTestEnv(t testing.TB) (*AccessControlStore, rs.Store, user.Service, team.Service, org.Service) { func setupTestEnv(t testing.TB) (*database.AccessControlStore, rs.Store, user.Service, team.Service, org.Service, *sqlstore.SQLStore) {
sql, cfg := db.InitTestDBWithCfg(t) sql, cfg := db.InitTestDBWithCfg(t)
cfg.AutoAssignOrg = true cfg.AutoAssignOrg = true
cfg.AutoAssignOrgRole = "Viewer" cfg.AutoAssignOrgRole = "Viewer"
cfg.AutoAssignOrgId = 1 cfg.AutoAssignOrgId = 1
acstore := ProvideService(sql) acstore := database.ProvideService(sql)
asService := rs.NewActionSetService() permissionStore := rs.NewStore(sql, featuremgmt.WithFeatures())
permissionStore := rs.NewStore(sql, featuremgmt.WithFeatures(), &asService)
teamService, err := teamimpl.ProvideService(sql, cfg, tracing.InitializeTracerForTest()) teamService, err := teamimpl.ProvideService(sql, cfg, tracing.InitializeTracerForTest())
require.NoError(t, err) require.NoError(t, err)
orgService, err := orgimpl.ProvideService(sql, cfg, quotatest.New(false, nil)) orgService, err := orgimpl.ProvideService(sql, cfg, quotatest.New(false, nil))
@ -490,7 +491,7 @@ func setupTestEnv(t testing.TB) (*AccessControlStore, rs.Store, user.Service, te
quotatest.New(false, nil), supportbundlestest.NewFakeBundleService(), quotatest.New(false, nil), supportbundlestest.NewFakeBundleService(),
) )
require.NoError(t, err) require.NoError(t, err)
return acstore, permissionStore, userService, teamService, orgService return acstore, permissionStore, userService, teamService, orgService, sql
} }
func TestIntegrationAccessControlStore_SearchUsersPermissions(t *testing.T) { func TestIntegrationAccessControlStore_SearchUsersPermissions(t *testing.T) {
@ -735,8 +736,8 @@ func TestIntegrationAccessControlStore_SearchUsersPermissions(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
acStore, permissionsStore, userSvc, teamSvc, orgSvc := setupTestEnv(t) acStore, permissionsStore, userSvc, teamSvc, orgSvc, sql := setupTestEnv(t)
dbUsers := createUsersAndTeams(t, acStore.sql, helperServices{userSvc, teamSvc, orgSvc}, 1, tt.users) dbUsers := createUsersAndTeams(t, sql, helperServices{userSvc, teamSvc, orgSvc}, 1, tt.users)
// Switch userID and TeamID by the real stored ones // Switch userID and TeamID by the real stored ones
for i := range tt.permCmds { for i := range tt.permCmds {
@ -815,8 +816,8 @@ func TestAccessControlStore_GetUsersBasicRoles(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
acStore, _, userSvc, teamSvc, orgSvc := setupTestEnv(t) acStore, _, userSvc, teamSvc, orgSvc, sql := setupTestEnv(t)
dbUsers := createUsersAndTeams(t, acStore.sql, helperServices{userSvc, teamSvc, orgSvc}, 1, tt.users) dbUsers := createUsersAndTeams(t, sql, helperServices{userSvc, teamSvc, orgSvc}, 1, tt.users)
// Test // Test
dbRoles, err := acStore.GetUsersBasicRoles(ctx, tt.userFilter, 1) dbRoles, err := acStore.GetUsersBasicRoles(ctx, tt.userFilter, 1)

View File

@ -16,6 +16,9 @@ type Evaluator interface {
Evaluate(permissions map[string][]string) bool Evaluate(permissions map[string][]string) bool
// MutateScopes executes a sequence of ScopeModifier functions on all embedded scopes of an evaluator and returns a new Evaluator // MutateScopes executes a sequence of ScopeModifier functions on all embedded scopes of an evaluator and returns a new Evaluator
MutateScopes(ctx context.Context, mutate ScopeAttributeMutator) (Evaluator, error) MutateScopes(ctx context.Context, mutate ScopeAttributeMutator) (Evaluator, error)
// AppendActionSets extends the evaluator with relevant action sets
// (e.g. evaluator checking `folders:write` is extended to check for any of `folders:write`, `folders:edit`, `folders:admin`)
AppendActionSets(ctx context.Context, mutate ActionSetResolver) Evaluator
// String returns a string representation of permission required by the evaluator // String returns a string representation of permission required by the evaluator
fmt.Stringer fmt.Stringer
fmt.GoStringer fmt.GoStringer
@ -107,6 +110,17 @@ func (p permissionEvaluator) MutateScopes(ctx context.Context, mutate ScopeAttri
return EvalPermission(p.Action, scopes...), nil return EvalPermission(p.Action, scopes...), nil
} }
func (p permissionEvaluator) AppendActionSets(ctx context.Context, resolve ActionSetResolver) Evaluator {
resolvedActions := resolve(ctx, p.Action)
evals := make([]Evaluator, 0, len(resolvedActions))
for _, action := range resolvedActions {
evals = append(evals, EvalPermission(action, p.Scopes...))
}
return EvalAny(evals...)
}
func (p permissionEvaluator) String() string { func (p permissionEvaluator) String() string {
return p.Action return p.Action
} }
@ -157,6 +171,16 @@ func (a allEvaluator) MutateScopes(ctx context.Context, mutate ScopeAttributeMut
return EvalAll(modified...), nil return EvalAll(modified...), nil
} }
func (a allEvaluator) AppendActionSets(ctx context.Context, resolve ActionSetResolver) Evaluator {
evals := make([]Evaluator, 0, len(a.allOf))
for _, e := range a.allOf {
resolvedSets := e.AppendActionSets(ctx, resolve)
evals = append(evals, resolvedSets)
}
return EvalAll(evals...)
}
func (a allEvaluator) String() string { func (a allEvaluator) String() string {
permissions := make([]string, 0, len(a.allOf)) permissions := make([]string, 0, len(a.allOf))
for _, e := range a.allOf { for _, e := range a.allOf {
@ -218,6 +242,16 @@ func (a anyEvaluator) MutateScopes(ctx context.Context, mutate ScopeAttributeMut
return EvalAny(modified...), nil return EvalAny(modified...), nil
} }
func (a anyEvaluator) AppendActionSets(ctx context.Context, resolve ActionSetResolver) Evaluator {
evals := make([]Evaluator, 0, len(a.anyOf))
for _, e := range a.anyOf {
resolvedSets := e.AppendActionSets(ctx, resolve)
evals = append(evals, resolvedSets)
}
return EvalAny(evals...)
}
func (a anyEvaluator) String() string { func (a anyEvaluator) String() string {
permissions := make([]string, 0, len(a.anyOf)) permissions := make([]string, 0, len(a.anyOf))
for _, e := range a.anyOf { for _, e := range a.anyOf {

View File

@ -288,9 +288,9 @@ var DatasourceQueryActions = []string{
datasources.ActionQuery, datasources.ActionQuery,
} }
func ProvideDatasourcePermissionsService(features featuremgmt.FeatureToggles, db db.DB, actionSetService resourcepermissions.ActionSetService) *DatasourcePermissionsService { func ProvideDatasourcePermissionsService(features featuremgmt.FeatureToggles, db db.DB) *DatasourcePermissionsService {
return &DatasourcePermissionsService{ return &DatasourcePermissionsService{
store: resourcepermissions.NewStore(db, features, &actionSetService), store: resourcepermissions.NewStore(db, features),
} }
} }

View File

@ -15,6 +15,10 @@ type ScopeAttributeResolver interface {
Resolve(ctx context.Context, orgID int64, scope string) ([]string, error) Resolve(ctx context.Context, orgID int64, scope string) ([]string, error)
} }
type ActionResolver interface {
Resolve(action string) []string
}
// ScopeAttributeResolverFunc is an adapter to allow functions to implement ScopeAttributeResolver interface // ScopeAttributeResolverFunc is an adapter to allow functions to implement ScopeAttributeResolver interface
type ScopeAttributeResolverFunc func(ctx context.Context, orgID int64, scope string) ([]string, error) type ScopeAttributeResolverFunc func(ctx context.Context, orgID int64, scope string) ([]string, error)
@ -24,6 +28,8 @@ func (f ScopeAttributeResolverFunc) Resolve(ctx context.Context, orgID int64, sc
type ScopeAttributeMutator func(context.Context, string) ([]string, error) type ScopeAttributeMutator func(context.Context, string) ([]string, error)
type ActionSetResolver func(context.Context, string) []string
const ( const (
ttl = 30 * time.Second ttl = 30 * time.Second
cleanInterval = 2 * time.Minute cleanInterval = 2 * time.Minute
@ -41,6 +47,7 @@ type Resolvers struct {
log log.Logger log log.Logger
cache *localcache.CacheService cache *localcache.CacheService
attributeResolvers map[string]ScopeAttributeResolver attributeResolvers map[string]ScopeAttributeResolver
actionResolver ActionResolver
} }
func (s *Resolvers) AddScopeAttributeResolver(prefix string, resolver ScopeAttributeResolver) { func (s *Resolvers) AddScopeAttributeResolver(prefix string, resolver ScopeAttributeResolver) {
@ -48,6 +55,10 @@ func (s *Resolvers) AddScopeAttributeResolver(prefix string, resolver ScopeAttri
s.attributeResolvers[prefix] = resolver s.attributeResolvers[prefix] = resolver
} }
func (s *Resolvers) SetActionResolver(resolver ActionResolver) {
s.actionResolver = resolver
}
func (s *Resolvers) GetScopeAttributeMutator(orgID int64) ScopeAttributeMutator { func (s *Resolvers) GetScopeAttributeMutator(orgID int64) ScopeAttributeMutator {
return func(ctx context.Context, scope string) ([]string, error) { return func(ctx context.Context, scope string) ([]string, error) {
key := getScopeCacheKey(orgID, scope) key := getScopeCacheKey(orgID, scope)
@ -77,3 +88,15 @@ func (s *Resolvers) GetScopeAttributeMutator(orgID int64) ScopeAttributeMutator
func getScopeCacheKey(orgID int64, scope string) string { func getScopeCacheKey(orgID int64, scope string) string {
return fmt.Sprintf("%s-%v", scope, orgID) return fmt.Sprintf("%s-%v", scope, orgID)
} }
func (s *Resolvers) GetActionSetResolver() ActionSetResolver {
return func(ctx context.Context, action string) []string {
if s.actionResolver == nil {
return []string{action}
}
actionSetActions := s.actionResolver.Resolve(action)
actions := append(actionSetActions, action)
s.log.Debug("Resolved action", "action", action, "resolved_actions", actions)
return actions
}
}

View File

@ -66,7 +66,9 @@ func New(cfg *setting.Cfg,
for _, a := range actions { for _, a := range actions {
actionSet[a] = struct{}{} actionSet[a] = struct{}{}
} }
actionSetService.StoreActionSet(options.Resource, permission, actions) if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) {
actionSetService.StoreActionSet(options.Resource, permission, actions)
}
} }
// Sort all permissions based on action length. Will be used when mapping between actions to permissions // Sort all permissions based on action length. Will be used when mapping between actions to permissions
@ -81,7 +83,7 @@ func New(cfg *setting.Cfg,
s := &Service{ s := &Service{
ac: ac, ac: ac,
store: NewStore(sqlStore, features, &actionSetService), store: NewStore(sqlStore, features),
options: options, options: options,
license: license, license: license,
permissions: permissions, permissions: permissions,

View File

@ -224,6 +224,102 @@ func TestService_SetPermissions(t *testing.T) {
} }
} }
func TestService_RegisterActionSets(t *testing.T) {
type registerActionSetsTest struct {
desc string
actionSetsEnabled bool
options Options
expectedActionSets []ActionSet
}
tests := []registerActionSetsTest{
{
desc: "should register folder action sets if action sets are enabled",
actionSetsEnabled: true,
options: Options{
Resource: "folders",
PermissionsToActions: map[string][]string{
"View": {"folders:read", "dashboards:read"},
"Edit": {"folders:read", "dashboards:read", "folders:write", "dashboards:write"},
},
},
expectedActionSets: []ActionSet{
{
Action: "folders:view",
Actions: []string{"folders:read", "dashboards:read"},
},
{
Action: "folders:edit",
Actions: []string{"folders:read", "dashboards:read", "folders:write", "dashboards:write"},
},
},
},
{
desc: "should register dashboard action set if action sets are enabled",
actionSetsEnabled: true,
options: Options{
Resource: "dashboards",
PermissionsToActions: map[string][]string{
"View": {"dashboards:read"},
},
},
expectedActionSets: []ActionSet{
{
Action: "dashboards:view",
Actions: []string{"dashboards:read"},
},
},
},
{
desc: "should not register dashboard action set if action sets are not enabled",
actionSetsEnabled: false,
options: Options{
Resource: "dashboards",
PermissionsToActions: map[string][]string{
"View": {"dashboards:read"},
},
},
expectedActionSets: []ActionSet{},
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
cfg := setting.NewCfg()
cfg.IsFeatureToggleEnabled = func(ft string) bool {
if ft == featuremgmt.FlagAccessActionSets {
return tt.actionSetsEnabled
}
return false
}
ac := acimpl.ProvideAccessControl(cfg)
actionSets := NewActionSetService(ac)
features := featuremgmt.WithFeatures()
if tt.actionSetsEnabled {
features = featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets)
}
_, err := New(
setting.NewCfg(), tt.options, features, routing.NewRouteRegister(), licensingtest.NewFakeLicensing(),
ac, &actest.FakeService{}, db.InitTestDB(t), nil, nil, actionSets,
)
require.NoError(t, err)
if len(tt.expectedActionSets) > 0 {
for _, expectedActionSet := range tt.expectedActionSets {
actionSet := actionSets.GetActionSet(expectedActionSet.Action)
assert.ElementsMatch(t, expectedActionSet.Actions, actionSet)
}
} else {
// Check that action sets have not been registered
for permission := range tt.options.PermissionsToActions {
actionSetName := GetActionSetName(tt.options.Resource, permission)
assert.Nil(t, actionSets.GetActionSet(actionSetName))
}
}
})
}
}
func setupTestEnvironment(t *testing.T, ops Options) (*Service, user.Service, team.Service) { func setupTestEnvironment(t *testing.T, ops Options) (*Service, user.Service, team.Service) {
t.Helper() t.Helper()
@ -245,11 +341,11 @@ func setupTestEnvironment(t *testing.T, ops Options) (*Service, user.Service, te
license := licensingtest.NewFakeLicensing() license := licensingtest.NewFakeLicensing()
license.On("FeatureEnabled", "accesscontrol.enforcement").Return(true).Maybe() license.On("FeatureEnabled", "accesscontrol.enforcement").Return(true).Maybe()
ac := acimpl.ProvideAccessControl(cfg) ac := acimpl.ProvideAccessControl(setting.NewCfg())
acService := &actest.FakeService{} acService := &actest.FakeService{}
service, err := New( service, err := New(
cfg, ops, featuremgmt.WithFeatures(), routing.NewRouteRegister(), license, cfg, ops, featuremgmt.WithFeatures(), routing.NewRouteRegister(), license,
ac, acService, sql, teamSvc, userSvc, NewActionSetService(), ac, acService, sql, teamSvc, userSvc, NewActionSetService(ac),
) )
require.NoError(t, err) require.NoError(t, err)

View File

@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/services/serviceaccounts"
@ -17,14 +18,14 @@ import (
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
) )
func NewStore(sql db.DB, features featuremgmt.FeatureToggles, actionsetService *ActionSetService) *store { func NewStore(sql db.DB, features featuremgmt.FeatureToggles) *store {
return &store{sql, features, *actionsetService} store := &store{sql: sql, features: features}
return store
} }
type store struct { type store struct {
sql db.DB sql db.DB
features featuremgmt.FeatureToggles features featuremgmt.FeatureToggles
actionSetService ActionSetService
} }
type flatResourcePermission struct { type flatResourcePermission struct {
@ -665,7 +666,7 @@ func (s *store) createPermissions(sess *db.Session, roleID int64, resource, reso
Add ACTION SET of managed permissions to in-memory store Add ACTION SET of managed permissions to in-memory store
*/ */
if s.features.IsEnabled(context.TODO(), featuremgmt.FlagAccessActionSets) && permission != "" { if s.features.IsEnabled(context.TODO(), featuremgmt.FlagAccessActionSets) && permission != "" {
actionSetName := s.actionSetService.GetActionSetName(resource, permission) actionSetName := GetActionSetName(resource, permission)
p := managedPermission(actionSetName, resource, resourceID, resourceAttribute) p := managedPermission(actionSetName, resource, resourceID, resourceAttribute)
p.RoleID = roleID p.RoleID = roleID
p.Created = time.Now() p.Created = time.Now()
@ -735,8 +736,10 @@ Stores actionsets IN MEMORY
// An example of an action set is "folders:edit" which represents the set of RBAC actions that are granted by edit access to a folder. // An example of an action set is "folders:edit" which represents the set of RBAC actions that are granted by edit access to a folder.
type ActionSetService interface { type ActionSetService interface {
accesscontrol.ActionResolver
GetActionSet(actionName string) []string GetActionSet(actionName string) []string
GetActionSetName(resource, permission string) string //GetActionSetName(resource, permission string) string
StoreActionSet(resource, permission string, actions []string) StoreActionSet(resource, permission string, actions []string)
} }
@ -747,21 +750,47 @@ type ActionSet struct {
// InMemoryActionSets is an in-memory implementation of the ActionSetService. // InMemoryActionSets is an in-memory implementation of the ActionSetService.
type InMemoryActionSets struct { type InMemoryActionSets struct {
log log.Logger log log.Logger
actionSets map[string][]string actionSetToActions map[string][]string
actionToActionSets map[string][]string
} }
// NewActionSetService returns a new instance of InMemoryActionSetService. // NewActionSetService returns a new instance of InMemoryActionSetService.
func NewActionSetService() ActionSetService { func NewActionSetService(a *acimpl.AccessControl) ActionSetService {
return &InMemoryActionSets{ actionSets := &InMemoryActionSets{
actionSets: make(map[string][]string), log: log.New("resourcepermissions.actionsets"),
log: log.New("resourcepermissions.actionsets"), actionSetToActions: make(map[string][]string),
actionToActionSets: make(map[string][]string),
} }
a.RegisterActionResolver(actionSets)
return actionSets
}
func (s *InMemoryActionSets) Resolve(action string) []string {
actionSets := s.actionToActionSets[action]
sets := make([]string, 0, len(actionSets))
for _, actionSet := range actionSets {
setParts := strings.Split(actionSet, ":")
if len(setParts) != 2 {
s.log.Debug("skipping resolution for action set with invalid name", "action set", actionSet)
continue
}
prefix := setParts[0]
// Only use action sets for folders and dashboards for now
// We need to verify that action sets for other resources do not share names with actions (eg, `datasources:query`)
if prefix != "folders" && prefix != "dashboards" {
continue
}
sets = append(sets, actionSet)
}
return sets
} }
// GetActionSet returns the action set for the given action. // GetActionSet returns the action set for the given action.
func (s *InMemoryActionSets) GetActionSet(actionName string) []string { func (s *InMemoryActionSets) GetActionSet(actionName string) []string {
actionSet, ok := s.actionSets[actionName] actionSet, ok := s.actionSetToActions[actionName]
if !ok { if !ok {
return nil return nil
} }
@ -769,17 +798,24 @@ func (s *InMemoryActionSets) GetActionSet(actionName string) []string {
} }
func (s *InMemoryActionSets) StoreActionSet(resource, permission string, actions []string) { func (s *InMemoryActionSets) StoreActionSet(resource, permission string, actions []string) {
s.log.Debug("storing action set\n") name := GetActionSetName(resource, permission)
name := s.GetActionSetName(resource, permission)
actionSet := &ActionSet{ actionSet := &ActionSet{
Action: name, Action: name,
Actions: actions, Actions: actions,
} }
s.actionSets[actionSet.Action] = actions s.actionSetToActions[actionSet.Action] = actions
for _, action := range actions {
if _, ok := s.actionToActionSets[action]; !ok {
s.actionToActionSets[action] = []string{}
}
s.actionToActionSets[action] = append(s.actionToActionSets[action], actionSet.Action)
}
s.log.Debug("stored action set", "action set name", actionSet.Action)
} }
// GetActionSetName function creates an action set from a list of actions and stores it inmemory. // GetActionSetName function creates an action set from a list of actions and stores it inmemory.
func (s *InMemoryActionSets) GetActionSetName(resource, permission string) string { func GetActionSetName(resource, permission string) string {
// lower cased // lower cased
resource = strings.ToLower(resource) resource = strings.ToLower(resource)
permission = strings.ToLower(permission) permission = strings.ToLower(permission)

View File

@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
@ -563,8 +564,7 @@ func seedResourcePermissions(
func setupTestEnv(t testing.TB) (*store, db.DB, *setting.Cfg) { func setupTestEnv(t testing.TB) (*store, db.DB, *setting.Cfg) {
sql, cfg := db.InitTestDBWithCfg(t) sql, cfg := db.InitTestDBWithCfg(t)
asService := NewActionSetService() return NewStore(sql, featuremgmt.WithFeatures()), sql, cfg
return NewStore(sql, featuremgmt.WithFeatures(), &asService), sql, cfg
} }
func TestStore_IsInherited(t *testing.T) { func TestStore_IsInherited(t *testing.T) {
@ -759,49 +759,85 @@ func retrievePermissionsHelper(store *store, t *testing.T) []orgPermission {
return permissions return permissions
} }
func TestStore_ResourcePermissionsActionSets(t *testing.T) { func TestStore_StoreActionSet(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("skipping integration test") t.Skip("skipping integration test")
} }
type actionSetTest struct { type actionSetTest struct {
desc string desc string
orgID int64 resource string
actionSet ActionSet action string
actions []string
} }
tests := []actionSetTest{ tests := []actionSetTest{
{ {
desc: "should be able to store actionset", desc: "should be able to store action set",
orgID: 1, resource: "folders",
actionSet: ActionSet{ action: "edit",
Actions: []string{"folders:read", "folders:write"}, actions: []string{"folders:read", "folders:write"},
},
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) { t.Run(tt.desc, func(t *testing.T) {
store, _, _ := setupTestEnv(t) store, _, _ := setupTestEnv(t)
store.features = featuremgmt.WithFeatures([]any{featuremgmt.FlagAccessActionSets}) store.features = featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets)
ac := acimpl.ProvideAccessControl(setting.NewCfg())
asService := NewActionSetService(ac)
asService.StoreActionSet(tt.resource, tt.action, tt.actions)
_, err := store.SetResourcePermissions(context.Background(), 1, []SetResourcePermissionsCommand{ actionSetName := GetActionSetName(tt.resource, tt.action)
{ actionSet := asService.GetActionSet(actionSetName)
User: accesscontrol.User{ID: 1}, require.Equal(t, tt.actions, actionSet)
SetResourcePermissionCommand: SetResourcePermissionCommand{ })
Actions: tt.actionSet.Actions, }
Resource: "folders", }
Permission: "edit",
ResourceID: "1", func TestStore_ResolveActionSet(t *testing.T) {
ResourceAttribute: "uid", if testing.Short() {
}, t.Skip("skipping integration test")
}, }
}, ResourceHooks{})
require.NoError(t, err) actionSetService := NewActionSetService(acimpl.ProvideAccessControl(setting.NewCfg()))
actionSetService.StoreActionSet("folders", "edit", []string{"folders:read", "folders:write", "dashboards:read", "dashboards:write"})
actionname := fmt.Sprintf("%s:%s", "folders", "edit") actionSetService.StoreActionSet("folders", "view", []string{"folders:read", "dashboards:read"})
actionSet := store.actionSetService.GetActionSet(actionname) actionSetService.StoreActionSet("dashboards", "view", []string{"dashboards:read"})
require.Equal(t, tt.actionSet.Actions, actionSet)
type actionSetTest struct {
desc string
action string
expectedActionSets []string
}
tests := []actionSetTest{
{
desc: "should return empty list for an action that is not a part of any action sets",
action: "datasources:query",
expectedActionSets: []string{},
},
{
desc: "should be able to resolve one action set for the resource of the same type",
action: "folders:write",
expectedActionSets: []string{"folders:edit"},
},
{
desc: "should be able to resolve multiple action sets for the resource of the same type",
action: "folders:read",
expectedActionSets: []string{"folders:view", "folders:edit"},
},
{
desc: "should be able to resolve multiple action sets for the resource of a different type",
action: "dashboards:read",
expectedActionSets: []string{"folders:view", "folders:edit", "dashboards:view"},
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
actionSets := actionSetService.Resolve(tt.action)
require.ElementsMatch(t, tt.expectedActionSets, actionSets)
}) })
} }
} }

View File

@ -306,8 +306,7 @@ func TestIntegrationSilenceAuth(t *testing.T) {
apiClient := newAlertingApiClient(grafanaListedAddr, randomLogin, randomLogin) apiClient := newAlertingApiClient(grafanaListedAddr, randomLogin, randomLogin)
// Set permissions. // Set permissions.
asService := resourcepermissions.NewActionSetService() permissionsStore := resourcepermissions.NewStore(env.SQLStore, featuremgmt.WithFeatures())
permissionsStore := resourcepermissions.NewStore(env.SQLStore, featuremgmt.WithFeatures(), &asService)
for _, cmd := range tt.permissions { for _, cmd := range tt.permissions {
_, err := permissionsStore.SetUserResourcePermission( _, err := permissionsStore.SetUserResourcePermission(
context.Background(), context.Background(),

View File

@ -107,9 +107,8 @@ func TestBacktesting(t *testing.T) {
require.Equalf(t, http.StatusForbidden, status, "Response: %s", body) require.Equalf(t, http.StatusForbidden, status, "Response: %s", body)
}) })
asService := resourcepermissions.NewActionSetService()
// access control permissions store // access control permissions store
permissionsStore := resourcepermissions.NewStore(env.SQLStore, featuremgmt.WithFeatures(), &asService) permissionsStore := resourcepermissions.NewStore(env.SQLStore, featuremgmt.WithFeatures())
_, err := permissionsStore.SetUserResourcePermission(context.Background(), _, err := permissionsStore.SetUserResourcePermission(context.Background(),
accesscontrol.GlobalOrgID, accesscontrol.GlobalOrgID,
accesscontrol.User{ID: testUserId}, accesscontrol.User{ID: testUserId},

View File

@ -674,9 +674,8 @@ func TestIntegrationPrometheusRulesPermissions(t *testing.T) {
apiClient := newAlertingApiClient(grafanaListedAddr, "grafana", "password") apiClient := newAlertingApiClient(grafanaListedAddr, "grafana", "password")
asService := resourcepermissions.NewActionSetService()
// access control permissions store // access control permissions store
permissionsStore := resourcepermissions.NewStore(env.SQLStore, featuremgmt.WithFeatures(), &asService) permissionsStore := resourcepermissions.NewStore(env.SQLStore, featuremgmt.WithFeatures())
// Create the namespace we'll save our alerts to. // Create the namespace we'll save our alerts to.
apiClient.CreateFolder(t, "folder1", "folder1") apiClient.CreateFolder(t, "folder1", "folder1")

View File

@ -52,8 +52,7 @@ func TestIntegrationAlertRulePermissions(t *testing.T) {
}) })
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p) grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p)
asService := resourcepermissions.NewActionSetService() permissionsStore := resourcepermissions.NewStore(env.SQLStore, featuremgmt.WithFeatures())
permissionsStore := resourcepermissions.NewStore(env.SQLStore, featuremgmt.WithFeatures(), &asService)
// Create a user to make authenticated requests // Create a user to make authenticated requests
userID := createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ userID := createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
@ -337,8 +336,7 @@ func TestIntegrationAlertRuleNestedPermissions(t *testing.T) {
}) })
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p) grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p)
asService := resourcepermissions.NewActionSetService() permissionsStore := resourcepermissions.NewStore(env.SQLStore, featuremgmt.WithFeatures())
permissionsStore := resourcepermissions.NewStore(env.SQLStore, featuremgmt.WithFeatures(), &asService)
// Create a user to make authenticated requests // Create a user to make authenticated requests
userID := createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ userID := createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
@ -734,8 +732,7 @@ func TestAlertRulePostExport(t *testing.T) {
}) })
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p) grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p)
asService := resourcepermissions.NewActionSetService() permissionsStore := resourcepermissions.NewStore(env.SQLStore, featuremgmt.WithFeatures())
permissionsStore := resourcepermissions.NewStore(env.SQLStore, featuremgmt.WithFeatures(), &asService)
// Create a user to make authenticated requests // Create a user to make authenticated requests
userID := createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ userID := createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
@ -1415,8 +1412,7 @@ func TestIntegrationRuleUpdate(t *testing.T) {
AppModeProduction: true, AppModeProduction: true,
}) })
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path) grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
asService := resourcepermissions.NewActionSetService() permissionsStore := resourcepermissions.NewStore(env.SQLStore, featuremgmt.WithFeatures())
permissionsStore := resourcepermissions.NewStore(env.SQLStore, featuremgmt.WithFeatures(), &asService)
// Create a user to make authenticated requests // Create a user to make authenticated requests
userID := createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ userID := createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{

View File

@ -275,8 +275,7 @@ func TestGrafanaRuleConfig(t *testing.T) {
}) })
// access control permissions store // access control permissions store
asService := resourcepermissions.NewActionSetService() permissionsStore := resourcepermissions.NewStore(env.SQLStore, featuremgmt.WithFeatures())
permissionsStore := resourcepermissions.NewStore(env.SQLStore, featuremgmt.WithFeatures(), &asService)
_, err := permissionsStore.SetUserResourcePermission(context.Background(), _, err := permissionsStore.SetUserResourcePermission(context.Background(),
accesscontrol.GlobalOrgID, accesscontrol.GlobalOrgID,
accesscontrol.User{ID: testUserId}, accesscontrol.User{ID: testUserId},

View File

@ -65,8 +65,7 @@ func TestGetFolders(t *testing.T) {
viewerClient := tests.GetClient(grafanaListedAddr, "viewer", "viewer") viewerClient := tests.GetClient(grafanaListedAddr, "viewer", "viewer")
// access control permissions store // access control permissions store
actionSetService := resourcepermissions.NewActionSetService() permissionsStore := resourcepermissions.NewStore(store, featuremgmt.WithFeatures())
permissionsStore := resourcepermissions.NewStore(store, featuremgmt.WithFeatures(), &actionSetService)
numberOfFolders := 5 numberOfFolders := 5
indexWithoutPermission := 3 indexWithoutPermission := 3