diff --git a/pkg/registry/apis/provisioning/register.go b/pkg/registry/apis/provisioning/register.go index ff29b948faa..4ed4594d391 100644 --- a/pkg/registry/apis/provisioning/register.go +++ b/pkg/registry/apis/provisioning/register.go @@ -330,7 +330,7 @@ func (b *APIBuilder) GetAuthorizer() authorizer.Authorizer { // // Repositories: // - CRUD: repositories:create/read/write/delete -// - Subresources (files, refs, resources, history, status): repositories:read +// - Subresources: files (any auth), refs (editor), resources/history/status (admin) // - Test: repositories:write // - Jobs subresource: jobs:create/read // @@ -413,8 +413,18 @@ func (b *APIBuilder) authorizeRepositorySubresource(ctx context.Context, a autho case "files": return authorizer.DecisionAllow, "", nil - // Read-only subresources: refs, resources, history, status - case "refs", "resources", "history", "status": + // refs subresource - editors need to see branches to push changes + case "refs": + return toAuthorizerDecision(b.accessWithEditor.Check(ctx, authlib.CheckRequest{ + Verb: apiutils.VerbGet, + Group: provisioning.GROUP, + Resource: provisioning.RepositoryResourceInfo.GetName(), + Name: a.GetName(), + Namespace: a.GetNamespace(), + }, "")) + + // Read-only subresources: resources, history, status (admin only) + case "resources", "history", "status": return toAuthorizerDecision(b.accessWithAdmin.Check(ctx, authlib.CheckRequest{ Verb: apiutils.VerbGet, Group: provisioning.GROUP, diff --git a/pkg/tests/apis/provisioning/repository_test.go b/pkg/tests/apis/provisioning/repository_test.go index 877ed0d5f98..03fcb4394ca 100644 --- a/pkg/tests/apis/provisioning/repository_test.go +++ b/pkg/tests/apis/provisioning/repository_test.go @@ -950,3 +950,65 @@ func TestIntegrationProvisioning_JobPermissions(t *testing.T) { require.Equal(t, http.StatusAccepted, statusCode, "should return 202 Accepted") }) } + +func TestIntegrationProvisioning_RefsPermissions(t *testing.T) { + testutil.SkipIntegrationTestInShortMode(t) + + helper := runGrafana(t) + ctx := context.Background() + + const repo = "refs-permissions-test" + testRepo := TestRepo{ + Name: repo, + Target: "folder", + Copies: map[string]string{}, // No files needed for this test + ExpectedDashboards: 0, + ExpectedFolders: 1, // Repository creates a folder + } + helper.CreateRepo(t, testRepo) + + t.Run("editor can GET refs", func(t *testing.T) { + var statusCode int + result := helper.EditorREST.Get(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("refs"). + Do(ctx).StatusCode(&statusCode) + + require.NoError(t, result.Error(), "editor should be able to GET refs") + require.Equal(t, http.StatusOK, statusCode, "should return 200 OK") + + // Verify we can parse the refs + refs := &provisioning.RefList{} + err := result.Into(refs) + require.NoError(t, err, "should parse refs response") + }) + + t.Run("viewer cannot GET refs", func(t *testing.T) { + var statusCode int + result := helper.ViewerREST.Get(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("refs"). + Do(ctx).StatusCode(&statusCode) + + require.Error(t, result.Error(), "viewer should not be able to GET refs") + require.Equal(t, http.StatusForbidden, statusCode, "should return 403 Forbidden") + require.True(t, apierrors.IsForbidden(result.Error()), "error should be forbidden") + }) + + t.Run("admin can GET refs", func(t *testing.T) { + var statusCode int + result := helper.AdminREST.Get(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("refs"). + Do(ctx).StatusCode(&statusCode) + + require.NoError(t, result.Error(), "admin should be able to GET refs") + require.Equal(t, http.StatusOK, statusCode, "should return 200 OK") + }) +}