RBAC: Update library element tests to use RBAC (#66782)

* update library element tests to use RBAC

* update bits of code to use RBAC

* update library panel tests

* linting

* more linting

* and more linting

* PR feedback
This commit is contained in:
Ieva 2023-04-20 10:24:41 +01:00 committed by GitHub
parent 4e10118c3a
commit a37bf1ac2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 390 additions and 380 deletions

View File

@ -250,9 +250,7 @@ func getLibraryElements(c context.Context, store db.DB, cfg *setting.Cfg, signed
builder.Write(getFromLibraryElementDTOWithMeta(store.GetDialect())) builder.Write(getFromLibraryElementDTOWithMeta(store.GetDialect()))
builder.Write(" INNER JOIN dashboard AS dashboard on le.folder_id = dashboard.id AND le.folder_id <> 0") builder.Write(" INNER JOIN dashboard AS dashboard on le.folder_id = dashboard.id AND le.folder_id <> 0")
writeParamSelectorSQL(&builder, params...) writeParamSelectorSQL(&builder, params...)
if signedInUser.OrgRole != org.RoleAdmin {
builder.WriteDashboardPermissionFilter(signedInUser, dashboards.PERMISSION_VIEW) builder.WriteDashboardPermissionFilter(signedInUser, dashboards.PERMISSION_VIEW)
}
builder.Write(` OR dashboard.id=0`) builder.Write(` OR dashboard.id=0`)
if err := session.SQL(builder.GetSQLString(), builder.GetParams()...).Find(&libraryElements); err != nil { if err := session.SQL(builder.GetSQLString(), builder.GetParams()...).Find(&libraryElements); err != nil {
return err return err

View File

@ -32,6 +32,7 @@ func (l *LibraryElementService) requireSupportedElementKind(kindAsInt int64) err
} }
func (l *LibraryElementService) requireEditPermissionsOnFolder(ctx context.Context, user *user.SignedInUser, folderID int64) error { func (l *LibraryElementService) requireEditPermissionsOnFolder(ctx context.Context, user *user.SignedInUser, folderID int64) error {
// TODO remove these special cases and handle General folder case in access control guardian
if isGeneralFolder(folderID) && user.HasRole(org.RoleEditor) { if isGeneralFolder(folderID) && user.HasRole(org.RoleEditor) {
return nil return nil
} }

View File

@ -524,7 +524,7 @@ func TestGetAllLibraryElements(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and folderFilter is set to existing folders, it should succeed and the result should be correct", scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and folderFilter is set to existing folders, it should succeed and the result should be correct",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
newFolder := createFolderWithACL(t, sc.sqlStore, "NewFolder", sc.user, []folderACLItem{}) newFolder := createFolder(t, sc, "NewFolder")
command := getCreatePanelCommand(newFolder.ID, "Text - Library Panel2") command := getCreatePanelCommand(newFolder.ID, "Text - Library Panel2")
sc.reqContext.Req.Body = mockRequestBody(command) sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext) resp := sc.service.createHandler(sc.reqContext)
@ -591,7 +591,7 @@ func TestGetAllLibraryElements(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and folderFilter is set to a nonexistent folders, it should succeed and the result should be correct", scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and folderFilter is set to a nonexistent folders, it should succeed and the result should be correct",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
newFolder := createFolderWithACL(t, sc.sqlStore, "NewFolder", sc.user, []folderACLItem{}) newFolder := createFolder(t, sc, "NewFolder")
command := getCreatePanelCommand(newFolder.ID, "Text - Library Panel2") command := getCreatePanelCommand(newFolder.ID, "Text - Library Panel2")
sc.reqContext.Req.Body = mockRequestBody(command) sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext) resp := sc.service.createHandler(sc.reqContext)

View File

@ -69,6 +69,8 @@ func TestGetLibraryElement(t *testing.T) {
} }
} }
sc.reqContext.SignedInUser.Permissions[sc.reqContext.OrgID][dashboards.ActionFoldersRead] = []string{dashboards.ScopeFoldersAll}
// by uid // by uid
sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID}) sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID})
resp := sc.service.getHandler(sc.reqContext) resp := sc.service.getHandler(sc.reqContext)
@ -164,6 +166,8 @@ func TestGetLibraryElement(t *testing.T) {
} }
} }
sc.reqContext.SignedInUser.Permissions[sc.reqContext.OrgID][dashboards.ActionFoldersRead] = []string{dashboards.ScopeFoldersAll}
// by uid // by uid
sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID}) sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID})
resp := sc.service.getHandler(sc.reqContext) resp := sc.service.getHandler(sc.reqContext)

View File

@ -24,7 +24,7 @@ func TestPatchLibraryElement(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to patch a library panel that exists, it should succeed", scenarioWithPanel(t, "When an admin tries to patch a library panel that exists, it should succeed",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
newFolder := createFolderWithACL(t, sc.sqlStore, "NewFolder", sc.user, []folderACLItem{}) newFolder := createFolder(t, sc, "NewFolder")
cmd := model.PatchLibraryElementCommand{ cmd := model.PatchLibraryElementCommand{
FolderID: newFolder.ID, FolderID: newFolder.ID,
Name: "Panel - New name", Name: "Panel - New name",
@ -89,7 +89,7 @@ func TestPatchLibraryElement(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to patch a library panel with folder only, it should change folder successfully and return correct result", scenarioWithPanel(t, "When an admin tries to patch a library panel with folder only, it should change folder successfully and return correct result",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
newFolder := createFolderWithACL(t, sc.sqlStore, "NewFolder", sc.user, []folderACLItem{}) newFolder := createFolder(t, sc, "NewFolder")
cmd := model.PatchLibraryElementCommand{ cmd := model.PatchLibraryElementCommand{
FolderID: newFolder.ID, FolderID: newFolder.ID,
Kind: int64(model.PanelElement), Kind: int64(model.PanelElement),
@ -325,7 +325,7 @@ func TestPatchLibraryElement(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to patch a library panel with a folder where a library panel with the same name already exists, it should fail", scenarioWithPanel(t, "When an admin tries to patch a library panel with a folder where a library panel with the same name already exists, it should fail",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
newFolder := createFolderWithACL(t, sc.sqlStore, "NewFolder", sc.user, []folderACLItem{}) newFolder := createFolder(t, sc, "NewFolder")
command := getCreatePanelCommand(newFolder.ID, "Text - Library Panel") command := getCreatePanelCommand(newFolder.ID, "Text - Library Panel")
sc.ctx.Req.Body = mockRequestBody(command) sc.ctx.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext) resp := sc.service.createHandler(sc.reqContext)

View File

@ -3,6 +3,7 @@ package libraryelements
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http"
"testing" "testing"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
@ -14,122 +15,7 @@ import (
"github.com/grafana/grafana/pkg/web" "github.com/grafana/grafana/pkg/web"
) )
func TestLibraryElementPermissions(t *testing.T) { func TestLibraryElementPermissionsGeneralFolder(t *testing.T) {
var defaultPermissions = []folderACLItem{}
var adminOnlyPermissions = []folderACLItem{{org.RoleAdmin, dashboards.PERMISSION_EDIT}}
var editorOnlyPermissions = []folderACLItem{{org.RoleEditor, dashboards.PERMISSION_EDIT}}
var editorAndViewerPermissions = []folderACLItem{{org.RoleEditor, dashboards.PERMISSION_EDIT}, {org.RoleViewer, dashboards.PERMISSION_EDIT}}
var viewerOnlyPermissions = []folderACLItem{{org.RoleViewer, dashboards.PERMISSION_EDIT}}
var everyonePermissions = []folderACLItem{{org.RoleAdmin, dashboards.PERMISSION_EDIT}, {org.RoleEditor, dashboards.PERMISSION_EDIT}, {org.RoleViewer, dashboards.PERMISSION_EDIT}}
var noPermissions = []folderACLItem{{org.RoleViewer, dashboards.PERMISSION_VIEW}}
var folderCases = [][]folderACLItem{
defaultPermissions,
adminOnlyPermissions,
editorOnlyPermissions,
editorAndViewerPermissions,
viewerOnlyPermissions,
everyonePermissions,
noPermissions,
}
var defaultDesc = "default permissions"
var adminOnlyDesc = "admin only permissions"
var editorOnlyDesc = "editor only permissions"
var editorAndViewerDesc = "editor and viewer permissions"
var viewerOnlyDesc = "viewer only permissions"
var everyoneDesc = "everyone has editor permissions"
var noDesc = "everyone has view permissions"
var accessCases = []struct {
role org.RoleType
items []folderACLItem
desc string
status int
}{
{org.RoleAdmin, defaultPermissions, defaultDesc, 200},
{org.RoleAdmin, adminOnlyPermissions, adminOnlyDesc, 200},
{org.RoleAdmin, editorOnlyPermissions, editorOnlyDesc, 200},
{org.RoleAdmin, editorAndViewerPermissions, editorAndViewerDesc, 200},
{org.RoleAdmin, viewerOnlyPermissions, viewerOnlyDesc, 200},
{org.RoleAdmin, everyonePermissions, everyoneDesc, 200},
{org.RoleAdmin, noPermissions, noDesc, 200},
{org.RoleEditor, defaultPermissions, defaultDesc, 200},
{org.RoleEditor, adminOnlyPermissions, adminOnlyDesc, 403},
{org.RoleEditor, editorOnlyPermissions, editorOnlyDesc, 200},
{org.RoleEditor, editorAndViewerPermissions, editorAndViewerDesc, 200},
{org.RoleEditor, viewerOnlyPermissions, viewerOnlyDesc, 403},
{org.RoleEditor, everyonePermissions, everyoneDesc, 200},
{org.RoleEditor, noPermissions, noDesc, 403},
{org.RoleViewer, defaultPermissions, defaultDesc, 403},
{org.RoleViewer, adminOnlyPermissions, adminOnlyDesc, 403},
{org.RoleViewer, editorOnlyPermissions, editorOnlyDesc, 403},
{org.RoleViewer, editorAndViewerPermissions, editorAndViewerDesc, 200},
{org.RoleViewer, viewerOnlyPermissions, viewerOnlyDesc, 200},
{org.RoleViewer, everyonePermissions, everyoneDesc, 200},
{org.RoleViewer, noPermissions, noDesc, 403},
}
for _, testCase := range accessCases {
testScenario(t, fmt.Sprintf("When %s tries to create a library panel in a folder with %s, it should return correct status", testCase.role, testCase.desc),
func(t *testing.T, sc scenarioContext) {
folder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, testCase.items)
sc.reqContext.SignedInUser.OrgRole = testCase.role
command := getCreatePanelCommand(folder.ID, "Library Panel Name")
sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, testCase.status, resp.Status())
})
testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it to a folder with %s, it should return correct status", testCase.role, testCase.desc),
func(t *testing.T, sc scenarioContext) {
fromFolder := createFolderWithACL(t, sc.sqlStore, "Everyone", sc.user, everyonePermissions)
command := getCreatePanelCommand(fromFolder.ID, "Library Panel Name")
sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext)
result := validateAndUnMarshalResponse(t, resp)
toFolder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, testCase.items)
sc.reqContext.SignedInUser.OrgRole = testCase.role
cmd := model.PatchLibraryElementCommand{FolderID: toFolder.ID, 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())
})
testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it from a folder with %s, it should return correct status", testCase.role, testCase.desc),
func(t *testing.T, sc scenarioContext) {
fromFolder := createFolderWithACL(t, sc.sqlStore, "Everyone", sc.user, testCase.items)
command := getCreatePanelCommand(fromFolder.ID, "Library Panel Name")
sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext)
result := validateAndUnMarshalResponse(t, resp)
toFolder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, everyonePermissions)
sc.reqContext.SignedInUser.OrgRole = testCase.role
cmd := model.PatchLibraryElementCommand{FolderID: toFolder.ID, 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())
})
testScenario(t, fmt.Sprintf("When %s tries to delete a library panel in a folder with %s, it should return correct status", testCase.role, testCase.desc),
func(t *testing.T, sc scenarioContext) {
folder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, testCase.items)
cmd := getCreatePanelCommand(folder.ID, "Library Panel Name")
sc.reqContext.Req.Body = mockRequestBody(cmd)
resp := sc.service.createHandler(sc.reqContext)
result := validateAndUnMarshalResponse(t, resp)
sc.reqContext.SignedInUser.OrgRole = testCase.role
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())
})
}
var generalFolderCases = []struct { var generalFolderCases = []struct {
role org.RoleType role org.RoleType
status int status int
@ -152,7 +38,7 @@ func TestLibraryElementPermissions(t *testing.T) {
testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it to the General folder, it should return correct status", testCase.role), testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it to the General folder, it should return correct status", testCase.role),
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
folder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, everyonePermissions) folder := createFolder(t, sc, "Folder")
command := getCreatePanelCommand(folder.ID, "Library Panel Name") command := getCreatePanelCommand(folder.ID, "Library Panel Name")
sc.reqContext.Req.Body = mockRequestBody(command) sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext) resp := sc.service.createHandler(sc.reqContext)
@ -168,7 +54,7 @@ func TestLibraryElementPermissions(t *testing.T) {
testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it from the General folder, it should return correct status", testCase.role), testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it from the General folder, it should return correct status", testCase.role),
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
folder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, everyonePermissions) folder := createFolder(t, sc, "Folder")
command := getCreatePanelCommand(0, "Library Panel Name") command := getCreatePanelCommand(0, "Library Panel Name")
sc.reqContext.Req.Body = mockRequestBody(command) sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext) resp := sc.service.createHandler(sc.reqContext)
@ -194,79 +80,6 @@ func TestLibraryElementPermissions(t *testing.T) {
resp = sc.service.deleteHandler(sc.reqContext) resp = sc.service.deleteHandler(sc.reqContext)
require.Equal(t, testCase.status, resp.Status()) require.Equal(t, testCase.status, resp.Status())
}) })
}
var missingFolderCases = []struct {
role org.RoleType
}{
{org.RoleAdmin},
{org.RoleEditor},
{org.RoleViewer},
}
for _, testCase := range missingFolderCases {
testScenario(t, fmt.Sprintf("When %s tries to create a library panel in a folder that doesn't exist, it should fail", testCase.role),
func(t *testing.T, sc scenarioContext) {
sc.reqContext.SignedInUser.OrgRole = testCase.role
command := getCreatePanelCommand(-100, "Library Panel Name")
sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 404, resp.Status())
})
testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it to a folder that doesn't exist, it should fail", testCase.role),
func(t *testing.T, sc scenarioContext) {
folder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, everyonePermissions)
command := getCreatePanelCommand(folder.ID, "Library Panel Name")
sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext)
result := validateAndUnMarshalResponse(t, resp)
sc.reqContext.SignedInUser.OrgRole = testCase.role
cmd := model.PatchLibraryElementCommand{FolderID: -100, 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, 404, resp.Status())
})
}
var getCases = []struct {
role org.RoleType
statuses []int
}{
{org.RoleAdmin, []int{200, 200, 200, 200, 200, 200, 200}},
{org.RoleEditor, []int{200, 404, 200, 200, 200, 200, 200}},
{org.RoleViewer, []int{200, 404, 404, 200, 200, 200, 200}},
}
for _, testCase := range getCases {
testScenario(t, fmt.Sprintf("When %s tries to get a library panel, it should return correct response", testCase.role),
func(t *testing.T, sc scenarioContext) {
var results []libraryElement
for i, folderCase := range folderCases {
folder := createFolderWithACL(t, sc.sqlStore, fmt.Sprintf("Folder%v", i), sc.user, folderCase)
cmd := getCreatePanelCommand(folder.ID, fmt.Sprintf("Library Panel in Folder%v", i))
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
results = append(results, result.Result)
}
sc.reqContext.SignedInUser.OrgRole = testCase.role
for i, result := range results {
sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": result.UID})
resp := sc.service.getHandler(sc.reqContext)
require.Equal(t, testCase.statuses[i], resp.Status())
}
})
testScenario(t, fmt.Sprintf("When %s tries to get a library panel from General folder, it should return correct response", testCase.role), testScenario(t, fmt.Sprintf("When %s tries to get a library panel from General folder, it should return correct response", testCase.role),
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
@ -292,73 +105,6 @@ func TestLibraryElementPermissions(t *testing.T) {
t.Fatalf("Result mismatch (-want +got):\n%s", diff) t.Fatalf("Result mismatch (-want +got):\n%s", diff)
} }
}) })
}
var getAllCases = []struct {
role org.RoleType
panels int
folderIndexes []int
}{
{org.RoleAdmin, 7, []int{0, 1, 2, 3, 4, 5, 6}},
{org.RoleEditor, 6, []int{0, 2, 3, 4, 5, 6}},
{org.RoleViewer, 5, []int{0, 3, 4, 5, 6}},
}
for _, testCase := range getAllCases {
testScenario(t, fmt.Sprintf("When %s tries to get all library panels, it should return correct response", testCase.role),
func(t *testing.T, sc scenarioContext) {
var results []libraryElement
for i, folderCase := range folderCases {
folder := createFolderWithACL(t, sc.sqlStore, fmt.Sprintf("Folder%v", i), sc.user, folderCase)
cmd := getCreatePanelCommand(folder.ID, fmt.Sprintf("Library Panel in Folder%v", i))
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
results = append(results, result.Result)
}
sc.reqContext.SignedInUser.OrgRole = testCase.role
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.panels, len(actual.Result.Elements))
for _, folderIndex := range testCase.folderIndexes {
var folderID = int64(folderIndex + 2) // testScenario creates one folder and general folder doesn't count
var foundExists = false
var foundResult libraryElement
var actualExists = false
var actualResult libraryElement
for _, result := range results {
if result.FolderID == folderID {
foundExists = true
foundResult = result
break
}
}
require.Equal(t, foundExists, true)
for _, result := range actual.Result.Elements {
if result.FolderID == folderID {
actualExists = true
actualResult = result
break
}
}
require.Equal(t, actualExists, true)
if diff := cmp.Diff(foundResult, actualResult, getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
}
})
testScenario(t, fmt.Sprintf("When %s tries to get all library panels from General folder, it should return correct response", testCase.role), testScenario(t, fmt.Sprintf("When %s tries to get all library panels from General folder, it should return correct response", testCase.role),
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
@ -385,3 +131,300 @@ func TestLibraryElementPermissions(t *testing.T) {
}) })
} }
} }
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("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("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("Folder")},
},
status: http.StatusForbidden,
},
}
for _, testCase := range accessCases {
testScenario(t, testCase.desc,
func(t *testing.T, sc scenarioContext) {
folder := createFolder(t, sc, "Folder")
sc.reqContext.SignedInUser.Permissions = map[int64]map[string][]string{
1: testCase.permissions,
}
command := getCreatePanelCommand(folder.ID, "Library Panel Name")
sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, testCase.status, resp.Status())
})
}
}
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("FromFolder"), dashboards.ScopeFoldersProvider.GetResourceScopeUID("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,
},
}
for _, testCase := range accessCases {
testScenario(t, testCase.desc,
func(t *testing.T, sc scenarioContext) {
fromFolder := createFolder(t, sc, "FromFolder")
command := getCreatePanelCommand(fromFolder.ID, "Library Panel Name")
sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext)
result := validateAndUnMarshalResponse(t, resp)
toFolder := createFolder(t, sc, "ToFolder")
sc.reqContext.SignedInUser.Permissions = map[int64]map[string][]string{
1: testCase.permissions,
}
cmd := model.PatchLibraryElementCommand{FolderID: toFolder.ID, 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())
})
}
}
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("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()},
},
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")},
},
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,
},
}
for _, testCase := range accessCases {
testScenario(t, testCase.desc,
func(t *testing.T, sc scenarioContext) {
folder := createFolder(t, sc, "Folder")
command := getCreatePanelCommand(folder.ID, "Library Panel Name")
sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext)
result := validateAndUnMarshalResponse(t, resp)
sc.reqContext.SignedInUser.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 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(-100, "Library Panel Name")
sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 404, resp.Status())
})
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")
command := getCreatePanelCommand(folder.ID, "Library Panel Name")
sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext)
result := validateAndUnMarshalResponse(t, resp)
cmd := model.PatchLibraryElementCommand{FolderID: -100, 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, 404, resp.Status())
})
}
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.StatusNotFound,
},
}
for _, testCase := range getCases {
testScenario(t, testCase.desc,
func(t *testing.T, sc scenarioContext) {
folder := createFolder(t, sc, "Folder")
cmd := getCreatePanelCommand(folder.ID, "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.SignedInUser.OrgRole = org.RoleViewer
sc.reqContext.SignedInUser.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 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()},
},
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))
cmd := getCreatePanelCommand(folder.ID, 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.SignedInUser.OrgRole = org.RoleViewer
sc.reqContext.SignedInUser.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))
})
}
}

View File

@ -10,6 +10,7 @@ import (
"time" "time"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
@ -17,9 +18,11 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/appcontext" "github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/db/dbtest"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/kinds/librarypanel" "github.com/grafana/grafana/pkg/kinds/librarypanel"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
@ -37,7 +40,6 @@ import (
"github.com/grafana/grafana/pkg/services/quota/quotatest" "github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl" "github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/team/teamtest"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/services/user/userimpl" "github.com/grafana/grafana/pkg/services/user/userimpl"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
@ -268,11 +270,6 @@ type scenarioContext struct {
sqlStore db.DB sqlStore db.DB
} }
type folderACLItem struct {
roleType org.RoleType
permission dashboards.PermissionType
}
func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash *dashboards.Dashboard, folderID int64) *dashboards.Dashboard { func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash *dashboards.Dashboard, folderID int64) *dashboards.Dashboard {
dash.FolderID = folderID dash.FolderID = folderID
dashItem := &dashboards.SaveDashboardDTO{ dashItem := &dashboards.SaveDashboardDTO{
@ -284,16 +281,16 @@ func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash
} }
cfg := setting.NewCfg() cfg := setting.NewCfg()
cfg.RBACEnabled = false
features := featuremgmt.WithFeatures() features := featuremgmt.WithFeatures()
cfg.IsFeatureToggleEnabled = features.IsEnabled cfg.IsFeatureToggleEnabled = features.IsEnabled
quotaService := quotatest.New(false, nil) quotaService := quotatest.New(false, nil)
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
require.NoError(t, err) require.NoError(t, err)
dashAlertExtractor := alerting.ProvideDashAlertExtractorService(nil, nil, nil) dashAlertExtractor := alerting.ProvideDashAlertExtractorService(nil, nil, nil)
ac := acmock.New() ac := actest.FakeAccessControl{ExpectedEvaluate: true}
folderPermissions := acmock.NewMockedPermissionsService() folderPermissions := acmock.NewMockedPermissionsService()
dashboardPermissions := acmock.NewMockedPermissionsService() dashboardPermissions := acmock.NewMockedPermissionsService()
dashboardPermissions.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil)
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
service, err := dashboardservice.ProvideDashboardServiceImpl( service, err := dashboardservice.ProvideDashboardServiceImpl(
cfg, dashboardStore, folderStore, dashAlertExtractor, cfg, dashboardStore, folderStore, dashAlertExtractor,
@ -307,57 +304,34 @@ func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash
return dashboard return dashboard
} }
func createFolderWithACL(t *testing.T, sqlStore db.DB, title string, user user.SignedInUser, func createFolder(t *testing.T, sc scenarioContext, title string) *folder.Folder {
items []folderACLItem) *folder.Folder {
t.Helper() t.Helper()
cfg := setting.NewCfg() cfg := setting.NewCfg()
cfg.RBACEnabled = false
features := featuremgmt.WithFeatures() features := featuremgmt.WithFeatures()
cfg.IsFeatureToggleEnabled = features.IsEnabled cfg.IsFeatureToggleEnabled = features.IsEnabled
ac := acmock.New() ac := actest.FakeAccessControl{}
quotaService := quotatest.New(false, nil) quotaService := quotatest.New(false, nil)
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) dashboardStore, err := database.ProvideDashboardStore(sc.sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sc.sqlStore, cfg), quotaService)
require.NoError(t, err) require.NoError(t, err)
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) folderStore := folderimpl.ProvideDashboardFolderStore(sc.sqlStore)
s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, nil, features) s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, nil, features)
t.Logf("Creating folder with title and UID %q", title) t.Logf("Creating folder with title and UID %q", title)
ctx := appcontext.WithUser(context.Background(), &user) ctx := appcontext.WithUser(context.Background(), &sc.user)
folder, err := s.Create(ctx, &folder.CreateFolderCommand{ folder, err := s.Create(ctx, &folder.CreateFolderCommand{
OrgID: user.OrgID, Title: title, UID: title, SignedInUser: &user, OrgID: sc.user.OrgID, Title: title, UID: title, SignedInUser: &sc.user,
}) })
require.NoError(t, err) require.NoError(t, err)
updateFolderACL(t, dashboardStore, folder.ID, items) // Set user permissions on the newly created folder so that they can interact with library elements stored in it
sc.reqContext.SignedInUser.Permissions[sc.user.OrgID][dashboards.ActionFoldersWrite] = append(sc.reqContext.SignedInUser.Permissions[sc.user.OrgID][dashboards.ActionFoldersWrite], dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.UID))
sc.reqContext.SignedInUser.Permissions[sc.user.OrgID][dashboards.ActionFoldersRead] = append(sc.reqContext.SignedInUser.Permissions[sc.user.OrgID][dashboards.ActionFoldersRead], dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.UID))
sc.reqContext.SignedInUser.Permissions[sc.user.OrgID][dashboards.ActionDashboardsCreate] = append(sc.reqContext.SignedInUser.Permissions[sc.user.OrgID][dashboards.ActionDashboardsCreate], dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.UID))
return folder return folder
} }
func updateFolderACL(t *testing.T, dashboardStore dashboards.Store, folderID int64, items []folderACLItem) {
t.Helper()
if len(items) == 0 {
return
}
var aclItems []*dashboards.DashboardACL
for _, item := range items {
role := item.roleType
permission := item.permission
aclItems = append(aclItems, &dashboards.DashboardACL{
DashboardID: folderID,
Role: &role,
Permission: permission,
Created: time.Now(),
Updated: time.Now(),
})
}
err := dashboardStore.UpdateDashboardACL(context.Background(), folderID, aclItems)
require.NoError(t, err)
}
func validateAndUnMarshalResponse(t *testing.T, resp response.Response) libraryElementResult { func validateAndUnMarshalResponse(t *testing.T, resp response.Response) libraryElementResult {
t.Helper() t.Helper()
@ -392,8 +366,23 @@ func validateAndUnMarshalArrayResponse(t *testing.T, resp response.Response) lib
func scenarioWithPanel(t *testing.T, desc string, fn func(t *testing.T, sc scenarioContext)) { func scenarioWithPanel(t *testing.T, desc string, fn func(t *testing.T, sc scenarioContext)) {
t.Helper() t.Helper()
store := dbtest.NewFakeDB()
guardian.InitLegacyGuardian(setting.NewCfg(), store, &dashboards.FakeDashboardService{}, &teamtest.FakeService{}) sqlStore := db.InitTestDB(t)
ac := actest.FakeAccessControl{}
quotaService := quotatest.New(false, nil)
dashboardStore, err := database.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
require.NoError(t, err)
features := featuremgmt.WithFeatures()
folderPermissions := acmock.NewMockedPermissionsService()
dashboardPermissions := acmock.NewMockedPermissionsService()
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
dashboardService, svcErr := dashboardservice.ProvideDashboardServiceImpl(
sqlStore.Cfg, dashboardStore, folderStore, nil,
features, folderPermissions, dashboardPermissions, ac,
foldertest.NewFakeService(),
)
require.NoError(t, svcErr)
guardian.InitAccessControlGuardian(sqlStore.Cfg, sqlStore, ac, folderPermissions, dashboardPermissions, dashboardService)
testScenario(t, desc, func(t *testing.T, sc scenarioContext) { testScenario(t, desc, func(t *testing.T, sc scenarioContext) {
command := getCreatePanelCommand(sc.folder.ID, "Text - Library Panel") command := getCreatePanelCommand(sc.folder.ID, "Text - Library Panel")
@ -421,6 +410,10 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
OrgID: orgID, OrgID: orgID,
OrgRole: role, OrgRole: role,
LastSeenAt: time.Now(), LastSeenAt: time.Now(),
// Allow user to create folders
Permissions: map[int64]map[string][]string{
1: {dashboards.ActionFoldersCreate: {}},
},
} }
req := &http.Request{ req := &http.Request{
Header: http.Header{ Header: http.Header{
@ -436,21 +429,21 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
dashboardStore, err := database.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService) dashboardStore, err := database.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
require.NoError(t, err) require.NoError(t, err)
features := featuremgmt.WithFeatures() features := featuremgmt.WithFeatures()
ac := acmock.New().WithDisabled() ac := acimpl.ProvideAccessControl(sqlStore.Cfg)
// TODO: Update tests to work with rbac
sqlStore.Cfg.RBACEnabled = false
folderPermissions := acmock.NewMockedPermissionsService() folderPermissions := acmock.NewMockedPermissionsService()
folderPermissions.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil)
dashboardPermissions := acmock.NewMockedPermissionsService() dashboardPermissions := acmock.NewMockedPermissionsService()
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
dashboardService, err := dashboardservice.ProvideDashboardServiceImpl( dashService, dashSvcErr := dashboardservice.ProvideDashboardServiceImpl(
sqlStore.Cfg, dashboardStore, folderStore, nil, sqlStore.Cfg, dashboardStore, folderStore, nil,
features, folderPermissions, dashboardPermissions, ac, features, folderPermissions, dashboardPermissions, ac,
foldertest.NewFakeService(), foldertest.NewFakeService(),
) )
require.NoError(t, err) require.NoError(t, dashSvcErr)
guardian.InitLegacyGuardian(sqlStore.Cfg, sqlStore, dashboardService, &teamtest.FakeService{}) guardian.InitAccessControlGuardian(sqlStore.Cfg, sqlStore, ac, folderPermissions, dashboardPermissions, dashService)
service := LibraryElementService{ service := LibraryElementService{
Cfg: sqlStore.Cfg, Cfg: sqlStore.Cfg,
features: featuremgmt.WithFeatures(),
SQLStore: sqlStore, SQLStore: sqlStore,
folderService: folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardStore, folderStore, nil, features), folderService: folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardStore, folderStore, nil, features),
} }
@ -481,7 +474,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
}, },
} }
sc.folder = createFolderWithACL(t, sc.sqlStore, "ScenarioFolder", sc.user, []folderACLItem{}) sc.folder = createFolder(t, sc, "ScenarioFolder")
fn(t, sc) fn(t, sc)
}) })

View File

@ -15,10 +15,11 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/appcontext" "github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/db/dbtest"
"github.com/grafana/grafana/pkg/infra/slugify" "github.com/grafana/grafana/pkg/infra/slugify"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/kinds/librarypanel" "github.com/grafana/grafana/pkg/kinds/librarypanel"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
@ -36,7 +37,6 @@ import (
"github.com/grafana/grafana/pkg/services/quota/quotatest" "github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl" "github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/team/teamtest"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/services/user/userimpl" "github.com/grafana/grafana/pkg/services/user/userimpl"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
@ -609,11 +609,6 @@ type scenarioContext struct {
sqlStore db.DB sqlStore db.DB
} }
type folderACLItem struct {
roleType org.RoleType
permission dashboards.PermissionType
}
func toLibraryElement(t *testing.T, res model.LibraryElementDTO) libraryElement { func toLibraryElement(t *testing.T, res model.LibraryElementDTO) libraryElement {
var libraryElementModel = libraryElementModel{} var libraryElementModel = libraryElementModel{}
err := json.Unmarshal(res.Model, &libraryElementModel) err := json.Unmarshal(res.Model, &libraryElementModel)
@ -699,17 +694,18 @@ func createDashboard(t *testing.T, sqlStore db.DB, user *user.SignedInUser, dash
} }
cfg := setting.NewCfg() cfg := setting.NewCfg()
cfg.RBACEnabled = false
cfg.IsFeatureToggleEnabled = featuremgmt.WithFeatures().IsEnabled cfg.IsFeatureToggleEnabled = featuremgmt.WithFeatures().IsEnabled
quotaService := quotatest.New(false, nil) quotaService := quotatest.New(false, nil)
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
require.NoError(t, err) require.NoError(t, err)
dashAlertService := alerting.ProvideDashAlertExtractorService(nil, nil, nil) dashAlertService := alerting.ProvideDashAlertExtractorService(nil, nil, nil)
ac := acmock.New() ac := actest.FakeAccessControl{ExpectedEvaluate: true}
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
dashPermissionService := acmock.NewMockedPermissionsService()
dashPermissionService.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil)
service, err := dashboardservice.ProvideDashboardServiceImpl( service, err := dashboardservice.ProvideDashboardServiceImpl(
cfg, dashboardStore, folderStore, dashAlertService, cfg, dashboardStore, folderStore, dashAlertService,
featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(), acmock.NewMockedPermissionsService(), ac, featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(), dashPermissionService, ac,
foldertest.NewFakeService(), foldertest.NewFakeService(),
) )
require.NoError(t, err) require.NoError(t, err)
@ -719,68 +715,28 @@ func createDashboard(t *testing.T, sqlStore db.DB, user *user.SignedInUser, dash
return dashboard return dashboard
} }
func createFolderWithACL(t *testing.T, sqlStore db.DB, title string, user *user.SignedInUser, func createFolder(t *testing.T, sc scenarioContext, title string) *folder.Folder {
items []folderACLItem) *folder.Folder {
t.Helper() t.Helper()
ac := acmock.New() ac := actest.FakeAccessControl{ExpectedEvaluate: true}
cfg := setting.NewCfg() cfg := setting.NewCfg()
cfg.RBACEnabled = false
cfg.IsFeatureToggleEnabled = featuremgmt.WithFeatures().IsEnabled cfg.IsFeatureToggleEnabled = featuremgmt.WithFeatures().IsEnabled
features := featuremgmt.WithFeatures() features := featuremgmt.WithFeatures()
quotaService := quotatest.New(false, nil) quotaService := quotatest.New(false, nil)
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) dashboardStore, err := database.ProvideDashboardStore(sc.sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sc.sqlStore, cfg), quotaService)
require.NoError(t, err) require.NoError(t, err)
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) folderStore := folderimpl.ProvideDashboardFolderStore(sc.sqlStore)
s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, nil, features) s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, nil, features)
t.Logf("Creating folder with title and UID %q", title) t.Logf("Creating folder with title and UID %q", title)
ctx := appcontext.WithUser(context.Background(), user) ctx := appcontext.WithUser(context.Background(), sc.user)
folder, err := s.Create(ctx, &folder.CreateFolderCommand{OrgID: user.OrgID, Title: title, UID: title, SignedInUser: user}) folder, err := s.Create(ctx, &folder.CreateFolderCommand{OrgID: sc.user.OrgID, Title: title, UID: title, SignedInUser: sc.user})
require.NoError(t, err) require.NoError(t, err)
updateFolderACL(t, dashboardStore, folder.ID, items)
return folder return folder
} }
func updateFolderACL(t *testing.T, dashboardStore dashboards.Store, folderID int64, items []folderACLItem) {
t.Helper()
if len(items) == 0 {
return
}
var aclItems []*dashboards.DashboardACL
for _, item := range items {
role := item.roleType
permission := item.permission
aclItems = append(aclItems, &dashboards.DashboardACL{
DashboardID: folderID,
Role: &role,
Permission: permission,
Created: time.Now(),
Updated: time.Now(),
})
}
err := dashboardStore.UpdateDashboardACL(context.Background(), folderID, aclItems)
require.NoError(t, err)
}
func scenarioWithLibraryPanel(t *testing.T, desc string, fn func(t *testing.T, sc scenarioContext)) { func scenarioWithLibraryPanel(t *testing.T, desc string, fn func(t *testing.T, sc scenarioContext)) {
store := dbtest.NewFakeDB()
dashSvc := dashboards.NewFakeDashboardService(t)
var result *dashboards.Dashboard
dashSvc.On("GetDashboard", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*dashboards.GetDashboardQuery)
result = &dashboards.Dashboard{
ID: q.ID,
UID: q.UID,
}
}).Return(result, nil)
guardian.InitLegacyGuardian(setting.NewCfg(), store, dashSvc, &teamtest.FakeService{})
t.Helper() t.Helper()
testScenario(t, desc, func(t *testing.T, sc scenarioContext) { testScenario(t, desc, func(t *testing.T, sc scenarioContext) {
@ -829,19 +785,28 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
t.Helper() t.Helper()
t.Run(desc, func(t *testing.T) { t.Run(desc, func(t *testing.T) {
cfg := setting.NewCfg()
cfg.RBACEnabled = false
orgID := int64(1) orgID := int64(1)
role := org.RoleAdmin role := org.RoleAdmin
sqlStore, cfg := db.InitTestDBwithCfg(t) sqlStore, cfg := db.InitTestDBwithCfg(t)
quotaService := quotatest.New(false, nil) quotaService := quotatest.New(false, nil)
ac := actest.FakeAccessControl{ExpectedEvaluate: true}
dashStore := &dashboards.FakeDashboardStore{}
dashStore.On("GetDashboard", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{ID: 1}, nil)
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
dashAlertService := alerting.ProvideDashAlertExtractorService(nil, nil, nil)
dashPermissionService := acmock.NewMockedPermissionsService()
dashService, err := dashboardservice.ProvideDashboardServiceImpl(
setting.NewCfg(), dashStore, folderStore, dashAlertService,
featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(), dashPermissionService, ac,
foldertest.NewFakeService(),
)
require.NoError(t, err)
guardian.InitAccessControlGuardian(setting.NewCfg(), sqlStore, ac, acmock.NewMockedPermissionsService(), acmock.NewMockedPermissionsService(), dashService)
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService) dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
require.NoError(t, err) require.NoError(t, err)
features := featuremgmt.WithFeatures() features := featuremgmt.WithFeatures()
ac := acmock.New()
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
folderService := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, nil, features) folderService := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, nil, features)
elementService := libraryelements.ProvideService(cfg, sqlStore, routing.NewRouteRegister(), folderService, featuremgmt.WithFeatures()) elementService := libraryelements.ProvideService(cfg, sqlStore, routing.NewRouteRegister(), folderService, featuremgmt.WithFeatures())
@ -859,6 +824,12 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
OrgID: orgID, OrgID: orgID,
OrgRole: role, OrgRole: role,
LastSeenAt: time.Now(), LastSeenAt: time.Now(),
// Allow the user to create folders
Permissions: map[int64]map[string][]string{
orgID: {
dashboards.ActionFoldersRead: {dashboards.ScopeFoldersAll},
},
},
} }
// deliberate difference between signed in user and user in db to make it crystal clear // deliberate difference between signed in user and user in db to make it crystal clear
@ -884,7 +855,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
sqlStore: sqlStore, sqlStore: sqlStore,
} }
foldr := createFolderWithACL(t, sc.sqlStore, "ScenarioFolder", sc.user, []folderACLItem{}) foldr := createFolder(t, sc, "ScenarioFolder")
sc.folder = &folder.Folder{ sc.folder = &folder.Folder{
ID: foldr.ID, ID: foldr.ID,
UID: foldr.UID, UID: foldr.UID,