RBAC: Remove action set feature toggle (#101959)
* remove action set feature toggle * don't pass feature toggles to action set service instantiation * linting * test fixes and frontend clean-up * fix test
This commit is contained in:
@@ -21,67 +21,66 @@ For more information about feature release stages, refer to [Release life cycle
|
||||
|
||||
Most [generally available](https://grafana.com/docs/release-life-cycle/#general-availability) features are enabled by default. You can disable these feature by setting the feature flag to "false" in the configuration.
|
||||
|
||||
| Feature toggle name | Description | Enabled by default |
|
||||
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ |
|
||||
| `disableEnvelopeEncryption` | Disable envelope encryption (emergency only) | |
|
||||
| `publicDashboardsScene` | Enables public dashboard rendering using scenes | Yes |
|
||||
| `featureHighlights` | Highlight Grafana Enterprise features | |
|
||||
| `correlations` | Correlations page | Yes |
|
||||
| `cloudWatchCrossAccountQuerying` | Enables cross-account querying in CloudWatch datasources | Yes |
|
||||
| `nestedFolders` | Enable folder nesting | Yes |
|
||||
| `logsContextDatasourceUi` | Allow datasource to provide custom UI for context view | Yes |
|
||||
| `lokiQuerySplitting` | Split large interval queries into subqueries with smaller time intervals | Yes |
|
||||
| `influxdbBackendMigration` | Query InfluxDB InfluxQL without the proxy | Yes |
|
||||
| `dataplaneFrontendFallback` | Support dataplane contract field name change for transformations and field name matchers where the name is different | Yes |
|
||||
| `unifiedRequestLog` | Writes error logs to the request logger | Yes |
|
||||
| `recordedQueriesMulti` | Enables writing multiple items from a single query within Recorded Queries | Yes |
|
||||
| `logsExploreTableVisualisation` | A table visualisation for logs in Explore | Yes |
|
||||
| `transformationsRedesign` | Enables the transformations redesign | Yes |
|
||||
| `traceQLStreaming` | Enables response streaming of TraceQL queries of the Tempo data source | |
|
||||
| `awsAsyncQueryCaching` | Enable caching for async queries for Redshift and Athena. Requires that the datasource has caching and async query support enabled | Yes |
|
||||
| `alertingNoDataErrorExecution` | Changes how Alerting state manager handles execution of NoData/Error | Yes |
|
||||
| `angularDeprecationUI` | Display Angular warnings in dashboards and panels | Yes |
|
||||
| `dashgpt` | Enable AI powered features in dashboards | Yes |
|
||||
| `alertingInsights` | Show the new alerting insights landing page | Yes |
|
||||
| `panelMonitoring` | Enables panel monitoring through logs and measurements | Yes |
|
||||
| `formatString` | Enable format string transformer | Yes |
|
||||
| `kubernetesPlaylists` | Use the kubernetes API in the frontend for playlists, and route /api/playlist requests to k8s | Yes |
|
||||
| `recoveryThreshold` | Enables feature recovery threshold (aka hysteresis) for threshold server-side expression | Yes |
|
||||
| `lokiStructuredMetadata` | Enables the loki data source to request structured metadata from the Loki server | Yes |
|
||||
| `addFieldFromCalculationStatFunctions` | Add cumulative and window functions to the add field from calculation transformation | Yes |
|
||||
| `annotationPermissionUpdate` | Change the way annotation permissions work by scoping them to folders and dashboards. | Yes |
|
||||
| `dashboardSceneForViewers` | Enables dashboard rendering using Scenes for viewer roles | Yes |
|
||||
| `dashboardSceneSolo` | Enables rendering dashboards using scenes for solo panels | Yes |
|
||||
| `dashboardScene` | Enables dashboard rendering using scenes for all roles | Yes |
|
||||
| `ssoSettingsApi` | Enables the SSO settings API and the OAuth configuration UIs in Grafana | Yes |
|
||||
| `logsInfiniteScrolling` | Enables infinite scrolling for the Logs panel in Explore and Dashboards | Yes |
|
||||
| `exploreMetrics` | Enables the new Grafana Metrics Drilldown core app | Yes |
|
||||
| `alertingSimplifiedRouting` | Enables users to easily configure alert notifications by specifying a contact point directly when editing or creating an alert rule | Yes |
|
||||
| `logRowsPopoverMenu` | Enable filtering menu displayed when text of a log line is selected | Yes |
|
||||
| `lokiQueryHints` | Enables query hints for Loki | Yes |
|
||||
| `alertingQueryOptimization` | Optimizes eligible queries in order to reduce load on datasources | |
|
||||
| `groupToNestedTableTransformation` | Enables the group to nested table transformation | Yes |
|
||||
| `newPDFRendering` | New implementation for the dashboard-to-PDF rendering | Yes |
|
||||
| `tlsMemcached` | Use TLS-enabled memcached in the enterprise caching feature | Yes |
|
||||
| `ssoSettingsSAML` | Use the new SSO Settings API to configure the SAML connector | Yes |
|
||||
| `cloudWatchNewLabelParsing` | Updates CloudWatch label parsing to be more accurate | Yes |
|
||||
| `accessActionSets` | Introduces action sets for resource permissions. Also ensures that all folder editors and admins can create subfolders without needing any additional permissions. | Yes |
|
||||
| `newDashboardSharingComponent` | Enables the new sharing drawer design | Yes |
|
||||
| `pluginProxyPreserveTrailingSlash` | Preserve plugin proxy trailing slash. | |
|
||||
| `pinNavItems` | Enables pinning of nav items | Yes |
|
||||
| `alertingApiServer` | Register Alerting APIs with the K8s API server | Yes |
|
||||
| `cloudWatchRoundUpEndTime` | Round up end time for metric queries to the next minute to avoid missing data | Yes |
|
||||
| `newFiltersUI` | Enables new combobox style UI for the Ad hoc filters variable in scenes architecture | Yes |
|
||||
| `alertingQueryAndExpressionsStepMode` | Enables step mode for alerting queries and expressions | Yes |
|
||||
| `useSessionStorageForRedirection` | Use session storage for handling the redirection after login | Yes |
|
||||
| `userStorageAPI` | Enables the user storage API | Yes |
|
||||
| `azureMonitorDisableLogLimit` | Disables the log limit restriction for Azure Monitor when true. The limit is enabled by default. | |
|
||||
| `preinstallAutoUpdate` | Enables automatic updates for pre-installed plugins | Yes |
|
||||
| `reportingUseRawTimeRange` | Uses the original report or dashboard time range instead of making an absolute transformation | Yes |
|
||||
| `alertingUIOptimizeReducer` | Enables removing the reducer from the alerting UI when creating a new alert rule and using instant query | Yes |
|
||||
| `azureMonitorEnableUserAuth` | Enables user auth for Azure Monitor datasource only | Yes |
|
||||
| `alertingNotificationsStepMode` | Enables simplified step mode in the notifications section | Yes |
|
||||
| `lokiLabelNamesQueryApi` | Defaults to using the Loki `/labels` API instead of `/series` | Yes |
|
||||
| Feature toggle name | Description | Enabled by default |
|
||||
| -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
|
||||
| `disableEnvelopeEncryption` | Disable envelope encryption (emergency only) | |
|
||||
| `publicDashboardsScene` | Enables public dashboard rendering using scenes | Yes |
|
||||
| `featureHighlights` | Highlight Grafana Enterprise features | |
|
||||
| `correlations` | Correlations page | Yes |
|
||||
| `cloudWatchCrossAccountQuerying` | Enables cross-account querying in CloudWatch datasources | Yes |
|
||||
| `nestedFolders` | Enable folder nesting | Yes |
|
||||
| `logsContextDatasourceUi` | Allow datasource to provide custom UI for context view | Yes |
|
||||
| `lokiQuerySplitting` | Split large interval queries into subqueries with smaller time intervals | Yes |
|
||||
| `influxdbBackendMigration` | Query InfluxDB InfluxQL without the proxy | Yes |
|
||||
| `dataplaneFrontendFallback` | Support dataplane contract field name change for transformations and field name matchers where the name is different | Yes |
|
||||
| `unifiedRequestLog` | Writes error logs to the request logger | Yes |
|
||||
| `recordedQueriesMulti` | Enables writing multiple items from a single query within Recorded Queries | Yes |
|
||||
| `logsExploreTableVisualisation` | A table visualisation for logs in Explore | Yes |
|
||||
| `transformationsRedesign` | Enables the transformations redesign | Yes |
|
||||
| `traceQLStreaming` | Enables response streaming of TraceQL queries of the Tempo data source | |
|
||||
| `awsAsyncQueryCaching` | Enable caching for async queries for Redshift and Athena. Requires that the datasource has caching and async query support enabled | Yes |
|
||||
| `alertingNoDataErrorExecution` | Changes how Alerting state manager handles execution of NoData/Error | Yes |
|
||||
| `angularDeprecationUI` | Display Angular warnings in dashboards and panels | Yes |
|
||||
| `dashgpt` | Enable AI powered features in dashboards | Yes |
|
||||
| `alertingInsights` | Show the new alerting insights landing page | Yes |
|
||||
| `panelMonitoring` | Enables panel monitoring through logs and measurements | Yes |
|
||||
| `formatString` | Enable format string transformer | Yes |
|
||||
| `kubernetesPlaylists` | Use the kubernetes API in the frontend for playlists, and route /api/playlist requests to k8s | Yes |
|
||||
| `recoveryThreshold` | Enables feature recovery threshold (aka hysteresis) for threshold server-side expression | Yes |
|
||||
| `lokiStructuredMetadata` | Enables the loki data source to request structured metadata from the Loki server | Yes |
|
||||
| `addFieldFromCalculationStatFunctions` | Add cumulative and window functions to the add field from calculation transformation | Yes |
|
||||
| `annotationPermissionUpdate` | Change the way annotation permissions work by scoping them to folders and dashboards. | Yes |
|
||||
| `dashboardSceneForViewers` | Enables dashboard rendering using Scenes for viewer roles | Yes |
|
||||
| `dashboardSceneSolo` | Enables rendering dashboards using scenes for solo panels | Yes |
|
||||
| `dashboardScene` | Enables dashboard rendering using scenes for all roles | Yes |
|
||||
| `ssoSettingsApi` | Enables the SSO settings API and the OAuth configuration UIs in Grafana | Yes |
|
||||
| `logsInfiniteScrolling` | Enables infinite scrolling for the Logs panel in Explore and Dashboards | Yes |
|
||||
| `exploreMetrics` | Enables the new Grafana Metrics Drilldown core app | Yes |
|
||||
| `alertingSimplifiedRouting` | Enables users to easily configure alert notifications by specifying a contact point directly when editing or creating an alert rule | Yes |
|
||||
| `logRowsPopoverMenu` | Enable filtering menu displayed when text of a log line is selected | Yes |
|
||||
| `lokiQueryHints` | Enables query hints for Loki | Yes |
|
||||
| `alertingQueryOptimization` | Optimizes eligible queries in order to reduce load on datasources | |
|
||||
| `groupToNestedTableTransformation` | Enables the group to nested table transformation | Yes |
|
||||
| `newPDFRendering` | New implementation for the dashboard-to-PDF rendering | Yes |
|
||||
| `tlsMemcached` | Use TLS-enabled memcached in the enterprise caching feature | Yes |
|
||||
| `ssoSettingsSAML` | Use the new SSO Settings API to configure the SAML connector | Yes |
|
||||
| `cloudWatchNewLabelParsing` | Updates CloudWatch label parsing to be more accurate | Yes |
|
||||
| `newDashboardSharingComponent` | Enables the new sharing drawer design | Yes |
|
||||
| `pluginProxyPreserveTrailingSlash` | Preserve plugin proxy trailing slash. | |
|
||||
| `pinNavItems` | Enables pinning of nav items | Yes |
|
||||
| `alertingApiServer` | Register Alerting APIs with the K8s API server | Yes |
|
||||
| `cloudWatchRoundUpEndTime` | Round up end time for metric queries to the next minute to avoid missing data | Yes |
|
||||
| `newFiltersUI` | Enables new combobox style UI for the Ad hoc filters variable in scenes architecture | Yes |
|
||||
| `alertingQueryAndExpressionsStepMode` | Enables step mode for alerting queries and expressions | Yes |
|
||||
| `useSessionStorageForRedirection` | Use session storage for handling the redirection after login | Yes |
|
||||
| `userStorageAPI` | Enables the user storage API | Yes |
|
||||
| `azureMonitorDisableLogLimit` | Disables the log limit restriction for Azure Monitor when true. The limit is enabled by default. | |
|
||||
| `preinstallAutoUpdate` | Enables automatic updates for pre-installed plugins | Yes |
|
||||
| `reportingUseRawTimeRange` | Uses the original report or dashboard time range instead of making an absolute transformation | Yes |
|
||||
| `alertingUIOptimizeReducer` | Enables removing the reducer from the alerting UI when creating a new alert rule and using instant query | Yes |
|
||||
| `azureMonitorEnableUserAuth` | Enables user auth for Azure Monitor datasource only | Yes |
|
||||
| `alertingNotificationsStepMode` | Enables simplified step mode in the notifications section | Yes |
|
||||
| `lokiLabelNamesQueryApi` | Defaults to using the Loki `/labels` API instead of `/series` | Yes |
|
||||
|
||||
## Public preview feature toggles
|
||||
|
||||
|
||||
@@ -163,7 +163,6 @@ export interface FeatureToggles {
|
||||
oauthRequireSubClaim?: boolean;
|
||||
newDashboardWithFiltersAndGroupBy?: boolean;
|
||||
cloudWatchNewLabelParsing?: boolean;
|
||||
accessActionSets?: boolean;
|
||||
disableNumericMetricsSortingInExpressions?: boolean;
|
||||
grafanaManagedRecordingRules?: boolean;
|
||||
queryLibrary?: boolean;
|
||||
|
||||
@@ -460,7 +460,7 @@ func setupServer(b testing.TB, sc benchScenario, features featuremgmt.FeatureTog
|
||||
|
||||
ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures())
|
||||
cfg := setting.NewCfg()
|
||||
actionSets := resourcepermissions.NewActionSetService(features)
|
||||
actionSets := resourcepermissions.NewActionSetService()
|
||||
fStore := folderimpl.ProvideStore(sc.db)
|
||||
folderServiceWithFlagOn := folderimpl.ProvideService(
|
||||
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore,
|
||||
|
||||
@@ -65,7 +65,7 @@ func TestAccessControl_Evaluate(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets))
|
||||
ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures())
|
||||
|
||||
if tt.scopeResolver != nil {
|
||||
ac.RegisterScopeAttributeResolver(tt.resolverPrefix, tt.scopeResolver)
|
||||
|
||||
@@ -166,10 +166,8 @@ func (s *Service) getUserPermissions(ctx context.Context, user identity.Requeste
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.features.IsEnabled(ctx, featuremgmt.FlagAccessActionSets) {
|
||||
dbPermissions = s.actionResolver.ExpandActionSets(dbPermissions)
|
||||
}
|
||||
|
||||
dbPermissions = s.actionResolver.ExpandActionSets(dbPermissions)
|
||||
return append(permissions, dbPermissions...), nil
|
||||
}
|
||||
|
||||
@@ -188,10 +186,8 @@ func (s *Service) getBasicRolePermissions(ctx context.Context, role string, orgI
|
||||
OrgID: orgID,
|
||||
RolePrefixes: OSSRolesPrefixes,
|
||||
})
|
||||
if s.features.IsEnabled(ctx, featuremgmt.FlagAccessActionSets) {
|
||||
dbPermissions = s.actionResolver.ExpandActionSets(dbPermissions)
|
||||
}
|
||||
|
||||
dbPermissions = s.actionResolver.ExpandActionSets(dbPermissions)
|
||||
return append(permissions, dbPermissions...), err
|
||||
}
|
||||
|
||||
@@ -205,10 +201,8 @@ func (s *Service) getTeamsPermissions(ctx context.Context, teamIDs []int64, orgI
|
||||
RolePrefixes: OSSRolesPrefixes,
|
||||
})
|
||||
|
||||
if s.features.IsEnabled(ctx, featuremgmt.FlagAccessActionSets) {
|
||||
for teamID, permissions := range teamPermissions {
|
||||
teamPermissions[teamID] = s.actionResolver.ExpandActionSets(permissions)
|
||||
}
|
||||
for teamID, permissions := range teamPermissions {
|
||||
teamPermissions[teamID] = s.actionResolver.ExpandActionSets(permissions)
|
||||
}
|
||||
|
||||
return teamPermissions, err
|
||||
@@ -237,9 +231,7 @@ func (s *Service) getUserDirectPermissions(ctx context.Context, user identity.Re
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if s.features.IsEnabled(ctx, featuremgmt.FlagAccessActionSets) {
|
||||
permissions = s.actionResolver.ExpandActionSets(permissions)
|
||||
}
|
||||
permissions = s.actionResolver.ExpandActionSets(permissions)
|
||||
if s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) {
|
||||
permissions = append(permissions, SharedWithMeFolderPermission)
|
||||
}
|
||||
@@ -545,11 +537,9 @@ func (s *Service) SearchUsersPermissions(ctx context.Context, usr identity.Reque
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if s.features.IsEnabled(ctx, featuremgmt.FlagAccessActionSets) {
|
||||
options.ActionSets = s.actionResolver.ResolveAction(options.Action)
|
||||
options.ActionSets = append(options.ActionSets,
|
||||
s.actionResolver.ResolveActionPrefix(options.ActionPrefix)...)
|
||||
}
|
||||
options.ActionSets = s.actionResolver.ResolveAction(options.Action)
|
||||
options.ActionSets = append(options.ActionSets,
|
||||
s.actionResolver.ResolveActionPrefix(options.ActionPrefix)...)
|
||||
|
||||
// Get managed permissions (DB)
|
||||
usersPermissions, err := s.store.SearchUsersPermissions(ctx, usr.GetOrgID(), options)
|
||||
@@ -610,7 +600,7 @@ func (s *Service) SearchUsersPermissions(ctx context.Context, usr identity.Reque
|
||||
}
|
||||
}
|
||||
|
||||
if s.features.IsEnabled(ctx, featuremgmt.FlagAccessActionSets) && len(options.ActionSets) > 0 {
|
||||
if len(options.ActionSets) > 0 {
|
||||
for id, perms := range res {
|
||||
res[id] = s.actionResolver.ExpandActionSetsWithFilter(perms, GetActionFilter(options))
|
||||
}
|
||||
@@ -661,11 +651,9 @@ func (s *Service) searchUserPermissions(ctx context.Context, orgID int64, search
|
||||
}
|
||||
}
|
||||
|
||||
if s.features.IsEnabled(ctx, featuremgmt.FlagAccessActionSets) {
|
||||
searchOptions.ActionSets = s.actionResolver.ResolveAction(searchOptions.Action)
|
||||
searchOptions.ActionSets = append(searchOptions.ActionSets,
|
||||
s.actionResolver.ResolveActionPrefix(searchOptions.ActionPrefix)...)
|
||||
}
|
||||
searchOptions.ActionSets = s.actionResolver.ResolveAction(searchOptions.Action)
|
||||
searchOptions.ActionSets = append(searchOptions.ActionSets,
|
||||
s.actionResolver.ResolveActionPrefix(searchOptions.ActionPrefix)...)
|
||||
|
||||
// Get permissions from the DB
|
||||
dbPermissions, err := s.store.SearchUsersPermissions(ctx, orgID, searchOptions)
|
||||
@@ -674,7 +662,7 @@ func (s *Service) searchUserPermissions(ctx context.Context, orgID int64, search
|
||||
}
|
||||
permissions = append(permissions, dbPermissions[searchOptions.UserID]...)
|
||||
|
||||
if s.features.IsEnabled(ctx, featuremgmt.FlagAccessActionSets) && len(searchOptions.ActionSets) != 0 {
|
||||
if len(searchOptions.ActionSets) != 0 {
|
||||
permissions = s.actionResolver.ExpandActionSetsWithFilter(permissions, GetActionFilter(searchOptions))
|
||||
}
|
||||
|
||||
|
||||
@@ -35,14 +35,15 @@ func setupTestEnv(t testing.TB) *Service {
|
||||
cfg := setting.NewCfg()
|
||||
|
||||
ac := &Service{
|
||||
cache: localcache.ProvideService(),
|
||||
cfg: cfg,
|
||||
features: featuremgmt.WithFeatures(),
|
||||
log: log.New("accesscontrol"),
|
||||
registrations: accesscontrol.RegistrationList{},
|
||||
roles: accesscontrol.BuildBasicRoleDefinitions(),
|
||||
store: database.ProvideService(db.InitTestDB(t)),
|
||||
permRegistry: permreg.ProvidePermissionRegistry(),
|
||||
cache: localcache.ProvideService(),
|
||||
cfg: cfg,
|
||||
features: featuremgmt.WithFeatures(),
|
||||
log: log.New("accesscontrol"),
|
||||
registrations: accesscontrol.RegistrationList{},
|
||||
roles: accesscontrol.BuildBasicRoleDefinitions(),
|
||||
store: database.ProvideService(db.InitTestDB(t)),
|
||||
permRegistry: permreg.ProvidePermissionRegistry(),
|
||||
actionResolver: resourcepermissions.NewActionSetService(),
|
||||
}
|
||||
require.NoError(t, ac.RegisterFixedRoles(context.Background()))
|
||||
return ac
|
||||
@@ -805,8 +806,7 @@ func TestService_SearchUserPermissions(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ac := setupTestEnv(t)
|
||||
if tt.withActionSets {
|
||||
ac.features = featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets)
|
||||
actionSetSvc := resourcepermissions.NewActionSetService(ac.features)
|
||||
actionSetSvc := resourcepermissions.NewActionSetService()
|
||||
for set, actions := range tt.actionSets {
|
||||
actionSetName := resourcepermissions.GetActionSetName(strings.Split(set, ":")[0], strings.Split(set, ":")[1])
|
||||
actionSetSvc.StoreActionSet(actionSetName, actions)
|
||||
|
||||
@@ -32,7 +32,7 @@ func ProvideFolderPermissions(
|
||||
cfg *setting.Cfg,
|
||||
sqlStore *sqlstore.SQLStore,
|
||||
) (*ossaccesscontrol.FolderPermissionsService, error) {
|
||||
actionSets := resourcepermissions.NewActionSetService(features)
|
||||
actionSets := resourcepermissions.NewActionSetService()
|
||||
|
||||
license := licensingtest.NewFakeLicensing()
|
||||
license.On("FeatureEnabled", "accesscontrol.enforcement").Return(true).Maybe()
|
||||
|
||||
@@ -76,9 +76,7 @@ func New(cfg *setting.Cfg,
|
||||
for _, a := range actions {
|
||||
actionSet[a] = struct{}{}
|
||||
}
|
||||
if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) {
|
||||
actionSetService.StoreActionSet(GetActionSetName(options.Resource, permission), actions)
|
||||
}
|
||||
actionSetService.StoreActionSet(GetActionSetName(options.Resource, permission), actions)
|
||||
}
|
||||
|
||||
// Sort all permissions based on action length. Will be used when mapping between actions to permissions
|
||||
@@ -151,13 +149,11 @@ func (s *Service) GetPermissions(ctx context.Context, user identity.Requester, r
|
||||
}
|
||||
|
||||
actions := s.actions
|
||||
if s.features.IsEnabled(ctx, featuremgmt.FlagAccessActionSets) {
|
||||
for _, action := range s.actions {
|
||||
actionSets := s.actionSetSvc.ResolveAction(action)
|
||||
for _, actionSet := range actionSets {
|
||||
if !slices.Contains(actions, actionSet) {
|
||||
actions = append(actions, actionSet)
|
||||
}
|
||||
for _, action := range s.actions {
|
||||
actionSets := s.actionSetSvc.ResolveAction(action)
|
||||
for _, actionSet := range actionSets {
|
||||
if !slices.Contains(actions, actionSet) {
|
||||
actions = append(actions, actionSet)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -176,33 +172,31 @@ func (s *Service) GetPermissions(ctx context.Context, user identity.Requester, r
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if s.features.IsEnabled(ctx, featuremgmt.FlagAccessActionSets) {
|
||||
for i := range resourcePermissions {
|
||||
actions := resourcePermissions[i].Actions
|
||||
var expandedActions []string
|
||||
for _, action := range actions {
|
||||
if isFolderOrDashboardAction(action) {
|
||||
actionSetActions := s.actionSetSvc.ResolveActionSet(action)
|
||||
if len(actionSetActions) > 0 {
|
||||
// Add all actions for folder
|
||||
if s.options.Resource == dashboards.ScopeFoldersRoot {
|
||||
expandedActions = append(expandedActions, actionSetActions...)
|
||||
continue
|
||||
}
|
||||
// This check is needed for resolving inherited permissions - we don't want to include
|
||||
// actions that are not related to dashboards when expanding dashboard action sets
|
||||
for _, actionSetAction := range actionSetActions {
|
||||
if slices.Contains(s.actions, actionSetAction) {
|
||||
expandedActions = append(expandedActions, actionSetAction)
|
||||
}
|
||||
}
|
||||
for i := range resourcePermissions {
|
||||
actions := resourcePermissions[i].Actions
|
||||
var expandedActions []string
|
||||
for _, action := range actions {
|
||||
if isFolderOrDashboardAction(action) {
|
||||
actionSetActions := s.actionSetSvc.ResolveActionSet(action)
|
||||
if len(actionSetActions) > 0 {
|
||||
// Add all actions for folder
|
||||
if s.options.Resource == dashboards.ScopeFoldersRoot {
|
||||
expandedActions = append(expandedActions, actionSetActions...)
|
||||
continue
|
||||
}
|
||||
// This check is needed for resolving inherited permissions - we don't want to include
|
||||
// actions that are not related to dashboards when expanding dashboard action sets
|
||||
for _, actionSetAction := range actionSetActions {
|
||||
if slices.Contains(s.actions, actionSetAction) {
|
||||
expandedActions = append(expandedActions, actionSetAction)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
expandedActions = append(expandedActions, action)
|
||||
}
|
||||
resourcePermissions[i].Actions = expandedActions
|
||||
expandedActions = append(expandedActions, action)
|
||||
}
|
||||
resourcePermissions[i].Actions = expandedActions
|
||||
}
|
||||
|
||||
return resourcePermissions, nil
|
||||
@@ -495,15 +489,13 @@ type ActionSetStore interface {
|
||||
}
|
||||
|
||||
type ActionSetSvc struct {
|
||||
features featuremgmt.FeatureToggles
|
||||
store ActionSetStore
|
||||
store ActionSetStore
|
||||
}
|
||||
|
||||
// NewActionSetService returns a new instance of InMemoryActionSetService.
|
||||
func NewActionSetService(features featuremgmt.FeatureToggles) ActionSetService {
|
||||
func NewActionSetService() ActionSetService {
|
||||
return &ActionSetSvc{
|
||||
features: features,
|
||||
store: NewInMemoryActionSetStore(features),
|
||||
store: NewInMemoryActionSetStore(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -580,12 +572,9 @@ func (a *ActionSetSvc) ExpandActionSetsWithFilter(permissions []accesscontrol.Pe
|
||||
// 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 {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.resourcepermissions.RegisterActionSets")
|
||||
_, span := tracer.Start(ctx, "accesscontrol.resourcepermissions.RegisterActionSets")
|
||||
defer span.End()
|
||||
|
||||
if !a.features.IsEnabled(ctx, featuremgmt.FlagAccessActionSets) {
|
||||
return nil
|
||||
}
|
||||
for _, reg := range registrations {
|
||||
if err := pluginutils.ValidatePluginActionSet(pluginID, reg); err != nil {
|
||||
return err
|
||||
|
||||
@@ -228,15 +228,13 @@ 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,
|
||||
desc: "should register folder action sets if action sets are enabled",
|
||||
options: Options{
|
||||
Resource: "folders",
|
||||
PermissionsToActions: map[string][]string{
|
||||
@@ -256,8 +254,7 @@ func TestService_RegisterActionSets(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "should register dashboard action set if action sets are enabled",
|
||||
actionSetsEnabled: true,
|
||||
desc: "should register dashboard action set if action sets are enabled",
|
||||
options: Options{
|
||||
Resource: "dashboards",
|
||||
PermissionsToActions: map[string][]string{
|
||||
@@ -271,27 +268,13 @@ func TestService_RegisterActionSets(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
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) {
|
||||
features := featuremgmt.WithFeatures()
|
||||
if tt.actionSetsEnabled {
|
||||
features = featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets)
|
||||
}
|
||||
ac := acimpl.ProvideAccessControl(features)
|
||||
actionSets := NewActionSetService(features)
|
||||
actionSets := NewActionSetService()
|
||||
_, err := New(
|
||||
setting.NewCfg(), tt.options, features, routing.NewRouteRegister(), licensingtest.NewFakeLicensing(),
|
||||
ac, &actest.FakeService{}, db.InitTestDB(t), nil, nil, actionSets,
|
||||
@@ -317,7 +300,6 @@ 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
|
||||
@@ -327,8 +309,7 @@ func TestStore_RegisterActionSet(t *testing.T) {
|
||||
|
||||
tests := []actionSetTest{
|
||||
{
|
||||
desc: "should be able to register a plugin action set if the right feature toggles are enabled",
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets),
|
||||
desc: "should be able to register a plugin action set",
|
||||
pluginID: "test-app",
|
||||
pluginActions: []plugins.ActionSet{
|
||||
{
|
||||
@@ -343,21 +324,8 @@ func TestStore_RegisterActionSet(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "should not register plugin action set if feature toggles are missing",
|
||||
features: featuremgmt.WithFeatures(),
|
||||
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),
|
||||
pluginID: "test-app",
|
||||
pluginActions: []plugins.ActionSet{
|
||||
{
|
||||
@@ -382,7 +350,6 @@ func TestStore_RegisterActionSet(t *testing.T) {
|
||||
},
|
||||
{
|
||||
desc: "action set actions should be added not replaced",
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets),
|
||||
pluginID: "test-app",
|
||||
pluginActions: []plugins.ActionSet{
|
||||
{
|
||||
@@ -425,7 +392,6 @@ func TestStore_RegisterActionSet(t *testing.T) {
|
||||
},
|
||||
{
|
||||
desc: "should not be able to register an action that doesn't have a plugin prefix",
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets),
|
||||
pluginID: "test-app",
|
||||
pluginActions: []plugins.ActionSet{
|
||||
{
|
||||
@@ -441,7 +407,6 @@ func TestStore_RegisterActionSet(t *testing.T) {
|
||||
},
|
||||
{
|
||||
desc: "should not be able to register action set that is not in the allow list",
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets),
|
||||
pluginID: "test-app",
|
||||
pluginActions: []plugins.ActionSet{
|
||||
{
|
||||
@@ -454,7 +419,7 @@ func TestStore_RegisterActionSet(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
asService := NewActionSetService(tt.features)
|
||||
asService := NewActionSetService()
|
||||
|
||||
err := asService.RegisterActionSets(context.Background(), tt.pluginID, tt.pluginActions)
|
||||
if tt.expectedErr {
|
||||
@@ -511,7 +476,7 @@ func setupTestEnvironment(t *testing.T, ops Options) (*Service, user.Service, te
|
||||
ac := acimpl.ProvideAccessControl(features)
|
||||
service, err := New(
|
||||
cfg, ops, features, routing.NewRouteRegister(), license,
|
||||
ac, acService, sql, teamSvc, userSvc, NewActionSetService(features),
|
||||
ac, acService, sql, teamSvc, userSvc, NewActionSetService(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
@@ -771,18 +771,16 @@ 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 {
|
||||
func NewInMemoryActionSetStore() *InMemoryActionSets {
|
||||
return &InMemoryActionSets{
|
||||
actionSetToActions: make(map[string][]string),
|
||||
actionToActionSets: make(map[string][]string),
|
||||
log: log.New("resourcepermissions.actionsets"),
|
||||
features: features,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -781,7 +781,7 @@ func TestStore_StoreActionSet(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
asService := NewInMemoryActionSetStore(featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets))
|
||||
asService := NewInMemoryActionSetStore()
|
||||
asService.StoreActionSet(GetActionSetName(tt.resource, tt.action), tt.actions)
|
||||
|
||||
actionSetName := GetActionSetName(tt.resource, tt.action)
|
||||
@@ -792,7 +792,7 @@ func TestStore_StoreActionSet(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStore_ResolveActionSet(t *testing.T) {
|
||||
actionSetService := NewActionSetService(featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets))
|
||||
actionSetService := NewActionSetService()
|
||||
actionSetService.StoreActionSet("folders:edit", []string{"folders:read", "folders:write", "dashboards:read", "dashboards:write"})
|
||||
actionSetService.StoreActionSet("folders:view", []string{"folders:read", "dashboards:read"})
|
||||
actionSetService.StoreActionSet("dashboards:view", []string{"dashboards:read"})
|
||||
@@ -835,7 +835,7 @@ func TestStore_ResolveActionSet(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStore_ExpandActions(t *testing.T) {
|
||||
actionSetService := NewActionSetService(featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets))
|
||||
actionSetService := NewActionSetService()
|
||||
actionSetService.StoreActionSet("folders:edit", []string{"folders:read", "folders:write", "dashboards:read", "dashboards:write"})
|
||||
actionSetService.StoreActionSet("folders:view", []string{"folders:read", "dashboards:read"})
|
||||
actionSetService.StoreActionSet("dashboards:view", []string{"dashboards:read"})
|
||||
|
||||
@@ -1105,13 +1105,6 @@ var (
|
||||
FrontendOnly: false,
|
||||
AllowSelfServe: false,
|
||||
},
|
||||
{
|
||||
Name: "accessActionSets",
|
||||
Description: "Introduces action sets for resource permissions. Also ensures that all folder editors and admins can create subfolders without needing any additional permissions.",
|
||||
Stage: FeatureStageGeneralAvailability,
|
||||
Owner: identityAccessTeam,
|
||||
Expression: "true", // enabled by default
|
||||
},
|
||||
{
|
||||
Name: "disableNumericMetricsSortingInExpressions",
|
||||
Description: "In server-side expressions, disable the sorting of numeric-kind metrics by their metric name or labels.",
|
||||
|
||||
@@ -144,7 +144,6 @@ ssoSettingsSAML,GA,@grafana/identity-access-team,false,false,false
|
||||
oauthRequireSubClaim,experimental,@grafana/identity-access-team,false,false,false
|
||||
newDashboardWithFiltersAndGroupBy,experimental,@grafana/dashboards-squad,false,false,false
|
||||
cloudWatchNewLabelParsing,GA,@grafana/aws-datasources,false,false,false
|
||||
accessActionSets,GA,@grafana/identity-access-team,false,false,false
|
||||
disableNumericMetricsSortingInExpressions,experimental,@grafana/oss-big-tent,false,true,false
|
||||
grafanaManagedRecordingRules,experimental,@grafana/alerting-squad,false,false,false
|
||||
queryLibrary,experimental,@grafana/grafana-frontend-platform,false,false,false
|
||||
|
||||
|
@@ -587,10 +587,6 @@ const (
|
||||
// Updates CloudWatch label parsing to be more accurate
|
||||
FlagCloudWatchNewLabelParsing = "cloudWatchNewLabelParsing"
|
||||
|
||||
// FlagAccessActionSets
|
||||
// Introduces action sets for resource permissions. Also ensures that all folder editors and admins can create subfolders without needing any additional permissions.
|
||||
FlagAccessActionSets = "accessActionSets"
|
||||
|
||||
// FlagDisableNumericMetricsSortingInExpressions
|
||||
// In server-side expressions, disable the sorting of numeric-kind metrics by their metric name or labels.
|
||||
FlagDisableNumericMetricsSortingInExpressions = "disableNumericMetricsSortingInExpressions"
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"name": "accessActionSets",
|
||||
"resourceVersion": "1731413707429",
|
||||
"creationTimestamp": "2024-04-12T16:19:25Z",
|
||||
"deletionTimestamp": "2025-03-11T16:34:55Z",
|
||||
"annotations": {
|
||||
"grafana.app/updatedTimestamp": "2024-11-12 12:15:07.42916 +0000 UTC"
|
||||
}
|
||||
|
||||
@@ -1248,12 +1248,6 @@ func (s *Service) canMove(ctx context.Context, cmd *folder.MoveFolderCommand) (b
|
||||
var evaluators []accesscontrol.Evaluator
|
||||
currentFolderScope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(cmd.UID)
|
||||
for action, scopes := range permissions {
|
||||
// Skip unexpanded action sets - they have no impact if action sets are not enabled
|
||||
if !s.features.IsEnabled(ctx, featuremgmt.FlagAccessActionSets) {
|
||||
if action == "folders:view" || action == "folders:edit" || action == "folders:admin" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
for _, scope := range newFolderAndParentUIDs {
|
||||
if slices.Contains(scopes, scope) {
|
||||
evaluators = append(evaluators, accesscontrol.EvalPermission(action, currentFolderScope))
|
||||
|
||||
@@ -842,12 +842,6 @@ func (s *Service) canMoveViaApiServer(ctx context.Context, cmd *folder.MoveFolde
|
||||
var evaluators []accesscontrol.Evaluator
|
||||
currentFolderScope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(cmd.UID)
|
||||
for action, scopes := range permissions {
|
||||
// Skip unexpanded action sets - they have no impact if action sets are not enabled
|
||||
if !s.features.IsEnabled(ctx, featuremgmt.FlagAccessActionSets) {
|
||||
if action == "folders:view" || action == "folders:edit" || action == "folders:admin" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
for _, scope := range newFolderAndParentUIDs {
|
||||
if slices.Contains(scopes, scope) {
|
||||
evaluators = append(evaluators, accesscontrol.EvalPermission(action, currentFolderScope))
|
||||
|
||||
@@ -2,7 +2,6 @@ package permissions
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
@@ -61,59 +60,41 @@ func NewAccessControlDashboardPermissionFilter(user identity.Requester, permissi
|
||||
var dashboardActionSets []string
|
||||
if queryType == searchstore.TypeFolder {
|
||||
folderAction = dashboards.ActionFoldersRead
|
||||
if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) {
|
||||
folderActionSets = []string{"folders:view", "folders:edit", "folders:admin"}
|
||||
}
|
||||
folderActionSets = []string{"folders:view", "folders:edit", "folders:admin"}
|
||||
if needEdit {
|
||||
folderAction = dashboards.ActionDashboardsCreate
|
||||
if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) {
|
||||
folderActionSets = []string{"folders:edit", "folders:admin"}
|
||||
}
|
||||
folderActionSets = []string{"folders:edit", "folders:admin"}
|
||||
}
|
||||
} else if queryType == searchstore.TypeDashboard {
|
||||
dashboardAction = dashboards.ActionDashboardsRead
|
||||
if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) {
|
||||
folderActionSets = []string{"folders:view", "folders:edit", "folders:admin"}
|
||||
dashboardActionSets = []string{"dashboards:view", "dashboards:edit", "dashboards:admin"}
|
||||
}
|
||||
folderActionSets = []string{"folders:view", "folders:edit", "folders:admin"}
|
||||
dashboardActionSets = []string{"dashboards:view", "dashboards:edit", "dashboards:admin"}
|
||||
if needEdit {
|
||||
dashboardAction = dashboards.ActionDashboardsWrite
|
||||
if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) {
|
||||
folderActionSets = []string{"folders:edit", "folders:admin"}
|
||||
dashboardActionSets = []string{"dashboards:edit", "dashboards:admin"}
|
||||
}
|
||||
folderActionSets = []string{"folders:edit", "folders:admin"}
|
||||
dashboardActionSets = []string{"dashboards:edit", "dashboards:admin"}
|
||||
}
|
||||
} else if queryType == searchstore.TypeAlertFolder {
|
||||
folderAction = accesscontrol.ActionAlertingRuleRead
|
||||
if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) {
|
||||
folderActionSets = []string{"folders:view", "folders:edit", "folders:admin"}
|
||||
}
|
||||
folderActionSets = []string{"folders:view", "folders:edit", "folders:admin"}
|
||||
if needEdit {
|
||||
folderAction = accesscontrol.ActionAlertingRuleCreate
|
||||
if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) {
|
||||
folderActionSets = []string{"folders:edit", "folders:admin"}
|
||||
}
|
||||
folderActionSets = []string{"folders:edit", "folders:admin"}
|
||||
}
|
||||
} else if queryType == searchstore.TypeAnnotation {
|
||||
dashboardAction = accesscontrol.ActionAnnotationsRead
|
||||
if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) {
|
||||
folderActionSets = []string{"folders:view", "folders:edit", "folders:admin"}
|
||||
dashboardActionSets = []string{"dashboards:view", "dashboards:edit", "dashboards:admin"}
|
||||
}
|
||||
folderActionSets = []string{"folders:view", "folders:edit", "folders:admin"}
|
||||
dashboardActionSets = []string{"dashboards:view", "dashboards:edit", "dashboards:admin"}
|
||||
} else {
|
||||
folderAction = dashboards.ActionFoldersRead
|
||||
dashboardAction = dashboards.ActionDashboardsRead
|
||||
if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) {
|
||||
folderActionSets = []string{"folders:view", "folders:edit", "folders:admin"}
|
||||
dashboardActionSets = []string{"dashboards:view", "dashboards:edit", "dashboards:admin"}
|
||||
}
|
||||
folderActionSets = []string{"folders:view", "folders:edit", "folders:admin"}
|
||||
dashboardActionSets = []string{"dashboards:view", "dashboards:edit", "dashboards:admin"}
|
||||
if needEdit {
|
||||
folderAction = dashboards.ActionDashboardsCreate
|
||||
dashboardAction = dashboards.ActionDashboardsWrite
|
||||
if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) {
|
||||
folderActionSets = []string{"folders:edit", "folders:admin"}
|
||||
dashboardActionSets = []string{"dashboards:edit", "dashboards:admin"}
|
||||
}
|
||||
folderActionSets = []string{"folders:edit", "folders:admin"}
|
||||
dashboardActionSets = []string{"dashboards:edit", "dashboards:admin"}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -417,7 +417,7 @@ func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) {
|
||||
permissions: []accesscontrol.Permission{
|
||||
{Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersAll},
|
||||
},
|
||||
features: []any{featuremgmt.FlagNestedFolders, featuremgmt.FlagAccessActionSets},
|
||||
features: []any{featuremgmt.FlagNestedFolders},
|
||||
expectedResult: []string{"dashboard under the root", "dashboard under parent folder", "dashboard under subfolder"},
|
||||
},
|
||||
{
|
||||
@@ -459,7 +459,7 @@ func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) {
|
||||
})
|
||||
usr := &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByActionContext(context.Background(), tc.permissions)}}
|
||||
|
||||
for _, features := range []featuremgmt.FeatureToggles{featuremgmt.WithFeatures(append(tc.features, featuremgmt.FlagAccessActionSets)...), featuremgmt.WithFeatures(tc.features...), featuremgmt.WithFeatures(append(tc.features, featuremgmt.FlagPermissionsFilterRemoveSubquery)...)} {
|
||||
for _, features := range []featuremgmt.FeatureToggles{featuremgmt.WithFeatures(tc.features...), featuremgmt.WithFeatures(append(tc.features, featuremgmt.FlagPermissionsFilterRemoveSubquery)...)} {
|
||||
m := features.GetEnabled(context.Background())
|
||||
keys := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
@@ -616,20 +616,17 @@ func TestIntegration_DashboardNestedPermissionFilter_WithActionSets(t *testing.T
|
||||
permission dashboardaccess.PermissionType
|
||||
signedInUserPermissions []accesscontrol.Permission
|
||||
expectedResult []string
|
||||
features []any
|
||||
}{
|
||||
{
|
||||
desc: "Should not list any dashboards if user has no permissions",
|
||||
permission: dashboardaccess.PERMISSION_VIEW,
|
||||
signedInUserPermissions: nil,
|
||||
features: []any{featuremgmt.FlagNestedFolders, featuremgmt.FlagAccessActionSets},
|
||||
expectedResult: nil,
|
||||
},
|
||||
{
|
||||
desc: "Should not list any folders if user has no permissions",
|
||||
permission: dashboardaccess.PERMISSION_VIEW,
|
||||
signedInUserPermissions: nil,
|
||||
features: []any{featuremgmt.FlagNestedFolders, featuremgmt.FlagAccessActionSets},
|
||||
expectedResult: nil,
|
||||
},
|
||||
{
|
||||
@@ -639,7 +636,6 @@ func TestIntegration_DashboardNestedPermissionFilter_WithActionSets(t *testing.T
|
||||
signedInUserPermissions: []accesscontrol.Permission{
|
||||
{Action: dashboards.ActionFoldersRead, Scope: dashboards.ScopeFoldersAll},
|
||||
},
|
||||
features: []any{featuremgmt.FlagNestedFolders, featuremgmt.FlagAccessActionSets},
|
||||
expectedResult: []string{"parent", "subfolder"},
|
||||
},
|
||||
{
|
||||
@@ -649,7 +645,6 @@ func TestIntegration_DashboardNestedPermissionFilter_WithActionSets(t *testing.T
|
||||
signedInUserPermissions: []accesscontrol.Permission{
|
||||
{Action: "folders:view", Scope: "folders:uid:parent", Kind: "folders", Identifier: "parent"},
|
||||
},
|
||||
features: []any{featuremgmt.FlagNestedFolders, featuremgmt.FlagAccessActionSets},
|
||||
expectedResult: []string{"parent", "subfolder"},
|
||||
},
|
||||
{
|
||||
@@ -659,7 +654,6 @@ func TestIntegration_DashboardNestedPermissionFilter_WithActionSets(t *testing.T
|
||||
signedInUserPermissions: []accesscontrol.Permission{
|
||||
{Action: "folders:admin", Scope: "folders:uid:subfolder", Kind: "folders", Identifier: "subfolder"},
|
||||
},
|
||||
features: []any{featuremgmt.FlagNestedFolders, featuremgmt.FlagAccessActionSets},
|
||||
expectedResult: []string{"subfolder"},
|
||||
},
|
||||
{
|
||||
@@ -670,7 +664,6 @@ func TestIntegration_DashboardNestedPermissionFilter_WithActionSets(t *testing.T
|
||||
{Action: "folders:edit", Scope: "folders:uid:subfolder", Kind: "folders", Identifier: "subfolder"},
|
||||
{Action: "folders:view", Scope: "folders:uid:parent", Kind: "folders", Identifier: "parent"},
|
||||
},
|
||||
features: []any{featuremgmt.FlagNestedFolders, featuremgmt.FlagAccessActionSets},
|
||||
expectedResult: []string{"subfolder"},
|
||||
},
|
||||
}
|
||||
@@ -696,7 +689,7 @@ func TestIntegration_DashboardNestedPermissionFilter_WithActionSets(t *testing.T
|
||||
Scope: "folders:uid:unrelated"})
|
||||
usr := &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByActionContext(context.Background(), tc.signedInUserPermissions)}}
|
||||
|
||||
for _, features := range []featuremgmt.FeatureToggles{featuremgmt.WithFeatures(tc.features...), featuremgmt.WithFeatures(append(tc.features, featuremgmt.FlagPermissionsFilterRemoveSubquery)...)} {
|
||||
for _, features := range []featuremgmt.FeatureToggles{featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders), featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders, featuremgmt.FlagPermissionsFilterRemoveSubquery)} {
|
||||
m := features.GetEnabled(context.Background())
|
||||
keys := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
|
||||
@@ -153,6 +153,9 @@ func TestBuilder_RBAC(t *testing.T) {
|
||||
int64(1),
|
||||
0,
|
||||
"dashboards:read",
|
||||
"dashboards:view",
|
||||
"dashboards:edit",
|
||||
"dashboards:admin",
|
||||
int64(1),
|
||||
int64(1),
|
||||
int64(1),
|
||||
@@ -161,6 +164,9 @@ func TestBuilder_RBAC(t *testing.T) {
|
||||
int64(1),
|
||||
0,
|
||||
"dashboards:read",
|
||||
"folders:view",
|
||||
"folders:edit",
|
||||
"folders:admin",
|
||||
int64(1),
|
||||
int64(1),
|
||||
0,
|
||||
@@ -168,6 +174,9 @@ func TestBuilder_RBAC(t *testing.T) {
|
||||
int64(1),
|
||||
0,
|
||||
"folders:read",
|
||||
"folders:view",
|
||||
"folders:edit",
|
||||
"folders:admin",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -186,6 +195,8 @@ func TestBuilder_RBAC(t *testing.T) {
|
||||
int64(1),
|
||||
0,
|
||||
"dashboards:write",
|
||||
"dashboards:edit",
|
||||
"dashboards:admin",
|
||||
int64(1),
|
||||
int64(1),
|
||||
int64(1),
|
||||
@@ -194,6 +205,8 @@ func TestBuilder_RBAC(t *testing.T) {
|
||||
int64(1),
|
||||
0,
|
||||
"dashboards:write",
|
||||
"folders:edit",
|
||||
"folders:admin",
|
||||
int64(1),
|
||||
int64(1),
|
||||
0,
|
||||
@@ -201,6 +214,8 @@ func TestBuilder_RBAC(t *testing.T) {
|
||||
int64(1),
|
||||
0,
|
||||
"dashboards:create",
|
||||
"folders:edit",
|
||||
"folders:admin",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -219,6 +234,9 @@ func TestBuilder_RBAC(t *testing.T) {
|
||||
int64(1),
|
||||
0,
|
||||
"dashboards:read",
|
||||
"folders:view",
|
||||
"folders:edit",
|
||||
"folders:admin",
|
||||
int64(1),
|
||||
int64(1),
|
||||
int64(1),
|
||||
@@ -227,6 +245,9 @@ func TestBuilder_RBAC(t *testing.T) {
|
||||
int64(1),
|
||||
0,
|
||||
"folders:read",
|
||||
"folders:view",
|
||||
"folders:edit",
|
||||
"folders:admin",
|
||||
int64(1),
|
||||
int64(1),
|
||||
int64(1),
|
||||
@@ -235,6 +256,9 @@ func TestBuilder_RBAC(t *testing.T) {
|
||||
int64(1),
|
||||
0,
|
||||
"dashboards:read",
|
||||
"dashboards:view",
|
||||
"dashboards:edit",
|
||||
"dashboards:admin",
|
||||
int64(1),
|
||||
},
|
||||
},
|
||||
@@ -254,6 +278,9 @@ func TestBuilder_RBAC(t *testing.T) {
|
||||
int64(1),
|
||||
0,
|
||||
"dashboards:read",
|
||||
"dashboards:view",
|
||||
"dashboards:edit",
|
||||
"dashboards:admin",
|
||||
int64(1),
|
||||
int64(1),
|
||||
0,
|
||||
@@ -261,6 +288,9 @@ func TestBuilder_RBAC(t *testing.T) {
|
||||
int64(1),
|
||||
0,
|
||||
"dashboards:read",
|
||||
"folders:view",
|
||||
"folders:edit",
|
||||
"folders:admin",
|
||||
int64(1),
|
||||
int64(1),
|
||||
0,
|
||||
@@ -268,6 +298,9 @@ func TestBuilder_RBAC(t *testing.T) {
|
||||
int64(1),
|
||||
0,
|
||||
"folders:read",
|
||||
"folders:view",
|
||||
"folders:edit",
|
||||
"folders:admin",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -286,6 +319,8 @@ func TestBuilder_RBAC(t *testing.T) {
|
||||
int64(1),
|
||||
0,
|
||||
"dashboards:write",
|
||||
"folders:edit",
|
||||
"folders:admin",
|
||||
int64(1),
|
||||
int64(1),
|
||||
int64(1),
|
||||
@@ -294,6 +329,8 @@ func TestBuilder_RBAC(t *testing.T) {
|
||||
int64(1),
|
||||
0,
|
||||
"dashboards:create",
|
||||
"folders:edit",
|
||||
"folders:admin",
|
||||
int64(1),
|
||||
int64(1),
|
||||
int64(1),
|
||||
@@ -302,6 +339,8 @@ func TestBuilder_RBAC(t *testing.T) {
|
||||
int64(1),
|
||||
0,
|
||||
"dashboards:write",
|
||||
"dashboards:edit",
|
||||
"dashboards:admin",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -607,7 +607,7 @@ func (c *K8sTestHelper) AddOrUpdateTeamMember(user User, teamID int64, permissio
|
||||
c.env.Server.HTTPServer.AlertNG.AccesscontrolService,
|
||||
c.teamSvc,
|
||||
c.userSvc,
|
||||
resourcepermissions.NewActionSetService(c.env.FeatureToggles),
|
||||
resourcepermissions.NewActionSetService(),
|
||||
)
|
||||
require.NoError(c.t, err)
|
||||
|
||||
|
||||
@@ -12,16 +12,6 @@ function checkCanCreateFolders(folderDTO?: FolderDTO) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!config.featureToggles.accessActionSets) {
|
||||
if (!folderDTO || folderDTO.uid === 'general') {
|
||||
return checkFolderPermission(AccessControlAction.FoldersCreate);
|
||||
}
|
||||
return (
|
||||
checkFolderPermission(AccessControlAction.FoldersCreate) &&
|
||||
checkFolderPermission(AccessControlAction.FoldersWrite, folderDTO)
|
||||
);
|
||||
}
|
||||
|
||||
return checkFolderPermission(AccessControlAction.FoldersCreate, folderDTO);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user