From a37bf1ac2a4d3b2f2c00935da1b85026de6cd3a2 Mon Sep 17 00:00:00 2001 From: Ieva Date: Thu, 20 Apr 2023 10:24:41 +0100 Subject: [PATCH] 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 --- pkg/services/libraryelements/database.go | 4 +- pkg/services/libraryelements/guard.go | 1 + .../libraryelements_get_all_test.go | 4 +- .../libraryelements_get_test.go | 4 + .../libraryelements_patch_test.go | 6 +- .../libraryelements_permissions_test.go | 559 ++++++++++-------- .../libraryelements/libraryelements_test.go | 95 ++- .../librarypanels/librarypanels_test.go | 97 ++- 8 files changed, 390 insertions(+), 380 deletions(-) diff --git a/pkg/services/libraryelements/database.go b/pkg/services/libraryelements/database.go index ee7cf98325d..5c1613a1775 100644 --- a/pkg/services/libraryelements/database.go +++ b/pkg/services/libraryelements/database.go @@ -250,9 +250,7 @@ func getLibraryElements(c context.Context, store db.DB, cfg *setting.Cfg, signed builder.Write(getFromLibraryElementDTOWithMeta(store.GetDialect())) builder.Write(" INNER JOIN dashboard AS dashboard on le.folder_id = dashboard.id AND le.folder_id <> 0") 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`) if err := session.SQL(builder.GetSQLString(), builder.GetParams()...).Find(&libraryElements); err != nil { return err diff --git a/pkg/services/libraryelements/guard.go b/pkg/services/libraryelements/guard.go index b866b19fb49..bad8521fe38 100644 --- a/pkg/services/libraryelements/guard.go +++ b/pkg/services/libraryelements/guard.go @@ -32,6 +32,7 @@ func (l *LibraryElementService) requireSupportedElementKind(kindAsInt int64) err } 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) { return nil } diff --git a/pkg/services/libraryelements/libraryelements_get_all_test.go b/pkg/services/libraryelements/libraryelements_get_all_test.go index d84b894bc6a..1b76dfc3e14 100644 --- a/pkg/services/libraryelements/libraryelements_get_all_test.go +++ b/pkg/services/libraryelements/libraryelements_get_all_test.go @@ -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", 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") sc.reqContext.Req.Body = mockRequestBody(command) 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", 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") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) diff --git a/pkg/services/libraryelements/libraryelements_get_test.go b/pkg/services/libraryelements/libraryelements_get_test.go index 376683d2e0c..c8a15c05b47 100644 --- a/pkg/services/libraryelements/libraryelements_get_test.go +++ b/pkg/services/libraryelements/libraryelements_get_test.go @@ -69,6 +69,8 @@ func TestGetLibraryElement(t *testing.T) { } } + sc.reqContext.SignedInUser.Permissions[sc.reqContext.OrgID][dashboards.ActionFoldersRead] = []string{dashboards.ScopeFoldersAll} + // by uid sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID}) 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 sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID}) resp := sc.service.getHandler(sc.reqContext) diff --git a/pkg/services/libraryelements/libraryelements_patch_test.go b/pkg/services/libraryelements/libraryelements_patch_test.go index 785da3da74e..6aed30212ee 100644 --- a/pkg/services/libraryelements/libraryelements_patch_test.go +++ b/pkg/services/libraryelements/libraryelements_patch_test.go @@ -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", func(t *testing.T, sc scenarioContext) { - newFolder := createFolderWithACL(t, sc.sqlStore, "NewFolder", sc.user, []folderACLItem{}) + newFolder := createFolder(t, sc, "NewFolder") cmd := model.PatchLibraryElementCommand{ FolderID: newFolder.ID, 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", func(t *testing.T, sc scenarioContext) { - newFolder := createFolderWithACL(t, sc.sqlStore, "NewFolder", sc.user, []folderACLItem{}) + newFolder := createFolder(t, sc, "NewFolder") cmd := model.PatchLibraryElementCommand{ FolderID: newFolder.ID, 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", 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") sc.ctx.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) diff --git a/pkg/services/libraryelements/libraryelements_permissions_test.go b/pkg/services/libraryelements/libraryelements_permissions_test.go index 036f5446622..662f823e649 100644 --- a/pkg/services/libraryelements/libraryelements_permissions_test.go +++ b/pkg/services/libraryelements/libraryelements_permissions_test.go @@ -3,6 +3,7 @@ package libraryelements import ( "encoding/json" "fmt" + "net/http" "testing" "github.com/google/go-cmp/cmp" @@ -14,122 +15,7 @@ import ( "github.com/grafana/grafana/pkg/web" ) -func TestLibraryElementPermissions(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()) - }) - } - +func TestLibraryElementPermissionsGeneralFolder(t *testing.T) { var generalFolderCases = []struct { role org.RoleType 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), 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") sc.reqContext.Req.Body = mockRequestBody(command) 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), 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") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) @@ -194,79 +80,6 @@ func TestLibraryElementPermissions(t *testing.T) { resp = sc.service.deleteHandler(sc.reqContext) 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), func(t *testing.T, sc scenarioContext) { @@ -292,73 +105,6 @@ func TestLibraryElementPermissions(t *testing.T) { 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), 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)) + }) + } +} diff --git a/pkg/services/libraryelements/libraryelements_test.go b/pkg/services/libraryelements/libraryelements_test.go index 967d43cbe3d..32db4c162c7 100644 --- a/pkg/services/libraryelements/libraryelements_test.go +++ b/pkg/services/libraryelements/libraryelements_test.go @@ -10,6 +10,7 @@ import ( "time" "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/api/response" @@ -17,9 +18,11 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/appcontext" "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/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" "github.com/grafana/grafana/pkg/services/alerting" 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/supportbundles/supportbundlestest" "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/userimpl" "github.com/grafana/grafana/pkg/setting" @@ -268,11 +270,6 @@ type scenarioContext struct { 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 { dash.FolderID = folderID dashItem := &dashboards.SaveDashboardDTO{ @@ -284,16 +281,16 @@ func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash } cfg := setting.NewCfg() - cfg.RBACEnabled = false features := featuremgmt.WithFeatures() cfg.IsFeatureToggleEnabled = features.IsEnabled quotaService := quotatest.New(false, nil) dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) require.NoError(t, err) dashAlertExtractor := alerting.ProvideDashAlertExtractorService(nil, nil, nil) - ac := acmock.New() + ac := actest.FakeAccessControl{ExpectedEvaluate: true} folderPermissions := acmock.NewMockedPermissionsService() dashboardPermissions := acmock.NewMockedPermissionsService() + dashboardPermissions.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil) folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) service, err := dashboardservice.ProvideDashboardServiceImpl( cfg, dashboardStore, folderStore, dashAlertExtractor, @@ -307,57 +304,34 @@ func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash return dashboard } -func createFolderWithACL(t *testing.T, sqlStore db.DB, title string, user user.SignedInUser, - items []folderACLItem) *folder.Folder { +func createFolder(t *testing.T, sc scenarioContext, title string) *folder.Folder { t.Helper() cfg := setting.NewCfg() - cfg.RBACEnabled = false features := featuremgmt.WithFeatures() cfg.IsFeatureToggleEnabled = features.IsEnabled - ac := acmock.New() + ac := actest.FakeAccessControl{} 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) - folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) + folderStore := folderimpl.ProvideDashboardFolderStore(sc.sqlStore) s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, nil, features) 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, + OrgID: sc.user.OrgID, Title: title, UID: title, SignedInUser: &sc.user, }) 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 } -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 { 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)) { 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) { 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, OrgRole: role, LastSeenAt: time.Now(), + // Allow user to create folders + Permissions: map[int64]map[string][]string{ + 1: {dashboards.ActionFoldersCreate: {}}, + }, } req := &http.Request{ 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) require.NoError(t, err) features := featuremgmt.WithFeatures() - ac := acmock.New().WithDisabled() - // TODO: Update tests to work with rbac - sqlStore.Cfg.RBACEnabled = false + ac := acimpl.ProvideAccessControl(sqlStore.Cfg) folderPermissions := acmock.NewMockedPermissionsService() + folderPermissions.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil) dashboardPermissions := acmock.NewMockedPermissionsService() folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) - dashboardService, err := dashboardservice.ProvideDashboardServiceImpl( + dashService, dashSvcErr := dashboardservice.ProvideDashboardServiceImpl( sqlStore.Cfg, dashboardStore, folderStore, nil, features, folderPermissions, dashboardPermissions, ac, foldertest.NewFakeService(), ) - require.NoError(t, err) - guardian.InitLegacyGuardian(sqlStore.Cfg, sqlStore, dashboardService, &teamtest.FakeService{}) + require.NoError(t, dashSvcErr) + guardian.InitAccessControlGuardian(sqlStore.Cfg, sqlStore, ac, folderPermissions, dashboardPermissions, dashService) service := LibraryElementService{ Cfg: sqlStore.Cfg, + features: featuremgmt.WithFeatures(), SQLStore: sqlStore, 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) }) diff --git a/pkg/services/librarypanels/librarypanels_test.go b/pkg/services/librarypanels/librarypanels_test.go index 37130266d95..36765e66966 100644 --- a/pkg/services/librarypanels/librarypanels_test.go +++ b/pkg/services/librarypanels/librarypanels_test.go @@ -15,10 +15,11 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/appcontext" "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/tracing" "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" "github.com/grafana/grafana/pkg/services/alerting" "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/supportbundles/supportbundlestest" "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/userimpl" "github.com/grafana/grafana/pkg/setting" @@ -609,11 +609,6 @@ type scenarioContext struct { sqlStore db.DB } -type folderACLItem struct { - roleType org.RoleType - permission dashboards.PermissionType -} - func toLibraryElement(t *testing.T, res model.LibraryElementDTO) libraryElement { var libraryElementModel = 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.RBACEnabled = false cfg.IsFeatureToggleEnabled = featuremgmt.WithFeatures().IsEnabled quotaService := quotatest.New(false, nil) dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) require.NoError(t, err) dashAlertService := alerting.ProvideDashAlertExtractorService(nil, nil, nil) - ac := acmock.New() + ac := actest.FakeAccessControl{ExpectedEvaluate: true} 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( cfg, dashboardStore, folderStore, dashAlertService, - featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(), acmock.NewMockedPermissionsService(), ac, + featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(), dashPermissionService, ac, foldertest.NewFakeService(), ) require.NoError(t, err) @@ -719,68 +715,28 @@ func createDashboard(t *testing.T, sqlStore db.DB, user *user.SignedInUser, dash return dashboard } -func createFolderWithACL(t *testing.T, sqlStore db.DB, title string, user *user.SignedInUser, - items []folderACLItem) *folder.Folder { +func createFolder(t *testing.T, sc scenarioContext, title string) *folder.Folder { t.Helper() - ac := acmock.New() + ac := actest.FakeAccessControl{ExpectedEvaluate: true} cfg := setting.NewCfg() - cfg.RBACEnabled = false cfg.IsFeatureToggleEnabled = featuremgmt.WithFeatures().IsEnabled features := featuremgmt.WithFeatures() 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) - folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) + folderStore := folderimpl.ProvideDashboardFolderStore(sc.sqlStore) s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, nil, features) t.Logf("Creating folder with title and UID %q", title) - ctx := appcontext.WithUser(context.Background(), user) - folder, err := s.Create(ctx, &folder.CreateFolderCommand{OrgID: user.OrgID, Title: title, UID: title, SignedInUser: user}) + ctx := appcontext.WithUser(context.Background(), sc.user) + folder, err := s.Create(ctx, &folder.CreateFolderCommand{OrgID: sc.user.OrgID, Title: title, UID: title, SignedInUser: sc.user}) require.NoError(t, err) - updateFolderACL(t, dashboardStore, folder.ID, items) - 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)) { - 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() 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.Run(desc, func(t *testing.T) { - cfg := setting.NewCfg() - cfg.RBACEnabled = false orgID := int64(1) role := org.RoleAdmin sqlStore, cfg := db.InitTestDBwithCfg(t) 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) require.NoError(t, err) - features := featuremgmt.WithFeatures() - ac := acmock.New() - - folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) folderService := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, nil, features) 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, OrgRole: role, 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 @@ -884,7 +855,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo sqlStore: sqlStore, } - foldr := createFolderWithACL(t, sc.sqlStore, "ScenarioFolder", sc.user, []folderACLItem{}) + foldr := createFolder(t, sc, "ScenarioFolder") sc.folder = &folder.Folder{ ID: foldr.ID, UID: foldr.UID,