RBAC: Add an endpoint to list all user permissions (#57644)
* RBAC: Add an endpoint to see all user permissions Co-authored-by: Joey Orlando <joey.orlando@grafana.com> * Fix mock * Add feature flag * Fix merging * Return normal permissions instead of simplified ones * Fix test * Fix tests * Fix tests * Create benchtests * Split function to get basic roles * Comments * Reorg * Add two more tests to the bench * bench comment * Re-ran the test * Rename GetUsersPermissions to SearchUsersPermissions and prepare search options * Remove from model unused struct * Start adding option to get permissions by Action+Scope * Wrong import * Action and Scope * slightly tweak users permissions actionPrefix query param validation logic * Fix xor check * Lint * Account for suggeston Co-authored-by: ievaVasiljeva <ieva.vasiljeva@grafana.com> * Add search * Remove comment on global scope * use union all and update test to make it run on all dbs * Fix MySQL needs a space * Account for suggestion. Co-authored-by: ievaVasiljeva <ieva.vasiljeva@grafana.com> Co-authored-by: Joey Orlando <joey.orlando@grafana.com> Co-authored-by: Joey Orlando <joseph.t.orlando@gmail.com> Co-authored-by: ievaVasiljeva <ieva.vasiljeva@grafana.com>
This commit is contained in:
@@ -3,6 +3,8 @@ package acimpl
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
@@ -30,11 +32,11 @@ const (
|
||||
)
|
||||
|
||||
func ProvideService(cfg *setting.Cfg, store db.DB, routeRegister routing.RouteRegister, cache *localcache.CacheService,
|
||||
features *featuremgmt.FeatureManager) (*Service, error) {
|
||||
accessControl accesscontrol.AccessControl, features *featuremgmt.FeatureManager) (*Service, error) {
|
||||
service := ProvideOSSService(cfg, database.ProvideService(store), cache, features)
|
||||
|
||||
if !accesscontrol.IsDisabled(cfg) {
|
||||
api.NewAccessControlAPI(routeRegister, service).RegisterAPIEndpoints()
|
||||
api.NewAccessControlAPI(routeRegister, accessControl, service, features).RegisterAPIEndpoints()
|
||||
if err := accesscontrol.DeclareFixedRoles(service); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -58,6 +60,8 @@ func ProvideOSSService(cfg *setting.Cfg, store store, cache *localcache.CacheSer
|
||||
|
||||
type store interface {
|
||||
GetUserPermissions(ctx context.Context, query accesscontrol.GetUserPermissionsQuery) ([]accesscontrol.Permission, error)
|
||||
SearchUsersPermissions(ctx context.Context, orgID int64, option accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error)
|
||||
GetUsersBasicRoles(ctx context.Context, orgID int64) (map[int64][]string, error)
|
||||
DeleteUserPermissions(ctx context.Context, orgID, userID int64) error
|
||||
}
|
||||
|
||||
@@ -244,3 +248,90 @@ func (s *Service) DeclarePluginRoles(_ context.Context, ID, name string, regs []
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SearchUsersPermissions returns all users' permissions filtered by action prefixes
|
||||
func (s *Service) SearchUsersPermissions(ctx context.Context, user *user.SignedInUser, orgID int64,
|
||||
options accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error) {
|
||||
// Filter ram permissions
|
||||
basicPermissions := map[string][]accesscontrol.Permission{}
|
||||
for role, basicRole := range s.roles {
|
||||
for i := range basicRole.Permissions {
|
||||
if options.ActionPrefix != "" {
|
||||
if strings.HasPrefix(basicRole.Permissions[i].Action, options.ActionPrefix) {
|
||||
basicPermissions[role] = append(basicPermissions[role], basicRole.Permissions[i])
|
||||
}
|
||||
}
|
||||
if options.Action != "" {
|
||||
if basicRole.Permissions[i].Action == options.Action {
|
||||
basicPermissions[role] = append(basicPermissions[role], basicRole.Permissions[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
usersRoles, err := s.store.GetUsersBasicRoles(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get managed permissions (DB)
|
||||
usersPermissions, err := s.store.SearchUsersPermissions(ctx, orgID, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// helper to filter out permissions the signed in users cannot see
|
||||
canView := func() func(userID int64) bool {
|
||||
siuPermissions, ok := user.Permissions[orgID]
|
||||
if !ok {
|
||||
return func(_ int64) bool { return false }
|
||||
}
|
||||
scopes, ok := siuPermissions[accesscontrol.ActionUsersPermissionsRead]
|
||||
if !ok {
|
||||
return func(_ int64) bool { return false }
|
||||
}
|
||||
|
||||
ids := map[int64]bool{}
|
||||
for i := range scopes {
|
||||
if strings.HasSuffix(scopes[i], "*") {
|
||||
return func(_ int64) bool { return true }
|
||||
}
|
||||
parts := strings.Split(scopes[i], ":")
|
||||
if len(parts) != 3 {
|
||||
continue
|
||||
}
|
||||
id, err := strconv.ParseInt(parts[2], 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ids[id] = true
|
||||
}
|
||||
|
||||
return func(userID int64) bool { return ids[userID] }
|
||||
}()
|
||||
|
||||
// Merge stored (DB) and basic role permissions (RAM)
|
||||
// Assumes that all users with stored permissions have org roles
|
||||
res := map[int64][]accesscontrol.Permission{}
|
||||
for userID, roles := range usersRoles {
|
||||
if !canView(userID) {
|
||||
continue
|
||||
}
|
||||
perms := []accesscontrol.Permission{}
|
||||
for i := range roles {
|
||||
basicPermission, ok := basicPermissions[roles[i]]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
perms = append(perms, basicPermission...)
|
||||
}
|
||||
if dbPerms, ok := usersPermissions[userID]; ok {
|
||||
perms = append(perms, dbPerms...)
|
||||
}
|
||||
if len(perms) > 0 {
|
||||
res[userID] = perms
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user