Add auth spans and remove deduplication code for scopes (#89804)

Adds more spans for timing in accesscontrol and remove permission deduplicating code after benchmarking

---------

Signed-off-by: Dave Henderson <dave.henderson@grafana.com>
Co-authored-by: Dave Henderson <dave.henderson@grafana.com>
Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
This commit is contained in:
Jeff Levin
2024-07-02 22:08:57 -08:00
committed by GitHub
parent 5b6edc96d9
commit cfe8317d45
36 changed files with 279 additions and 97 deletions

View File

@ -250,12 +250,12 @@ func setupScenarioContextSamlLogout(t *testing.T, url string) *scenarioContext {
// FIXME: This user should not be anonymous
func authedUserWithPermissions(userID, orgID int64, permissions []accesscontrol.Permission) *user.SignedInUser {
return &user.SignedInUser{UserID: userID, OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(permissions)}}
return &user.SignedInUser{UserID: userID, OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByActionContext(context.Background(), permissions)}}
}
// FIXME: This user should not be anonymous
func userWithPermissions(orgID int64, permissions []accesscontrol.Permission) *user.SignedInUser {
return &user.SignedInUser{IsAnonymous: true, OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(permissions)}}
return &user.SignedInUser{IsAnonymous: true, OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByActionContext(context.Background(), permissions)}}
}
func setupSimpleHTTPServer(features featuremgmt.FeatureToggles) *HTTPServer {

View File

@ -1,6 +1,7 @@
package api
import (
"context"
"encoding/json"
"fmt"
"net/http"
@ -282,7 +283,7 @@ func TestHTTPServer_FolderMetadata(t *testing.T) {
req := server.NewGetRequest("/api/folders/folderUid?accesscontrol=true")
webtest.RequestWithSignedInUser(req, &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{
1: accesscontrol.GroupScopesByAction([]accesscontrol.Permission{
1: accesscontrol.GroupScopesByActionContext(context.Background(), []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: dashboards.ScopeFoldersAll},
{Action: dashboards.ActionFoldersWrite, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID("folderUid")},
}),
@ -311,7 +312,7 @@ func TestHTTPServer_FolderMetadata(t *testing.T) {
req := server.NewGetRequest("/api/folders/folderUid?accesscontrol=true")
webtest.RequestWithSignedInUser(req, &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{
1: accesscontrol.GroupScopesByAction([]accesscontrol.Permission{
1: accesscontrol.GroupScopesByActionContext(context.Background(), []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: dashboards.ScopeFoldersAll},
{Action: dashboards.ActionFoldersWrite, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID("parentUid")},
{Action: dashboards.ActionDashboardsCreate, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID("folderUid")},
@ -336,7 +337,7 @@ func TestHTTPServer_FolderMetadata(t *testing.T) {
req := server.NewGetRequest("/api/folders/folderUid")
webtest.RequestWithSignedInUser(req, &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{
1: accesscontrol.GroupScopesByAction([]accesscontrol.Permission{
1: accesscontrol.GroupScopesByActionContext(context.Background(), []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: dashboards.ScopeFoldersAll},
{Action: dashboards.ActionFoldersWrite, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID("folderUid")},
}),

View File

@ -1,6 +1,7 @@
package api
import (
"context"
"net/http"
"strings"
"testing"
@ -220,7 +221,7 @@ func TestAPIEndpoint_DeleteOrgs(t *testing.T) {
expectedIdentity := &authn.Identity{
OrgID: 1,
Permissions: map[int64]map[string][]string{
1: accesscontrol.GroupScopesByAction(tt.permission),
1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permission),
},
}
@ -269,8 +270,8 @@ func TestAPIEndpoint_GetOrg(t *testing.T) {
ID: authn.MustParseNamespaceID("user:1"),
OrgID: 1,
Permissions: map[int64]map[string][]string{
0: accesscontrol.GroupScopesByAction(tt.permissions),
1: accesscontrol.GroupScopesByAction(tt.permissions),
0: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions),
1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions),
},
}

View File

@ -70,7 +70,7 @@ func TestCallResource(t *testing.T) {
t.Run("Test successful response is received for valid request", func(t *testing.T) {
req := srv.NewPostRequest("/api/plugins/grafana-testdata-datasource/resources/test", strings.NewReader(`{"test": "true"}`))
webtest.RequestWithSignedInUser(req, &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{
1: accesscontrol.GroupScopesByAction([]accesscontrol.Permission{
1: accesscontrol.GroupScopesByActionContext(context.Background(), []accesscontrol.Permission{
{Action: pluginaccesscontrol.ActionAppAccess, Scope: pluginaccesscontrol.ScopeProvider.GetResourceAllScope()},
}),
}})
@ -92,7 +92,7 @@ func TestCallResource(t *testing.T) {
t.Run("Test successful response is received for valid request with the colon character", func(t *testing.T) {
req := srv.NewPostRequest("/api/plugins/grafana-testdata-datasource/resources/test-*,*:test-*/_mapping", strings.NewReader(`{"test": "true"}`))
webtest.RequestWithSignedInUser(req, &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{
1: accesscontrol.GroupScopesByAction([]accesscontrol.Permission{
1: accesscontrol.GroupScopesByActionContext(context.Background(), []accesscontrol.Permission{
{Action: pluginaccesscontrol.ActionAppAccess, Scope: pluginaccesscontrol.ScopeProvider.GetResourceAllScope()},
}),
}})
@ -146,7 +146,7 @@ func TestCallResource(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
req := srv.NewPostRequest(tc.url, strings.NewReader(`{"test": "true"}`))
webtest.RequestWithSignedInUser(req, &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{
1: accesscontrol.GroupScopesByAction([]accesscontrol.Permission{
1: accesscontrol.GroupScopesByActionContext(context.Background(), []accesscontrol.Permission{
{Action: pluginaccesscontrol.ActionAppAccess, Scope: pluginaccesscontrol.ScopeProvider.GetResourceAllScope()},
}),
}})
@ -192,7 +192,7 @@ func TestCallResource(t *testing.T) {
t.Run("Test error is properly propagated to API response", func(t *testing.T) {
req := srv.NewGetRequest("/api/plugins/grafana-testdata-datasource/resources/scenarios")
webtest.RequestWithSignedInUser(req, &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{
1: accesscontrol.GroupScopesByAction([]accesscontrol.Permission{
1: accesscontrol.GroupScopesByActionContext(context.Background(), []accesscontrol.Permission{
{Action: pluginaccesscontrol.ActionAppAccess, Scope: pluginaccesscontrol.ScopeProvider.GetResourceAllScope()},
}),
}})

View File

@ -104,7 +104,7 @@ func Test_PluginsInstallAndUninstall(t *testing.T) {
Permissions: map[int64]map[string][]string{},
OrgRoles: map[int64]org.RoleType{},
}
expectedIdentity.Permissions[tc.permissionOrg] = ac.GroupScopesByAction(tc.permissions)
expectedIdentity.Permissions[tc.permissionOrg] = ac.GroupScopesByActionContext(context.Background(), tc.permissions)
hs.authnService = &authntest.FakeService{
ExpectedIdentity: expectedIdentity,
}

View File

@ -1,6 +1,7 @@
package api
import (
"context"
"fmt"
"net/http"
"strings"
@ -156,7 +157,7 @@ func TestAPIEndpoint_PutOrgQuotas(t *testing.T) {
Permissions: map[int64]map[string][]string{},
}
for orgID, permissions := range tt.permissions {
expectedIdentity.Permissions[orgID] = accesscontrol.GroupScopesByAction(permissions)
expectedIdentity.Permissions[orgID] = accesscontrol.GroupScopesByActionContext(context.Background(), permissions)
}
server := SetupAPITestServer(t, func(hs *HTTPServer) {

View File

@ -12,8 +12,13 @@ import (
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/user"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
var tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/accesscontrol")
type AccessControl interface {
// Evaluate evaluates access to the given resources.
Evaluate(ctx context.Context, user identity.Requester, evaluator Evaluator) (bool, error)
@ -232,30 +237,24 @@ func BuildPermissionsMap(permissions []Permission) map[string]bool {
}
// GroupScopesByAction will group scopes on action
//
// Deprecated: use GroupScopesByActionContext instead
func GroupScopesByAction(permissions []Permission) map[string][]string {
// Use a map to deduplicate scopes.
// User can have the same permission from multiple sources (e.g. team, basic role, directly assigned etc).
// User will also have duplicate permissions if action sets are used, as we will be double writing permissions for a while.
m := make(map[string]map[string]struct{})
return GroupScopesByActionContext(context.Background(), permissions)
}
// GroupScopesByAction will group scopes on action
func GroupScopesByActionContext(ctx context.Context, permissions []Permission) map[string][]string {
_, span := tracer.Start(ctx, "accesscontrol.GroupScopesByActionContext", trace.WithAttributes(
attribute.Int("permissions_count", len(permissions)),
))
defer span.End()
m := make(map[string][]string)
for i := range permissions {
if _, ok := m[permissions[i].Action]; !ok {
m[permissions[i].Action] = make(map[string]struct{})
}
m[permissions[i].Action][permissions[i].Scope] = struct{}{}
m[permissions[i].Action] = append(m[permissions[i].Action], permissions[i].Scope)
}
res := make(map[string][]string, len(m))
for action, scopes := range m {
scopeList := make([]string, len(scopes))
i := 0
for scope := range scopes {
scopeList[i] = scope
i++
}
res[action] = scopeList
}
return res
return m
}
// Reduce will reduce a list of permissions to its minimal form, grouping scopes by action

View File

@ -1,8 +1,11 @@
package accesscontrol
import (
"context"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
// this import is needed for github.com/grafana/grafana/pkg/web hack_wrap to work
@ -125,3 +128,54 @@ func TestReduce(t *testing.T) {
})
}
}
func TestGroupScopesByActionContext(t *testing.T) {
// test data = 3 actions with 2+i scopes each, including a duplicate
permissions := []Permission{}
for i := 0; i < 3; i++ {
for j := 0; j < 2+i; j++ {
permissions = append(permissions, Permission{
Action: fmt.Sprintf("action:%d", i),
Scope: fmt.Sprintf("scope:%d_%d", i, j),
})
}
}
expected := map[string][]string{}
for i := 0; i < 3; i++ {
action := fmt.Sprintf("action:%d", i)
scopes := []string{}
for j := 0; j < 2+i; j++ {
scopes = append(scopes, fmt.Sprintf("scope:%d_%d", i, j))
}
expected[action] = scopes
}
assert.EqualValues(t, expected, GroupScopesByActionContext(context.Background(), permissions))
}
func BenchmarkGroupScopesByAction(b *testing.B) {
// create a big list of permissions with a bunch of duplicates
permissions := []Permission{}
for i := 0; i < 100; i++ {
for j := 0; j < 500+i; j++ {
permissions = append(permissions, Permission{
Action: fmt.Sprintf("action:%d", i),
Scope: fmt.Sprintf("scope:%d_%d", i, j),
})
}
// add duplicate scopes
for j := 0; j < 10; j++ {
permissions = append(permissions, Permission{
Action: fmt.Sprintf("action:%d", i),
Scope: fmt.Sprintf("scope:%d_%d", i, 0),
})
}
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
GroupScopesByActionContext(context.Background(), permissions)
}
}

View File

@ -114,6 +114,7 @@ func (s *Service) GetUsageStats(_ context.Context) map[string]any {
func (s *Service) GetUserPermissions(ctx context.Context, user identity.Requester, options accesscontrol.Options) ([]accesscontrol.Permission, error) {
ctx, span := s.tracer.Start(ctx, "authz.GetUserPermissionsOSS")
defer span.End()
timer := prometheus.NewTimer(metrics.MAccessPermissionsSummary)
defer timer.ObserveDuration()
@ -125,6 +126,9 @@ func (s *Service) GetUserPermissions(ctx context.Context, user identity.Requeste
}
func (s *Service) getUserPermissions(ctx context.Context, user identity.Requester, options accesscontrol.Options) ([]accesscontrol.Permission, error) {
ctx, span := s.tracer.Start(ctx, "authz.getUserPermissions")
defer span.End()
permissions := make([]accesscontrol.Permission, 0)
for _, builtin := range accesscontrol.GetOrgRoles(user) {
if basicRole, ok := s.roles[builtin]; ok {
@ -265,8 +269,10 @@ func (s *Service) getCachedBasicRolesPermissions(ctx context.Context, user ident
defer span.End()
basicRoles := accesscontrol.GetOrgRoles(user)
span.SetAttributes(attribute.Int("roles", len(basicRoles)))
for _, role := range basicRoles {
perms, err := s.getCachedBasicRolePermissions(ctx, role, user.GetOrgID(), options)
span.SetAttributes(attribute.Int(fmt.Sprintf("role_%s_permissions", role), len(perms)))
if err != nil {
return nil, err
}
@ -301,12 +307,13 @@ type getPermissionsFunc = func(ctx context.Context) ([]accesscontrol.Permission,
// Generic method for getting various permissions from cache
func (s *Service) getCachedPermissions(ctx context.Context, key string, getPermissionsFn getPermissionsFunc, options accesscontrol.Options) ([]accesscontrol.Permission, error) {
_, span := s.tracer.Start(ctx, "authz.getCachedPermissions")
ctx, span := s.tracer.Start(ctx, "authz.getCachedPermissions")
defer span.End()
if !options.ReloadCache {
permissions, ok := s.cache.Get(key)
if ok {
span.SetAttributes(attribute.Int("num_permissions_cached", len(permissions.([]accesscontrol.Permission))))
metrics.MAccessPermissionsCacheUsage.WithLabelValues(accesscontrol.CacheHit).Inc()
return permissions.([]accesscontrol.Permission), nil
}
@ -315,6 +322,7 @@ func (s *Service) getCachedPermissions(ctx context.Context, key string, getPermi
span.AddEvent("cache miss")
metrics.MAccessPermissionsCacheUsage.WithLabelValues(accesscontrol.CacheMiss).Inc()
permissions, err := getPermissionsFn(ctx)
span.SetAttributes(attribute.Int("num_permissions_fetched", len(permissions)))
if err != nil {
return nil, err
}
@ -338,6 +346,7 @@ func (s *Service) getCachedTeamsPermissions(ctx context.Context, user identity.R
teamPermissions, ok := s.cache.Get(key)
if ok {
metrics.MAccessPermissionsCacheUsage.WithLabelValues(accesscontrol.CacheHit).Inc()
span.SetAttributes(attribute.Int("num_permissions_cached", len(teamPermissions.([]accesscontrol.Permission))))
permissions = append(permissions, teamPermissions.([]accesscontrol.Permission)...)
} else {
miss = append(miss, teamID)
@ -349,6 +358,7 @@ func (s *Service) getCachedTeamsPermissions(ctx context.Context, user identity.R
span.AddEvent("cache miss")
metrics.MAccessPermissionsCacheUsage.WithLabelValues(accesscontrol.CacheMiss).Inc()
teamsPermissions, err := s.getTeamsPermissions(ctx, miss, orgID)
span.SetAttributes(attribute.Int("num_permissions_fetched", len(teamsPermissions)))
if err != nil {
return nil, err
}
@ -369,10 +379,16 @@ func (s *Service) ClearUserPermissionCache(user identity.Requester) {
}
func (s *Service) DeleteUserPermissions(ctx context.Context, orgID int64, userID int64) error {
ctx, span := s.tracer.Start(ctx, "authz.DeleteUserPermissions")
defer span.End()
return s.store.DeleteUserPermissions(ctx, orgID, userID)
}
func (s *Service) DeleteTeamPermissions(ctx context.Context, orgID int64, teamID int64) error {
ctx, span := s.tracer.Start(ctx, "authz.DeleteTeamPermissions")
defer span.End()
return s.store.DeleteTeamPermissions(ctx, orgID, teamID)
}
@ -398,6 +414,9 @@ func (s *Service) DeclareFixedRoles(registrations ...accesscontrol.RoleRegistrat
// RegisterFixedRoles registers all declared roles in RAM
func (s *Service) RegisterFixedRoles(ctx context.Context) error {
_, span := s.tracer.Start(ctx, "authz.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 {
@ -421,6 +440,9 @@ func (s *Service) RegisterFixedRoles(ctx context.Context) error {
// 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 {
ctx, span := s.tracer.Start(ctx, "authz.DeclarePluginRoles")
defer span.End()
// Protect behind feature toggle
if !s.features.IsEnabled(ctx, featuremgmt.FlagAccessControlOnCall) {
return nil
@ -455,6 +477,9 @@ func GetActionFilter(options accesscontrol.SearchOptions) func(action string) bo
// SearchUsersPermissions returns all users' permissions filtered by action prefixes
func (s *Service) SearchUsersPermissions(ctx context.Context, usr identity.Requester, options accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error) {
ctx, span := s.tracer.Start(ctx, "authz.SearchUsersPermissions")
defer span.End()
// Limit roles to available in OSS
options.RolePrefixes = OSSRolesPrefixes
if options.NamespacedID != "" {
@ -566,6 +591,9 @@ func (s *Service) SearchUsersPermissions(ctx context.Context, usr identity.Reque
}
func (s *Service) SearchUserPermissions(ctx context.Context, orgID int64, searchOptions accesscontrol.SearchOptions) ([]accesscontrol.Permission, error) {
ctx, span := s.tracer.Start(ctx, "authz.SearchUserPermissions")
defer span.End()
timer := prometheus.NewTimer(metrics.MAccessPermissionsSummary)
defer timer.ObserveDuration()
@ -573,13 +601,16 @@ func (s *Service) SearchUserPermissions(ctx context.Context, orgID int64, search
return nil, fmt.Errorf("expected namespaced ID to be specified")
}
if permissions, success := s.searchUserPermissionsFromCache(orgID, searchOptions); success {
if permissions, success := s.searchUserPermissionsFromCache(ctx, orgID, searchOptions); success {
return permissions, nil
}
return s.searchUserPermissions(ctx, orgID, searchOptions)
}
func (s *Service) searchUserPermissions(ctx context.Context, orgID int64, searchOptions accesscontrol.SearchOptions) ([]accesscontrol.Permission, error) {
ctx, span := s.tracer.Start(ctx, "authz.searchUserPermissions")
defer span.End()
userID, err := searchOptions.ComputeUserID()
if err != nil {
return nil, err
@ -629,7 +660,10 @@ func (s *Service) searchUserPermissions(ctx context.Context, orgID int64, search
return permissions, nil
}
func (s *Service) searchUserPermissionsFromCache(orgID int64, searchOptions accesscontrol.SearchOptions) ([]accesscontrol.Permission, bool) {
func (s *Service) searchUserPermissionsFromCache(ctx context.Context, orgID int64, searchOptions accesscontrol.SearchOptions) ([]accesscontrol.Permission, bool) {
_, span := s.tracer.Start(ctx, "authz.searchUserPermissionsFromCache")
defer span.End()
userID, err := searchOptions.ComputeUserID()
if err != nil {
return nil, false
@ -669,6 +703,9 @@ func PermissionMatchesSearchOptions(permission accesscontrol.Permission, searchO
}
func (s *Service) SaveExternalServiceRole(ctx context.Context, cmd accesscontrol.SaveExternalServiceRoleCommand) error {
ctx, span := s.tracer.Start(ctx, "authz.SaveExternalServiceRole")
defer span.End()
if !s.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAccounts) {
s.log.Debug("Registering an external service role is behind a feature flag, enable it to use this feature.")
return nil
@ -682,6 +719,9 @@ func (s *Service) SaveExternalServiceRole(ctx context.Context, cmd accesscontrol
}
func (s *Service) DeleteExternalServiceRole(ctx context.Context, externalServiceID string) error {
ctx, span := s.tracer.Start(ctx, "authz.DeleteExternalServiceRole")
defer span.End()
if !s.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAccounts) {
s.log.Debug("Deleting an external service role is behind a feature flag, enable it to use this feature.")
return nil
@ -697,6 +737,9 @@ func (*Service) SyncUserRoles(ctx context.Context, orgID int64, cmd accesscontro
}
func (s *Service) GetRoleByName(ctx context.Context, orgID int64, roleName string) (*accesscontrol.RoleDTO, error) {
_, span := s.tracer.Start(ctx, "authz.GetRoleByName")
defer span.End()
err := accesscontrol.ErrRoleNotFound
if _, ok := s.roles[roleName]; ok {
return nil, err

View File

@ -42,6 +42,7 @@ func setupTestEnv(t testing.TB) *Service {
registrations: accesscontrol.RegistrationList{},
roles: accesscontrol.BuildBasicRoleDefinitions(),
store: database.ProvideService(db.InitTestDB(t)),
tracer: tracing.InitializeTracerForTest(),
}
require.NoError(t, ac.RegisterFixedRoles(context.Background()))
return ac

View File

@ -62,7 +62,7 @@ func (api *AccessControlAPI) getUserPermissions(c *contextmodel.ReqContext) resp
return response.JSON(http.StatusInternalServerError, err)
}
return response.JSON(http.StatusOK, ac.GroupScopesByAction(permissions))
return response.JSON(http.StatusOK, ac.GroupScopesByActionContext(c.Req.Context(), permissions))
}
// GET /api/access-control/users/permissions/search

View File

@ -120,7 +120,7 @@ func (m *Mock) Evaluate(ctx context.Context, usr identity.Requester, evaluator a
if err != nil {
return false, err
}
permissions = accesscontrol.GroupScopesByAction(userPermissions)
permissions = accesscontrol.GroupScopesByActionContext(ctx, userPermissions)
}
if evaluator.Evaluate(permissions) {

View File

@ -111,7 +111,7 @@ func TestApi_getDescription(t *testing.T) {
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
service, _, _ := setupTestEnvironment(t, tt.options)
server := setupTestServer(t, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}, service)
server := setupTestServer(t, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}}, service)
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/access-control/%s/description", tt.options.Resource), nil)
require.NoError(t, err)
@ -158,7 +158,7 @@ func TestApi_getPermissions(t *testing.T) {
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
service, usrSvc, teamSvc := setupTestEnvironment(t, testOptions)
server := setupTestServer(t, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}, service)
server := setupTestServer(t, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}}, service)
seedPermissions(t, tt.resourceID, usrSvc, teamSvc, service)
@ -235,7 +235,7 @@ func TestApi_setBuiltinRolePermission(t *testing.T) {
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
service, _, _ := setupTestEnvironment(t, testOptions)
server := setupTestServer(t, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}, service)
server := setupTestServer(t, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}}, service)
recorder := setPermission(t, server, testOptions.Resource, tt.resourceID, tt.permission, "builtInRoles", tt.builtInRole)
assert.Equal(t, tt.expectedStatus, recorder.Code)
@ -313,7 +313,7 @@ func TestApi_setTeamPermission(t *testing.T) {
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
service, _, teamSvc := setupTestEnvironment(t, testOptions)
server := setupTestServer(t, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}, service)
server := setupTestServer(t, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}}, service)
// seed team
_, err := teamSvc.CreateTeam(context.Background(), "test", "test@test.com", 1)
@ -398,7 +398,7 @@ func TestApi_setUserPermission(t *testing.T) {
service, usrSvc, _ := setupTestEnvironment(t, testOptions)
server := setupTestServer(t, &user.SignedInUser{
OrgID: 1,
Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)},
Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)},
}, service)
_, err := usrSvc.Create(context.Background(), &user.CreateUserCommand{Login: "test", OrgID: 1})

View File

@ -195,7 +195,7 @@ func TestIntegrationAnnotationListingWithInheritedRBAC(t *testing.T) {
usr := &user.SignedInUser{
UserID: 1,
OrgID: orgID,
Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(permissions)},
Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByActionContext(context.Background(), permissions)},
}
var role *accesscontrol.Role

View File

@ -3,6 +3,7 @@ package authnimpl
import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/remotecache"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/login/social"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apikey"
@ -36,6 +37,7 @@ func ProvideRegistration(
features *featuremgmt.FeatureManager, oauthTokenService oauthtoken.OAuthTokenService,
socialService social.Service, cache *remotecache.RemoteCache,
ldapService service.LDAP, settingsProviderService setting.Provider,
tracer tracing.Tracer,
) Registration {
logger := log.New("authn.registration")
@ -95,16 +97,16 @@ func ProvideRegistration(
}
// FIXME (jguer): move to User package
userSync := sync.ProvideUserSync(userService, userProtectionService, authInfoService, quotaService)
orgSync := sync.ProvideOrgSync(userService, orgService, accessControlService, cfg)
userSync := sync.ProvideUserSync(userService, userProtectionService, authInfoService, quotaService, tracer)
orgSync := sync.ProvideOrgSync(userService, orgService, accessControlService, cfg, tracer)
authnSvc.RegisterPostAuthHook(userSync.SyncUserHook, 10)
authnSvc.RegisterPostAuthHook(userSync.EnableUserHook, 20)
authnSvc.RegisterPostAuthHook(orgSync.SyncOrgRolesHook, 30)
authnSvc.RegisterPostAuthHook(userSync.SyncLastSeenHook, 130)
authnSvc.RegisterPostAuthHook(sync.ProvideOAuthTokenSync(oauthTokenService, sessionService, socialService).SyncOauthTokenHook, 60)
authnSvc.RegisterPostAuthHook(sync.ProvideOAuthTokenSync(oauthTokenService, sessionService, socialService, tracer).SyncOauthTokenHook, 60)
authnSvc.RegisterPostAuthHook(userSync.FetchSyncedUserHook, 100)
rbacSync := sync.ProvideRBACSync(accessControlService)
rbacSync := sync.ProvideRBACSync(accessControlService, tracer)
if features.IsEnabledGlobally(featuremgmt.FlagCloudRBACRoles) {
authnSvc.RegisterPostAuthHook(rbacSync.SyncCloudRoles, 110)
authnSvc.RegisterPreLogoutHook(gcomsso.ProvideGComSSOService(cfg).LogoutHook, 50)

View File

@ -323,6 +323,9 @@ Default:
}
func (s *Service) ResolveIdentity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) {
ctx, span := s.tracer.Start(ctx, "authn.ResolveIdentity")
defer span.End()
r := &authn.Request{}
r.OrgID = orgID
// hack to not update last seen
@ -358,6 +361,9 @@ func (s *Service) IsClientEnabled(name string) bool {
}
func (s *Service) SyncIdentity(ctx context.Context, identity *authn.Identity) error {
ctx, span := s.tracer.Start(ctx, "authn.SyncIdentity")
defer span.End()
r := &authn.Request{OrgID: identity.OrgID}
// hack to not update last seen on external syncs
r.SetMeta(authn.MetaKeyIsLogin, "true")
@ -365,6 +371,9 @@ func (s *Service) SyncIdentity(ctx context.Context, identity *authn.Identity) er
}
func (s *Service) resolveIdenity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) {
ctx, span := s.tracer.Start(ctx, "authn.resolveIdentity")
defer span.End()
if namespaceID.IsNamespace(authn.NamespaceUser) {
return &authn.Identity{
OrgID: orgID,

View File

@ -9,19 +9,21 @@ import (
"golang.org/x/sync/singleflight"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/login/social"
"github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/oauthtoken"
)
func ProvideOAuthTokenSync(service oauthtoken.OAuthTokenService, sessionService auth.UserTokenService, socialService social.Service) *OAuthTokenSync {
func ProvideOAuthTokenSync(service oauthtoken.OAuthTokenService, sessionService auth.UserTokenService, socialService social.Service, tracer tracing.Tracer) *OAuthTokenSync {
return &OAuthTokenSync{
log.New("oauth_token.sync"),
service,
sessionService,
socialService,
new(singleflight.Group),
tracer,
}
}
@ -31,9 +33,13 @@ type OAuthTokenSync struct {
sessionService auth.UserTokenService
socialService social.Service
singleflightGroup *singleflight.Group
tracer tracing.Tracer
}
func (s *OAuthTokenSync) SyncOauthTokenHook(ctx context.Context, identity *authn.Identity, _ *authn.Request) error {
ctx, span := s.tracer.Start(ctx, "oauth.sync.SyncOauthTokenHook")
defer span.End()
// only perform oauth token check if identity is a user
if !identity.ID.IsNamespace(authn.NamespaceUser) {
return nil

View File

@ -10,6 +10,7 @@ import (
"golang.org/x/sync/singleflight"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/login/social"
"github.com/grafana/grafana/pkg/login/social/socialtest"
"github.com/grafana/grafana/pkg/services/auth"
@ -128,6 +129,7 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) {
sessionService: sessionService,
socialService: socialService,
singleflightGroup: new(singleflight.Group),
tracer: tracing.InitializeTracerForTest(),
}
err := sync.SyncOauthTokenHook(context.Background(), tt.identity, nil)

View File

@ -7,6 +7,7 @@ import (
"sort"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/org"
@ -14,8 +15,8 @@ import (
"github.com/grafana/grafana/pkg/setting"
)
func ProvideOrgSync(userService user.Service, orgService org.Service, accessControl accesscontrol.Service, cfg *setting.Cfg) *OrgSync {
return &OrgSync{userService, orgService, accessControl, cfg, log.New("org.sync")}
func ProvideOrgSync(userService user.Service, orgService org.Service, accessControl accesscontrol.Service, cfg *setting.Cfg, tracer tracing.Tracer) *OrgSync {
return &OrgSync{userService, orgService, accessControl, cfg, log.New("org.sync"), tracer}
}
type OrgSync struct {
@ -23,11 +24,14 @@ type OrgSync struct {
orgService org.Service
accessControl accesscontrol.Service
cfg *setting.Cfg
log log.Logger
log log.Logger
tracer tracing.Tracer
}
func (s *OrgSync) SyncOrgRolesHook(ctx context.Context, id *authn.Identity, _ *authn.Request) error {
ctx, span := s.tracer.Start(ctx, "org.sync.SyncOrgRolesHook")
defer span.End()
if !id.ClientParams.SyncOrgRoles {
return nil
}
@ -131,6 +135,9 @@ func (s *OrgSync) SyncOrgRolesHook(ctx context.Context, id *authn.Identity, _ *a
}
func (s *OrgSync) SetDefaultOrgHook(ctx context.Context, currentIdentity *authn.Identity, r *authn.Request, err error) {
ctx, span := s.tracer.Start(ctx, "org.sync.SetDefaultOrgHook")
defer span.End()
if s.cfg.LoginDefaultOrgId < 1 || currentIdentity == nil || err != nil {
return
}
@ -166,6 +173,9 @@ func (s *OrgSync) SetDefaultOrgHook(ctx context.Context, currentIdentity *authn.
}
func (s *OrgSync) validateUsingOrg(ctx context.Context, userID int64, orgID int64) (bool, error) {
ctx, span := s.tracer.Start(ctx, "org.sync.validateUsingOrg")
defer span.End()
query := org.GetUserOrgListQuery{UserID: userID}
result, err := s.orgService.GetUserOrgList(ctx, &query)

View File

@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
"github.com/grafana/grafana/pkg/services/authn"
@ -116,6 +117,7 @@ func TestOrgSync_SyncOrgRolesHook(t *testing.T) {
orgService: tt.fields.orgService,
accessControl: tt.fields.accessControl,
log: tt.fields.log,
tracer: tracing.InitializeTracerForTest(),
}
if err := s.SyncOrgRolesHook(tt.args.ctx, tt.args.id, nil); (err != nil) != tt.wantErr {
t.Errorf("OrgSync.SyncOrgRolesHook() error = %v, wantErr %v", err, tt.wantErr)
@ -214,6 +216,7 @@ func TestOrgSync_SetDefaultOrgHook(t *testing.T) {
accessControl: actest.FakeService{},
log: log.NewNopLogger(),
cfg: cfg,
tracer: tracing.InitializeTracerForTest(),
}
s.SetDefaultOrgHook(context.Background(), tt.identity, nil, tt.inputErr)

View File

@ -6,6 +6,7 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/login"
@ -17,19 +18,24 @@ var (
errSyncPermissionsForbidden = errutil.Forbidden("permissions.sync.forbidden")
)
func ProvideRBACSync(acService accesscontrol.Service) *RBACSync {
func ProvideRBACSync(acService accesscontrol.Service, tracer tracing.Tracer) *RBACSync {
return &RBACSync{
ac: acService,
log: log.New("permissions.sync"),
ac: acService,
log: log.New("permissions.sync"),
tracer: tracer,
}
}
type RBACSync struct {
ac accesscontrol.Service
log log.Logger
ac accesscontrol.Service
log log.Logger
tracer tracing.Tracer
}
func (s *RBACSync) SyncPermissionsHook(ctx context.Context, ident *authn.Identity, _ *authn.Request) error {
ctx, span := s.tracer.Start(ctx, "rbac.sync.SyncPermissionsHook")
defer span.End()
if !ident.ClientParams.SyncPermissions {
return nil
}
@ -43,7 +49,8 @@ func (s *RBACSync) SyncPermissionsHook(ctx context.Context, ident *authn.Identit
if ident.Permissions == nil {
ident.Permissions = make(map[int64]map[string][]string, 1)
}
grouped := accesscontrol.GroupScopesByAction(permissions)
grouped := accesscontrol.GroupScopesByActionContext(ctx, permissions)
// Restrict access to the list of actions
actionsLookup := ident.ClientParams.FetchPermissionsParams.ActionsLookup
@ -56,12 +63,15 @@ func (s *RBACSync) SyncPermissionsHook(ctx context.Context, ident *authn.Identit
}
grouped = filtered
}
ident.Permissions[ident.OrgID] = grouped
return nil
}
func (s *RBACSync) fetchPermissions(ctx context.Context, ident *authn.Identity) ([]accesscontrol.Permission, error) {
ctx, span := s.tracer.Start(ctx, "rbac.sync.fetchPermissions")
defer span.End()
permissions := make([]accesscontrol.Permission, 0, 8)
roles := ident.ClientParams.FetchPermissionsParams.Roles
if len(roles) > 0 {
@ -94,6 +104,9 @@ var fixedCloudRoles = map[org.RoleType]string{
}
func (s *RBACSync) SyncCloudRoles(ctx context.Context, ident *authn.Identity, r *authn.Request) error {
ctx, span := s.tracer.Start(ctx, "rbac.sync.SyncCloudRoles")
defer span.End()
// we only want to run this hook during login and if the module used is grafana com
if r.GetMeta(authn.MetaKeyAuthModule) != login.GrafanaComAuthModule {
return nil

View File

@ -5,6 +5,7 @@ import (
"testing"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/authn"
@ -45,7 +46,7 @@ func TestRBACSync_SyncPermission(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, 1, len(tt.identity.Permissions))
assert.Equal(t, accesscontrol.GroupScopesByAction(tt.expectedPermissions), tt.identity.Permissions[tt.identity.OrgID])
assert.Equal(t, accesscontrol.GroupScopesByActionContext(context.Background(), tt.expectedPermissions), tt.identity.Permissions[tt.identity.OrgID])
})
}
}
@ -127,7 +128,8 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) {
return nil
},
},
log: log.NewNopLogger(),
log: log.NewNopLogger(),
tracer: tracing.InitializeTracerForTest(),
}
req := &authn.Request{}
@ -149,8 +151,9 @@ func setupTestEnv() *RBACSync {
},
}
s := &RBACSync{
ac: acMock,
log: log.NewNopLogger(),
ac: acMock,
log: log.NewNopLogger(),
tracer: tracing.InitializeTracerForTest(),
}
return s
}

View File

@ -7,6 +7,7 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/org"
@ -47,15 +48,14 @@ var (
errSignupNotAllowed = errors.New("system administrator has disabled signup")
)
func ProvideUserSync(userService user.Service,
userProtectionService login.UserProtectionService,
authInfoService login.AuthInfoService, quotaService quota.Service) *UserSync {
func ProvideUserSync(userService user.Service, userProtectionService login.UserProtectionService, authInfoService login.AuthInfoService, quotaService quota.Service, tracer tracing.Tracer) *UserSync {
return &UserSync{
userService: userService,
authInfoService: authInfoService,
userProtectionService: userProtectionService,
quotaService: quotaService,
log: log.New("user.sync"),
tracer: tracer,
}
}
@ -65,10 +65,14 @@ type UserSync struct {
userProtectionService login.UserProtectionService
quotaService quota.Service
log log.Logger
tracer tracing.Tracer
}
// SyncUserHook syncs a user with the database
func (s *UserSync) SyncUserHook(ctx context.Context, id *authn.Identity, _ *authn.Request) error {
ctx, span := s.tracer.Start(ctx, "user.sync.SyncUserHook")
defer span.End()
if !id.ClientParams.SyncUser {
return nil
}
@ -106,6 +110,9 @@ func (s *UserSync) SyncUserHook(ctx context.Context, id *authn.Identity, _ *auth
}
func (s *UserSync) FetchSyncedUserHook(ctx context.Context, identity *authn.Identity, r *authn.Request) error {
ctx, span := s.tracer.Start(ctx, "user.sync.FetchSyncedUserHook")
defer span.End()
if !identity.ClientParams.FetchSyncedUser {
return nil
}
@ -143,6 +150,9 @@ func (s *UserSync) FetchSyncedUserHook(ctx context.Context, identity *authn.Iden
}
func (s *UserSync) SyncLastSeenHook(ctx context.Context, identity *authn.Identity, r *authn.Request) error {
ctx, span := s.tracer.Start(ctx, "user.sync.SyncLastSeenHook")
defer span.End()
if r.GetMeta(authn.MetaKeyIsLogin) != "" {
// Do not sync last seen for login requests
return nil
@ -177,6 +187,9 @@ func (s *UserSync) SyncLastSeenHook(ctx context.Context, identity *authn.Identit
}
func (s *UserSync) EnableUserHook(ctx context.Context, identity *authn.Identity, _ *authn.Request) error {
ctx, span := s.tracer.Start(ctx, "user.sync.EnableUserHook")
defer span.End()
if !identity.ClientParams.EnableUser {
return nil
}
@ -196,6 +209,9 @@ func (s *UserSync) EnableUserHook(ctx context.Context, identity *authn.Identity,
}
func (s *UserSync) upsertAuthConnection(ctx context.Context, userID int64, identity *authn.Identity, createConnection bool) error {
ctx, span := s.tracer.Start(ctx, "user.sync.upsertAuthConnection")
defer span.End()
if identity.AuthenticatedBy == "" {
return nil
}
@ -222,6 +238,9 @@ func (s *UserSync) upsertAuthConnection(ctx context.Context, userID int64, ident
}
func (s *UserSync) updateUserAttributes(ctx context.Context, usr *user.User, id *authn.Identity, userAuth *login.UserAuth) error {
ctx, span := s.tracer.Start(ctx, "user.sync.updateUserAttributes")
defer span.End()
if errProtection := s.userProtectionService.AllowUserMapping(usr, id.AuthenticatedBy); errProtection != nil {
return errUserProtection.Errorf("user mapping not allowed: %w", errProtection)
}
@ -273,6 +292,8 @@ func (s *UserSync) updateUserAttributes(ctx context.Context, usr *user.User, id
}
func (s *UserSync) createUser(ctx context.Context, id *authn.Identity) (*user.User, error) {
ctx, span := s.tracer.Start(ctx, "user.sync.createUser")
defer span.End()
// FIXME(jguer): this should be done in the user service
// quota check: we can have quotas on both global and org level
// therefore we need to query check quota for both user and org services
@ -312,6 +333,9 @@ func (s *UserSync) createUser(ctx context.Context, id *authn.Identity) (*user.Us
}
func (s *UserSync) getUser(ctx context.Context, identity *authn.Identity) (*user.User, *login.UserAuth, error) {
ctx, span := s.tracer.Start(ctx, "user.sync.getUser")
defer span.End()
// Check auth info fist
if identity.AuthID != "" && identity.AuthenticatedBy != "" {
query := &login.GetAuthInfoQuery{AuthId: identity.AuthID, AuthModule: identity.AuthenticatedBy}
@ -361,6 +385,9 @@ func (s *UserSync) getUser(ctx context.Context, identity *authn.Identity) (*user
}
func (s *UserSync) lookupByOneOf(ctx context.Context, params login.UserLookupParams) (*user.User, error) {
ctx, span := s.tracer.Start(ctx, "user.sync.lookupByOneOf")
defer span.End()
var usr *user.User
var err error

View File

@ -7,6 +7,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/login/authinfoimpl"
@ -426,7 +427,7 @@ func TestUserSync_SyncUserHook(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := ProvideUserSync(tt.fields.userService, userProtection, tt.fields.authInfoService, tt.fields.quotaService)
s := ProvideUserSync(tt.fields.userService, userProtection, tt.fields.authInfoService, tt.fields.quotaService, tracing.InitializeTracerForTest())
err := s.SyncUserHook(tt.args.ctx, tt.args.id, nil)
if tt.wantErr {
require.Error(t, err)
@ -462,7 +463,9 @@ func TestUserSync_FetchSyncedUserHook(t *testing.T) {
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
s := UserSync{}
s := UserSync{
tracer: tracing.InitializeTracerForTest(),
}
err := s.FetchSyncedUserHook(context.Background(), tt.identity, tt.req)
require.ErrorIs(t, err, tt.expectedErr)
})
@ -515,7 +518,7 @@ func TestUserSync_EnableDisabledUserHook(t *testing.T) {
return nil
}
s := UserSync{userService: userSvc}
s := UserSync{userService: userSvc, tracer: tracing.InitializeTracerForTest()}
err := s.EnableUserHook(context.Background(), tt.identity, nil)
require.NoError(t, err)
assert.Equal(t, tt.enableUser, called)

View File

@ -288,7 +288,7 @@ func TestIntegrationDashboardInheritedFolderRBAC(t *testing.T) {
UserID: u.ID,
OrgID: u.OrgID,
OrgRole: org.RoleAdmin,
Permissions: map[int64]map[string][]string{u.OrgID: accesscontrol.GroupScopesByAction([]accesscontrol.Permission{
Permissions: map[int64]map[string][]string{u.OrgID: accesscontrol.GroupScopesByActionContext(context.Background(), []accesscontrol.Permission{
{
Action: dashboards.ActionFoldersCreate,
}, {

View File

@ -126,7 +126,7 @@ func (a *authenticator) getSignedInUser(ctx context.Context, token string) (*use
if err != nil {
a.logger.Error("failed fetching permissions for user", "userID", signedInUser.UserID, "error", err)
}
signedInUser.Permissions[signedInUser.OrgID] = accesscontrol.GroupScopesByAction(permissions)
signedInUser.Permissions[signedInUser.OrgID] = accesscontrol.GroupScopesByActionContext(context.Background(), permissions)
}
return signedInUser, nil

View File

@ -1,6 +1,7 @@
package api
import (
"context"
"encoding/json"
"errors"
"io"
@ -663,5 +664,5 @@ search_base_dns = ["dc=grafana,dc=org"]`)
}
func userWithPermissions(orgID int64, permissions []accesscontrol.Permission) *user.SignedInUser {
return &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(permissions)}}
return &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByActionContext(context.Background(), permissions)}}
}

View File

@ -91,7 +91,7 @@ func TestIntegrationListPublicDashboard(t *testing.T) {
{Action: dashboards.ActionDashboardsRead, Scope: fmt.Sprintf("dashboards:uid:%s", cDash.UID)},
}
usr := &user.SignedInUser{UserID: 1, OrgID: orgId, Permissions: map[int64]map[string][]string{orgId: accesscontrol.GroupScopesByAction(permissions)}}
usr := &user.SignedInUser{UserID: 1, OrgID: orgId, Permissions: map[int64]map[string][]string{orgId: accesscontrol.GroupScopesByActionContext(context.Background(), permissions)}}
actest.AddUserPermissionToDB(t, sqlStore, usr)
@ -120,7 +120,7 @@ func TestIntegrationListPublicDashboard(t *testing.T) {
{Action: dashboards.ActionDashboardsRead, Scope: fmt.Sprintf("dashboards:uid:%s", cDash.UID)},
}
usr := &user.SignedInUser{UserID: 1, OrgID: orgId, Permissions: map[int64]map[string][]string{orgId: accesscontrol.GroupScopesByAction(permissions)}}
usr := &user.SignedInUser{UserID: 1, OrgID: orgId, Permissions: map[int64]map[string][]string{orgId: accesscontrol.GroupScopesByActionContext(context.Background(), permissions)}}
actest.AddUserPermissionToDB(t, sqlStore, usr)
@ -148,7 +148,7 @@ func TestIntegrationListPublicDashboard(t *testing.T) {
{Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:another-dashboard-2-uid"},
}
usr := &user.SignedInUser{UserID: 1, OrgID: orgId, Permissions: map[int64]map[string][]string{orgId: accesscontrol.GroupScopesByAction(permissions)}}
usr := &user.SignedInUser{UserID: 1, OrgID: orgId, Permissions: map[int64]map[string][]string{orgId: accesscontrol.GroupScopesByActionContext(context.Background(), permissions)}}
actest.AddUserPermissionToDB(t, sqlStore, usr)

View File

@ -199,7 +199,7 @@ func (s *StandardSearchService) getUser(ctx context.Context, backendUser *backen
return nil, errors.New("auth error")
}
usr.Permissions[orgId] = accesscontrol.GroupScopesByAction(permissions)
usr.Permissions[orgId] = accesscontrol.GroupScopesByActionContext(ctx, permissions)
return usr, nil
}

View File

@ -1,6 +1,7 @@
package api
import (
"context"
"encoding/json"
"fmt"
"net/http"
@ -87,7 +88,7 @@ func TestServiceAccountsAPI_CreateServiceAccount(t *testing.T) {
req := server.NewRequest(http.MethodPost, "/api/serviceaccounts/", strings.NewReader(tt.body))
webtest.RequestWithSignedInUser(req, &user.SignedInUser{
OrgRole: tt.basicRole, OrgID: 1, IsAnonymous: true,
Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}})
Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}})
res, err := server.SendJSON(req)
require.NoError(t, err)
@ -124,7 +125,7 @@ func TestServiceAccountsAPI_DeleteServiceAccount(t *testing.T) {
t.Run(tt.desc, func(t *testing.T) {
server := setupTests(t)
req := server.NewRequest(http.MethodDelete, fmt.Sprintf("/api/serviceaccounts/%d", tt.id), nil)
webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}})
webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}})
res, err := server.Send(req)
require.NoError(t, err)
@ -165,7 +166,7 @@ func TestServiceAccountsAPI_RetrieveServiceAccount(t *testing.T) {
a.service = &satests.FakeServiceAccountService{ExpectedServiceAccountProfile: tt.expectedSA}
})
req := server.NewGetRequest(fmt.Sprintf("/api/serviceaccounts/%d", tt.id))
webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}})
webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}})
res, err := server.Send(req)
require.NoError(t, err)
assert.Equal(t, tt.expectedCode, res.StatusCode)
@ -228,7 +229,7 @@ func TestServiceAccountsAPI_UpdateServiceAccount(t *testing.T) {
})
req := server.NewRequest(http.MethodPatch, fmt.Sprintf("/api/serviceaccounts/%d", tt.id), strings.NewReader(tt.body))
webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgRole: tt.basicRole, OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}})
webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgRole: tt.basicRole, OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}})
res, err := server.SendJSON(req)
require.NoError(t, err)
@ -282,7 +283,7 @@ func TestServiceAccountsAPI_MigrateApiKeysToServiceAccounts(t *testing.T) {
})
req := server.NewRequest(http.MethodPost, "/api/serviceaccounts/migrate", nil)
webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgRole: tt.basicRole, OrgID: tt.orgId, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}})
webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgRole: tt.basicRole, OrgID: tt.orgId, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}})
res, err := server.SendJSON(req)
require.NoError(t, err)

View File

@ -1,6 +1,7 @@
package api
import (
"context"
"fmt"
"net/http"
"strings"
@ -47,7 +48,7 @@ func TestServiceAccountsAPI_ListTokens(t *testing.T) {
a.service = &satests.FakeServiceAccountService{}
})
req := server.NewGetRequest(fmt.Sprintf("/api/serviceaccounts/%d/tokens", tt.id))
webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}})
webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}})
res, err := server.Send(req)
require.NoError(t, err)
@ -116,7 +117,7 @@ func TestServiceAccountsAPI_CreateToken(t *testing.T) {
}
})
req := server.NewRequest(http.MethodPost, fmt.Sprintf("/api/serviceaccounts/%d/tokens", tt.id), strings.NewReader(tt.body))
webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}})
webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}})
res, err := server.SendJSON(req)
require.NoError(t, err)
@ -168,7 +169,7 @@ func TestServiceAccountsAPI_DeleteToken(t *testing.T) {
})
req := server.NewRequest(http.MethodDelete, fmt.Sprintf("/api/serviceaccounts/%d/tokens/%d", tt.saID, tt.apikeyID), nil)
webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}})
webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}})
res, err := server.SendJSON(req)
require.NoError(t, err)

View File

@ -173,7 +173,7 @@ func TestIntegration_DashboardPermissionFilter(t *testing.T) {
recursiveQueriesAreSupported, err := store.RecursiveQueriesAreSupported()
require.NoError(t, err)
usr := &user.SignedInUser{OrgID: 1, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}
usr := &user.SignedInUser{OrgID: 1, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}}
for _, features := range []featuremgmt.FeatureToggles{featuremgmt.WithFeatures(), featuremgmt.WithFeatures(featuremgmt.FlagPermissionsFilterRemoveSubquery)} {
m := features.GetEnabled(context.Background())
@ -345,7 +345,7 @@ func TestIntegration_DashboardPermissionFilter_WithSelfContainedPermissions(t *t
recursiveQueriesAreSupported, err := store.RecursiveQueriesAreSupported()
require.NoError(t, err)
usr := &user.SignedInUser{OrgID: 1, OrgRole: org.RoleViewer, AuthenticatedBy: login.ExtendedJWTModule, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.signedInUserPermissions)}}
usr := &user.SignedInUser{OrgID: 1, OrgRole: org.RoleViewer, AuthenticatedBy: login.ExtendedJWTModule, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.signedInUserPermissions)}}
for _, features := range []featuremgmt.FeatureToggles{featuremgmt.WithFeatures(), featuremgmt.WithFeatures(featuremgmt.FlagPermissionsFilterRemoveSubquery)} {
m := features.GetEnabled(context.Background())
@ -456,7 +456,7 @@ func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) {
Action: dashboards.ActionFoldersWrite,
Scope: dashboards.ScopeFoldersAll,
})
usr := &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(tc.permissions)}}
usr := &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByActionContext(context.Background(), tc.permissions)}}
for _, features := range []featuremgmt.FeatureToggles{featuremgmt.WithFeatures(append(tc.features, featuremgmt.FlagAccessActionSets)...), featuremgmt.WithFeatures(tc.features...), featuremgmt.WithFeatures(append(tc.features, featuremgmt.FlagPermissionsFilterRemoveSubquery)...)} {
m := features.GetEnabled(context.Background())
@ -564,7 +564,7 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission
for _, tc := range testCases {
helperUser := &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, AuthenticatedBy: login.ExtendedJWTModule,
Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction([]accesscontrol.Permission{
Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByActionContext(context.Background(), []accesscontrol.Permission{
{
Action: dashboards.ActionFoldersCreate,
},
@ -583,7 +583,7 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission
}
t.Run(tc.desc+" with features "+strings.Join(keys, ","), func(t *testing.T) {
usr := &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, AuthenticatedBy: login.ExtendedJWTModule, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(tc.signedInUserPermissions)}}
usr := &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, AuthenticatedBy: login.ExtendedJWTModule, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByActionContext(context.Background(), tc.signedInUserPermissions)}}
db := setupNestedTest(t, helperUser, []accesscontrol.Permission{}, orgID, features)
recursiveQueriesAreSupported, err := db.RecursiveQueriesAreSupported()
require.NoError(t, err)
@ -693,7 +693,7 @@ func TestIntegration_DashboardNestedPermissionFilter_WithActionSets(t *testing.T
Scope: "folders:uid:unrelated"}, accesscontrol.Permission{
Action: dashboards.ActionDashboardsCreate,
Scope: "folders:uid:unrelated"})
usr := &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(tc.signedInUserPermissions)}}
usr := &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByActionContext(context.Background(), tc.signedInUserPermissions)}}
for _, features := range []featuremgmt.FeatureToggles{featuremgmt.WithFeatures(tc.features...), featuremgmt.WithFeatures(append(tc.features, featuremgmt.FlagPermissionsFilterRemoveSubquery)...)} {
m := features.GetEnabled(context.Background())

View File

@ -34,7 +34,7 @@ import (
func benchmarkDashboardPermissionFilter(b *testing.B, numUsers, numDashboards, numFolders, nestingLevel int) {
usr := user.SignedInUser{UserID: 1, OrgID: 1, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{
1: accesscontrol.GroupScopesByAction([]accesscontrol.Permission{
1: accesscontrol.GroupScopesByActionContext(context.Background(), []accesscontrol.Permission{
{
Action: dashboards.ActionFoldersCreate,
},

View File

@ -320,7 +320,7 @@ func TestBuilder_RBAC(t *testing.T) {
for _, tc := range testsCases {
t.Run(tc.desc, func(t *testing.T) {
if len(tc.userPermissions) > 0 {
user.Permissions = map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tc.userPermissions)}
user.Permissions = map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tc.userPermissions)}
}
builder := &searchstore.Builder{

View File

@ -2,6 +2,7 @@ package api
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
@ -550,7 +551,7 @@ func TestSSOSettingsAPI_List(t *testing.T) {
func getPermissionsForActionAndScope(action, scope string) map[int64]map[string][]string {
return map[int64]map[string][]string{
1: accesscontrol.GroupScopesByAction([]accesscontrol.Permission{{
1: accesscontrol.GroupScopesByActionContext(context.Background(), []accesscontrol.Permission{{
Action: action, Scope: scope,
}}),
}

View File

@ -281,5 +281,5 @@ func Test_getTeamMembershipUpdates(t *testing.T) {
}
func authedUserWithPermissions(userID, orgID int64, permissions []accesscontrol.Permission) *user.SignedInUser {
return &user.SignedInUser{UserID: userID, OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(permissions)}}
return &user.SignedInUser{UserID: userID, OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByActionContext(context.Background(), permissions)}}
}