mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
RBAC: Add permission registry (#91247)
* RBAC: Permission registry * Populate permission registry * Wire * conflic_user_cmd * Update pkg/services/accesscontrol/permreg/permreg_test.go Co-authored-by: Ieva <ieva.vasiljeva@grafana.com> * PR feedback Co-authored-by: Ieva <ieva.vasiljeva@grafana.com> * Remove ToDo, tackle in subsequent PR --------- Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
This commit is contained in:
parent
391284bb33
commit
8988e04044
@ -25,6 +25,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
||||
acdb "github.com/grafana/grafana/pkg/services/accesscontrol/database"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/permreg"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
|
||||
"github.com/grafana/grafana/pkg/services/authz/zanzana"
|
||||
"github.com/grafana/grafana/pkg/services/contexthandler/ctxkey"
|
||||
@ -463,7 +464,7 @@ func setupServer(b testing.TB, sc benchScenario, features featuremgmt.FeatureTog
|
||||
actionSets := resourcepermissions.NewActionSetService(features)
|
||||
acSvc := acimpl.ProvideOSSService(
|
||||
sc.cfg, acdb.ProvideService(sc.db), actionSets, localcache.ProvideService(),
|
||||
features, tracing.InitializeTracerForTest(), zanzana.NewNoopClient(), sc.db.DB(),
|
||||
features, tracing.InitializeTracerForTest(), zanzana.NewNoopClient(), sc.db.DB(), permreg.ProvidePermissionRegistry(),
|
||||
)
|
||||
|
||||
folderPermissions, err := ossaccesscontrol.ProvideFolderPermissions(
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/permreg"
|
||||
"github.com/grafana/grafana/pkg/services/authz/zanzana"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotaimpl"
|
||||
@ -90,7 +91,7 @@ func initializeConflictResolver(cmd *utils.ContextCommandLine, f Formatter, ctx
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %w", "failed to initialize tracer service", err)
|
||||
}
|
||||
acService, err := acimpl.ProvideService(cfg, replstore, routing, nil, nil, nil, features, tracer, zanzana.NewNoopClient())
|
||||
acService, err := acimpl.ProvideService(cfg, replstore, routing, nil, nil, nil, features, tracer, zanzana.NewNoopClient(), permreg.ProvidePermissionRegistry())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %w", "failed to get access control", err)
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/permreg"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
|
||||
"github.com/grafana/grafana/pkg/services/annotations"
|
||||
"github.com/grafana/grafana/pkg/services/annotations/annotationsimpl"
|
||||
@ -345,6 +346,7 @@ var wireBasicSet = wire.NewSet(
|
||||
resourcepermissions.NewActionSetService,
|
||||
wire.Bind(new(accesscontrol.ActionResolver), new(resourcepermissions.ActionSetService)),
|
||||
wire.Bind(new(pluginaccesscontrol.ActionSetRegistry), new(resourcepermissions.ActionSetService)),
|
||||
permreg.ProvidePermissionRegistry,
|
||||
acimpl.ProvideAccessControl,
|
||||
navtreeimpl.ProvideService,
|
||||
wire.Bind(new(accesscontrol.AccessControl), new(*acimpl.AccessControl)),
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/api"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/database"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/migrator"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/permreg"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/pluginutils"
|
||||
"github.com/grafana/grafana/pkg/services/authz/zanzana"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
@ -50,9 +51,9 @@ var OSSRolesPrefixes = []string{accesscontrol.ManagedRolePrefix, accesscontrol.E
|
||||
func ProvideService(
|
||||
cfg *setting.Cfg, db db.ReplDB, routeRegister routing.RouteRegister, cache *localcache.CacheService,
|
||||
accessControl accesscontrol.AccessControl, actionResolver accesscontrol.ActionResolver,
|
||||
features featuremgmt.FeatureToggles, tracer tracing.Tracer, zclient zanzana.Client,
|
||||
features featuremgmt.FeatureToggles, tracer tracing.Tracer, zclient zanzana.Client, permRegistry permreg.PermissionRegistry,
|
||||
) (*Service, error) {
|
||||
service := ProvideOSSService(cfg, database.ProvideService(db), actionResolver, cache, features, tracer, zclient, db.DB())
|
||||
service := ProvideOSSService(cfg, database.ProvideService(db), actionResolver, cache, features, tracer, zclient, db.DB(), permRegistry)
|
||||
|
||||
api.NewAccessControlAPI(routeRegister, accessControl, service, features).RegisterAPIEndpoints()
|
||||
if err := accesscontrol.DeclareFixedRoles(service, cfg); err != nil {
|
||||
@ -73,7 +74,7 @@ func ProvideService(
|
||||
func ProvideOSSService(
|
||||
cfg *setting.Cfg, store accesscontrol.Store, actionResolver accesscontrol.ActionResolver,
|
||||
cache *localcache.CacheService, features featuremgmt.FeatureToggles, tracer tracing.Tracer,
|
||||
zclient zanzana.Client, db db.DB,
|
||||
zclient zanzana.Client, db db.DB, permRegistry permreg.PermissionRegistry,
|
||||
) *Service {
|
||||
s := &Service{
|
||||
actionResolver: actionResolver,
|
||||
@ -85,6 +86,7 @@ func ProvideOSSService(
|
||||
store: store,
|
||||
tracer: tracer,
|
||||
sync: migrator.NewZanzanaSynchroniser(zclient, db),
|
||||
permRegistry: permRegistry,
|
||||
}
|
||||
|
||||
return s
|
||||
@ -102,6 +104,7 @@ type Service struct {
|
||||
store accesscontrol.Store
|
||||
tracer tracing.Tracer
|
||||
sync *migrator.ZanzanaSynchroniser
|
||||
permRegistry permreg.PermissionRegistry
|
||||
}
|
||||
|
||||
func (s *Service) GetUsageStats(_ context.Context) map[string]any {
|
||||
@ -406,6 +409,10 @@ func (s *Service) DeclareFixedRoles(registrations ...accesscontrol.RoleRegistrat
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range r.Role.Permissions {
|
||||
s.permRegistry.RegisterPermission(r.Role.Permissions[i].Action, r.Role.Permissions[i].Scope)
|
||||
}
|
||||
|
||||
s.registrations.Append(r)
|
||||
}
|
||||
|
||||
@ -458,6 +465,12 @@ func (s *Service) DeclarePluginRoles(ctx context.Context, ID, name string, regs
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range r.Role.Permissions {
|
||||
// Register plugin actions and their possible scopes for permission validation
|
||||
s.permRegistry.RegisterPluginScope(r.Role.Permissions[i].Scope)
|
||||
s.permRegistry.RegisterPermission(r.Role.Permissions[i].Action, r.Role.Permissions[i].Scope)
|
||||
}
|
||||
|
||||
s.log.Debug("Registering plugin role", "role", r.Role.Name)
|
||||
s.registrations.Append(r)
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/database"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/permreg"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/licensing"
|
||||
@ -42,6 +43,7 @@ func setupTestEnv(t testing.TB) *Service {
|
||||
roles: accesscontrol.BuildBasicRoleDefinitions(),
|
||||
tracer: tracing.InitializeTracerForTest(),
|
||||
store: database.ProvideService(db.InitTestReplDB(t)),
|
||||
permRegistry: permreg.ProvidePermissionRegistry(),
|
||||
}
|
||||
require.NoError(t, ac.RegisterFixedRoles(context.Background()))
|
||||
return ac
|
||||
@ -71,6 +73,7 @@ func TestUsageMetrics(t *testing.T) {
|
||||
tracing.InitializeTracerForTest(),
|
||||
nil,
|
||||
nil,
|
||||
permreg.ProvidePermissionRegistry(),
|
||||
)
|
||||
assert.Equal(t, tt.expectedValue, s.GetUsageStats(context.Background())["stats.oss.accesscontrol.enabled.count"])
|
||||
})
|
||||
|
183
pkg/services/accesscontrol/permreg/permreg.go
Normal file
183
pkg/services/accesscontrol/permreg/permreg.go
Normal file
@ -0,0 +1,183 @@
|
||||
package permreg
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/errutil"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrInvalidScope is returned when the scope is not valid for the action
|
||||
ErrInvalidScopeTplt = "invalid scope: {{.Public.Scope}}, for action: {{.Public.Action}}, expected prefixes are {{.Public.ValidScopesFormat}}"
|
||||
ErrBaseInvalidScope = errutil.BadRequest("permreg.invalid-scope").MustTemplate(ErrInvalidScopeTplt, errutil.WithPublic(ErrInvalidScopeTplt))
|
||||
|
||||
ErrUnknownActionTplt = "unknown action: {{.Public.Action}}, was not found in the list of valid actions"
|
||||
ErrBaseUnknownAction = errutil.BadRequest("permreg.unknown-action").MustTemplate(ErrUnknownActionTplt, errutil.WithPublic(ErrUnknownActionTplt))
|
||||
)
|
||||
|
||||
func ErrInvalidScope(scope string, action string, validScopePrefixes PrefixSet) error {
|
||||
if len(validScopePrefixes) == 0 {
|
||||
return ErrBaseInvalidScope.Build(errutil.TemplateData{Public: map[string]any{"Scope": scope, "Action": action, "ValidScopesFormat": "[none]"}})
|
||||
}
|
||||
formats := generateValidScopeFormats(validScopePrefixes)
|
||||
return ErrBaseInvalidScope.Build(errutil.TemplateData{Public: map[string]any{"Scope": scope, "Action": action, "ValidScopesFormat": formats}})
|
||||
}
|
||||
|
||||
func ErrUnknownAction(action string) error {
|
||||
return ErrBaseUnknownAction.Build(errutil.TemplateData{Public: map[string]any{"Action": action}})
|
||||
}
|
||||
|
||||
func generateValidScopeFormats(acceptedScopePrefixes PrefixSet) []string {
|
||||
if len(acceptedScopePrefixes) == 0 {
|
||||
return []string{}
|
||||
}
|
||||
acceptedPrefixesList := make([]string, 0, 10)
|
||||
acceptedPrefixesList = append(acceptedPrefixesList, "*")
|
||||
for prefix := range acceptedScopePrefixes {
|
||||
parts := strings.Split(prefix, ":")
|
||||
// If the prefix has an attribute part add the intermediate format kind:*
|
||||
if len(parts) > 2 {
|
||||
acceptedPrefixesList = append(acceptedPrefixesList, parts[0]+":*")
|
||||
}
|
||||
// Add the most specific format kind:attribute:*
|
||||
acceptedPrefixesList = append(acceptedPrefixesList, prefix+"*")
|
||||
}
|
||||
return acceptedPrefixesList
|
||||
}
|
||||
|
||||
type PermissionRegistry interface {
|
||||
RegisterPluginScope(scope string)
|
||||
RegisterPermission(action, scope string)
|
||||
IsPermissionValid(action, scope string) error
|
||||
GetScopePrefixes(action string) (PrefixSet, bool)
|
||||
}
|
||||
|
||||
type PrefixSet map[string]bool
|
||||
|
||||
var _ PermissionRegistry = &permissionRegistry{}
|
||||
|
||||
type permissionRegistry struct {
|
||||
actionScopePrefixes map[string]PrefixSet // TODO use thread safe map
|
||||
kindScopePrefix map[string]string
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
func ProvidePermissionRegistry() PermissionRegistry {
|
||||
return newPermissionRegistry()
|
||||
}
|
||||
|
||||
func newPermissionRegistry() *permissionRegistry {
|
||||
// defaultKindScopes maps the most specific accepted scope prefix for a given kind (folders, dashboards, etc)
|
||||
defaultKindScopes := map[string]string{
|
||||
"teams": "teams:id:",
|
||||
"users": "users:id:",
|
||||
"datasources": "datasources:uid:",
|
||||
"dashboards": "dashboards:uid:",
|
||||
"folders": "folders:uid:",
|
||||
"annotations": "annotations:type:",
|
||||
"apikeys": "apikeys:id:",
|
||||
"orgs": "orgs:id:",
|
||||
"plugins": "plugins:id:",
|
||||
"provisioners": "provisioners:",
|
||||
"reports": "reports:id:",
|
||||
"permissions": "permissions:type:",
|
||||
"serviceaccounts": "serviceaccounts:id:",
|
||||
"settings": "settings:",
|
||||
"global.users": "global.users:id:",
|
||||
"roles": "roles:uid:",
|
||||
"services": "services:",
|
||||
}
|
||||
return &permissionRegistry{
|
||||
actionScopePrefixes: make(map[string]PrefixSet, 200),
|
||||
kindScopePrefix: defaultKindScopes,
|
||||
logger: log.New("accesscontrol.permreg"),
|
||||
}
|
||||
}
|
||||
|
||||
func (pr *permissionRegistry) RegisterPluginScope(scope string) {
|
||||
if scope == "" {
|
||||
return
|
||||
}
|
||||
|
||||
scopeParts := strings.Split(scope, ":")
|
||||
// If the scope is already registered, return
|
||||
if _, found := pr.kindScopePrefix[scopeParts[0]]; found {
|
||||
return
|
||||
}
|
||||
|
||||
// If the scope contains an attribute part, register the kind and attribute
|
||||
if len(scopeParts) > 2 {
|
||||
kind, attr := scopeParts[0], scopeParts[1]
|
||||
pr.kindScopePrefix[kind] = kind + ":" + attr + ":"
|
||||
pr.logger.Debug("registered scope prefix", "kind", kind, "scope_prefix", kind+":"+attr+":")
|
||||
return
|
||||
}
|
||||
|
||||
pr.logger.Debug("registered scope prefix", "kind", scopeParts[0], "scope_prefix", scopeParts[0]+":")
|
||||
pr.kindScopePrefix[scopeParts[0]] = scopeParts[0] + ":"
|
||||
}
|
||||
|
||||
func (pr *permissionRegistry) RegisterPermission(action, scope string) {
|
||||
if _, ok := pr.actionScopePrefixes[action]; !ok {
|
||||
pr.actionScopePrefixes[action] = PrefixSet{}
|
||||
}
|
||||
|
||||
if scope == "" {
|
||||
// scopeless action
|
||||
return
|
||||
}
|
||||
|
||||
kind := strings.Split(scope, ":")[0]
|
||||
scopePrefix, ok := pr.kindScopePrefix[kind]
|
||||
if !ok {
|
||||
pr.logger.Warn("unknown scope prefix", "scope", scope)
|
||||
return
|
||||
}
|
||||
|
||||
// Add a new entry in case the scope is not empty
|
||||
pr.actionScopePrefixes[action][scopePrefix] = true
|
||||
}
|
||||
|
||||
func (pr *permissionRegistry) IsPermissionValid(action, scope string) error {
|
||||
validScopePrefixes, ok := pr.actionScopePrefixes[action]
|
||||
if !ok {
|
||||
return ErrUnknownAction(action)
|
||||
}
|
||||
|
||||
if ok && len(validScopePrefixes) == 0 {
|
||||
// Expecting action without any scope
|
||||
if scope != "" {
|
||||
return ErrInvalidScope(scope, action, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if !isScopeValid(scope, validScopePrefixes) {
|
||||
return ErrInvalidScope(scope, action, validScopePrefixes)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isScopeValid(scope string, validScopePrefixes PrefixSet) bool {
|
||||
// Super wildcard scope
|
||||
if scope == "*" {
|
||||
return true
|
||||
}
|
||||
for scopePrefix := range validScopePrefixes {
|
||||
// Correct scope prefix
|
||||
if strings.HasPrefix(scope, scopePrefix) {
|
||||
return true
|
||||
}
|
||||
// Scope is wildcard of the correct prefix
|
||||
if strings.HasSuffix(scope, ":*") && strings.HasPrefix(scopePrefix, scope[:len(scope)-2]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (pr *permissionRegistry) GetScopePrefixes(action string) (PrefixSet, bool) {
|
||||
set, ok := pr.actionScopePrefixes[action]
|
||||
return set, ok
|
||||
}
|
246
pkg/services/accesscontrol/permreg/permreg_test.go
Normal file
246
pkg/services/accesscontrol/permreg/permreg_test.go
Normal file
@ -0,0 +1,246 @@
|
||||
package permreg
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_permissionRegistry_RegisterPluginScope(t *testing.T) {
|
||||
tests := []struct {
|
||||
scope string
|
||||
wantKind string
|
||||
wantScope string
|
||||
}{
|
||||
{
|
||||
scope: "folders:uid:AABBCC",
|
||||
wantKind: "folders",
|
||||
wantScope: "folders:uid:",
|
||||
},
|
||||
{
|
||||
scope: "plugins:id:test-app",
|
||||
wantKind: "plugins",
|
||||
wantScope: "plugins:id:",
|
||||
},
|
||||
{
|
||||
scope: "resource:uid:res",
|
||||
wantKind: "resource",
|
||||
wantScope: "resource:uid:",
|
||||
},
|
||||
{
|
||||
scope: "resource:*",
|
||||
wantKind: "resource",
|
||||
wantScope: "resource:",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.scope, func(t *testing.T) {
|
||||
pr := newPermissionRegistry()
|
||||
pr.RegisterPluginScope(tt.scope)
|
||||
got, ok := pr.kindScopePrefix[tt.wantKind]
|
||||
require.True(t, ok)
|
||||
require.Equal(t, tt.wantScope, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_permissionRegistry_RegisterPermission(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
action string
|
||||
scope string
|
||||
wantKind string
|
||||
wantPrefixSet PrefixSet
|
||||
wantSkip bool
|
||||
}{
|
||||
{
|
||||
name: "register folders read",
|
||||
action: "folders:read",
|
||||
scope: "folders:*",
|
||||
wantKind: "folders",
|
||||
wantPrefixSet: PrefixSet{"folders:uid:": true},
|
||||
},
|
||||
{
|
||||
name: "register app plugin settings read",
|
||||
action: "test-app.settings:read",
|
||||
wantKind: "settings",
|
||||
wantPrefixSet: PrefixSet{},
|
||||
},
|
||||
{
|
||||
name: "register an action on an unknown kind",
|
||||
action: "unknown:action",
|
||||
scope: "unknown:uid:*",
|
||||
wantPrefixSet: PrefixSet{},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
pr := newPermissionRegistry()
|
||||
pr.RegisterPermission(tt.action, tt.scope)
|
||||
got, ok := pr.actionScopePrefixes[tt.action]
|
||||
require.True(t, ok)
|
||||
for k, v := range got {
|
||||
require.Equal(t, v, tt.wantPrefixSet[k])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_permissionRegistry_IsPermissionValid(t *testing.T) {
|
||||
pr := newPermissionRegistry()
|
||||
pr.RegisterPermission("folders:read", "folders:uid:")
|
||||
pr.RegisterPermission("test-app.settings:read", "")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
action string
|
||||
scope string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid folders read",
|
||||
action: "folders:read",
|
||||
scope: "folders:uid:AABBCC",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid folders read with wildcard",
|
||||
action: "folders:read",
|
||||
scope: "folders:uid:*",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid folders read with kind level wildcard",
|
||||
action: "folders:read",
|
||||
scope: "folders:*",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid folders read with super wildcard",
|
||||
action: "folders:read",
|
||||
scope: "*",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid folders read with wrong kind",
|
||||
action: "folders:read",
|
||||
scope: "unknown:uid:AABBCC",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid folders read with wrong attribute",
|
||||
action: "folders:read",
|
||||
scope: "folders:id:3",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "valid app plugin settings read",
|
||||
action: "test-app.settings:read",
|
||||
scope: "",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "app plugin settings read with a scope",
|
||||
action: "test-app.settings:read",
|
||||
scope: "folders:uid:*",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "unknown action",
|
||||
action: "unknown:write",
|
||||
scope: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := pr.IsPermissionValid(tt.action, tt.scope)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_permissionRegistry_GetScopePrefixes(t *testing.T) {
|
||||
pr := newPermissionRegistry()
|
||||
pr.RegisterPermission("folders:read", "folders:uid:")
|
||||
pr.RegisterPermission("test-app.settings:read", "")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
action string
|
||||
want PrefixSet
|
||||
shouldExist bool
|
||||
}{
|
||||
{
|
||||
name: "get folders read scope prefixes",
|
||||
action: "folders:read",
|
||||
want: PrefixSet{"folders:uid:": true},
|
||||
shouldExist: true,
|
||||
},
|
||||
{
|
||||
name: "get app plugin settings read scope prefixes",
|
||||
action: "test-app.settings:read",
|
||||
want: PrefixSet{},
|
||||
shouldExist: true,
|
||||
},
|
||||
{
|
||||
name: "get unknown action scope prefixes",
|
||||
action: "unknown:write",
|
||||
want: PrefixSet{},
|
||||
shouldExist: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, got1 := pr.GetScopePrefixes(tt.action)
|
||||
if !tt.shouldExist {
|
||||
require.False(t, got1)
|
||||
return
|
||||
}
|
||||
require.True(t, got1)
|
||||
require.Len(t, tt.want, len(got))
|
||||
for k, v := range got {
|
||||
require.Equal(t, v, tt.want[k])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_generateValidScopeFormats(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
prefixSet PrefixSet
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "empty prefix set",
|
||||
prefixSet: PrefixSet{},
|
||||
want: []string{},
|
||||
},
|
||||
{
|
||||
name: "short prefix",
|
||||
prefixSet: PrefixSet{"folders:": true},
|
||||
want: []string{"*", "folders:*"},
|
||||
},
|
||||
{
|
||||
name: "single prefix",
|
||||
prefixSet: PrefixSet{"folders:uid:": true},
|
||||
want: []string{"*", "folders:*", "folders:uid:*"},
|
||||
},
|
||||
{
|
||||
name: "multiple prefixes",
|
||||
prefixSet: PrefixSet{"folders:uid:": true, "dashboards:uid:": true},
|
||||
want: []string{"*", "folders:*", "folders:uid:*", "dashboards:*", "dashboards:uid:*"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := generateValidScopeFormats(tt.prefixSet)
|
||||
require.ElementsMatch(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
22
pkg/services/accesscontrol/permreg/test/testreg.go
Normal file
22
pkg/services/accesscontrol/permreg/test/testreg.go
Normal file
@ -0,0 +1,22 @@
|
||||
package test
|
||||
|
||||
import "github.com/grafana/grafana/pkg/services/accesscontrol/permreg"
|
||||
|
||||
func ProvidePermissionRegistry() permreg.PermissionRegistry {
|
||||
permReg := permreg.ProvidePermissionRegistry()
|
||||
// Test core permissions
|
||||
permReg.RegisterPermission("datasources:read", "datasources:uid:")
|
||||
permReg.RegisterPermission("dashboards:read", "dashboards:uid:")
|
||||
permReg.RegisterPermission("dashboards:read", "folders:uid:")
|
||||
permReg.RegisterPermission("folders:read", "folders:uid:")
|
||||
// Test plugins permissions
|
||||
permReg.RegisterPermission("plugins.app:access", "plugins:id:")
|
||||
// App
|
||||
permReg.RegisterPermission("test-app:read", "")
|
||||
permReg.RegisterPermission("test-app.settings:read", "")
|
||||
permReg.RegisterPermission("test-app.projects:read", "")
|
||||
// App 1
|
||||
permReg.RegisterPermission("test-app1.catalog:read", "")
|
||||
permReg.RegisterPermission("test-app1.announcements:read", "")
|
||||
return permReg
|
||||
}
|
@ -15,6 +15,7 @@ import (
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/permreg"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
|
||||
"github.com/grafana/grafana/pkg/services/apikey"
|
||||
"github.com/grafana/grafana/pkg/services/extsvcauth"
|
||||
@ -48,6 +49,7 @@ func setupTestEnv(t *testing.T) *TestEnv {
|
||||
acSvc: acimpl.ProvideOSSService(
|
||||
cfg, env.AcStore, &resourcepermissions.FakeActionSetSvc{},
|
||||
localcache.New(0, 0), fmgt, tracing.InitializeTracerForTest(), nil, nil,
|
||||
permreg.ProvidePermissionRegistry(),
|
||||
),
|
||||
features: fmgt,
|
||||
logger: logger,
|
||||
|
Loading…
Reference in New Issue
Block a user