2021-12-20 16:52:24 +08:00
package resourcepermissions
import (
"context"
2024-04-17 21:53:28 +08:00
"errors"
2021-12-20 16:52:24 +08:00
"fmt"
2025-09-12 03:13:07 +08:00
"slices"
2021-12-20 16:52:24 +08:00
"sort"
2024-08-12 17:07:33 +08:00
"strings"
2021-12-20 16:52:24 +08:00
"github.com/grafana/grafana/pkg/api/routing"
2024-06-13 12:11:35 +08:00
"github.com/grafana/grafana/pkg/apimachinery/identity"
2022-10-19 21:02:15 +08:00
"github.com/grafana/grafana/pkg/infra/db"
2024-05-23 19:14:01 +08:00
"github.com/grafana/grafana/pkg/infra/log"
2024-08-12 17:07:33 +08:00
"github.com/grafana/grafana/pkg/plugins"
2021-12-20 16:52:24 +08:00
"github.com/grafana/grafana/pkg/services/accesscontrol"
2024-08-12 17:07:33 +08:00
"github.com/grafana/grafana/pkg/services/accesscontrol/pluginutils"
2024-07-19 23:16:23 +08:00
"github.com/grafana/grafana/pkg/services/dashboards"
2023-07-21 22:23:01 +08:00
"github.com/grafana/grafana/pkg/services/featuremgmt"
2023-01-24 00:53:43 +08:00
"github.com/grafana/grafana/pkg/services/licensing"
2022-08-10 17:56:48 +08:00
"github.com/grafana/grafana/pkg/services/org"
2024-07-22 16:31:36 +08:00
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol"
2022-09-23 01:16:21 +08:00
"github.com/grafana/grafana/pkg/services/team"
2022-08-10 17:56:48 +08:00
"github.com/grafana/grafana/pkg/services/user"
2024-01-23 19:36:22 +08:00
"github.com/grafana/grafana/pkg/setting"
2021-12-20 16:52:24 +08:00
)
2024-08-12 17:07:33 +08:00
var _ pluginaccesscontrol . ActionSetRegistry = ( ActionSetService ) ( nil )
2024-07-19 23:16:23 +08:00
2022-01-26 00:12:00 +08:00
type Store interface {
// SetUserResourcePermission sets permission for managed user role on a resource
SetUserResourcePermission (
2022-02-03 23:27:05 +08:00
ctx context . Context , orgID int64 ,
user accesscontrol . User ,
2022-08-18 15:43:45 +08:00
cmd SetResourcePermissionCommand ,
hook UserResourceHookFunc ,
2022-01-26 00:12:00 +08:00
) ( * accesscontrol . ResourcePermission , error )
// SetTeamResourcePermission sets permission for managed team role on a resource
SetTeamResourcePermission (
ctx context . Context , orgID , teamID int64 ,
2022-08-18 15:43:45 +08:00
cmd SetResourcePermissionCommand ,
hook TeamResourceHookFunc ,
2022-01-26 00:12:00 +08:00
) ( * accesscontrol . ResourcePermission , error )
// SetBuiltInResourcePermission sets permissions for managed builtin role on a resource
SetBuiltInResourcePermission (
ctx context . Context , orgID int64 , builtinRole string ,
2022-08-18 15:43:45 +08:00
cmd SetResourcePermissionCommand ,
hook BuiltinResourceHookFunc ,
2022-01-26 00:12:00 +08:00
) ( * accesscontrol . ResourcePermission , error )
2022-02-08 00:04:32 +08:00
SetResourcePermissions (
ctx context . Context , orgID int64 ,
2022-08-18 15:43:45 +08:00
commands [ ] SetResourcePermissionsCommand ,
hooks ResourceHooks ,
2022-02-08 00:04:32 +08:00
) ( [ ] accesscontrol . ResourcePermission , error )
2022-03-18 00:08:51 +08:00
// GetResourcePermissions will return all permission for supplied resource id
2022-08-18 15:43:45 +08:00
GetResourcePermissions ( ctx context . Context , orgID int64 , query GetResourcePermissionsQuery ) ( [ ] accesscontrol . ResourcePermission , error )
2023-07-10 17:13:05 +08:00
// DeleteResourcePermissions will delete all permissions for supplied resource id
DeleteResourcePermissions ( ctx context . Context , orgID int64 , cmd * DeleteResourcePermissionsCmd ) error
2022-01-26 00:12:00 +08:00
}
2024-01-23 19:36:22 +08:00
func New ( cfg * setting . Cfg ,
2023-07-21 22:23:01 +08:00
options Options , features featuremgmt . FeatureToggles , router routing . RouteRegister , license licensing . Licensing ,
2022-10-15 03:33:06 +08:00
ac accesscontrol . AccessControl , service accesscontrol . Service , sqlStore db . DB ,
2024-04-19 22:38:14 +08:00
teamService team . Service , userService user . Service , actionSetService ActionSetService ,
2022-05-26 02:40:41 +08:00
) ( * Service , error ) {
2022-11-28 20:10:24 +08:00
permissions := make ( [ ] string , 0 , len ( options . PermissionsToActions ) )
2022-01-26 00:12:00 +08:00
actionSet := make ( map [ string ] struct { } )
2021-12-20 16:52:24 +08:00
for permission , actions := range options . PermissionsToActions {
permissions = append ( permissions , permission )
for _ , a := range actions {
2022-01-26 00:12:00 +08:00
actionSet [ a ] = struct { } { }
2021-12-20 16:52:24 +08:00
}
2025-03-13 23:18:23 +08:00
actionSetService . StoreActionSet ( GetActionSetName ( options . Resource , permission ) , actions )
2021-12-20 16:52:24 +08:00
}
// Sort all permissions based on action length. Will be used when mapping between actions to permissions
sort . Slice ( permissions , func ( i , j int ) bool {
return len ( options . PermissionsToActions [ permissions [ i ] ] ) > len ( options . PermissionsToActions [ permissions [ j ] ] )
} )
2022-01-26 00:12:00 +08:00
actions := make ( [ ] string , 0 , len ( actionSet ) )
for action := range actionSet {
2021-12-20 16:52:24 +08:00
actions = append ( actions , action )
}
s := & Service {
2024-06-14 01:06:37 +08:00
ac : ac ,
features : features ,
store : NewStore ( cfg , sqlStore , features ) ,
options : options ,
license : license ,
2024-07-19 23:16:23 +08:00
log : log . New ( "resourcepermissions" ) ,
2024-06-14 01:06:37 +08:00
permissions : permissions ,
actions : actions ,
sqlStore : sqlStore ,
service : service ,
teamService : teamService ,
userService : userService ,
actionSetSvc : actionSetService ,
2021-12-20 16:52:24 +08:00
}
2024-01-23 19:36:22 +08:00
s . api = newApi ( cfg , ac , router , s )
2021-12-20 16:52:24 +08:00
if err := s . declareFixedRoles ( ) ; err != nil {
return nil , err
}
s . api . registerEndpoints ( )
return s , nil
}
// Service is used to create access control sub system including api / and service for managed resource permission
type Service struct {
2024-06-14 01:06:37 +08:00
ac accesscontrol . AccessControl
features featuremgmt . FeatureToggles
service accesscontrol . Service
store Store
api * api
license licensing . Licensing
2024-07-19 23:16:23 +08:00
log log . Logger
2024-06-14 01:06:37 +08:00
options Options
permissions [ ] string
actions [ ] string
sqlStore db . DB
teamService team . Service
userService user . Service
actionSetSvc ActionSetService
2021-12-20 16:52:24 +08:00
}
2023-08-18 18:42:18 +08:00
func ( s * Service ) GetPermissions ( ctx context . Context , user identity . Requester , resourceID string ) ( [ ] accesscontrol . ResourcePermission , error ) {
2024-08-30 14:26:15 +08:00
ctx , span := tracer . Start ( ctx , "accesscontrol.resourcepermissions.GetPermissions" )
2024-08-17 06:08:19 +08:00
defer span . End ( )
2022-03-18 00:08:51 +08:00
var inheritedScopes [ ] string
if s . options . InheritedScopesSolver != nil {
var err error
2023-08-18 18:42:18 +08:00
inheritedScopes , err = s . options . InheritedScopesSolver ( ctx , user . GetOrgID ( ) , resourceID )
2022-03-18 00:08:51 +08:00
if err != nil {
return nil , err
}
}
2024-06-14 01:06:37 +08:00
actions := s . actions
2025-03-13 23:18:23 +08:00
for _ , action := range s . actions {
actionSets := s . actionSetSvc . ResolveAction ( action )
for _ , actionSet := range actionSets {
if ! slices . Contains ( actions , actionSet ) {
actions = append ( actions , actionSet )
2024-06-14 01:06:37 +08:00
}
}
}
resourcePermissions , err := s . store . GetResourcePermissions ( ctx , user . GetOrgID ( ) , GetResourcePermissionsQuery {
2022-10-19 17:53:59 +08:00
User : user ,
2024-06-14 01:06:37 +08:00
Actions : actions ,
2022-10-19 17:53:59 +08:00
Resource : s . options . Resource ,
ResourceID : resourceID ,
ResourceAttribute : s . options . ResourceAttribute ,
InheritedScopes : inheritedScopes ,
OnlyManaged : s . options . OnlyManaged ,
EnforceAccessControl : s . license . FeatureEnabled ( "accesscontrol.enforcement" ) ,
2021-12-20 16:52:24 +08:00
} )
2024-06-14 01:06:37 +08:00
if err != nil {
return nil , err
}
2025-03-13 23:18:23 +08:00
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 ... )
2024-06-14 01:06:37 +08:00
continue
}
2025-03-13 23:18:23 +08:00
// 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
2024-06-14 01:06:37 +08:00
}
}
2025-03-13 23:18:23 +08:00
expandedActions = append ( expandedActions , action )
2024-06-14 01:06:37 +08:00
}
2025-03-13 23:18:23 +08:00
resourcePermissions [ i ] . Actions = expandedActions
2024-06-14 01:06:37 +08:00
}
return resourcePermissions , nil
2021-12-20 16:52:24 +08:00
}
2022-02-03 23:27:05 +08:00
func ( s * Service ) SetUserPermission ( ctx context . Context , orgID int64 , user accesscontrol . User , resourceID , permission string ) ( * accesscontrol . ResourcePermission , error ) {
2024-08-30 14:26:15 +08:00
ctx , span := tracer . Start ( ctx , "accesscontrol.resourcepermissions.SetUserPermission" )
2024-08-17 06:08:19 +08:00
defer span . End ( )
2022-01-26 00:12:00 +08:00
actions , err := s . mapPermission ( permission )
if err != nil {
return nil , err
2021-12-20 16:52:24 +08:00
}
if err := s . validateResource ( ctx , orgID , resourceID ) ; err != nil {
return nil , err
}
2022-02-03 23:27:05 +08:00
if err := s . validateUser ( ctx , orgID , user . ID ) ; err != nil {
2021-12-20 16:52:24 +08:00
return nil , err
}
2022-08-18 15:43:45 +08:00
return s . store . SetUserResourcePermission ( ctx , orgID , user , SetResourcePermissionCommand {
2022-03-22 00:58:18 +08:00
Actions : actions ,
Permission : permission ,
Resource : s . options . Resource ,
ResourceID : resourceID ,
ResourceAttribute : s . options . ResourceAttribute ,
2022-01-26 00:12:00 +08:00
} , s . options . OnSetUser )
2021-12-20 16:52:24 +08:00
}
2022-01-26 00:12:00 +08:00
func ( s * Service ) SetTeamPermission ( ctx context . Context , orgID , teamID int64 , resourceID , permission string ) ( * accesscontrol . ResourcePermission , error ) {
2024-08-30 14:26:15 +08:00
ctx , span := tracer . Start ( ctx , "accesscontrol.resourcepermissions.SetTeamPermission" )
2024-08-17 06:08:19 +08:00
defer span . End ( )
2022-01-26 00:12:00 +08:00
actions , err := s . mapPermission ( permission )
if err != nil {
return nil , err
2021-12-20 16:52:24 +08:00
}
if err := s . validateTeam ( ctx , orgID , teamID ) ; err != nil {
return nil , err
}
if err := s . validateResource ( ctx , orgID , resourceID ) ; err != nil {
return nil , err
}
2022-08-18 15:43:45 +08:00
return s . store . SetTeamResourcePermission ( ctx , orgID , teamID , SetResourcePermissionCommand {
2022-03-22 00:58:18 +08:00
Actions : actions ,
Permission : permission ,
Resource : s . options . Resource ,
ResourceID : resourceID ,
ResourceAttribute : s . options . ResourceAttribute ,
2022-01-26 00:12:00 +08:00
} , s . options . OnSetTeam )
2021-12-20 16:52:24 +08:00
}
2022-01-26 00:12:00 +08:00
func ( s * Service ) SetBuiltInRolePermission ( ctx context . Context , orgID int64 , builtInRole , resourceID , permission string ) ( * accesscontrol . ResourcePermission , error ) {
2024-08-30 14:26:15 +08:00
ctx , span := tracer . Start ( ctx , "accesscontrol.resourcepermissions.SetBuiltInRolePermission" )
2024-08-17 06:08:19 +08:00
defer span . End ( )
2022-01-26 00:12:00 +08:00
actions , err := s . mapPermission ( permission )
if err != nil {
return nil , err
2021-12-20 16:52:24 +08:00
}
if err := s . validateBuiltinRole ( ctx , builtInRole ) ; err != nil {
return nil , err
}
if err := s . validateResource ( ctx , orgID , resourceID ) ; err != nil {
return nil , err
}
2022-08-18 15:43:45 +08:00
return s . store . SetBuiltInResourcePermission ( ctx , orgID , builtInRole , SetResourcePermissionCommand {
2022-03-22 00:58:18 +08:00
Actions : actions ,
Permission : permission ,
Resource : s . options . Resource ,
ResourceID : resourceID ,
ResourceAttribute : s . options . ResourceAttribute ,
2022-01-26 00:12:00 +08:00
} , s . options . OnSetBuiltInRole )
2021-12-20 16:52:24 +08:00
}
2022-02-08 00:04:32 +08:00
func ( s * Service ) SetPermissions (
ctx context . Context , orgID int64 , resourceID string ,
commands ... accesscontrol . SetResourcePermissionCommand ,
) ( [ ] accesscontrol . ResourcePermission , error ) {
2024-08-30 14:26:15 +08:00
ctx , span := tracer . Start ( ctx , "accesscontrol.resourcepermissions.SetPermissions" )
2024-08-17 06:08:19 +08:00
defer span . End ( )
2022-02-08 00:04:32 +08:00
if err := s . validateResource ( ctx , orgID , resourceID ) ; err != nil {
return nil , err
}
2022-08-18 15:43:45 +08:00
dbCommands := make ( [ ] SetResourcePermissionsCommand , 0 , len ( commands ) )
2022-02-08 00:04:32 +08:00
for _ , cmd := range commands {
if cmd . UserID != 0 {
if err := s . validateUser ( ctx , orgID , cmd . UserID ) ; err != nil {
return nil , err
}
} else if cmd . TeamID != 0 {
if err := s . validateTeam ( ctx , orgID , cmd . TeamID ) ; err != nil {
return nil , err
}
} else {
if err := s . validateBuiltinRole ( ctx , cmd . BuiltinRole ) ; err != nil {
return nil , err
}
}
actions , err := s . mapPermission ( cmd . Permission )
if err != nil {
return nil , err
}
2022-08-18 15:43:45 +08:00
dbCommands = append ( dbCommands , SetResourcePermissionsCommand {
2022-02-08 00:04:32 +08:00
User : accesscontrol . User { ID : cmd . UserID } ,
TeamID : cmd . TeamID ,
BuiltinRole : cmd . BuiltinRole ,
2022-08-18 15:43:45 +08:00
SetResourcePermissionCommand : SetResourcePermissionCommand {
2022-03-22 00:58:18 +08:00
Actions : actions ,
Resource : s . options . Resource ,
ResourceID : resourceID ,
ResourceAttribute : s . options . ResourceAttribute ,
Permission : cmd . Permission ,
2022-02-08 00:04:32 +08:00
} ,
} )
}
2022-08-18 15:43:45 +08:00
return s . store . SetResourcePermissions ( ctx , orgID , dbCommands , ResourceHooks {
2022-02-08 00:04:32 +08:00
User : s . options . OnSetUser ,
Team : s . options . OnSetTeam ,
BuiltInRole : s . options . OnSetBuiltInRole ,
} )
}
2022-03-03 22:05:47 +08:00
func ( s * Service ) MapActions ( permission accesscontrol . ResourcePermission ) string {
2021-12-20 16:52:24 +08:00
for _ , p := range s . permissions {
if permission . Contains ( s . options . PermissionsToActions [ p ] ) {
return p
}
}
return ""
}
2023-07-10 17:13:05 +08:00
func ( s * Service ) DeleteResourcePermissions ( ctx context . Context , orgID int64 , resourceID string ) error {
return s . store . DeleteResourcePermissions ( ctx , orgID , & DeleteResourcePermissionsCmd {
Resource : s . options . Resource ,
ResourceAttribute : s . options . ResourceAttribute ,
ResourceID : resourceID ,
} )
}
2022-01-26 00:12:00 +08:00
func ( s * Service ) mapPermission ( permission string ) ( [ ] string , error ) {
if permission == "" {
return [ ] string { } , nil
}
2021-12-20 16:52:24 +08:00
for k , v := range s . options . PermissionsToActions {
if permission == k {
2022-01-26 00:12:00 +08:00
return v , nil
2021-12-20 16:52:24 +08:00
}
}
2024-04-17 21:53:28 +08:00
return nil , ErrInvalidPermission . Build ( ErrInvalidPermissionData ( permission ) )
2021-12-20 16:52:24 +08:00
}
func ( s * Service ) validateResource ( ctx context . Context , orgID int64 , resourceID string ) error {
2024-08-30 14:26:15 +08:00
ctx , span := tracer . Start ( ctx , "accesscontrol.resourcepermissions.validateResource" )
2024-08-17 06:08:19 +08:00
defer span . End ( )
2021-12-20 16:52:24 +08:00
if s . options . ResourceValidator != nil {
return s . options . ResourceValidator ( ctx , orgID , resourceID )
}
return nil
}
func ( s * Service ) validateUser ( ctx context . Context , orgID , userID int64 ) error {
2024-08-30 14:26:15 +08:00
ctx , span := tracer . Start ( ctx , "accesscontrol.resourcepermissions.validateUser" )
2024-08-17 06:08:19 +08:00
defer span . End ( )
2022-07-26 16:43:29 +08:00
if ! s . options . Assignments . Users {
2024-04-17 21:53:28 +08:00
return ErrInvalidAssignment . Build ( ErrInvalidAssignmentData ( "users" ) )
2022-02-08 00:04:32 +08:00
}
2022-09-27 19:58:49 +08:00
_ , err := s . userService . GetSignedInUser ( ctx , & user . GetSignedInUserQuery { OrgID : orgID , UserID : userID } )
2024-04-17 21:53:28 +08:00
switch {
case errors . Is ( err , user . ErrUserNotFound ) :
return accesscontrol . ErrAssignmentEntityNotFound . Build ( accesscontrol . ErrAssignmentEntityNotFoundData ( "user" ) )
default :
return err
}
2021-12-20 16:52:24 +08:00
}
func ( s * Service ) validateTeam ( ctx context . Context , orgID , teamID int64 ) error {
2024-08-30 14:26:15 +08:00
ctx , span := tracer . Start ( ctx , "accesscontrol.resourcepermissions.validateTeam" )
2024-08-17 06:08:19 +08:00
defer span . End ( )
2022-02-08 00:04:32 +08:00
if ! s . options . Assignments . Teams {
2024-04-17 21:53:28 +08:00
return ErrInvalidAssignment . Build ( ErrInvalidAssignmentData ( "teams" ) )
2022-02-08 00:04:32 +08:00
}
2023-01-11 21:20:09 +08:00
if _ , err := s . teamService . GetTeamByID ( ctx , & team . GetTeamByIDQuery { OrgID : orgID , ID : teamID } ) ; err != nil {
2024-04-17 21:53:28 +08:00
switch {
case errors . Is ( err , team . ErrTeamNotFound ) :
return accesscontrol . ErrAssignmentEntityNotFound . Build ( accesscontrol . ErrAssignmentEntityNotFoundData ( "team" ) )
default :
return err
}
2021-12-20 16:52:24 +08:00
}
return nil
}
2024-08-17 06:08:19 +08:00
func ( s * Service ) validateBuiltinRole ( ctx context . Context , builtinRole string ) error {
2024-08-30 14:26:15 +08:00
_ , span := tracer . Start ( ctx , "accesscontrol.resourcepermissions.validateBuiltinRole" )
2024-08-17 06:08:19 +08:00
defer span . End ( )
2022-02-08 00:04:32 +08:00
if ! s . options . Assignments . BuiltInRoles {
2024-04-17 21:53:28 +08:00
return ErrInvalidAssignment . Build ( ErrInvalidAssignmentData ( "builtInRoles" ) )
2022-02-08 00:04:32 +08:00
}
2021-12-20 16:52:24 +08:00
if err := accesscontrol . ValidateBuiltInRoles ( [ ] string { builtinRole } ) ; err != nil {
return err
}
return nil
}
func ( s * Service ) declareFixedRoles ( ) error {
scopeAll := accesscontrol . Scope ( s . options . Resource , "*" )
readerRole := accesscontrol . RoleRegistration {
Role : accesscontrol . RoleDTO {
Name : fmt . Sprintf ( "fixed:%s.permissions:reader" , s . options . Resource ) ,
DisplayName : s . options . ReaderRoleName ,
Group : s . options . RoleGroup ,
Permissions : [ ] accesscontrol . Permission {
{ Action : fmt . Sprintf ( "%s.permissions:read" , s . options . Resource ) , Scope : scopeAll } ,
} ,
} ,
2022-08-10 17:56:48 +08:00
Grants : [ ] string { string ( org . RoleAdmin ) } ,
2021-12-20 16:52:24 +08:00
}
writerRole := accesscontrol . RoleRegistration {
Role : accesscontrol . RoleDTO {
Name : fmt . Sprintf ( "fixed:%s.permissions:writer" , s . options . Resource ) ,
DisplayName : s . options . WriterRoleName ,
Group : s . options . RoleGroup ,
Permissions : accesscontrol . ConcatPermissions ( readerRole . Role . Permissions , [ ] accesscontrol . Permission {
{ Action : fmt . Sprintf ( "%s.permissions:write" , s . options . Resource ) , Scope : scopeAll } ,
} ) ,
} ,
2022-08-10 17:56:48 +08:00
Grants : [ ] string { string ( org . RoleAdmin ) } ,
2021-12-20 16:52:24 +08:00
}
2022-08-24 19:29:17 +08:00
return s . service . DeclareFixedRoles ( readerRole , writerRole )
2021-12-20 16:52:24 +08:00
}
2024-05-23 19:14:01 +08:00
type ActionSetService interface {
// ActionResolver defines method for expanding permissions from permissions with action sets to fine-grained permissions.
// We use an ActionResolver interface to avoid circular dependencies
accesscontrol . ActionResolver
// ResolveAction returns all the action sets that the action belongs to.
ResolveAction ( action string ) [ ] string
// ResolveActionSet resolves an action set to a list of corresponding actions.
ResolveActionSet ( actionSet string ) [ ] string
2024-08-12 17:07:33 +08:00
// 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.
2024-07-19 23:16:23 +08:00
StoreActionSet ( name string , actions [ ] string )
2024-07-22 16:31:36 +08:00
pluginaccesscontrol . ActionSetRegistry
2024-05-23 19:14:01 +08:00
}
// ActionSet is a struct that represents a set of actions that can be performed on a resource.
// An example of an action set is "folders:edit" which represents the set of RBAC actions that are granted by edit access to a folder.
type ActionSet struct {
Action string ` json:"action" `
Actions [ ] string ` json:"actions" `
}
2024-08-12 17:07:33 +08:00
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 {
2025-03-13 23:18:23 +08:00
store ActionSetStore
2024-05-23 19:14:01 +08:00
}
// NewActionSetService returns a new instance of InMemoryActionSetService.
2025-03-13 23:18:23 +08:00
func NewActionSetService ( ) ActionSetService {
2024-08-12 17:07:33 +08:00
return & ActionSetSvc {
2025-03-13 23:18:23 +08:00
store : NewInMemoryActionSetStore ( ) ,
2024-08-12 17:07:33 +08:00
}
}
// 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
2024-05-23 19:14:01 +08:00
}
2024-08-12 17:07:33 +08:00
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 {
2025-03-13 23:18:23 +08:00
_ , span := tracer . Start ( ctx , "accesscontrol.resourcepermissions.RegisterActionSets" )
2024-08-17 06:08:19 +08:00
defer span . End ( )
2024-08-12 17:07:33 +08:00
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 )
2024-05-23 19:14:01 +08:00
}