diff --git a/pkg/services/accesscontrol/resourcepermissions/service.go b/pkg/services/accesscontrol/resourcepermissions/service.go index 7808f0a7665..82fe2c6e99e 100644 --- a/pkg/services/accesscontrol/resourcepermissions/service.go +++ b/pkg/services/accesscontrol/resourcepermissions/service.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "sort" + "strings" "golang.org/x/exp/slices" @@ -12,7 +13,9 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/accesscontrol/pluginutils" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/licensing" @@ -23,7 +26,7 @@ import ( "github.com/grafana/grafana/pkg/setting" ) -var _ pluginaccesscontrol.ActionSetRegistry = (*InMemoryActionSets)(nil) +var _ pluginaccesscontrol.ActionSetRegistry = (ActionSetService)(nil) type Store interface { // SetUserResourcePermission sets permission for managed user role on a resource @@ -437,7 +440,7 @@ type ActionSetService interface { ResolveAction(action string) []string // ResolveActionSet resolves an action set to a list of corresponding actions. ResolveActionSet(actionSet string) []string - + // StoreActionSet stores action set. If a set with the given name has already been stored, the new actions will be appended to the existing actions. StoreActionSet(name string, actions []string) pluginaccesscontrol.ActionSetRegistry @@ -450,21 +453,126 @@ type ActionSet struct { Actions []string `json:"actions"` } -// InMemoryActionSets is an in-memory implementation of the ActionSetService. -type InMemoryActionSets struct { - features featuremgmt.FeatureToggles - log log.Logger - actionSetToActions map[string][]string - actionToActionSets map[string][]string +type ActionSetStore interface { + // StoreActionSet stores action set. If a set with the given name has already been stored, the new actions will be appended to the existing actions. + StoreActionSet(name string, actions []string) + // ResolveActionSet resolves an action set to a list of corresponding actions. + ResolveActionSet(actionSet string) []string + // ResolveAction returns all the action sets that the action belongs to. + ResolveAction(action string) []string + // ResolveActionPrefix returns all action sets that include at least one action with the specified prefix + ResolveActionPrefix(prefix string) []string + // ExpandActionSetsWithFilter takes a set of permissions that might include some action set permissions, and returns a set of permissions with action sets expanded into underlying permissions. + // When action sets are expanded into the underlying permissions only those permissions whose action is matched by actionMatcher are included. + ExpandActionSetsWithFilter(permissions []accesscontrol.Permission, actionMatcher func(action string) bool) []accesscontrol.Permission +} + +type ActionSetSvc struct { + features featuremgmt.FeatureToggles + store ActionSetStore } // NewActionSetService returns a new instance of InMemoryActionSetService. func NewActionSetService(features featuremgmt.FeatureToggles) ActionSetService { - actionSets := &InMemoryActionSets{ - features: features, - log: log.New("resourcepermissions.actionsets"), - actionSetToActions: make(map[string][]string), - actionToActionSets: make(map[string][]string), + return &ActionSetSvc{ + features: features, + store: NewInMemoryActionSetStore(features), } - return actionSets +} + +// ResolveAction returns all the action sets that the action belongs to. +func (a *ActionSetSvc) ResolveAction(action string) []string { + sets := a.store.ResolveAction(action) + filteredSets := make([]string, 0, len(sets)) + for _, set := range sets { + // 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:read`) + if !isFolderOrDashboardAction(set) { + continue + } + filteredSets = append(filteredSets, set) + } + + return filteredSets +} + +// ResolveActionPrefix returns all action sets that include at least one action with the specified prefix +func (a *ActionSetSvc) ResolveActionPrefix(actionPrefix string) []string { + sets := a.store.ResolveActionPrefix(actionPrefix) + filteredSets := make([]string, 0, len(sets)) + for _, set := range sets { + // 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:read`) + if !isFolderOrDashboardAction(set) { + continue + } + filteredSets = append(filteredSets, set) + } + + return filteredSets +} + +// ResolveActionSet resolves an action set to a list of corresponding actions. +func (a *ActionSetSvc) ResolveActionSet(actionSet string) []string { + // 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:read`) + if !isFolderOrDashboardAction(actionSet) { + return nil + } + return a.store.ResolveActionSet(actionSet) +} + +// StoreActionSet stores action set. If a set with the given name has already been stored, the new actions will be appended to the existing actions. +func (a *ActionSetSvc) StoreActionSet(name string, actions []string) { + // To avoid backwards incompatible changes, we don't want to store these actions in the DB + // Once action sets are fully enabled, we can include dashboards.ActionFoldersCreate in the list of other folder edit/admin actions + // Tracked in https://github.com/grafana/identity-access-team/issues/794 + if name == "folders:edit" || name == "folders:admin" { + if !slices.Contains(a.ResolveActionSet(name), dashboards.ActionFoldersCreate) { + actions = append(actions, dashboards.ActionFoldersCreate) + } + } + + a.store.StoreActionSet(name, actions) +} + +// ExpandActionSets takes a set of permissions that might include some action set permissions, and returns a set of permissions with action sets expanded into underlying permissions +func (a *ActionSetSvc) ExpandActionSets(permissions []accesscontrol.Permission) []accesscontrol.Permission { + actionMatcher := func(_ string) bool { + return true + } + return a.ExpandActionSetsWithFilter(permissions, actionMatcher) +} + +// ExpandActionSetsWithFilter works like ExpandActionSets, but it also takes a function for action filtering. When action sets are expanded into the underlying permissions, +// only those permissions whose action is matched by actionMatcher are included. +func (a *ActionSetSvc) ExpandActionSetsWithFilter(permissions []accesscontrol.Permission, actionMatcher func(action string) bool) []accesscontrol.Permission { + return a.store.ExpandActionSetsWithFilter(permissions, actionMatcher) +} + +// RegisterActionSets allow the caller to expand the existing action sets with additional permissions +// This is intended to be used by plugins, and currently supports extending folder and dashboard action sets +func (a *ActionSetSvc) RegisterActionSets(ctx context.Context, pluginID string, registrations []plugins.ActionSet) error { + if !a.features.IsEnabled(ctx, featuremgmt.FlagAccessActionSets) || !a.features.IsEnabled(ctx, featuremgmt.FlagAccessControlOnCall) { + return nil + } + for _, reg := range registrations { + if err := pluginutils.ValidatePluginActionSet(pluginID, reg); err != nil { + return err + } + a.StoreActionSet(reg.Action, reg.Actions) + } + return nil +} + +func isFolderOrDashboardAction(action string) bool { + return strings.HasPrefix(action, dashboards.ScopeDashboardsRoot) || strings.HasPrefix(action, dashboards.ScopeFoldersRoot) +} + +// GetActionSetName function creates an action set from a list of actions and stores it inmemory. +func GetActionSetName(resource, permission string) string { + // lower cased + resource = strings.ToLower(resource) + permission = strings.ToLower(permission) + return fmt.Sprintf("%s:%s", resource, permission) } diff --git a/pkg/services/accesscontrol/resourcepermissions/service_test.go b/pkg/services/accesscontrol/resourcepermissions/service_test.go index 1da32d62d3e..94ead810dd7 100644 --- a/pkg/services/accesscontrol/resourcepermissions/service_test.go +++ b/pkg/services/accesscontrol/resourcepermissions/service_test.go @@ -10,6 +10,7 @@ import ( "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" "github.com/grafana/grafana/pkg/services/accesscontrol/actest" @@ -314,6 +315,177 @@ func TestService_RegisterActionSets(t *testing.T) { } } +func TestStore_RegisterActionSet(t *testing.T) { + type actionSetTest struct { + desc string + features featuremgmt.FeatureToggles + pluginID string + pluginActions []plugins.ActionSet + coreActionSets []ActionSet + expectedErr bool + expectedActionSets []ActionSet + } + + tests := []actionSetTest{ + { + desc: "should be able to register a plugin action set if the right feature toggles are enabled", + features: featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets, featuremgmt.FlagAccessControlOnCall), + pluginID: "test-app", + pluginActions: []plugins.ActionSet{ + { + Action: "folders:view", + Actions: []string{"test-app.resource:read"}, + }, + }, + expectedActionSets: []ActionSet{ + { + Action: "folders:view", + Actions: []string{"test-app.resource:read"}, + }, + }, + }, + { + desc: "should not register plugin action set if feature toggles are missing", + features: featuremgmt.WithFeatures(featuremgmt.FlagAccessControlOnCall), + pluginID: "test-app", + pluginActions: []plugins.ActionSet{ + { + Action: "folders:view", + Actions: []string{"test-app.resource:read"}, + }, + }, + expectedActionSets: []ActionSet{}, + }, + { + desc: "should be able to register multiple plugin action sets", + features: featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets, featuremgmt.FlagAccessControlOnCall), + pluginID: "test-app", + pluginActions: []plugins.ActionSet{ + { + Action: "folders:view", + Actions: []string{"test-app.resource:read"}, + }, + { + Action: "folders:edit", + Actions: []string{"test-app.resource:write", "test-app.resource:delete"}, + }, + }, + expectedActionSets: []ActionSet{ + { + Action: "folders:view", + Actions: []string{"test-app.resource:read"}, + }, + { + Action: "folders:edit", + Actions: []string{"test-app.resource:write", "test-app.resource:delete"}, + }, + }, + }, + { + desc: "action set actions should be added not replaced", + features: featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets, featuremgmt.FlagAccessControlOnCall), + pluginID: "test-app", + pluginActions: []plugins.ActionSet{ + { + Action: "folders:view", + Actions: []string{"test-app.resource:read"}, + }, + { + Action: "folders:edit", + Actions: []string{"test-app.resource:write", "test-app.resource:delete"}, + }, + }, + coreActionSets: []ActionSet{ + { + Action: "folders:view", + Actions: []string{"folders:read"}, + }, + { + Action: "folders:edit", + Actions: []string{"folders:write", "folders:delete"}, + }, + { + Action: "folders:admin", + Actions: []string{"folders.permissions:read"}, + }, + }, + expectedActionSets: []ActionSet{ + { + Action: "folders:view", + Actions: []string{"folders:read", "test-app.resource:read"}, + }, + { + Action: "folders:edit", + Actions: []string{"folders:write", "test-app.resource:write", "folders:delete", "test-app.resource:delete"}, + }, + { + Action: "folders:admin", + Actions: []string{"folders.permissions:read"}, + }, + }, + }, + { + desc: "should not be able to register an action that doesn't have a plugin prefix", + features: featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets, featuremgmt.FlagAccessControlOnCall), + pluginID: "test-app", + pluginActions: []plugins.ActionSet{ + { + Action: "folders:view", + Actions: []string{"test-app.resource:read"}, + }, + { + Action: "folders:edit", + Actions: []string{"users:read", "test-app.resource:delete"}, + }, + }, + expectedErr: true, + }, + { + desc: "should not be able to register action set that is not in the allow list", + features: featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets, featuremgmt.FlagAccessControlOnCall), + pluginID: "test-app", + pluginActions: []plugins.ActionSet{ + { + Action: "folders:super-admin", + Actions: []string{"test-app.resource:read"}, + }, + }, + expectedErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + asService := NewActionSetService(tt.features) + + err := asService.RegisterActionSets(context.Background(), tt.pluginID, tt.pluginActions) + if tt.expectedErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + for _, set := range tt.coreActionSets { + asService.StoreActionSet(set.Action, set.Actions) + } + + for _, expected := range tt.expectedActionSets { + actions := asService.ResolveActionSet(expected.Action) + if expected.Action == "folders:edit" || expected.Action == "folders:admin" { + expected.Actions = append(expected.Actions, "folders:create") + } + assert.ElementsMatch(t, expected.Actions, actions) + } + + if len(tt.expectedActionSets) == 0 { + for _, set := range tt.pluginActions { + registeredActions := asService.ResolveActionSet(set.Action) + assert.Empty(t, registeredActions, "no actions from plugin action sets should have been registered") + } + } + }) + } +} + func setupTestEnvironment(t *testing.T, ops Options) (*Service, user.Service, team.Service) { t.Helper() diff --git a/pkg/services/accesscontrol/resourcepermissions/store.go b/pkg/services/accesscontrol/resourcepermissions/store.go index 5e4b45b7442..755e1977ff9 100644 --- a/pkg/services/accesscontrol/resourcepermissions/store.go +++ b/pkg/services/accesscontrol/resourcepermissions/store.go @@ -3,15 +3,12 @@ package resourcepermissions import ( "context" "fmt" - "slices" "strings" "time" "github.com/grafana/grafana/pkg/infra/db" - "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/accesscontrol" - "github.com/grafana/grafana/pkg/services/accesscontrol/pluginutils" - "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/serviceaccounts" @@ -744,6 +741,23 @@ func managedPermission(action, resource string, resourceID, resourceAttribute st } } +// InMemoryActionSets is an in-memory implementation of the ActionSetStore. +type InMemoryActionSets struct { + features featuremgmt.FeatureToggles + log log.Logger + actionSetToActions map[string][]string + actionToActionSets map[string][]string +} + +func NewInMemoryActionSetStore(features featuremgmt.FeatureToggles) *InMemoryActionSets { + return &InMemoryActionSets{ + actionSetToActions: make(map[string][]string), + actionToActionSets: make(map[string][]string), + log: log.New("resourcepermissions.actionsets"), + features: features, + } +} + // ResolveActionPrefix returns all action sets that include at least one action with the specified prefix func (s *InMemoryActionSets) ResolveActionPrefix(prefix string) []string { if prefix == "" { @@ -753,11 +767,6 @@ func (s *InMemoryActionSets) ResolveActionPrefix(prefix string) []string { sets := make([]string, 0, len(s.actionSetToActions)) for set, actions := range s.actionSetToActions { - // 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:read`) - if !isFolderOrDashboardAction(set) { - continue - } for _, action := range actions { if strings.HasPrefix(action, prefix) { sets = append(sets, set) @@ -770,44 +779,13 @@ func (s *InMemoryActionSets) ResolveActionPrefix(prefix string) []string { } func (s *InMemoryActionSets) ResolveAction(action string) []string { - actionSets := s.actionToActionSets[action] - sets := make([]string, 0, len(actionSets)) - - for _, actionSet := range actionSets { - // 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:read`) - if !isFolderOrDashboardAction(actionSet) { - continue - } - sets = append(sets, actionSet) - } - - return sets + return s.actionToActionSets[action] } func (s *InMemoryActionSets) ResolveActionSet(actionSet string) []string { - // 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:read`) - if !isFolderOrDashboardAction(actionSet) { - return nil - } return s.actionSetToActions[actionSet] } -func isFolderOrDashboardAction(action string) bool { - return strings.HasPrefix(action, dashboards.ScopeDashboardsRoot) || strings.HasPrefix(action, dashboards.ScopeFoldersRoot) -} - -// ExpandActionSets takes a set of permissions that might include some action set permissions, and returns a set of permissions with action sets expanded into underlying permissions -func (s *InMemoryActionSets) ExpandActionSets(permissions []accesscontrol.Permission) []accesscontrol.Permission { - actionMatcher := func(_ string) bool { - return true - } - return s.ExpandActionSetsWithFilter(permissions, actionMatcher) -} - -// ExpandActionSetsWithFilter works like ExpandActionSets, but it also takes a function for action filtering. When action sets are expanded into the underlying permissions, -// only those permissions whose action is matched by actionMatcher are included. func (s *InMemoryActionSets) ExpandActionSetsWithFilter(permissions []accesscontrol.Permission, actionMatcher func(action string) bool) []accesscontrol.Permission { var expandedPermissions []accesscontrol.Permission for _, permission := range permissions { @@ -828,15 +806,6 @@ func (s *InMemoryActionSets) ExpandActionSetsWithFilter(permissions []accesscont } func (s *InMemoryActionSets) StoreActionSet(name string, actions []string) { - // To avoid backwards incompatible changes, we don't want to store these actions in the DB - // Once action sets are fully enabled, we can include dashboards.ActionFoldersCreate in the list of other folder edit/admin actions - // Tracked in https://github.com/grafana/identity-access-team/issues/794 - if name == "folders:edit" || name == "folders:admin" { - if !slices.Contains(s.actionSetToActions[name], dashboards.ActionFoldersCreate) { - actions = append(actions, dashboards.ActionFoldersCreate) - } - } - s.actionSetToActions[name] = append(s.actionSetToActions[name], actions...) for _, action := range actions { @@ -847,26 +816,3 @@ func (s *InMemoryActionSets) StoreActionSet(name string, actions []string) { } s.log.Debug("stored action set", "action set name", name) } - -// RegisterActionSets allow the caller to expand the existing action sets with additional permissions -// This is intended to be used by plugins, and currently supports extending folder and dashboard action sets -func (s *InMemoryActionSets) RegisterActionSets(ctx context.Context, pluginID string, registrations []plugins.ActionSet) error { - if !s.features.IsEnabled(ctx, featuremgmt.FlagAccessActionSets) || !s.features.IsEnabled(ctx, featuremgmt.FlagAccessControlOnCall) { - return nil - } - for _, reg := range registrations { - if err := pluginutils.ValidatePluginActionSet(pluginID, reg); err != nil { - return err - } - s.StoreActionSet(reg.Action, reg.Actions) - } - return nil -} - -// GetActionSetName function creates an action set from a list of actions and stores it inmemory. -func GetActionSetName(resource, permission string) string { - // lower cased - resource = strings.ToLower(resource) - permission = strings.ToLower(permission) - return fmt.Sprintf("%s:%s", resource, permission) -} diff --git a/pkg/services/accesscontrol/resourcepermissions/store_test.go b/pkg/services/accesscontrol/resourcepermissions/store_test.go index 1c8cc91107d..de5b5de7c82 100644 --- a/pkg/services/accesscontrol/resourcepermissions/store_test.go +++ b/pkg/services/accesscontrol/resourcepermissions/store_test.go @@ -11,7 +11,6 @@ import ( "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/tracing" - "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -782,183 +781,12 @@ func TestStore_StoreActionSet(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { - asService := NewActionSetService(featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets)) + asService := NewInMemoryActionSetStore(featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets)) asService.StoreActionSet(GetActionSetName(tt.resource, tt.action), tt.actions) actionSetName := GetActionSetName(tt.resource, tt.action) actionSet := asService.ResolveActionSet(actionSetName) - require.Equal(t, append(tt.actions, "folders:create"), actionSet) - }) - } -} - -func TestStore_RegisterActionSet(t *testing.T) { - type actionSetTest struct { - desc string - features featuremgmt.FeatureToggles - pluginID string - pluginActions []plugins.ActionSet - coreActionSets []ActionSet - expectedErr bool - expectedActionSets []ActionSet - } - - tests := []actionSetTest{ - { - desc: "should be able to register a plugin action set if the right feature toggles are enabled", - features: featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets, featuremgmt.FlagAccessControlOnCall), - pluginID: "test-app", - pluginActions: []plugins.ActionSet{ - { - Action: "folders:view", - Actions: []string{"test-app.resource:read"}, - }, - }, - expectedActionSets: []ActionSet{ - { - Action: "folders:view", - Actions: []string{"test-app.resource:read"}, - }, - }, - }, - { - desc: "should not register plugin action set if feature toggles are missing", - features: featuremgmt.WithFeatures(featuremgmt.FlagAccessControlOnCall), - pluginID: "test-app", - pluginActions: []plugins.ActionSet{ - { - Action: "folders:view", - Actions: []string{"test-app.resource:read"}, - }, - }, - expectedActionSets: []ActionSet{}, - }, - { - desc: "should be able to register multiple plugin action sets", - features: featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets, featuremgmt.FlagAccessControlOnCall), - pluginID: "test-app", - pluginActions: []plugins.ActionSet{ - { - Action: "folders:view", - Actions: []string{"test-app.resource:read"}, - }, - { - Action: "folders:edit", - Actions: []string{"test-app.resource:write", "test-app.resource:delete"}, - }, - }, - expectedActionSets: []ActionSet{ - { - Action: "folders:view", - Actions: []string{"test-app.resource:read"}, - }, - { - Action: "folders:edit", - Actions: []string{"test-app.resource:write", "test-app.resource:delete"}, - }, - }, - }, - { - desc: "action set actions should be added not replaced", - features: featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets, featuremgmt.FlagAccessControlOnCall), - pluginID: "test-app", - pluginActions: []plugins.ActionSet{ - { - Action: "folders:view", - Actions: []string{"test-app.resource:read"}, - }, - { - Action: "folders:edit", - Actions: []string{"test-app.resource:write", "test-app.resource:delete"}, - }, - }, - coreActionSets: []ActionSet{ - { - Action: "folders:view", - Actions: []string{"folders:read"}, - }, - { - Action: "folders:edit", - Actions: []string{"folders:write", "folders:delete"}, - }, - { - Action: "folders:admin", - Actions: []string{"folders.permissions:read"}, - }, - }, - expectedActionSets: []ActionSet{ - { - Action: "folders:view", - Actions: []string{"folders:read", "test-app.resource:read"}, - }, - { - Action: "folders:edit", - Actions: []string{"folders:write", "test-app.resource:write", "folders:delete", "test-app.resource:delete"}, - }, - { - Action: "folders:admin", - Actions: []string{"folders.permissions:read"}, - }, - }, - }, - { - desc: "should not be able to register an action that doesn't have a plugin prefix", - features: featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets, featuremgmt.FlagAccessControlOnCall), - pluginID: "test-app", - pluginActions: []plugins.ActionSet{ - { - Action: "folders:view", - Actions: []string{"test-app.resource:read"}, - }, - { - Action: "folders:edit", - Actions: []string{"users:read", "test-app.resource:delete"}, - }, - }, - expectedErr: true, - }, - { - desc: "should not be able to register action set that is not in the allow list", - features: featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets, featuremgmt.FlagAccessControlOnCall), - pluginID: "test-app", - pluginActions: []plugins.ActionSet{ - { - Action: "folders:super-admin", - Actions: []string{"test-app.resource:read"}, - }, - }, - expectedErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - asService := NewActionSetService(tt.features) - - err := asService.RegisterActionSets(context.Background(), tt.pluginID, tt.pluginActions) - if tt.expectedErr { - require.Error(t, err) - return - } - require.NoError(t, err) - - for _, set := range tt.coreActionSets { - asService.StoreActionSet(set.Action, set.Actions) - } - - for _, expected := range tt.expectedActionSets { - actions := asService.ResolveActionSet(expected.Action) - if expected.Action == "folders:edit" || expected.Action == "folders:admin" { - expected.Actions = append(expected.Actions, "folders:create") - } - assert.ElementsMatch(t, expected.Actions, actions) - } - - if len(tt.expectedActionSets) == 0 { - for _, set := range tt.pluginActions { - registeredActions := asService.ResolveActionSet(set.Action) - assert.Empty(t, registeredActions, "no actions from plugin action sets should have been registered") - } - } + require.Equal(t, tt.actions, actionSet) }) } }