Compare commits
2 Commits
sriram/SQL
...
bugfix/pro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a276557b0a | ||
|
|
2804351da1 |
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/api/dtos"
|
"github.com/grafana/grafana/pkg/api/dtos"
|
||||||
"github.com/grafana/grafana/pkg/api/response"
|
"github.com/grafana/grafana/pkg/api/response"
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
@@ -92,6 +93,11 @@ func (hs *HTTPServer) UpdateFolderPermissions(c *contextmodel.ReqContext) respon
|
|||||||
return apierrors.ToFolderErrorResponse(err)
|
return apierrors.ToFolderErrorResponse(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Block permission changes for folders managed by provisioning
|
||||||
|
if folder.ManagedBy == utils.ManagerKindRepo {
|
||||||
|
return response.Error(http.StatusForbidden, "Cannot update permissions for folders managed by provisioning.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
items := make([]*dashboards.DashboardACL, 0, len(apiCmd.Items))
|
items := make([]*dashboards.DashboardACL, 0, len(apiCmd.Items))
|
||||||
for _, item := range apiCmd.Items {
|
for _, item := range apiCmd.Items {
|
||||||
items = append(items, &dashboards.DashboardACL{
|
items = append(items, &dashboards.DashboardACL{
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
@@ -149,4 +150,58 @@ func TestHTTPServer_UpdateFolderPermissions(t *testing.T) {
|
|||||||
assert.Equal(t, http.StatusBadRequest, res.StatusCode)
|
assert.Equal(t, http.StatusBadRequest, res.StatusCode)
|
||||||
require.NoError(t, res.Body.Close())
|
require.NoError(t, res.Body.Close())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("should not be able to update permissions for folders managed by provisioning", func(t *testing.T) {
|
||||||
|
server := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||||
|
fakeFolderService := foldertest.NewFakeService()
|
||||||
|
fakeFolderService.ExpectedFolder = &folder.Folder{
|
||||||
|
ID: 1,
|
||||||
|
OrgID: 1,
|
||||||
|
UID: "1",
|
||||||
|
ManagedBy: utils.ManagerKindRepo,
|
||||||
|
}
|
||||||
|
hs.folderService = fakeFolderService
|
||||||
|
hs.folderPermissionsService = &actest.FakePermissionsService{
|
||||||
|
ExpectedPermissions: []accesscontrol.ResourcePermission{},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
body := `{"items": [{"role": "Viewer", "permission": 1}]}`
|
||||||
|
res, err := server.SendJSON(webtest.RequestWithSignedInUser(server.NewPostRequest("/api/folders/1/permissions", strings.NewReader(body)), userWithPermissions(1, []accesscontrol.Permission{
|
||||||
|
{Action: dashboards.ActionFoldersPermissionsWrite, Scope: "folders:uid:1"},
|
||||||
|
})))
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusForbidden, res.StatusCode)
|
||||||
|
|
||||||
|
var result map[string]interface{}
|
||||||
|
require.NoError(t, json.NewDecoder(res.Body).Decode(&result))
|
||||||
|
assert.Contains(t, result["message"].(string), "Cannot update permissions for folders managed by provisioning")
|
||||||
|
require.NoError(t, res.Body.Close())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should be able to update permissions for non-provisioned folders", func(t *testing.T) {
|
||||||
|
server := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||||
|
fakeFolderService := foldertest.NewFakeService()
|
||||||
|
fakeFolderService.ExpectedFolder = &folder.Folder{
|
||||||
|
ID: 1,
|
||||||
|
OrgID: 1,
|
||||||
|
UID: "1",
|
||||||
|
ManagedBy: utils.ManagerKindUnknown, // Not managed by provisioning
|
||||||
|
}
|
||||||
|
hs.folderService = fakeFolderService
|
||||||
|
hs.folderPermissionsService = &actest.FakePermissionsService{
|
||||||
|
ExpectedPermissions: []accesscontrol.ResourcePermission{},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
body := `{"items": [{"role": "Viewer", "permission": 1}]}`
|
||||||
|
res, err := server.SendJSON(webtest.RequestWithSignedInUser(server.NewPostRequest("/api/folders/1/permissions", strings.NewReader(body)), userWithPermissions(1, []accesscontrol.Permission{
|
||||||
|
{Action: dashboards.ActionFoldersPermissionsWrite, Scope: "folders:uid:1"},
|
||||||
|
})))
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||||
|
require.NoError(t, res.Body.Close())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
193
pkg/tests/apis/provisioning/folderpermissions_test.go
Normal file
193
pkg/tests/apis/provisioning/folderpermissions_test.go
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
package provisioning
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
foldersV1 "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
|
"github.com/grafana/grafana/pkg/util/testutil"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// We currently block permission updates for folders managed by provisioning.
|
||||||
|
func TestIntegrationFolderPermissions_ProvisionedFolders(t *testing.T) {
|
||||||
|
testutil.SkipIntegrationTestInShortMode(t)
|
||||||
|
|
||||||
|
helper := runGrafana(t)
|
||||||
|
helper.CreateRepo(t, TestRepo{
|
||||||
|
Name: "test-repo",
|
||||||
|
Target: "folder",
|
||||||
|
ExpectedFolders: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should fail to update permissions for provisioned folder", func(t *testing.T) {
|
||||||
|
folders, err := helper.Folders.Resource.List(t.Context(), metav1.ListOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, folders.Items, 1)
|
||||||
|
|
||||||
|
managedFolderName := folders.Items[0].GetName()
|
||||||
|
permissionsPayload := map[string]interface{}{
|
||||||
|
"items": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"role": "Viewer",
|
||||||
|
"permission": 1, // View permission
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
permissionsURL := fmt.Sprintf("/api/folders/%s/permissions", managedFolderName)
|
||||||
|
permissionsData, code, err := postHelper(t, *helper.K8sTestHelper, permissionsURL, permissionsPayload, helper.Org1.Admin)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, http.StatusForbidden, code)
|
||||||
|
require.NotNil(t, permissionsData)
|
||||||
|
require.Equal(t, "Cannot update permissions for folders managed by provisioning.", permissionsData["message"])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should fail to update permissions for nested provisioned folder", func(t *testing.T) {
|
||||||
|
// Create a new repo with nested folder structure
|
||||||
|
nestedHelper := runGrafana(t)
|
||||||
|
nestedHelper.CreateRepo(t, TestRepo{
|
||||||
|
Name: "nested-folder-repo",
|
||||||
|
Target: "instance",
|
||||||
|
Copies: map[string]string{
|
||||||
|
"testdata/all-panels.json": "folder/subfolder/dashboard.json",
|
||||||
|
},
|
||||||
|
SkipResourceAssertions: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
folders, err := nestedHelper.Folders.Resource.List(t.Context(), metav1.ListOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.GreaterOrEqual(t, len(folders.Items), 2, "should have at least 2 folders (root and nested)")
|
||||||
|
|
||||||
|
// Find the nested folder by checking for sourcePath annotation
|
||||||
|
// The root folder has sourcePath equal to the repo name, nested folders have paths like "folder" or "folder/subfolder"
|
||||||
|
var nestedFolder *unstructured.Unstructured
|
||||||
|
repoName := "nested-folder-repo"
|
||||||
|
for i := range folders.Items {
|
||||||
|
sourcePath, found, _ := unstructured.NestedString(folders.Items[i].Object, "metadata", "annotations", utils.AnnoKeySourcePath)
|
||||||
|
if found && sourcePath != "" && sourcePath != repoName {
|
||||||
|
// This is a nested folder (not the root folder)
|
||||||
|
nestedFolder = &folders.Items[i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.NotNil(t, nestedFolder, "should find a nested folder")
|
||||||
|
|
||||||
|
nestedFolderName := nestedFolder.GetName()
|
||||||
|
require.Contains(t, nestedFolder.GetAnnotations(), utils.AnnoKeyManagerKind, "nested folder should be managed")
|
||||||
|
require.Contains(t, nestedFolder.GetAnnotations(), utils.AnnoKeyManagerIdentity, "nested folder should be managed")
|
||||||
|
|
||||||
|
permissionsPayload := map[string]interface{}{
|
||||||
|
"items": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"role": "Viewer",
|
||||||
|
"permission": 1, // View permission
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
permissionsURL := fmt.Sprintf("/api/folders/%s/permissions", nestedFolderName)
|
||||||
|
permissionsData, code, err := postHelper(t, *nestedHelper.K8sTestHelper, permissionsURL, permissionsPayload, nestedHelper.Org1.Admin)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, http.StatusForbidden, code)
|
||||||
|
require.NotNil(t, permissionsData)
|
||||||
|
require.Equal(t, "Cannot update permissions for folders managed by provisioning.", permissionsData["message"])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntegrationFolderPermissions_UnprovisionedFolders(t *testing.T) {
|
||||||
|
const repo = "test-repo"
|
||||||
|
helper := runGrafana(t)
|
||||||
|
helper.CreateRepo(t, TestRepo{
|
||||||
|
Name: repo,
|
||||||
|
Target: "folder",
|
||||||
|
ExpectedFolders: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should update permissions when folder is released", func(t *testing.T) {
|
||||||
|
folders, err := helper.Folders.Resource.List(t.Context(), metav1.ListOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, folders.Items, 1)
|
||||||
|
managedFolderName := folders.Items[0].GetName()
|
||||||
|
require.Contains(t, folders.Items[0].GetAnnotations(), utils.AnnoKeyManagerKind, "folder should be managed")
|
||||||
|
require.Contains(t, folders.Items[0].GetAnnotations(), utils.AnnoKeyManagerIdentity, "folder should be managed")
|
||||||
|
|
||||||
|
_, err = helper.Repositories.Resource.Patch(t.Context(), repo, types.JSONPatchType, []byte(`[
|
||||||
|
{
|
||||||
|
"op": "replace",
|
||||||
|
"path": "/metadata/finalizers",
|
||||||
|
"value": ["cleanup", "release-orphan-resources"]
|
||||||
|
}
|
||||||
|
]`), metav1.PatchOptions{})
|
||||||
|
require.NoError(t, err, "should successfully patch finalizers")
|
||||||
|
|
||||||
|
require.NoError(t, helper.Repositories.Resource.Delete(t.Context(), repo, metav1.DeleteOptions{}))
|
||||||
|
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||||
|
_, err := helper.Repositories.Resource.Get(t.Context(), repo, metav1.GetOptions{})
|
||||||
|
assert.True(collect, apierrors.IsNotFound(err), "repository should be deleted")
|
||||||
|
}, time.Second*10, time.Millisecond*50, "repository should be deleted")
|
||||||
|
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||||
|
foundFolders, err := helper.Folders.Resource.List(t.Context(), metav1.ListOptions{})
|
||||||
|
require.NoError(t, err, "can list values")
|
||||||
|
for _, v := range foundFolders.Items {
|
||||||
|
assert.NotContains(t, v.GetAnnotations(), utils.AnnoKeyManagerKind)
|
||||||
|
assert.NotContains(t, v.GetAnnotations(), utils.AnnoKeyManagerIdentity)
|
||||||
|
assert.NotContains(t, v.GetAnnotations(), utils.AnnoKeySourcePath)
|
||||||
|
assert.NotContains(t, v.GetAnnotations(), utils.AnnoKeySourceChecksum)
|
||||||
|
}
|
||||||
|
}, time.Second*20, time.Millisecond*10, "Expected folders to be released")
|
||||||
|
|
||||||
|
permissionsPayload := map[string]interface{}{
|
||||||
|
"items": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"role": "Viewer",
|
||||||
|
"permission": 1, // View permission
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
permissionsURL := fmt.Sprintf("/api/folders/%s/permissions", managedFolderName)
|
||||||
|
permissionsData, code, err := postHelper(t, *helper.K8sTestHelper, permissionsURL, permissionsPayload, helper.Org1.Admin)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, http.StatusOK, code)
|
||||||
|
require.NotNil(t, permissionsData)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should update permissions for unmanaged folder", func(t *testing.T) {
|
||||||
|
unmanagedFolder := &unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"apiVersion": foldersV1.FolderResourceInfo.GroupVersion().String(),
|
||||||
|
"kind": foldersV1.FolderResourceInfo.GroupVersionKind().Kind,
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"generateName": "test-folder-",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"title": "Unmanaged Folder",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
createdFolder, err := helper.Folders.Resource.Create(t.Context(), unmanagedFolder, metav1.CreateOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, createdFolder)
|
||||||
|
|
||||||
|
unmanagedFolderName := createdFolder.GetName()
|
||||||
|
permissionsPayload := map[string]interface{}{
|
||||||
|
"items": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"role": "Editor",
|
||||||
|
"permission": 2, // Edit permission
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
permissionsURL := fmt.Sprintf("/api/folders/%s/permissions", unmanagedFolderName)
|
||||||
|
permissionsData, code, err := postHelper(t, *helper.K8sTestHelper, permissionsURL, permissionsPayload, helper.Org1.Admin)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, http.StatusOK, code)
|
||||||
|
require.NotNil(t, permissionsData)
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user