diff --git a/pkg/registry/apis/provisioning/files.go b/pkg/registry/apis/provisioning/files.go index af611489ad5..be36d540c68 100644 --- a/pkg/registry/apis/provisioning/files.go +++ b/pkg/registry/apis/provisioning/files.go @@ -15,6 +15,7 @@ import ( provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" "github.com/grafana/grafana/apps/provisioning/pkg/repository" "github.com/grafana/grafana/apps/provisioning/pkg/safepath" + "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/registry/apis/provisioning/resources" ) @@ -235,8 +236,17 @@ func (c *filesConnector) Connect(ctx context.Context, name string, opts runtime. // listFolderFiles returns a list of files in a folder func (c *filesConnector) listFolderFiles(ctx context.Context, filePath string, ref string, readWriter repository.ReaderWriter) (*provisioning.FileList, error) { - // Authorization is handled at the route level - any authenticated user who can access - // the files endpoint can list files. The AccessClient controls per-file access. + id, err := identity.GetRequester(ctx) + if err != nil { + return nil, apierrors.NewUnauthorized("missing auth info in context") + } + + // Check admin access - the frontend provisioning pages require admin access (controlled by navtree). + // AccessPolicy identities (ST->MT flow) are trusted internal callers. + if !isAdminOrAccessPolicy(id) { + return nil, apierrors.NewForbidden(resources.DashboardResource.GroupResource(), "", + fmt.Errorf("admin role is required")) + } // TODO: Implement folder navigation if len(filePath) > 0 { diff --git a/pkg/registry/apis/provisioning/register.go b/pkg/registry/apis/provisioning/register.go index 8fe384123f5..a955bd0bc85 100644 --- a/pkg/registry/apis/provisioning/register.go +++ b/pkg/registry/apis/provisioning/register.go @@ -422,25 +422,42 @@ func (b *APIBuilder) authorizeRepositorySubresource(a authorizer.Attributes, id } } +// isAdminOrAccessPolicy checks if the requester is either an admin user or an AccessPolicy identity. +// AccessPolicy identities are used in trusted internal flows (e.g., ST->MT) and don't have org roles, +// so we allow them through. Regular users must have admin role. +func isAdminOrAccessPolicy(id identity.Requester) bool { + // AccessPolicy identities are trusted internal callers (ST->MT flow) + if authlib.IsIdentityType(id.GetIdentityType(), authlib.TypeAccessPolicy) { + return true + } + // Regular users need admin role + return id.GetOrgRole().Includes(identity.RoleAdmin) +} + // authorizeStats handles authorization for stats resource. -func (b *APIBuilder) authorizeStats(_ identity.Requester) (authorizer.Decision, string, error) { +func (b *APIBuilder) authorizeStats(id identity.Requester) (authorizer.Decision, string, error) { // Stats is a read-only endpoint that returns resource counts. - // Any authenticated user can access it - authentication is verified before this point. - return authorizer.DecisionAllow, "", nil + // The frontend provisioning pages require admin access (controlled by navtree). + if isAdminOrAccessPolicy(id) { + return authorizer.DecisionAllow, "", nil + } + return authorizer.DecisionDeny, "admin role is required", nil } // authorizeSettings handles authorization for settings resource. -func (b *APIBuilder) authorizeSettings(_ identity.Requester) (authorizer.Decision, string, error) { +func (b *APIBuilder) authorizeSettings(id identity.Requester) (authorizer.Decision, string, error) { // Settings is a read-only endpoint that returns available repository types and configuration. - // Any authenticated user can access it - authentication is verified before this point. - // The frontend needs this to display the provisioning wizard. - return authorizer.DecisionAllow, "", nil + // The frontend provisioning pages require admin access (controlled by navtree). + if isAdminOrAccessPolicy(id) { + return authorizer.DecisionAllow, "", nil + } + return authorizer.DecisionDeny, "admin role is required", nil } // authorizeJobs handles authorization for job resources. func (b *APIBuilder) authorizeJobs(id identity.Requester) (authorizer.Decision, string, error) { // Jobs are shown on the configuration page. - if id.GetOrgRole().Includes(identity.RoleAdmin) { + if isAdminOrAccessPolicy(id) { return authorizer.DecisionAllow, "", nil } return authorizer.DecisionDeny, "admin role is required", nil