Auth: Add SignedIn user interface NamespacedID (#72944)

* wip

* scope active user to 1 org

* remove TODOs

* add render auth namespace

* import cycle fix

* make condition more readable

* convert Evaluate to user Requester

* only use active OrgID for SearchUserPermissions

* add cache key to interface definition

* change final SignedInUsers to interface

* fix api key managed roles fetch

* fix anon auth id parsing

* Update pkg/services/accesscontrol/acimpl/accesscontrol.go

Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>

---------

Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
This commit is contained in:
Jo 2023-08-09 09:35:50 +02:00 committed by GitHub
parent 144e4887ee
commit bd1a856d33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 128 additions and 55 deletions

View File

@ -6,6 +6,7 @@ import (
"strings" "strings"
"github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/auth/identity"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
@ -14,7 +15,7 @@ import (
type AccessControl interface { type AccessControl interface {
// Evaluate evaluates access to the given resources. // Evaluate evaluates access to the given resources.
Evaluate(ctx context.Context, user *user.SignedInUser, evaluator Evaluator) (bool, error) Evaluate(ctx context.Context, user identity.Requester, evaluator Evaluator) (bool, error)
// RegisterScopeAttributeResolver allows the caller to register a scope resolver for a // RegisterScopeAttributeResolver allows the caller to register a scope resolver for a
// specific scope prefix (ex: datasources:name:) // specific scope prefix (ex: datasources:name:)
RegisterScopeAttributeResolver(prefix string, resolver ScopeAttributeResolver) RegisterScopeAttributeResolver(prefix string, resolver ScopeAttributeResolver)
@ -25,11 +26,11 @@ type AccessControl interface {
type Service interface { type Service interface {
registry.ProvidesUsageStats registry.ProvidesUsageStats
// GetUserPermissions returns user permissions with only action and scope fields set. // GetUserPermissions returns user permissions with only action and scope fields set.
GetUserPermissions(ctx context.Context, user *user.SignedInUser, options Options) ([]Permission, error) GetUserPermissions(ctx context.Context, user identity.Requester, options Options) ([]Permission, error)
// SearchUsersPermissions returns all users' permissions filtered by an action prefix // SearchUsersPermissions returns all users' permissions filtered by an action prefix
SearchUsersPermissions(ctx context.Context, user *user.SignedInUser, orgID int64, options SearchOptions) (map[int64][]Permission, error) SearchUsersPermissions(ctx context.Context, user identity.Requester, options SearchOptions) (map[int64][]Permission, error)
// ClearUserPermissionCache removes the permission cache entry for the given user // ClearUserPermissionCache removes the permission cache entry for the given user
ClearUserPermissionCache(user *user.SignedInUser) ClearUserPermissionCache(user identity.Requester)
// SearchUserPermissions returns single user's permissions filtered by an action prefix or an action // SearchUserPermissions returns single user's permissions filtered by an action prefix or an action
SearchUserPermissions(ctx context.Context, orgID int64, filterOptions SearchOptions) ([]Permission, error) SearchUserPermissions(ctx context.Context, orgID int64, filterOptions SearchOptions) ([]Permission, error)
// DeleteUserPermissions removes all permissions user has in org and all permission to that user // DeleteUserPermissions removes all permissions user has in org and all permission to that user
@ -375,10 +376,10 @@ func IsDisabled(cfg *setting.Cfg) bool {
} }
// GetOrgRoles returns legacy org roles for a user // GetOrgRoles returns legacy org roles for a user
func GetOrgRoles(user *user.SignedInUser) []string { func GetOrgRoles(user identity.Requester) []string {
roles := []string{string(user.OrgRole)} roles := []string{string(user.GetOrgRole())}
if user.IsGrafanaAdmin { if user.GetIsGrafanaAdmin() {
roles = append(roles, RoleGrafanaAdmin) roles = append(roles, RoleGrafanaAdmin)
} }

View File

@ -9,7 +9,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
@ -28,21 +28,28 @@ type AccessControl struct {
resolvers accesscontrol.Resolvers resolvers accesscontrol.Resolvers
} }
func (a *AccessControl) Evaluate(ctx context.Context, user *user.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) { func (a *AccessControl) Evaluate(ctx context.Context, user identity.Requester, evaluator accesscontrol.Evaluator) (bool, error) {
timer := prometheus.NewTimer(metrics.MAccessEvaluationsSummary) timer := prometheus.NewTimer(metrics.MAccessEvaluationsSummary)
defer timer.ObserveDuration() defer timer.ObserveDuration()
metrics.MAccessEvaluationCount.Inc() metrics.MAccessEvaluationCount.Inc()
if !verifyPermissions(user) { if user == nil || user.IsNil() {
a.log.Warn("no permissions set for user", "userID", user.UserID, "orgID", user.OrgID, "login", user.Login) a.log.Warn("no entity set for access control evaluation")
return false, nil return false, nil
} }
namespace, identifier := user.GetNamespacedID()
if len(user.GetPermissions()) == 0 {
a.log.Warn("no permissions set for entity", "namespace", namespace, "id", identifier, "orgID", user.GetOrgID(), "login", user.GetLogin())
return false, nil
}
// Test evaluation without scope resolver first, this will prevent 403 for wildcard scopes when resource does not exist // Test evaluation without scope resolver first, this will prevent 403 for wildcard scopes when resource does not exist
if evaluator.Evaluate(user.Permissions[user.OrgID]) { if evaluator.Evaluate(user.GetPermissions()) {
return true, nil return true, nil
} }
resolvedEvaluator, err := evaluator.MutateScopes(ctx, a.resolvers.GetScopeAttributeMutator(user.OrgID)) resolvedEvaluator, err := evaluator.MutateScopes(ctx, a.resolvers.GetScopeAttributeMutator(user.GetOrgID()))
if err != nil { if err != nil {
if errors.Is(err, accesscontrol.ErrResolverNotFound) { if errors.Is(err, accesscontrol.ErrResolverNotFound) {
return false, nil return false, nil
@ -50,7 +57,7 @@ func (a *AccessControl) Evaluate(ctx context.Context, user *user.SignedInUser, e
return false, err return false, err
} }
return resolvedEvaluator.Evaluate(user.Permissions[user.OrgID]), nil return resolvedEvaluator.Evaluate(user.GetPermissions()), nil
} }
func (a *AccessControl) RegisterScopeAttributeResolver(prefix string, resolver accesscontrol.ScopeAttributeResolver) { func (a *AccessControl) RegisterScopeAttributeResolver(prefix string, resolver accesscontrol.ScopeAttributeResolver) {
@ -60,7 +67,3 @@ func (a *AccessControl) RegisterScopeAttributeResolver(prefix string, resolver a
func (a *AccessControl) IsDisabled() bool { func (a *AccessControl) IsDisabled() bool {
return accesscontrol.IsDisabled(a.cfg) return accesscontrol.IsDisabled(a.cfg)
} }
func verifyPermissions(u *user.SignedInUser) bool {
return u.Permissions != nil || u.Permissions[u.OrgID] != nil
}

View File

@ -21,6 +21,8 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol/database" "github.com/grafana/grafana/pkg/services/accesscontrol/database"
"github.com/grafana/grafana/pkg/services/accesscontrol/migrator" "github.com/grafana/grafana/pkg/services/accesscontrol/migrator"
"github.com/grafana/grafana/pkg/services/accesscontrol/pluginutils" "github.com/grafana/grafana/pkg/services/accesscontrol/pluginutils"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
@ -95,7 +97,7 @@ func (s *Service) GetUsageStats(_ context.Context) map[string]interface{} {
} }
// GetUserPermissions returns user permissions based on built-in roles // GetUserPermissions returns user permissions based on built-in roles
func (s *Service) GetUserPermissions(ctx context.Context, user *user.SignedInUser, options accesscontrol.Options) ([]accesscontrol.Permission, error) { func (s *Service) GetUserPermissions(ctx context.Context, user identity.Requester, options accesscontrol.Options) ([]accesscontrol.Permission, error) {
timer := prometheus.NewTimer(metrics.MAccessPermissionsSummary) timer := prometheus.NewTimer(metrics.MAccessPermissionsSummary)
defer timer.ObserveDuration() defer timer.ObserveDuration()
@ -106,7 +108,7 @@ func (s *Service) GetUserPermissions(ctx context.Context, user *user.SignedInUse
return s.getCachedUserPermissions(ctx, user, options) return s.getCachedUserPermissions(ctx, user, options)
} }
func (s *Service) getUserPermissions(ctx context.Context, user *user.SignedInUser, options accesscontrol.Options) ([]accesscontrol.Permission, error) { func (s *Service) getUserPermissions(ctx context.Context, user identity.Requester, options accesscontrol.Options) ([]accesscontrol.Permission, error) {
permissions := make([]accesscontrol.Permission, 0) permissions := make([]accesscontrol.Permission, 0)
for _, builtin := range accesscontrol.GetOrgRoles(user) { for _, builtin := range accesscontrol.GetOrgRoles(user) {
if basicRole, ok := s.roles[builtin]; ok { if basicRole, ok := s.roles[builtin]; ok {
@ -114,11 +116,23 @@ func (s *Service) getUserPermissions(ctx context.Context, user *user.SignedInUse
} }
} }
namespace, identifier := user.GetNamespacedID()
var userID int64
switch namespace {
case authn.NamespaceUser, authn.NamespaceServiceAccount, identity.NamespaceRenderService:
var err error
userID, err = strconv.ParseInt(identifier, 10, 64)
if err != nil {
return nil, err
}
}
dbPermissions, err := s.store.GetUserPermissions(ctx, accesscontrol.GetUserPermissionsQuery{ dbPermissions, err := s.store.GetUserPermissions(ctx, accesscontrol.GetUserPermissionsQuery{
OrgID: user.OrgID, OrgID: user.GetOrgID(),
UserID: user.UserID, UserID: userID,
Roles: accesscontrol.GetOrgRoles(user), Roles: accesscontrol.GetOrgRoles(user),
TeamIDs: user.Teams, TeamIDs: user.GetTeams(),
RolePrefixes: []string{accesscontrol.ManagedRolePrefix, accesscontrol.ExternalServiceRolePrefix}, RolePrefixes: []string{accesscontrol.ManagedRolePrefix, accesscontrol.ExternalServiceRolePrefix},
}) })
if err != nil { if err != nil {
@ -128,7 +142,7 @@ func (s *Service) getUserPermissions(ctx context.Context, user *user.SignedInUse
return append(permissions, dbPermissions...), nil return append(permissions, dbPermissions...), nil
} }
func (s *Service) getCachedUserPermissions(ctx context.Context, user *user.SignedInUser, options accesscontrol.Options) ([]accesscontrol.Permission, error) { func (s *Service) getCachedUserPermissions(ctx context.Context, user identity.Requester, options accesscontrol.Options) ([]accesscontrol.Permission, error) {
key, err := permissionCacheKey(user) key, err := permissionCacheKey(user)
if err != nil { if err != nil {
return nil, err return nil, err
@ -154,7 +168,7 @@ func (s *Service) getCachedUserPermissions(ctx context.Context, user *user.Signe
return permissions, nil return permissions, nil
} }
func (s *Service) ClearUserPermissionCache(user *user.SignedInUser) { func (s *Service) ClearUserPermissionCache(user identity.Requester) {
key, err := permissionCacheKey(user) key, err := permissionCacheKey(user)
if err != nil { if err != nil {
return return
@ -209,7 +223,7 @@ func (s *Service) IsDisabled() bool {
return accesscontrol.IsDisabled(s.cfg) return accesscontrol.IsDisabled(s.cfg)
} }
func permissionCacheKey(user *user.SignedInUser) (string, error) { func permissionCacheKey(user identity.Requester) (string, error) {
key, err := user.GetCacheKey() key, err := user.GetCacheKey()
if err != nil { if err != nil {
return "", err return "", err
@ -248,7 +262,7 @@ func (s *Service) DeclarePluginRoles(_ context.Context, ID, name string, regs []
} }
// SearchUsersPermissions returns all users' permissions filtered by action prefixes // SearchUsersPermissions returns all users' permissions filtered by action prefixes
func (s *Service) SearchUsersPermissions(ctx context.Context, user *user.SignedInUser, orgID int64, func (s *Service) SearchUsersPermissions(ctx context.Context, user identity.Requester,
options accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error) { options accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error) {
// Filter ram permissions // Filter ram permissions
basicPermissions := map[string][]accesscontrol.Permission{} basicPermissions := map[string][]accesscontrol.Permission{}
@ -260,21 +274,21 @@ func (s *Service) SearchUsersPermissions(ctx context.Context, user *user.SignedI
} }
} }
usersRoles, err := s.store.GetUsersBasicRoles(ctx, nil, orgID) usersRoles, err := s.store.GetUsersBasicRoles(ctx, nil, user.GetOrgID())
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Get managed permissions (DB) // Get managed permissions (DB)
usersPermissions, err := s.store.SearchUsersPermissions(ctx, orgID, options) usersPermissions, err := s.store.SearchUsersPermissions(ctx, user.GetOrgID(), options)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// helper to filter out permissions the signed in users cannot see // helper to filter out permissions the signed in users cannot see
canView := func() func(userID int64) bool { canView := func() func(userID int64) bool {
siuPermissions, ok := user.Permissions[orgID] siuPermissions := user.GetPermissions()
if !ok { if len(siuPermissions) == 0 {
return func(_ int64) bool { return false } return func(_ int64) bool { return false }
} }
scopes, ok := siuPermissions[accesscontrol.ActionUsersPermissionsRead] scopes, ok := siuPermissions[accesscontrol.ActionUsersPermissionsRead]

View File

@ -141,7 +141,7 @@ func benchSearchUsersPermissions(b *testing.B, usersCount, resourceCount int) {
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
usersPermissions, err := acService.SearchUsersPermissions(context.Background(), siu, 1, accesscontrol.SearchOptions{ActionPrefix: "resources:"}) usersPermissions, err := acService.SearchUsersPermissions(context.Background(), siu, accesscontrol.SearchOptions{ActionPrefix: "resources:"})
require.NoError(b, err) require.NoError(b, err)
require.Len(b, usersPermissions, usersCount) require.Len(b, usersPermissions, usersCount)
for _, permissions := range usersPermissions { for _, permissions := range usersPermissions {
@ -206,7 +206,7 @@ func benchSearchUsersWithPerm(b *testing.B, usersCount, resourceCount int) {
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
usersPermissions, err := acService.SearchUsersPermissions(context.Background(), siu, 1, usersPermissions, err := acService.SearchUsersPermissions(context.Background(), siu,
accesscontrol.SearchOptions{Action: "resources:action2", Scope: "resources:id:1"}) accesscontrol.SearchOptions{Action: "resources:action2", Scope: "resources:id:1"})
require.NoError(b, err) require.NoError(b, err)
require.Len(b, usersPermissions, usersCount) require.Len(b, usersPermissions, usersCount)

View File

@ -523,7 +523,7 @@ func TestService_SearchUsersPermissions(t *testing.T) {
} }
siu := &user.SignedInUser{OrgID: 2, Permissions: map[int64]map[string][]string{2: tt.siuPermissions}} siu := &user.SignedInUser{OrgID: 2, Permissions: map[int64]map[string][]string{2: tt.siuPermissions}}
got, err := ac.SearchUsersPermissions(ctx, siu, 2, tt.searchOption) got, err := ac.SearchUsersPermissions(ctx, siu, tt.searchOption)
if tt.wantErr { if tt.wantErr {
require.NotNil(t, err) require.NotNil(t, err)
return return

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
) )
@ -23,11 +24,11 @@ func (f FakeService) GetUsageStats(ctx context.Context) map[string]interface{} {
return map[string]interface{}{} return map[string]interface{}{}
} }
func (f FakeService) GetUserPermissions(ctx context.Context, user *user.SignedInUser, options accesscontrol.Options) ([]accesscontrol.Permission, error) { func (f FakeService) GetUserPermissions(ctx context.Context, user identity.Requester, options accesscontrol.Options) ([]accesscontrol.Permission, error) {
return f.ExpectedPermissions, f.ExpectedErr return f.ExpectedPermissions, f.ExpectedErr
} }
func (f FakeService) SearchUsersPermissions(ctx context.Context, user *user.SignedInUser, orgID int64, options accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error) { func (f FakeService) SearchUsersPermissions(ctx context.Context, user identity.Requester, options accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error) {
return f.ExpectedUsersPermissions, f.ExpectedErr return f.ExpectedUsersPermissions, f.ExpectedErr
} }
@ -35,7 +36,7 @@ func (f FakeService) SearchUserPermissions(ctx context.Context, orgID int64, sea
return f.ExpectedFilteredUserPermissions, f.ExpectedErr return f.ExpectedFilteredUserPermissions, f.ExpectedErr
} }
func (f FakeService) ClearUserPermissionCache(user *user.SignedInUser) {} func (f FakeService) ClearUserPermissionCache(user identity.Requester) {}
func (f FakeService) DeleteUserPermissions(ctx context.Context, orgID, userID int64) error { func (f FakeService) DeleteUserPermissions(ctx context.Context, orgID, userID int64) error {
return f.ExpectedErr return f.ExpectedErr
@ -69,7 +70,7 @@ type FakeAccessControl struct {
ExpectedEvaluate bool ExpectedEvaluate bool
} }
func (f FakeAccessControl) Evaluate(ctx context.Context, user *user.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) { func (f FakeAccessControl) Evaluate(ctx context.Context, user identity.Requester, evaluator accesscontrol.Evaluator) (bool, error) {
return f.ExpectedEvaluate, f.ExpectedErr return f.ExpectedEvaluate, f.ExpectedErr
} }

View File

@ -82,7 +82,7 @@ func (api *AccessControlAPI) searchUsersPermissions(c *contextmodel.ReqContext)
} }
// Compute metadata // Compute metadata
permissions, err := api.Service.SearchUsersPermissions(c.Req.Context(), c.SignedInUser, c.OrgID, searchOptions) permissions, err := api.Service.SearchUsersPermissions(c.Req.Context(), c.SignedInUser, searchOptions)
if err != nil { if err != nil {
return response.Error(http.StatusInternalServerError, "could not get org user permissions", err) return response.Error(http.StatusInternalServerError, "could not get org user permissions", err)
} }

View File

@ -45,7 +45,7 @@ func Filter(user identity.Requester, sqlID, prefix string, actions ...string) (S
wildcards := 0 wildcards := 0
result := make(map[interface{}]int) result := make(map[interface{}]int)
for _, a := range actions { for _, a := range actions {
ids, hasWildcard := ParseScopes(prefix, user.GetPermissions(user.GetOrgID())[a]) ids, hasWildcard := ParseScopes(prefix, user.GetPermissions()[a])
if hasWildcard { if hasWildcard {
wildcards += 1 wildcards += 1
continue continue

View File

@ -7,6 +7,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
) )
@ -101,7 +102,8 @@ func (m *Mock) WithBuiltInRoles(builtInRoles []string) *Mock {
// Evaluate evaluates access to the given resource. // Evaluate evaluates access to the given resource.
// This mock uses GetUserPermissions to then call the evaluator Evaluate function. // This mock uses GetUserPermissions to then call the evaluator Evaluate function.
func (m *Mock) Evaluate(ctx context.Context, usr *user.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) { func (m *Mock) Evaluate(ctx context.Context, us identity.Requester, evaluator accesscontrol.Evaluator) (bool, error) {
usr := us.(*user.SignedInUser)
m.Calls.Evaluate = append(m.Calls.Evaluate, []interface{}{ctx, usr, evaluator}) m.Calls.Evaluate = append(m.Calls.Evaluate, []interface{}{ctx, usr, evaluator})
// Use override if provided // Use override if provided
if m.EvaluateFunc != nil { if m.EvaluateFunc != nil {
@ -138,7 +140,8 @@ func (m *Mock) Evaluate(ctx context.Context, usr *user.SignedInUser, evaluator a
// GetUserPermissions returns user permissions. // GetUserPermissions returns user permissions.
// This mock return m.permissions unless an override is provided. // This mock return m.permissions unless an override is provided.
func (m *Mock) GetUserPermissions(ctx context.Context, user *user.SignedInUser, opts accesscontrol.Options) ([]accesscontrol.Permission, error) { func (m *Mock) GetUserPermissions(ctx context.Context, usr identity.Requester, opts accesscontrol.Options) ([]accesscontrol.Permission, error) {
user := usr.(*user.SignedInUser)
m.Calls.GetUserPermissions = append(m.Calls.GetUserPermissions, []interface{}{ctx, user, opts}) m.Calls.GetUserPermissions = append(m.Calls.GetUserPermissions, []interface{}{ctx, user, opts})
// Use override if provided // Use override if provided
if m.GetUserPermissionsFunc != nil { if m.GetUserPermissionsFunc != nil {
@ -148,7 +151,8 @@ func (m *Mock) GetUserPermissions(ctx context.Context, user *user.SignedInUser,
return m.permissions, nil return m.permissions, nil
} }
func (m *Mock) ClearUserPermissionCache(user *user.SignedInUser) { func (m *Mock) ClearUserPermissionCache(usr identity.Requester) {
user := usr.(*user.SignedInUser)
m.Calls.ClearUserPermissionCache = append(m.Calls.ClearUserPermissionCache, []interface{}{user}) m.Calls.ClearUserPermissionCache = append(m.Calls.ClearUserPermissionCache, []interface{}{user})
// Use override if provided // Use override if provided
if m.ClearUserPermissionCacheFunc != nil { if m.ClearUserPermissionCacheFunc != nil {
@ -222,11 +226,12 @@ func (m *Mock) DeleteUserPermissions(ctx context.Context, orgID, userID int64) e
} }
// SearchUsersPermissions returns all users' permissions filtered by an action prefix // SearchUsersPermissions returns all users' permissions filtered by an action prefix
func (m *Mock) SearchUsersPermissions(ctx context.Context, user *user.SignedInUser, orgID int64, options accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error) { func (m *Mock) SearchUsersPermissions(ctx context.Context, usr identity.Requester, options accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error) {
m.Calls.SearchUsersPermissions = append(m.Calls.SearchUsersPermissions, []interface{}{ctx, user, orgID, options}) user := usr.(*user.SignedInUser)
m.Calls.SearchUsersPermissions = append(m.Calls.SearchUsersPermissions, []interface{}{ctx, user, options})
// Use override if provided // Use override if provided
if m.SearchUsersPermissionsFunc != nil { if m.SearchUsersPermissionsFunc != nil {
return m.SearchUsersPermissionsFunc(ctx, user, orgID, options) return m.SearchUsersPermissionsFunc(ctx, user, usr.GetOrgID(), options)
} }
return nil, nil return nil, nil
} }

View File

@ -1,9 +1,26 @@
package identity package identity
import "github.com/grafana/grafana/pkg/models/roletype"
const (
NamespaceUser = "user"
NamespaceAPIKey = "api-key"
NamespaceServiceAccount = "service-account"
NamespaceAnonymous = "anonymous"
NamespaceRenderService = "render"
)
type Requester interface { type Requester interface {
GetIsGrafanaAdmin() bool GetIsGrafanaAdmin() bool
GetLogin() string GetLogin() string
GetOrgID() int64 GetOrgID() int64
GetPermissions(orgID int64) map[string][]string GetPermissions() map[string][]string
GetTeams() []int64
GetOrgRole() roletype.RoleType
GetNamespacedID() (string, string)
IsNil() bool IsNil() bool
// Legacy
GetCacheKey() (string, error)
HasUniqueId() bool
} }

View File

@ -14,6 +14,7 @@ import (
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/middleware/cookies" "github.com/grafana/grafana/pkg/middleware/cookies"
"github.com/grafana/grafana/pkg/models/usertoken" "github.com/grafana/grafana/pkg/models/usertoken"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
@ -172,9 +173,9 @@ type Redirect struct {
} }
const ( const (
NamespaceUser = "user" NamespaceUser = identity.NamespaceUser
NamespaceAPIKey = "api-key" NamespaceAPIKey = identity.NamespaceAPIKey
NamespaceServiceAccount = "service-account" NamespaceServiceAccount = identity.NamespaceServiceAccount
) )
type Identity struct { type Identity struct {

View File

@ -615,7 +615,7 @@ func TestIntegration_SQLStore_GetOrgUsers(t *testing.T) {
if !hasWildcardScope(tt.query.User, accesscontrol.ActionOrgUsersRead) { if !hasWildcardScope(tt.query.User, accesscontrol.ActionOrgUsersRead) {
for _, u := range result.OrgUsers { for _, u := range result.OrgUsers {
assert.Contains(t, tt.query.User.GetPermissions(tt.query.User.GetOrgID())[accesscontrol.ActionOrgUsersRead], fmt.Sprintf("users:id:%d", u.UserID)) assert.Contains(t, tt.query.User.GetPermissions()[accesscontrol.ActionOrgUsersRead], fmt.Sprintf("users:id:%d", u.UserID))
} }
} }
}) })
@ -647,7 +647,7 @@ func seedOrgUsers(t *testing.T, orgUserStore store, store *sqlstore.SQLStore, nu
} }
func hasWildcardScope(user identity.Requester, action string) bool { func hasWildcardScope(user identity.Requester, action string) bool {
for _, scope := range user.GetPermissions(user.GetOrgID())[action] { for _, scope := range user.GetPermissions()[action] {
if strings.HasSuffix(scope, ":*") { if strings.HasSuffix(scope, ":*") {
return true return true
} }
@ -792,7 +792,7 @@ func TestIntegration_SQLStore_SearchOrgUsers(t *testing.T) {
if !hasWildcardScope(tt.query.User, accesscontrol.ActionOrgUsersRead) { if !hasWildcardScope(tt.query.User, accesscontrol.ActionOrgUsersRead) {
for _, u := range result.OrgUsers { for _, u := range result.OrgUsers {
assert.Contains(t, tt.query.User.GetPermissions(tt.query.User.GetOrgID())[accesscontrol.ActionOrgUsersRead], fmt.Sprintf("users:id:%d", u.UserID)) assert.Contains(t, tt.query.User.GetPermissions()[accesscontrol.ActionOrgUsersRead], fmt.Sprintf("users:id:%d", u.UserID))
} }
} }
}) })

View File

@ -5,6 +5,7 @@ import (
"time" "time"
"github.com/grafana/grafana/pkg/models/roletype" "github.com/grafana/grafana/pkg/models/roletype"
"github.com/grafana/grafana/pkg/services/auth/identity"
) )
type SignedInUser struct { type SignedInUser struct {
@ -105,12 +106,42 @@ func (u *SignedInUser) GetOrgID() int64 {
return u.OrgID return u.OrgID
} }
func (u *SignedInUser) GetPermissions(orgID int64) map[string][]string { func (u *SignedInUser) GetPermissions() map[string][]string {
if u.Permissions == nil { if u.Permissions == nil {
return make(map[string][]string) return make(map[string][]string)
} }
return u.Permissions[orgID] if u.Permissions[u.GetOrgID()] == nil {
return make(map[string][]string)
}
return u.Permissions[u.GetOrgID()]
}
func (u *SignedInUser) GetTeams() []int64 {
return u.Teams
}
func (u *SignedInUser) GetOrgRole() roletype.RoleType {
return u.OrgRole
}
func (u *SignedInUser) GetNamespacedID() (string, string) {
switch {
case u.ApiKeyID != 0:
return identity.NamespaceAPIKey, fmt.Sprintf("%d", u.ApiKeyID)
case u.IsServiceAccount:
return identity.NamespaceServiceAccount, fmt.Sprintf("%d", u.UserID)
case u.UserID != 0:
return identity.NamespaceUser, fmt.Sprintf("%d", u.UserID)
case u.IsAnonymous:
return identity.NamespaceAnonymous, ""
case u.AuthenticatedBy == "render": //import cycle render
return identity.NamespaceRenderService, fmt.Sprintf("%d", u.UserID)
}
// backwards compatibility
return identity.NamespaceUser, fmt.Sprintf("%d", u.UserID)
} }
// FIXME: remove this method once all services are using an interface // FIXME: remove this method once all services are using an interface