diff --git a/pkg/services/authn/clients/render.go b/pkg/services/authn/clients/render.go index d5ecf60fc41..9e77053b893 100644 --- a/pkg/services/authn/clients/render.go +++ b/pkg/services/authn/clients/render.go @@ -41,9 +41,13 @@ func (c *Render) Authenticate(ctx context.Context, r *authn.Request) (*authn.Ide } if renderUsr.UserID <= 0 { + identityType := claims.TypeAnonymous + if org.RoleType(renderUsr.OrgRole) == org.RoleAdmin { + identityType = claims.TypeRenderService + } return &authn.Identity{ ID: "0", - Type: claims.TypeRenderService, + Type: identityType, OrgID: renderUsr.OrgID, OrgRoles: map[int64]org.RoleType{renderUsr.OrgID: org.RoleType(renderUsr.OrgRole)}, ClientParams: authn.ClientParams{SyncPermissions: true}, diff --git a/pkg/services/authn/clients/render_test.go b/pkg/services/authn/clients/render_test.go index 994edbd7fdd..a84c48ee68e 100644 --- a/pkg/services/authn/clients/render_test.go +++ b/pkg/services/authn/clients/render_test.go @@ -29,7 +29,29 @@ func TestRender_Authenticate(t *testing.T) { tests := []TestCase{ { - desc: "expect valid render key to return render user identity", + desc: "expect valid render key to return anonymous user identity for org role Viewer", + renderKey: "123", + req: &authn.Request{ + HTTPRequest: &http.Request{ + Header: map[string][]string{"Cookie": {"renderKey=123"}}, + }, + }, + expectedIdentity: &authn.Identity{ + ID: "0", + Type: claims.TypeAnonymous, + OrgID: 1, + OrgRoles: map[int64]org.RoleType{1: org.RoleViewer}, + AuthenticatedBy: login.RenderModule, + ClientParams: authn.ClientParams{SyncPermissions: true}, + }, + expectedRenderUsr: &rendering.RenderUser{ + OrgID: 1, + UserID: 0, + OrgRole: "Viewer", + }, + }, + { + desc: "expect valid render key to return render user identity for org role Admin", renderKey: "123", req: &authn.Request{ HTTPRequest: &http.Request{ @@ -40,14 +62,14 @@ func TestRender_Authenticate(t *testing.T) { ID: "0", Type: claims.TypeRenderService, OrgID: 1, - OrgRoles: map[int64]org.RoleType{1: org.RoleViewer}, + OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin}, AuthenticatedBy: login.RenderModule, ClientParams: authn.ClientParams{SyncPermissions: true}, }, expectedRenderUsr: &rendering.RenderUser{ OrgID: 1, UserID: 0, - OrgRole: "Viewer", + OrgRole: "Admin", }, }, { diff --git a/pkg/services/authz/rbac/service.go b/pkg/services/authz/rbac/service.go index b6a9bd9b863..9ccd1427035 100644 --- a/pkg/services/authz/rbac/service.go +++ b/pkg/services/authz/rbac/service.go @@ -98,7 +98,7 @@ func (s *Service) Check(ctx context.Context, req *authzv1.CheckRequest) (*authzv } ctx = request.WithNamespace(ctx, req.GetNamespace()) - permissions, err := s.getUserPermissions(ctx, checkReq.Namespace, checkReq.IdentityType, checkReq.UserUID, checkReq.Action) + permissions, err := s.getIdentityPermissions(ctx, checkReq.Namespace, checkReq.IdentityType, checkReq.UserUID, checkReq.Action) if err != nil { ctxLogger.Error("could not get user permissions", "subject", req.GetSubject(), "error", err) return deny, err @@ -124,7 +124,7 @@ func (s *Service) List(ctx context.Context, req *authzv1.ListRequest) (*authzv1. } ctx = request.WithNamespace(ctx, req.GetNamespace()) - permissions, err := s.getUserPermissions(ctx, listReq.Namespace, listReq.IdentityType, listReq.UserUID, listReq.Action) + permissions, err := s.getIdentityPermissions(ctx, listReq.Namespace, listReq.IdentityType, listReq.UserUID, listReq.Action) if err != nil { ctxLogger.Error("could not get user permissions", "subject", req.GetSubject(), "error", err) return nil, err @@ -226,8 +226,8 @@ func (s *Service) validateSubject(ctx context.Context, subject string) (string, if err != nil { return "", "", err } - // Permission check currently only checks user, anonymous user and service account permissions - if !(identityType == claims.TypeUser || identityType == claims.TypeServiceAccount || identityType == claims.TypeAnonymous) { + // Permission check currently only checks user, anonymous user, service account and renderer permissions + if !(identityType == claims.TypeUser || identityType == claims.TypeServiceAccount || identityType == claims.TypeAnonymous || identityType == claims.TypeRenderService) { ctxLogger.Error("unsupported identity type", "type", identityType) return "", "", status.Error(codes.PermissionDenied, "unsupported identity type") } @@ -252,8 +252,8 @@ func (s *Service) validateAction(ctx context.Context, group, resource, verb stri return action, nil } -func (s *Service) getUserPermissions(ctx context.Context, ns claims.NamespaceInfo, idType claims.IdentityType, userID, action string) (map[string]bool, error) { - ctx, span := s.tracer.Start(ctx, "authz_direct_db.service.getUserPermissions") +func (s *Service) getIdentityPermissions(ctx context.Context, ns claims.NamespaceInfo, idType claims.IdentityType, userID, action string) (map[string]bool, error) { + ctx, span := s.tracer.Start(ctx, "authz_direct_db.service.getIdentityPermissions") defer span.End() // When checking folder creation permissions, also check edit and admin action sets for folder, as the scoped folder create actions aren't stored in the DB separately @@ -263,9 +263,21 @@ func (s *Service) getUserPermissions(ctx context.Context, ns claims.NamespaceInf actionSets = append(actionSets, "folders:admin") } - if idType == claims.TypeAnonymous { + switch idType { + case claims.TypeAnonymous: return s.getAnonymousPermissions(ctx, ns, action, actionSets) + case claims.TypeRenderService: + return s.getRendererPermissions(ctx, action) + case claims.TypeUser, claims.TypeServiceAccount: + return s.getUserPermissions(ctx, ns, userID, action, actionSets) + default: + return nil, fmt.Errorf("unsupported identity type: %s", idType) } +} + +func (s *Service) getUserPermissions(ctx context.Context, ns claims.NamespaceInfo, userID, action string, actionSets []string) (map[string]bool, error) { + ctx, span := s.tracer.Start(ctx, "authz_direct_db.service.getUserPermissions") + defer span.End() userIdentifiers, err := s.GetUserIdentifiers(ctx, ns, userID) if err != nil { @@ -342,6 +354,17 @@ func (s *Service) getAnonymousPermissions(ctx context.Context, ns claims.Namespa return res.(map[string]bool), nil } +// Renderer is granted permissions to read all dashboards and folders, and no other permissions +func (s *Service) getRendererPermissions(ctx context.Context, action string) (map[string]bool, error) { + _, span := s.tracer.Start(ctx, "authz_direct_db.service.getRendererPermissions") + defer span.End() + + if action == "dashboards:read" || action == "folders:read" || action == "datasources:read" { + return map[string]bool{"*": true}, nil + } + return map[string]bool{}, nil +} + func (s *Service) GetUserIdentifiers(ctx context.Context, ns claims.NamespaceInfo, userUID string) (*store.UserIdentifiers, error) { uidCacheKey := userIdentifierCacheKey(ns.Value, userUID) if cached, ok := s.idCache.Get(uidCacheKey); ok { diff --git a/pkg/services/authz/rbac/service_test.go b/pkg/services/authz/rbac/service_test.go index 63d5f393cab..f4f301008e3 100644 --- a/pkg/services/authz/rbac/service_test.go +++ b/pkg/services/authz/rbac/service_test.go @@ -379,7 +379,7 @@ func TestService_getUserPermissions(t *testing.T) { teamCache: localcache.New(shortCacheTTL, shortCleanupInterval), } - perms, err := s.getUserPermissions(ctx, ns, claims.TypeUser, userID.UID, action) + perms, err := s.getIdentityPermissions(ctx, ns, claims.TypeUser, userID.UID, action) require.NoError(t, err) require.Len(t, perms, len(tc.expectedPerms)) for _, perm := range tc.permissions {