From 6eba5d74e1da500b680c917c69b003198159bc5a Mon Sep 17 00:00:00 2001 From: Stephanie Hingtgen Date: Thu, 10 Apr 2025 02:51:10 -0600 Subject: [PATCH] Anonymous access: Allow setting org role in new authz service (#103669) * Anonymous access: Allow setting org role in new authz service * back out change that is not needed; rename struct * cleanup * Fix tests --------- Co-authored-by: Gabriel Mabille --- pkg/services/authz/rbac.go | 7 +++ pkg/services/authz/rbac/service.go | 12 +++- pkg/services/authz/rbac/service_test.go | 84 +++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 1 deletion(-) diff --git a/pkg/services/authz/rbac.go b/pkg/services/authz/rbac.go index d3eca942323..d0ce26f0a9e 100644 --- a/pkg/services/authz/rbac.go +++ b/pkg/services/authz/rbac.go @@ -70,6 +70,11 @@ func ProvideAuthZClient( default: sql := legacysql.NewDatabaseProvider(db) + rbacSettings := rbac.Settings{} + if cfg != nil { + rbacSettings.AnonOrgRole = cfg.Anonymous.OrgRole + } + // Register the server server := rbac.NewService( sql, @@ -86,6 +91,7 @@ func ProvideAuthZClient( tracer, reg, cache.NewLocalCache(cache.Config{Expiry: 5 * time.Minute, CleanupInterval: 10 * time.Minute}), + rbacSettings, ) channel := &inprocgrpc.Channel{} @@ -209,6 +215,7 @@ func RegisterRBACAuthZService( tracer, reg, cache, + rbac.Settings{}, // anonymous org role can only be set in-proc ) srv := handler.GetServer() diff --git a/pkg/services/authz/rbac/service.go b/pkg/services/authz/rbac/service.go index 61a86c1a569..46a24006fdf 100644 --- a/pkg/services/authz/rbac/service.go +++ b/pkg/services/authz/rbac/service.go @@ -42,6 +42,7 @@ type Service struct { folderStore store.FolderStore permissionStore store.PermissionStore identityStore legacy.LegacyIdentityStore + settings Settings mapper mapper @@ -61,6 +62,10 @@ type Service struct { folderCache *cacheWrap[folderTree] } +type Settings struct { + AnonOrgRole string +} + func NewService( sql legacysql.LegacyDatabaseProvider, folderStore store.FolderStore, @@ -70,12 +75,17 @@ func NewService( tracer tracing.Tracer, reg prometheus.Registerer, cache cache.Cache, + settings Settings, ) *Service { + if settings.AnonOrgRole == "" { + settings.AnonOrgRole = "Viewer" + } return &Service{ store: store.NewStore(sql, tracer), folderStore: folderStore, permissionStore: permissionStore, identityStore: identityStore, + settings: settings, logger: logger, tracer: tracer, metrics: newMetrics(reg), @@ -422,7 +432,7 @@ func (s *Service) getAnonymousPermissions(ctx context.Context, ns types.Namespac anonPermKey := anonymousPermCacheKey(ns.Value, action) res, err, _ := s.sf.Do(anonPermKey+"_getAnonymousPermissions", func() (interface{}, error) { - permissions, err := s.permissionStore.GetUserPermissions(ctx, ns, store.PermissionsQuery{Action: action, ActionSets: actionSets, Role: "Viewer"}) + permissions, err := s.permissionStore.GetUserPermissions(ctx, ns, store.PermissionsQuery{Action: action, ActionSets: actionSets, Role: s.settings.AnonOrgRole}) if err != nil { return nil, err } diff --git a/pkg/services/authz/rbac/service_test.go b/pkg/services/authz/rbac/service_test.go index 454d9406c66..660669e8ba0 100644 --- a/pkg/services/authz/rbac/service_test.go +++ b/pkg/services/authz/rbac/service_test.go @@ -1288,6 +1288,89 @@ func TestService_List(t *testing.T) { }) } +func TestService_getAnonymousPermissions(t *testing.T) { + type testCase struct { + name string + permissions []accesscontrol.Permission + action string + expectedPerms map[string]bool + expectedError bool + anonRole string + } + + testCases := []testCase{ + { + name: "should return permissions from store if not in cache", + permissions: []accesscontrol.Permission{ + {Action: "dashboards:read", Scope: "dashboards:uid:some_dashboard"}, + }, + action: "dashboards:read", + expectedPerms: map[string]bool{"dashboards:uid:some_dashboard": true}, + expectedError: false, + anonRole: "Viewer", + }, + { + name: "should return error if store fails", + permissions: nil, + action: "dashboards:read", + expectedPerms: nil, + expectedError: true, + anonRole: "Viewer", + }, + { + name: "should handle wildcard permissions", + permissions: []accesscontrol.Permission{ + {Action: "dashboards:read", Scope: "*", Kind: "*"}, + }, + action: "dashboards:read", + expectedPerms: map[string]bool{"*": true}, + expectedError: false, + anonRole: "Viewer", + }, + { + name: "should use custom anonymous role when specified", + permissions: []accesscontrol.Permission{ + {Action: "dashboards:read", Scope: "dashboards:uid:some_dashboard"}, + }, + action: "dashboards:read", + expectedPerms: map[string]bool{"dashboards:uid:some_dashboard": true}, + expectedError: false, + anonRole: "Editor", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + s := setupService() + if tc.anonRole != "" { + s.settings.AnonOrgRole = tc.anonRole + } + ns := types.NamespaceInfo{Value: "stacks-12", OrgID: 1, StackID: 12} + store := &fakeStore{ + userPermissions: tc.permissions, + err: tc.expectedError, + disableNsCheck: true, + } + s.store = store + s.permissionStore = store + + perms, err := s.getAnonymousPermissions(ctx, ns, tc.action, []string{}) + if tc.expectedError { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, tc.expectedPerms, perms) + + // cache should then be set + cached, ok := s.permCache.Get(ctx, anonymousPermCacheKey(ns.Value, tc.action)) + require.True(t, ok) + require.Equal(t, tc.expectedPerms, cached) + }) + } +} + func TestService_CacheList(t *testing.T) { callingService := authn.NewAccessTokenAuthInfo(authn.Claims[authn.AccessTokenClaims]{ Claims: jwt.Claims{ @@ -1337,6 +1420,7 @@ func setupService() *Service { teamCache: newCacheWrap[[]int64](cache, logger, shortCacheTTL), basicRoleCache: newCacheWrap[store.BasicRole](cache, logger, longCacheTTL), folderCache: newCacheWrap[folderTree](cache, logger, shortCacheTTL), + settings: Settings{AnonOrgRole: "Viewer"}, store: fStore, permissionStore: fStore, folderStore: fStore,