mirror of https://github.com/grafana/grafana
Library panels: Remove `libraryPanelRBAC` feature flag, and enable rbac by default (#107222)
parent
3cd29d2cdd
commit
bc231922af
|
|
@ -1,469 +1,372 @@ |
||||
package libraryelements |
||||
package libraryelements_test |
||||
|
||||
import ( |
||||
"bytes" |
||||
"context" |
||||
"encoding/json" |
||||
"fmt" |
||||
"io" |
||||
"net/http" |
||||
"testing" |
||||
|
||||
"github.com/google/go-cmp/cmp" |
||||
"github.com/stretchr/testify/require" |
||||
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol" |
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" |
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/actest" |
||||
"github.com/grafana/grafana/pkg/services/dashboards" |
||||
"github.com/grafana/grafana/pkg/services/featuremgmt" |
||||
"github.com/grafana/grafana/pkg/services/folder/folderimpl" |
||||
"github.com/grafana/grafana-openapi-client-go/models" |
||||
"github.com/grafana/grafana/pkg/infra/db" |
||||
"github.com/grafana/grafana/pkg/infra/tracing" |
||||
"github.com/grafana/grafana/pkg/services/libraryelements/model" |
||||
"github.com/grafana/grafana/pkg/services/org" |
||||
"github.com/grafana/grafana/pkg/web" |
||||
"github.com/grafana/grafana/pkg/services/org/orgimpl" |
||||
"github.com/grafana/grafana/pkg/services/quota/quotaimpl" |
||||
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" |
||||
"github.com/grafana/grafana/pkg/services/user" |
||||
"github.com/grafana/grafana/pkg/services/user/userimpl" |
||||
"github.com/grafana/grafana/pkg/setting" |
||||
"github.com/grafana/grafana/pkg/tests/testinfra" |
||||
) |
||||
|
||||
func TestLibraryElementPermissionsGeneralFolder(t *testing.T) { |
||||
testScenario(t, "When user with tries to create a library panel in the General folder, it should return correct status", |
||||
func(t *testing.T, sc scenarioContext) { |
||||
sc.reqContext.OrgRole = org.RoleViewer |
||||
command := getCreatePanelCommand(0, "", "Library Panel Name") |
||||
sc.reqContext.Req.Body = mockRequestBody(command) |
||||
resp := sc.service.createHandler(sc.reqContext) |
||||
require.Equal(t, http.StatusForbidden, resp.Status()) |
||||
|
||||
sc.reqContext.OrgRole = org.RoleEditor |
||||
sc.reqContext.Req.Body = mockRequestBody(command) |
||||
resp = sc.service.createHandler(sc.reqContext) |
||||
require.Equal(t, http.StatusOK, resp.Status()) |
||||
func TestIntegrationLibraryElementPermissions(t *testing.T) { |
||||
if testing.Short() { |
||||
t.Skip("skipping integration test") |
||||
} |
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{}) |
||||
|
||||
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path) |
||||
quotaService := quotaimpl.ProvideService(env.SQLStore, env.Cfg) |
||||
orgService, err := orgimpl.ProvideService(env.SQLStore, env.Cfg, quotaService) |
||||
require.NoError(t, err) |
||||
|
||||
sharedOrg, err := orgService.CreateWithMember(context.Background(), &org.CreateOrgCommand{Name: "test org"}) |
||||
require.NoError(t, err) |
||||
|
||||
createUserInOrg(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ |
||||
DefaultOrgRole: string(org.RoleViewer), |
||||
Password: "viewer", |
||||
Login: "viewer", |
||||
OrgID: sharedOrg.ID, |
||||
}) |
||||
createUserInOrg(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ |
||||
DefaultOrgRole: string(org.RoleEditor), |
||||
Password: "editor", |
||||
Login: "editor", |
||||
OrgID: sharedOrg.ID, |
||||
}) |
||||
createUserInOrg(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ |
||||
DefaultOrgRole: string(org.RoleAdmin), |
||||
Password: "admin", |
||||
Login: "admin2", |
||||
OrgID: sharedOrg.ID, |
||||
}) |
||||
|
||||
uid := "" |
||||
t.Run("create", func(t *testing.T) { |
||||
t.Run("When viewer tries to create a library panel in the General folder, it should fail", func(t *testing.T) { |
||||
createLibraryElement(t, grafanaListedAddr, "viewer", "viewer", "", http.StatusForbidden) |
||||
}) |
||||
|
||||
t.Run("When a user tries to create a library panel in a folder that doesn't exist, it should fail", func(t *testing.T) { |
||||
createLibraryElement(t, grafanaListedAddr, "admin2", "admin", "non-existent-folder-uid", http.StatusBadRequest) |
||||
}) |
||||
|
||||
t.Run("When editor tries to create a library panel in the General folder, it should succeed", func(t *testing.T) { |
||||
uid = createLibraryElement(t, grafanaListedAddr, "editor", "editor", "", http.StatusOK) |
||||
require.NotEmpty(t, uid) |
||||
require.NotEqual(t, uid, "") |
||||
}) |
||||
}) |
||||
|
||||
t.Run("move to folder", func(t *testing.T) { |
||||
folderUID := createTestFolder(t, grafanaListedAddr) |
||||
|
||||
t.Run("When viewer tries to move library panel to folder, it should fail", func(t *testing.T) { |
||||
patchLibraryElement(t, grafanaListedAddr, "viewer", "viewer", uid, folderUID, http.StatusForbidden) |
||||
}) |
||||
|
||||
t.Run("When a user tries to patch a library panel by moving it to a folder that doesn't exist, it should fail", func(t *testing.T) { |
||||
patchLibraryElement(t, grafanaListedAddr, "admin2", "admin", uid, "non-existent-folder-uid", http.StatusBadRequest) |
||||
}) |
||||
|
||||
t.Run("When editor tries to move library panel to folder, it should succeed", func(t *testing.T) { |
||||
patchLibraryElement(t, grafanaListedAddr, "editor", "editor", uid, folderUID, http.StatusOK) |
||||
}) |
||||
}) |
||||
|
||||
testScenario(t, "When user tries to patch a library panel by moving it to the General folder, it should return correct status", |
||||
func(t *testing.T, sc scenarioContext) { |
||||
folder := createFolder(t, sc, "Folder", nil) |
||||
// nolint:staticcheck
|
||||
command := getCreatePanelCommand(folder.ID, folder.UID, "Library Panel Name") |
||||
sc.reqContext.Req.Body = mockRequestBody(command) |
||||
resp := sc.service.createHandler(sc.reqContext) |
||||
result := validateAndUnMarshalResponse(t, resp) |
||||
|
||||
// nolint:staticcheck
|
||||
sc.reqContext.OrgRole = org.RoleViewer |
||||
cmd := model.PatchLibraryElementCommand{FolderID: 0, Version: 1, Kind: int64(model.PanelElement)} |
||||
sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": result.Result.UID}) |
||||
sc.ctx.Req.Body = mockRequestBody(cmd) |
||||
resp = sc.service.patchHandler(sc.reqContext) |
||||
require.Equal(t, http.StatusForbidden, resp.Status()) |
||||
|
||||
sc.reqContext.OrgRole = org.RoleEditor |
||||
sc.ctx.Req.Body = mockRequestBody(cmd) |
||||
resp = sc.service.patchHandler(sc.reqContext) |
||||
require.Equal(t, http.StatusOK, resp.Status()) |
||||
t.Run("move to general folder", func(t *testing.T) { |
||||
t.Run("When viewer tries to move library panel back to general, it should fail", func(t *testing.T) { |
||||
patchLibraryElement(t, grafanaListedAddr, "viewer", "viewer", uid, "", http.StatusForbidden) |
||||
}) |
||||
|
||||
testScenario(t, "When user tries to patch a library panel by moving it from the General folder, it should return correct status", |
||||
func(t *testing.T, sc scenarioContext) { |
||||
folder := createFolder(t, sc, "Folder", nil) |
||||
command := getCreatePanelCommand(0, "", "Library Panel Name") |
||||
sc.reqContext.Req.Body = mockRequestBody(command) |
||||
sc.service.AccessControl = actest.FakeAccessControl{ExpectedEvaluate: true} |
||||
resp := sc.service.createHandler(sc.reqContext) |
||||
result := validateAndUnMarshalResponse(t, resp) |
||||
|
||||
sc.reqContext.OrgRole = org.RoleViewer |
||||
cmd := model.PatchLibraryElementCommand{FolderUID: &folder.UID, Version: 1, Kind: int64(model.PanelElement)} |
||||
sc.service.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) |
||||
sc.service.AccessControl.RegisterScopeAttributeResolver(dashboards.NewFolderIDScopeResolver(folderimpl.ProvideDashboardFolderStore(sc.sqlStore), sc.service.folderService)) |
||||
sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": result.Result.UID}) |
||||
sc.ctx.Req.Body = mockRequestBody(cmd) |
||||
resp = sc.service.patchHandler(sc.reqContext) |
||||
require.Equal(t, http.StatusForbidden, resp.Status()) |
||||
|
||||
sc.reqContext.OrgRole = org.RoleEditor |
||||
sc.reqContext.Permissions[sc.user.OrgID][dashboards.ActionFoldersWrite] = append(sc.reqContext.Permissions[sc.user.OrgID][dashboards.ActionFoldersWrite], dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.UID)) |
||||
sc.reqContext.Permissions[sc.user.OrgID][dashboards.ActionFoldersWrite] = append(sc.reqContext.Permissions[sc.user.OrgID][dashboards.ActionFoldersWrite], dashboards.ScopeFoldersProvider.GetResourceScopeUID(accesscontrol.GeneralFolderUID)) |
||||
sc.ctx.Req.Body = mockRequestBody(cmd) |
||||
resp = sc.service.patchHandler(sc.reqContext) |
||||
require.Equal(t, http.StatusOK, resp.Status()) |
||||
t.Run("When editor tries to move library panel back to general, it should succeed", func(t *testing.T) { |
||||
patchLibraryElement(t, grafanaListedAddr, "editor", "editor", uid, "", http.StatusOK) |
||||
}) |
||||
}) |
||||
|
||||
testScenario(t, "When user tries to delete a library panel in the General folder, it should return correct status", |
||||
func(t *testing.T, sc scenarioContext) { |
||||
cmd := getCreatePanelCommand(0, "", "Library Panel Name") |
||||
sc.reqContext.Req.Body = mockRequestBody(cmd) |
||||
sc.service.AccessControl = actest.FakeAccessControl{ExpectedEvaluate: true} |
||||
resp := sc.service.createHandler(sc.reqContext) |
||||
result := validateAndUnMarshalResponse(t, resp) |
||||
|
||||
sc.reqContext.OrgRole = org.RoleViewer |
||||
sc.service.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) |
||||
sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": result.Result.UID}) |
||||
resp = sc.service.deleteHandler(sc.reqContext) |
||||
require.Equal(t, http.StatusForbidden, resp.Status()) |
||||
|
||||
sc.reqContext.OrgRole = org.RoleEditor |
||||
resp = sc.service.deleteHandler(sc.reqContext) |
||||
require.Equal(t, http.StatusOK, resp.Status()) |
||||
t.Run("get", func(t *testing.T) { |
||||
t.Run("When viewer tries to get library panel, it should succeed", func(t *testing.T) { |
||||
getLibraryElement(t, grafanaListedAddr, "viewer", "viewer", uid, http.StatusOK) |
||||
}) |
||||
|
||||
testScenario(t, "When user tries to get a library panel from General folder, it should return correct response", |
||||
func(t *testing.T, sc scenarioContext) { |
||||
sc.service.AccessControl = actest.FakeAccessControl{ExpectedEvaluate: true} |
||||
cmd := getCreatePanelCommand(0, "", "Library Panel in General Folder") |
||||
sc.reqContext.Req.Body = mockRequestBody(cmd) |
||||
resp := sc.service.createHandler(sc.reqContext) |
||||
result := validateAndUnMarshalResponse(t, resp) |
||||
result.Result.Meta.CreatedBy.Name = userInDbName |
||||
result.Result.Meta.CreatedBy.AvatarUrl = userInDbAvatar |
||||
result.Result.Meta.UpdatedBy.Name = userInDbName |
||||
result.Result.Meta.UpdatedBy.AvatarUrl = userInDbAvatar |
||||
result.Result.Meta.FolderName = "General" |
||||
result.Result.Meta.FolderUID = "general" |
||||
result.Result.FolderUID = "general" |
||||
|
||||
sc.service.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) |
||||
sc.reqContext.Permissions[sc.user.OrgID][dashboards.ActionFoldersRead] = append(sc.reqContext.Permissions[sc.user.OrgID][dashboards.ActionFoldersRead], dashboards.ScopeFoldersProvider.GetResourceScopeUID(accesscontrol.GeneralFolderUID)) |
||||
sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": result.Result.UID}) |
||||
resp = sc.service.getHandler(sc.reqContext) |
||||
require.Equal(t, 200, resp.Status()) |
||||
var actual libraryElementResult |
||||
err := json.Unmarshal(resp.Body(), &actual) |
||||
require.NoError(t, err) |
||||
if diff := cmp.Diff(result.Result, actual.Result, getCompareOptions()...); diff != "" { |
||||
t.Fatalf("Result mismatch (-want +got):\n%s", diff) |
||||
} |
||||
t.Run("When editor tries to get library panel, it should succeed", func(t *testing.T) { |
||||
getLibraryElement(t, grafanaListedAddr, "editor", "editor", uid, http.StatusOK) |
||||
}) |
||||
}) |
||||
|
||||
testScenario(t, "When user tries to get all library panels from General folder, it should return correct response", |
||||
func(t *testing.T, sc scenarioContext) { |
||||
sc.service.AccessControl = actest.FakeAccessControl{ExpectedEvaluate: true} |
||||
cmd := getCreatePanelCommand(0, "", "Library Panel in General Folder") |
||||
sc.reqContext.Req.Body = mockRequestBody(cmd) |
||||
resp := sc.service.createHandler(sc.reqContext) |
||||
result := validateAndUnMarshalResponse(t, resp) |
||||
result.Result.Meta.CreatedBy.Name = userInDbName |
||||
result.Result.Meta.CreatedBy.AvatarUrl = userInDbAvatar |
||||
result.Result.Meta.UpdatedBy.Name = userInDbName |
||||
result.Result.Meta.UpdatedBy.AvatarUrl = userInDbAvatar |
||||
result.Result.Meta.FolderName = "General" |
||||
|
||||
sc.service.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) |
||||
sc.reqContext.Permissions[sc.user.OrgID][dashboards.ActionFoldersRead] = append(sc.reqContext.Permissions[sc.user.OrgID][dashboards.ActionFoldersRead], dashboards.ScopeFoldersProvider.GetResourceScopeUID(accesscontrol.GeneralFolderUID)) |
||||
resp = sc.service.getAllHandler(sc.reqContext) |
||||
require.Equal(t, 200, resp.Status()) |
||||
var actual libraryElementsSearch |
||||
err := json.Unmarshal(resp.Body(), &actual) |
||||
require.NoError(t, err) |
||||
require.Equal(t, 1, len(actual.Result.Elements)) |
||||
if diff := cmp.Diff(result.Result, actual.Result.Elements[0], getCompareOptions()...); diff != "" { |
||||
t.Fatalf("Result mismatch (-want +got):\n%s", diff) |
||||
} |
||||
t.Run("get all", func(t *testing.T) { |
||||
t.Run("When viewer tries to get all library elements, it should succeed", func(t *testing.T) { |
||||
getAllLibraryElements(t, grafanaListedAddr, "viewer", "viewer", http.StatusOK, 1) |
||||
}) |
||||
|
||||
t.Run("When editor tries to get all library elements, it should succeed", func(t *testing.T) { |
||||
getAllLibraryElements(t, grafanaListedAddr, "editor", "editor", http.StatusOK, 1) |
||||
}) |
||||
}) |
||||
|
||||
t.Run("delete", func(t *testing.T) { |
||||
t.Run("When viewer tries to delete library panel, it should fail", func(t *testing.T) { |
||||
deleteLibraryElement(t, grafanaListedAddr, "viewer", "viewer", uid, http.StatusForbidden) |
||||
}) |
||||
|
||||
t.Run("When editor tries to delete library panel, it should succeed", func(t *testing.T) { |
||||
deleteLibraryElement(t, grafanaListedAddr, "editor", "editor", uid, http.StatusOK) |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
func TestLibraryElementCreatePermissions(t *testing.T) { |
||||
var accessCases = []struct { |
||||
permissions map[string][]string |
||||
desc string |
||||
status int |
||||
}{ |
||||
{ |
||||
desc: "can create library elements when granted write access to the correct folder", |
||||
permissions: map[string][]string{ |
||||
dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersProvider.GetResourceScopeUID("uid_for_Folder")}, |
||||
dashboards.ActionFoldersRead: {dashboards.ScopeFoldersProvider.GetResourceAllScope()}, |
||||
}, |
||||
status: http.StatusOK, |
||||
}, |
||||
{ |
||||
desc: "can create library elements when granted write access to all folders", |
||||
permissions: map[string][]string{ |
||||
dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersProvider.GetResourceAllScope()}, |
||||
dashboards.ActionFoldersRead: {dashboards.ScopeFoldersProvider.GetResourceAllScope()}, |
||||
}, |
||||
status: http.StatusOK, |
||||
}, |
||||
{ |
||||
desc: "can't create library elements when granted write access to the wrong folder", |
||||
permissions: map[string][]string{ |
||||
dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersProvider.GetResourceScopeUID("uid_for_Other_folder")}, |
||||
dashboards.ActionFoldersRead: {dashboards.ScopeFoldersProvider.GetResourceAllScope()}, |
||||
}, |
||||
status: http.StatusForbidden, |
||||
}, |
||||
{ |
||||
desc: "can't create library elements when granted read access to the right folder", |
||||
permissions: map[string][]string{ |
||||
dashboards.ActionFoldersRead: {dashboards.ScopeFoldersProvider.GetResourceScopeUID("uid_for_Folder")}, |
||||
}, |
||||
status: http.StatusForbidden, |
||||
}, |
||||
func TestIntegrationLibraryElementGranularPermissions(t *testing.T) { |
||||
if testing.Short() { |
||||
t.Skip("skipping integration test") |
||||
} |
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{}) |
||||
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path) |
||||
quotaService := quotaimpl.ProvideService(env.SQLStore, env.Cfg) |
||||
orgService, err := orgimpl.ProvideService(env.SQLStore, env.Cfg, quotaService) |
||||
require.NoError(t, err) |
||||
|
||||
sharedOrg, err := orgService.CreateWithMember(context.Background(), &org.CreateOrgCommand{Name: "test org"}) |
||||
require.NoError(t, err) |
||||
|
||||
userID := createUserInOrg(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ |
||||
DefaultOrgRole: string(org.RoleViewer), |
||||
Password: "granular-viewer", |
||||
Login: "granular-viewer", |
||||
OrgID: sharedOrg.ID, |
||||
}) |
||||
createUserInOrg(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ |
||||
DefaultOrgRole: string(org.RoleAdmin), |
||||
Password: "admin", |
||||
Login: "admin2", |
||||
OrgID: sharedOrg.ID, |
||||
}) |
||||
|
||||
folder1UID := createTestFolder(t, grafanaListedAddr) |
||||
folder2UID := createTestFolder(t, grafanaListedAddr) |
||||
folder3UID := createTestFolder(t, grafanaListedAddr) |
||||
|
||||
// viewer only has access to folder 1 & 3
|
||||
grantFolderPermissions(t, grafanaListedAddr, "granular-viewer", "granular-viewer", folder1UID, userID) |
||||
grantFolderPermissions(t, grafanaListedAddr, "granular-viewer", "granular-viewer", folder3UID, userID) |
||||
// revoke view access to folder2
|
||||
revokeFolderPermissions(t, grafanaListedAddr, folder2UID, userID) |
||||
|
||||
uid := "" |
||||
t.Run("granular createpermissions", func(t *testing.T) { |
||||
t.Run("When viewer has write access to folder1, they can create library element in folder1", func(t *testing.T) { |
||||
uid = createLibraryElement(t, grafanaListedAddr, "granular-viewer", "granular-viewer", folder1UID, http.StatusOK) |
||||
require.NotEmpty(t, uid) |
||||
}) |
||||
|
||||
for _, testCase := range accessCases { |
||||
testScenario(t, testCase.desc, |
||||
func(t *testing.T, sc scenarioContext) { |
||||
folder := createFolder(t, sc, "Folder", nil) |
||||
sc.reqContext.Permissions = map[int64]map[string][]string{ |
||||
1: testCase.permissions, |
||||
} |
||||
|
||||
// nolint:staticcheck
|
||||
command := getCreatePanelCommand(folder.ID, folder.UID, "Library Panel Name") |
||||
sc.reqContext.Req.Body = mockRequestBody(command) |
||||
resp := sc.service.createHandler(sc.reqContext) |
||||
require.Equal(t, testCase.status, resp.Status()) |
||||
}) |
||||
} |
||||
t.Run("When viewer doesn't have read access to folder2, they cannot create library element in folder2", func(t *testing.T) { |
||||
createLibraryElement(t, grafanaListedAddr, "granular-viewer", "granular-viewer", folder2UID, http.StatusBadRequest) |
||||
}) |
||||
|
||||
t.Run("When viewer doesn't have write access to general folder, they cannot create library element in general", func(t *testing.T) { |
||||
createLibraryElement(t, grafanaListedAddr, "granular-viewer", "granular-viewer", "", http.StatusForbidden) |
||||
}) |
||||
}) |
||||
|
||||
t.Run("granular move permissions", func(t *testing.T) { |
||||
t.Run("When viewer has write access to folder3 and folder1, they can move library element from folder1 to folder3", func(t *testing.T) { |
||||
patchLibraryElement(t, grafanaListedAddr, "granular-viewer", "granular-viewer", uid, folder3UID, http.StatusOK) |
||||
}) |
||||
|
||||
t.Run("When viewer doesn't have read access to folder2, they cannot move library element to folder2", func(t *testing.T) { |
||||
patchLibraryElement(t, grafanaListedAddr, "granular-viewer", "granular-viewer", uid, folder2UID, http.StatusBadRequest) |
||||
}) |
||||
}) |
||||
|
||||
inGeneralFolder := createLibraryElement(t, grafanaListedAddr, "admin2", "admin", "", http.StatusOK) |
||||
inFolder2 := createLibraryElement(t, grafanaListedAddr, "admin2", "admin", folder2UID, http.StatusOK) |
||||
|
||||
t.Run("granular read permissions", func(t *testing.T) { |
||||
t.Run("When viewer has read access to folder1, they can get library element from folder1", func(t *testing.T) { |
||||
getLibraryElement(t, grafanaListedAddr, "granular-viewer", "granular-viewer", uid, http.StatusOK) |
||||
}) |
||||
|
||||
t.Run("When viewer doesn't have read access to folder2, they cannot get library element from folder2", func(t *testing.T) { |
||||
getLibraryElement(t, grafanaListedAddr, "granular-viewer", "granular-viewer", inFolder2, http.StatusNotFound) |
||||
}) |
||||
|
||||
t.Run("When viewer has limited folder access, they only see library elements from accessible folders", func(t *testing.T) { |
||||
getAllLibraryElements(t, grafanaListedAddr, "granular-viewer", "granular-viewer", http.StatusOK, 2) |
||||
}) |
||||
}) |
||||
|
||||
t.Run("granular delete permissions", func(t *testing.T) { |
||||
t.Run("When viewer has write access to folder1, they can delete library element from folder1", func(t *testing.T) { |
||||
deleteLibraryElement(t, grafanaListedAddr, "granular-viewer", "granular-viewer", uid, http.StatusOK) |
||||
}) |
||||
|
||||
t.Run("When viewer doesn't have write access to folder2, they cannot delete library element from folder2", func(t *testing.T) { |
||||
deleteLibraryElement(t, grafanaListedAddr, "granular-viewer", "granular-viewer", inFolder2, http.StatusForbidden) |
||||
}) |
||||
|
||||
t.Run("When viewer doesn't have write access to general folder, they cannot delete library element from general", func(t *testing.T) { |
||||
deleteLibraryElement(t, grafanaListedAddr, "granular-viewer", "granular-viewer", inGeneralFolder, http.StatusForbidden) |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
func TestLibraryElementPatchPermissions(t *testing.T) { |
||||
var accessCases = []struct { |
||||
permissions map[string][]string |
||||
desc string |
||||
status int |
||||
}{ |
||||
{ |
||||
desc: "can move library elements when granted write access to the source and destination folders", |
||||
permissions: map[string][]string{ |
||||
dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersProvider.GetResourceScopeUID("uid_for_FromFolder"), dashboards.ScopeFoldersProvider.GetResourceScopeUID("uid_for_ToFolder")}, |
||||
dashboards.ActionFoldersRead: {dashboards.ScopeFoldersProvider.GetResourceAllScope()}, |
||||
}, |
||||
status: http.StatusOK, |
||||
}, |
||||
{ |
||||
desc: "can move library elements when granted write access to all folders", |
||||
permissions: map[string][]string{ |
||||
dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersProvider.GetResourceAllScope()}, |
||||
dashboards.ActionFoldersRead: {dashboards.ScopeFoldersProvider.GetResourceAllScope()}, |
||||
}, |
||||
status: http.StatusOK, |
||||
}, |
||||
{ |
||||
desc: "can't move library elements when granted write access only to the source folder", |
||||
permissions: map[string][]string{ |
||||
dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersProvider.GetResourceScopeUID("FromFolder")}, |
||||
dashboards.ActionFoldersRead: {dashboards.ScopeFoldersProvider.GetResourceAllScope()}, |
||||
}, |
||||
status: http.StatusForbidden, |
||||
}, |
||||
{ |
||||
desc: "can't move library elements when granted write access to the destination folder", |
||||
permissions: map[string][]string{ |
||||
dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersProvider.GetResourceScopeUID("ToFolder")}, |
||||
dashboards.ActionFoldersRead: {dashboards.ScopeFoldersProvider.GetResourceAllScope()}, |
||||
}, |
||||
status: http.StatusForbidden, |
||||
}, |
||||
/* |
||||
Helper functions |
||||
*/ |
||||
|
||||
func createLibraryElement(t *testing.T, grafanaListedAddr, user, password, folderUID string, expectedStatus int) string { |
||||
m := map[string]interface{}{ |
||||
"datasource": "${DS_GDEV-TESTDATA}", |
||||
"id": 1, |
||||
"title": "Text - Library Panel", |
||||
"type": "text", |
||||
"description": "A description", |
||||
} |
||||
createRequest := map[string]interface{}{ |
||||
"name": "Library Panel Name", |
||||
"model": m, |
||||
"folderUid": folderUID, |
||||
"kind": int64(1), |
||||
} |
||||
|
||||
for _, testCase := range accessCases { |
||||
testScenario(t, testCase.desc, |
||||
func(t *testing.T, sc scenarioContext) { |
||||
fromFolder := createFolder(t, sc, "FromFolder", nil) |
||||
// nolint:staticcheck
|
||||
command := getCreatePanelCommand(fromFolder.ID, fromFolder.UID, "Library Panel Name") |
||||
sc.reqContext.Req.Body = mockRequestBody(command) |
||||
resp := sc.service.createHandler(sc.reqContext) |
||||
result := validateAndUnMarshalResponse(t, resp) |
||||
|
||||
toFolder := createFolder(t, sc, "ToFolder", nil) |
||||
|
||||
sc.reqContext.Permissions = map[int64]map[string][]string{ |
||||
1: testCase.permissions, |
||||
} |
||||
|
||||
// nolint:staticcheck
|
||||
cmd := model.PatchLibraryElementCommand{FolderID: toFolder.ID, FolderUID: &toFolder.UID, Version: 1, Kind: int64(model.PanelElement)} |
||||
sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": result.Result.UID}) |
||||
sc.reqContext.Req.Body = mockRequestBody(cmd) |
||||
resp = sc.service.patchHandler(sc.reqContext) |
||||
require.Equal(t, testCase.status, resp.Status()) |
||||
}) |
||||
resp := makeHTTPRequest(t, "POST", fmt.Sprintf("http://%s:%s@%s/api/library-elements", user, password, grafanaListedAddr), createRequest, expectedStatus) |
||||
if expectedStatus == http.StatusOK { |
||||
var result model.LibraryElementResponse |
||||
err := json.Unmarshal(resp, &result) |
||||
require.NoError(t, err) |
||||
return result.Result.UID |
||||
} |
||||
|
||||
return "" |
||||
} |
||||
|
||||
func TestLibraryElementDeletePermissions(t *testing.T) { |
||||
var accessCases = []struct { |
||||
permissions map[string][]string |
||||
desc string |
||||
status int |
||||
}{ |
||||
{ |
||||
desc: "can delete library elements when granted write access to the correct folder", |
||||
permissions: map[string][]string{ |
||||
dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersProvider.GetResourceScopeUID("uid_for_Folder")}, |
||||
dashboards.ActionFoldersRead: {dashboards.ScopeFoldersProvider.GetResourceScopeUID("uid_for_Folder")}, |
||||
}, |
||||
status: http.StatusOK, |
||||
}, |
||||
{ |
||||
desc: "can delete library elements when granted write access to all folders", |
||||
permissions: map[string][]string{ |
||||
dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersProvider.GetResourceAllScope()}, |
||||
dashboards.ActionFoldersRead: {dashboards.ScopeFoldersProvider.GetResourceAllScope()}, |
||||
}, |
||||
status: http.StatusOK, |
||||
}, |
||||
{ |
||||
desc: "can't delete library elements when granted write access to the wrong folder", |
||||
permissions: map[string][]string{ |
||||
dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersProvider.GetResourceScopeUID("Other_folder")}, |
||||
dashboards.ActionFoldersRead: {dashboards.ScopeFoldersProvider.GetResourceScopeUID("Other_folder")}, |
||||
}, |
||||
status: http.StatusForbidden, |
||||
}, |
||||
{ |
||||
desc: "can't delete library elements when granted read access to the right folder", |
||||
permissions: map[string][]string{ |
||||
dashboards.ActionFoldersRead: {dashboards.ScopeFoldersProvider.GetResourceScopeUID("Folder")}, |
||||
}, |
||||
status: http.StatusForbidden, |
||||
}, |
||||
func patchLibraryElement(t *testing.T, grafanaListedAddr, user, password, uid, folderUID string, expectedStatus int) { |
||||
version := getLibraryElementVersion(t, grafanaListedAddr, user, password, uid) |
||||
patchRequest := map[string]interface{}{ |
||||
"folderUid": folderUID, |
||||
"version": version, |
||||
"kind": 1, |
||||
} |
||||
makeHTTPRequest(t, "PATCH", fmt.Sprintf("http://%s:%s@%s/api/library-elements/%s", user, password, grafanaListedAddr, uid), patchRequest, expectedStatus) |
||||
} |
||||
|
||||
func deleteLibraryElement(t *testing.T, grafanaListedAddr, user, password, uid string, expectedStatus int) { |
||||
makeHTTPRequest(t, "DELETE", fmt.Sprintf("http://%s:%s@%s/api/library-elements/%s", user, password, grafanaListedAddr, uid), nil, expectedStatus) |
||||
} |
||||
|
||||
for _, testCase := range accessCases { |
||||
testScenario(t, testCase.desc, |
||||
func(t *testing.T, sc scenarioContext) { |
||||
folder := createFolder(t, sc, "Folder", sc.service.folderService) |
||||
// nolint:staticcheck
|
||||
command := getCreatePanelCommand(folder.ID, folder.UID, "Library Panel Name") |
||||
sc.reqContext.Req.Body = mockRequestBody(command) |
||||
resp := sc.service.createHandler(sc.reqContext) |
||||
result := validateAndUnMarshalResponse(t, resp) |
||||
|
||||
sc.reqContext.Permissions = map[int64]map[string][]string{ |
||||
1: testCase.permissions, |
||||
} |
||||
|
||||
sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": result.Result.UID}) |
||||
resp = sc.service.deleteHandler(sc.reqContext) |
||||
require.Equal(t, testCase.status, resp.Status()) |
||||
}) |
||||
func getLibraryElement(t *testing.T, grafanaListedAddr, user, password, uid string, expectedStatus int) { |
||||
makeHTTPRequest(t, "GET", fmt.Sprintf("http://%s:%s@%s/api/library-elements/%s", user, password, grafanaListedAddr, uid), nil, expectedStatus) |
||||
} |
||||
|
||||
func getAllLibraryElements(t *testing.T, grafanaListedAddr, user, password string, expectedStatus int, expectedLength int) { |
||||
resp := makeHTTPRequest(t, "GET", fmt.Sprintf("http://%s:%s@%s/api/library-elements", user, password, grafanaListedAddr), nil, expectedStatus) |
||||
if expectedStatus == http.StatusOK { |
||||
var result model.LibraryElementSearchResponse |
||||
err := json.Unmarshal(resp, &result) |
||||
require.NoError(t, err) |
||||
require.Len(t, result.Result.Elements, expectedLength) |
||||
} |
||||
} |
||||
|
||||
func TestLibraryElementsWithMissingFolders(t *testing.T) { |
||||
testScenario(t, "When a user tries to create a library panel in a folder that doesn't exist, it should fail", |
||||
func(t *testing.T, sc scenarioContext) { |
||||
command := getCreatePanelCommand(0, "badFolderUID", "Library Panel Name") |
||||
sc.reqContext.Req.Body = mockRequestBody(command) |
||||
resp := sc.service.createHandler(sc.reqContext) |
||||
fmt.Println(string(resp.Body())) |
||||
require.Equal(t, 400, resp.Status()) |
||||
}) |
||||
func getLibraryElementVersion(t *testing.T, grafanaListedAddr, user, password, uid string) int { |
||||
resp := makeHTTPRequest(t, "GET", fmt.Sprintf("http://%s:%s@%s/api/library-elements/%s", user, password, grafanaListedAddr, uid), nil, http.StatusOK) |
||||
var getResult model.LibraryElementResponse |
||||
err := json.Unmarshal(resp, &getResult) |
||||
require.NoError(t, err) |
||||
|
||||
testScenario(t, "When a user tries to patch a library panel by moving it to a folder that doesn't exist, it should fail", |
||||
func(t *testing.T, sc scenarioContext) { |
||||
folder := createFolder(t, sc, "Folder", nil) |
||||
// nolint:staticcheck
|
||||
command := getCreatePanelCommand(folder.ID, folder.UID, "Library Panel Name") |
||||
sc.reqContext.Req.Body = mockRequestBody(command) |
||||
resp := sc.service.createHandler(sc.reqContext) |
||||
result := validateAndUnMarshalResponse(t, resp) |
||||
|
||||
folderUID := "badFolderUID" |
||||
// nolint:staticcheck
|
||||
cmd := model.PatchLibraryElementCommand{FolderID: -100, FolderUID: &folderUID, Version: 1, Kind: int64(model.PanelElement)} |
||||
sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": result.Result.UID}) |
||||
sc.reqContext.Req.Body = mockRequestBody(cmd) |
||||
resp = sc.service.patchHandler(sc.reqContext) |
||||
require.Equal(t, 400, resp.Status()) |
||||
}) |
||||
return int(getResult.Result.Version) |
||||
} |
||||
|
||||
func TestLibraryElementsGetPermissions(t *testing.T) { |
||||
var getCases = []struct { |
||||
permissions map[string][]string |
||||
desc string |
||||
status int |
||||
}{ |
||||
{ |
||||
desc: "can get a library element when granted read access to all folders", |
||||
permissions: map[string][]string{ |
||||
dashboards.ActionFoldersRead: {dashboards.ScopeFoldersProvider.GetResourceAllScope()}, |
||||
}, |
||||
status: http.StatusOK, |
||||
}, |
||||
{ |
||||
desc: "can't list library element when granted read access to the wrong folder", |
||||
permissions: map[string][]string{ |
||||
dashboards.ActionFoldersRead: {dashboards.ScopeFoldersProvider.GetResourceScopeUID("Other_folder")}, |
||||
}, |
||||
status: http.StatusForbidden, |
||||
}, |
||||
} |
||||
for _, testCase := range getCases { |
||||
testScenario(t, testCase.desc, |
||||
func(t *testing.T, sc scenarioContext) { |
||||
folder := createFolder(t, sc, "Folder", nil) |
||||
// nolint:staticcheck
|
||||
cmd := getCreatePanelCommand(folder.ID, folder.UID, "Library Panel") |
||||
sc.reqContext.Req.Body = mockRequestBody(cmd) |
||||
resp := sc.service.createHandler(sc.reqContext) |
||||
result := validateAndUnMarshalResponse(t, resp) |
||||
result.Result.Meta.CreatedBy.Name = userInDbName |
||||
result.Result.Meta.CreatedBy.AvatarUrl = userInDbAvatar |
||||
result.Result.Meta.UpdatedBy.Name = userInDbName |
||||
result.Result.Meta.UpdatedBy.AvatarUrl = userInDbAvatar |
||||
result.Result.Meta.FolderName = folder.Title |
||||
result.Result.Meta.FolderUID = folder.UID |
||||
|
||||
sc.reqContext.OrgRole = org.RoleViewer |
||||
sc.reqContext.Permissions = map[int64]map[string][]string{ |
||||
1: testCase.permissions, |
||||
} |
||||
|
||||
sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": result.Result.UID}) |
||||
resp = sc.service.getHandler(sc.reqContext) |
||||
require.Equal(t, testCase.status, resp.Status()) |
||||
}) |
||||
func createTestFolder(t *testing.T, grafanaListedAddr string) string { |
||||
folderRequest := map[string]interface{}{ |
||||
"title": "Test Folder", |
||||
} |
||||
resp := makeHTTPRequest(t, "POST", fmt.Sprintf("http://admin2:admin@%s/api/folders", grafanaListedAddr), folderRequest, http.StatusOK) |
||||
var folder models.Folder |
||||
err := json.Unmarshal(resp, &folder) |
||||
require.NoError(t, err) |
||||
return folder.UID |
||||
} |
||||
|
||||
func TestLibraryElementsGetAllPermissions(t *testing.T) { |
||||
var getCases = []struct { |
||||
permissions map[string][]string |
||||
desc string |
||||
status int |
||||
expectedResultCount int |
||||
}{ |
||||
{ |
||||
desc: "can get all library elements when granted read access to all folders", |
||||
permissions: map[string][]string{ |
||||
dashboards.ActionFoldersRead: {dashboards.ScopeFoldersProvider.GetResourceAllScope()}, |
||||
func grantFolderPermissions(t *testing.T, grafanaListedAddr, user, password, folderUID string, userID int64) { |
||||
permissionRequest := map[string]interface{}{ |
||||
"items": []map[string]interface{}{ |
||||
{ |
||||
"userId": userID, |
||||
"permission": 2, // edit permission
|
||||
}, |
||||
expectedResultCount: 2, |
||||
status: http.StatusOK, |
||||
}, |
||||
{ |
||||
desc: "can't get any library element when doesn't have access to any folders", |
||||
permissions: map[string][]string{}, |
||||
expectedResultCount: 0, |
||||
status: http.StatusOK, |
||||
}, |
||||
} |
||||
for _, testCase := range getCases { |
||||
testScenario(t, testCase.desc, |
||||
func(t *testing.T, sc scenarioContext) { |
||||
for i := 1; i <= 2; i++ { |
||||
folder := createFolder(t, sc, fmt.Sprintf("Folder%d", i), nil) |
||||
// nolint:staticcheck
|
||||
cmd := getCreatePanelCommand(folder.ID, folder.UID, fmt.Sprintf("Library Panel %d", i)) |
||||
sc.reqContext.Req.Body = mockRequestBody(cmd) |
||||
resp := sc.service.createHandler(sc.reqContext) |
||||
result := validateAndUnMarshalResponse(t, resp) |
||||
result.Result.Meta.FolderUID = folder.UID |
||||
} |
||||
|
||||
sc.reqContext.OrgRole = org.RoleViewer |
||||
sc.reqContext.Permissions = map[int64]map[string][]string{ |
||||
1: testCase.permissions, |
||||
} |
||||
|
||||
resp := sc.service.getAllHandler(sc.reqContext) |
||||
require.Equal(t, 200, resp.Status()) |
||||
var actual libraryElementsSearch |
||||
err := json.Unmarshal(resp.Body(), &actual) |
||||
require.NoError(t, err) |
||||
require.Equal(t, testCase.expectedResultCount, len(actual.Result.Elements)) |
||||
}) |
||||
makeHTTPRequest(t, "POST", fmt.Sprintf("http://admin2:admin@%s/api/folders/%s/permissions", grafanaListedAddr, folderUID), permissionRequest, http.StatusOK) |
||||
} |
||||
|
||||
func revokeFolderPermissions(t *testing.T, grafanaListedAddr, folderUID string, userID int64) { |
||||
permissionRequest := map[string]interface{}{ |
||||
"items": []map[string]interface{}{}, |
||||
} |
||||
makeHTTPRequest(t, "POST", fmt.Sprintf("http://admin2:admin@%s/api/folders/%s/permissions", grafanaListedAddr, folderUID), permissionRequest, http.StatusOK) |
||||
} |
||||
|
||||
func makeHTTPRequest(t *testing.T, method, url string, body interface{}, expectedStatus int) []byte { |
||||
var req *http.Request |
||||
var err error |
||||
|
||||
if body != nil { |
||||
buf := &bytes.Buffer{} |
||||
err = json.NewEncoder(buf).Encode(body) |
||||
require.NoError(t, err) |
||||
req, err = http.NewRequest(method, url, buf) |
||||
require.NoError(t, err) |
||||
req.Header.Set("Content-Type", "application/json") |
||||
} else { |
||||
req, err = http.NewRequest(method, url, nil) |
||||
require.NoError(t, err) |
||||
} |
||||
|
||||
client := &http.Client{} |
||||
resp, err := client.Do(req) |
||||
require.NoError(t, err) |
||||
// nolint:errcheck
|
||||
defer resp.Body.Close() |
||||
require.Equal(t, expectedStatus, resp.StatusCode) |
||||
|
||||
respBody, err := io.ReadAll(resp.Body) |
||||
require.NoError(t, err) |
||||
return respBody |
||||
} |
||||
|
||||
func createUserInOrg(t *testing.T, db db.DB, cfg *setting.Cfg, cmd user.CreateUserCommand) int64 { |
||||
t.Helper() |
||||
|
||||
cfg.AutoAssignOrg = true |
||||
cfg.AutoAssignOrgId = 1 |
||||
|
||||
quotaService := quotaimpl.ProvideService(db, cfg) |
||||
orgService, err := orgimpl.ProvideService(db, cfg, quotaService) |
||||
require.NoError(t, err) |
||||
usrSvc, err := userimpl.ProvideService( |
||||
db, orgService, cfg, nil, nil, tracing.InitializeTracerForTest(), |
||||
quotaService, supportbundlestest.NewFakeBundleService(), |
||||
) |
||||
require.NoError(t, err) |
||||
|
||||
u, err := usrSvc.Create(context.Background(), &cmd) |
||||
require.NoError(t, err) |
||||
return u.ID |
||||
} |
||||
|
Loading…
Reference in new issue