RBAC: Search endpoint support wildcards (#80383)

* RBAC: Search endpoint support wildcards

* Allow wildcard filter with RAM permissions as well
pull/79570/head^2
Gabriel MABILLE 2 years ago committed by GitHub
parent c27bee567f
commit dce9d1e87c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 18
      pkg/services/accesscontrol/accesscontrol.go
  2. 17
      pkg/services/accesscontrol/acimpl/service.go
  3. 17
      pkg/services/accesscontrol/acimpl/service_test.go
  4. 8
      pkg/services/accesscontrol/database/database.go
  5. 14
      pkg/services/accesscontrol/database/database_test.go

@ -57,7 +57,23 @@ type SearchOptions struct {
ActionPrefix string // Needed for the PoC v1, it's probably going to be removed. ActionPrefix string // Needed for the PoC v1, it's probably going to be removed.
Action string Action string
Scope string Scope string
UserID int64 // ID for the user for which to return information, if none is specified information is returned for all users. UserID int64 // ID for the user for which to return information, if none is specified information is returned for all users.
wildcards Wildcards // private field computed based on the Scope
}
// Wildcards computes the wildcard scopes that include the scope
func (s *SearchOptions) Wildcards() []string {
if s.wildcards != nil {
return s.wildcards
}
if s.Scope == "" {
s.wildcards = []string{}
return s.wildcards
}
s.wildcards = WildcardsFromPrefix(ScopePrefix(s.Scope))
return s.wildcards
} }
type SyncUserRolesCommand struct { type SyncUserRolesCommand struct {

@ -3,6 +3,7 @@ package acimpl
import ( import (
"context" "context"
"fmt" "fmt"
"slices"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -249,7 +250,7 @@ func (s *Service) SearchUsersPermissions(ctx context.Context, user identity.Requ
basicPermissions := map[string][]accesscontrol.Permission{} basicPermissions := map[string][]accesscontrol.Permission{}
for role, basicRole := range s.roles { for role, basicRole := range s.roles {
for i := range basicRole.Permissions { for i := range basicRole.Permissions {
if PermissionMatchesSearchOptions(basicRole.Permissions[i], options) { if PermissionMatchesSearchOptions(basicRole.Permissions[i], &options) {
basicPermissions[role] = append(basicPermissions[role], basicRole.Permissions[i]) basicPermissions[role] = append(basicPermissions[role], basicRole.Permissions[i])
} }
} }
@ -351,7 +352,7 @@ func (s *Service) searchUserPermissions(ctx context.Context, orgID int64, search
for _, builtin := range roles { for _, builtin := range roles {
if basicRole, ok := s.roles[builtin]; ok { if basicRole, ok := s.roles[builtin]; ok {
for _, permission := range basicRole.Permissions { for _, permission := range basicRole.Permissions {
if PermissionMatchesSearchOptions(permission, searchOptions) { if PermissionMatchesSearchOptions(permission, &searchOptions) {
permissions = append(permissions, permission) permissions = append(permissions, permission)
} }
} }
@ -384,7 +385,7 @@ func (s *Service) searchUserPermissionsFromCache(orgID int64, searchOptions acce
s.log.Debug("Using cached permissions", "key", key) s.log.Debug("Using cached permissions", "key", key)
filteredPermissions := make([]accesscontrol.Permission, 0) filteredPermissions := make([]accesscontrol.Permission, 0)
for _, permission := range permissions.([]accesscontrol.Permission) { for _, permission := range permissions.([]accesscontrol.Permission) {
if PermissionMatchesSearchOptions(permission, searchOptions) { if PermissionMatchesSearchOptions(permission, &searchOptions) {
filteredPermissions = append(filteredPermissions, permission) filteredPermissions = append(filteredPermissions, permission)
} }
} }
@ -392,9 +393,13 @@ func (s *Service) searchUserPermissionsFromCache(orgID int64, searchOptions acce
return filteredPermissions, true return filteredPermissions, true
} }
func PermissionMatchesSearchOptions(permission accesscontrol.Permission, searchOptions accesscontrol.SearchOptions) bool { func PermissionMatchesSearchOptions(permission accesscontrol.Permission, searchOptions *accesscontrol.SearchOptions) bool {
if searchOptions.Scope != "" && permission.Scope != searchOptions.Scope { if searchOptions.Scope != "" {
return false // Permissions including the scope should also match
scopes := append(searchOptions.Wildcards(), searchOptions.Scope)
if !slices.Contains[[]string, string](scopes, permission.Scope) {
return false
}
} }
if searchOptions.Action != "" { if searchOptions.Action != "" {
return permission.Action == searchOptions.Action return permission.Action == searchOptions.Action

@ -448,6 +448,23 @@ func TestService_SearchUsersPermissions(t *testing.T) {
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"}}, {Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:*"}},
}, },
}, },
{
name: "ram only search on scope",
siuPermissions: listAllPerms,
searchOption: accesscontrol.SearchOptions{Scope: "teams:id:2"},
ramRoles: map[string]*accesscontrol.RoleDTO{
string(roletype.RoleAdmin): {Permissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"},
}},
},
storedRoles: map[int64][]string{
1: {string(roletype.RoleEditor)},
2: {string(roletype.RoleAdmin), accesscontrol.RoleGrafanaAdmin},
},
want: map[int64][]accesscontrol.Permission{
2: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"}},
},
},
{ {
name: "view permission on subset of users only", name: "view permission on subset of users only",
siuPermissions: listSomePerms, siuPermissions: listSomePerms,

@ -106,8 +106,12 @@ func (s *AccessControlStore) SearchUsersPermissions(ctx context.Context, orgID i
params = append(params, options.Action) params = append(params, options.Action)
} }
if options.Scope != "" { if options.Scope != "" {
q += ` AND scope = ?` // Search for scope and wildcard that include the scope
params = append(params, options.Scope) scopes := append(options.Wildcards(), options.Scope)
q += ` AND scope IN ( ? ` + strings.Repeat(", ?", len(scopes)-1) + ")"
for i := range scopes {
params = append(params, scopes[i])
}
} }
if options.UserID != 0 { if options.UserID != 0 {

@ -526,6 +526,20 @@ func TestIntegrationAccessControlStore_SearchUsersPermissions(t *testing.T) {
{Action: "teams:write", Scope: "teams:id:1"}, {Action: "teams:write", Scope: "teams:id:1"},
}}, }},
}, },
{
name: "user assignment by scope",
users: []testUser{{orgRole: org.RoleAdmin, isAdmin: false}},
permCmds: []rs.SetResourcePermissionsCommand{
{User: accesscontrol.User{ID: 1, IsExternal: false}, SetResourcePermissionCommand: readTeamPerm("*")}, // hack to have a global permission
{User: accesscontrol.User{ID: 1, IsExternal: false}, SetResourcePermissionCommand: writeTeamPerm("1")},
},
options: accesscontrol.SearchOptions{Scope: "teams:id:1"},
wantPerm: map[int64][]accesscontrol.Permission{1: {
{Action: "teams:read", Scope: "teams:id:*"},
{Action: "teams:read", Scope: "teams:id:1"},
{Action: "teams:write", Scope: "teams:id:1"},
}},
},
{ {
name: "user assignment by action and scope", name: "user assignment by action and scope",
users: []testUser{{orgRole: org.RoleAdmin, isAdmin: false}}, users: []testUser{{orgRole: org.RoleAdmin, isAdmin: false}},

Loading…
Cancel
Save