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:
Gabriel MABILLE
2022-11-30 15:38:49 +01:00
committed by GitHub
parent fee50be1bb
commit bf49c20050
14 changed files with 1003 additions and 22 deletions
+93 -2
View File
@@ -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
}