Access Control: Fix plugin async install role registration (#112123)

This commit is contained in:
Todd Treece
2025-10-10 09:44:02 -04:00
committed by GitHub
parent f4e7dfe827
commit 89da0bf178
3 changed files with 276 additions and 39 deletions
+64 -22
View File
@@ -6,6 +6,7 @@ import (
"slices"
"strconv"
"strings"
"sync"
"time"
"github.com/prometheus/client_golang/prometheus"
@@ -108,9 +109,11 @@ type Service struct {
features featuremgmt.FeatureToggles
log log.Logger
registrations accesscontrol.RegistrationList
rolesMu sync.RWMutex
roles map[string]*accesscontrol.RoleDTO
store accesscontrol.Store
permRegistry permreg.PermissionRegistry
isInitialized bool
}
func (s *Service) GetUsageStats(_ context.Context) map[string]any {
@@ -139,11 +142,13 @@ func (s *Service) getUserPermissions(ctx context.Context, user identity.Requeste
defer span.End()
permissions := make([]accesscontrol.Permission, 0)
s.rolesMu.RLock()
for _, builtin := range accesscontrol.GetOrgRoles(user) {
if basicRole, ok := s.roles[builtin]; ok {
permissions = append(permissions, basicRole.Permissions...)
}
}
s.rolesMu.RUnlock()
permissions = append(permissions, SharedWithMeFolderPermission)
// we don't care about the error here, if this fails we get 0 and no
@@ -170,9 +175,11 @@ func (s *Service) getBasicRolePermissions(ctx context.Context, role string, orgI
defer span.End()
var permissions []accesscontrol.Permission
s.rolesMu.RLock()
if basicRole, ok := s.roles[role]; ok {
permissions = basicRole.Permissions
}
s.rolesMu.RUnlock()
// Fetch managed role permissions assigned to basic roles
dbPermissions, err := s.store.GetBasicRolesPermissions(ctx, accesscontrol.GetUserPermissionsQuery{
@@ -423,33 +430,43 @@ func (s *Service) RegisterFixedRoles(ctx context.Context) error {
_, span := tracer.Start(ctx, "accesscontrol.acimpl.RegisterFixedRoles")
defer span.End()
s.registrations.Range(func(registration accesscontrol.RoleRegistration) bool {
for br := range accesscontrol.BuiltInRolesWithParents(registration.Grants) {
if basicRole, ok := s.roles[br]; ok {
for _, p := range registration.Role.Permissions {
if registration.Role.IsPlugin() && p.Action == pluginaccesscontrol.ActionAppAccess {
s.log.Debug("Plugin is attempting to grant access permission, but this permission is already granted by default and will be ignored",
"role", registration.Role.Name, "permission", p.Action, "scope", p.Scope)
continue
}
perm := accesscontrol.Permission{
Action: p.Action,
Scope: p.Scope,
}
s.rolesMu.Lock()
defer s.rolesMu.Unlock()
perm.Kind, perm.Attribute, perm.Identifier = accesscontrol.SplitScope(perm.Scope)
basicRole.Permissions = append(basicRole.Permissions, perm)
}
} else {
s.log.Error("Unknown builtin role", "builtInRole", br)
}
}
s.registrations.Range(func(registration accesscontrol.RoleRegistration) bool {
s.registerRolesLocked(registration)
return true
})
s.isInitialized = true
return nil
}
// registerRolesLocked processes a single role registration and adds permissions to basic roles.
// Must be called with s.rolesMu locked.
func (s *Service) registerRolesLocked(registration accesscontrol.RoleRegistration) {
for br := range accesscontrol.BuiltInRolesWithParents(registration.Grants) {
if basicRole, ok := s.roles[br]; ok {
for _, p := range registration.Role.Permissions {
if registration.Role.IsPlugin() && p.Action == pluginaccesscontrol.ActionAppAccess {
s.log.Debug("Plugin is attempting to grant access permission, but this permission is already granted by default and will be ignored",
"role", registration.Role.Name, "permission", p.Action, "scope", p.Scope)
continue
}
perm := accesscontrol.Permission{
Action: p.Action,
Scope: p.Scope,
}
perm.Kind, perm.Attribute, perm.Identifier = accesscontrol.SplitScope(perm.Scope)
basicRole.Permissions = append(basicRole.Permissions, perm)
}
} else {
s.log.Error("Unknown builtin role", "builtInRole", br)
}
}
}
// DeclarePluginRoles allow the caller to declare, to the service, plugin roles and their assignments
// to organization roles ("Viewer", "Editor", "Admin") or "Grafana Admin"
func (s *Service) DeclarePluginRoles(ctx context.Context, ID, name string, regs []plugins.RoleRegistration) error {
@@ -476,6 +493,16 @@ func (s *Service) DeclarePluginRoles(ctx context.Context, ID, name string, regs
s.log.Debug("Registering plugin role", "role", r.Role.Name)
s.registrations.Append(r)
s.rolesMu.RLock()
initialized := s.isInitialized
s.rolesMu.RUnlock()
if initialized {
s.rolesMu.Lock()
s.registerRolesLocked(r)
s.rolesMu.Unlock()
s.cache.Flush()
}
}
return nil
@@ -513,6 +540,7 @@ func (s *Service) SearchUsersPermissions(ctx context.Context, usr identity.Reque
// Filter ram permissions
basicPermissions := map[string][]accesscontrol.Permission{}
s.rolesMu.RLock()
for role, basicRole := range s.roles {
for i := range basicRole.Permissions {
if PermissionMatchesSearchOptions(basicRole.Permissions[i], &options) {
@@ -520,6 +548,7 @@ func (s *Service) SearchUsersPermissions(ctx context.Context, usr identity.Reque
}
}
}
s.rolesMu.RUnlock()
usersRoles, err := s.store.GetUsersBasicRoles(ctx, nil, usr.GetOrgID())
if err != nil {
@@ -630,6 +659,7 @@ func (s *Service) searchUserPermissions(ctx context.Context, orgID int64, search
return nil, fmt.Errorf("found no basic roles for user %d in organisation %d", searchOptions.UserID, orgID)
}
permissions := make([]accesscontrol.Permission, 0)
s.rolesMu.RLock()
for _, builtin := range roles {
if basicRole, ok := s.roles[builtin]; ok {
for _, permission := range basicRole.Permissions {
@@ -639,6 +669,7 @@ func (s *Service) searchUserPermissions(ctx context.Context, orgID int64, search
}
}
}
s.rolesMu.RUnlock()
searchOptions.ActionSets = s.actionResolver.ResolveAction(searchOptions.Action)
searchOptions.ActionSets = append(searchOptions.ActionSets,
@@ -741,7 +772,15 @@ func (s *Service) SyncUserRoles(ctx context.Context, orgID int64, cmd accesscont
}
func (s *Service) GetStaticRoles(ctx context.Context) map[string]*accesscontrol.RoleDTO {
return s.roles
s.rolesMu.RLock()
defer s.rolesMu.RUnlock()
// Return a copy to avoid external modifications
rolesCopy := make(map[string]*accesscontrol.RoleDTO, len(s.roles))
for k, v := range s.roles {
rolesCopy[k] = v
}
return rolesCopy
}
func (s *Service) GetRoleByName(ctx context.Context, orgID int64, roleName string) (*accesscontrol.RoleDTO, error) {
@@ -749,7 +788,10 @@ func (s *Service) GetRoleByName(ctx context.Context, orgID int64, roleName strin
defer span.End()
err := accesscontrol.ErrRoleNotFound
if _, ok := s.roles[roleName]; ok {
s.rolesMu.RLock()
_, isBasicRole := s.roles[roleName]
s.rolesMu.RUnlock()
if isBasicRole {
return nil, err
}