sqlstore split: dashboard permissions (#49962)

* backend/sqlstore split: remove unused GetDashboardPermissionsForUser from sqlstore
* remove debugging line
* backend/sqlstore: move dashboard permission related functions to dashboard service
This commit is contained in:
Kristin Laemmert
2022-06-01 14:16:26 -04:00
committed by GitHub
parent bb94681d5a
commit 2edfbb7767
30 changed files with 557 additions and 481 deletions
+11 -4
View File
@@ -7,14 +7,17 @@ import (
"github.com/grafana/grafana/pkg/api/routing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var (
@@ -46,9 +49,13 @@ func setUp(confs ...setUpConf) *HTTPServer {
aclMockResp = c.aclMockResp
}
}
store.ExpectedDashboardAclInfoList = aclMockResp
store.ExpectedTeamsByUser = []*models.TeamDTO{}
guardian.InitLegacyGuardian(store)
dashSvc := &dashboards.FakeDashboardService{}
dashSvc.On("GetDashboardAclInfoList", mock.Anything, mock.AnythingOfType("*models.GetDashboardAclInfoListQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardAclInfoListQuery)
q.Result = aclMockResp
}).Return(nil)
guardian.InitLegacyGuardian(store, dashSvc)
return hs
}
+10 -7
View File
@@ -1000,15 +1000,18 @@ func TestAPI_MassDeleteAnnotations_AccessControl(t *testing.T) {
func setUpACL() {
viewerRole := models.ROLE_VIEWER
editorRole := models.ROLE_EDITOR
aclMockResp := []*models.DashboardAclInfoDTO{
{Role: &viewerRole, Permission: models.PERMISSION_VIEW},
{Role: &editorRole, Permission: models.PERMISSION_EDIT},
}
store := mockstore.NewSQLStoreMock()
store.ExpectedDashboardAclInfoList = aclMockResp
store.ExpectedTeamsByUser = []*models.TeamDTO{}
guardian.InitLegacyGuardian(store)
dashSvc := &dashboards.FakeDashboardService{}
dashSvc.On("GetDashboardAclInfoList", mock.Anything, mock.AnythingOfType("*models.GetDashboardAclInfoListQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardAclInfoListQuery)
q.Result = []*models.DashboardAclInfoDTO{
{Role: &viewerRole, Permission: models.PERMISSION_VIEW},
{Role: &editorRole, Permission: models.PERMISSION_EDIT},
}
}).Return(nil)
guardian.InitLegacyGuardian(store, dashSvc)
}
func setUpRBACGuardian(t *testing.T) {
+1 -1
View File
@@ -28,7 +28,7 @@ func (hs *HTTPServer) registerRoutes() {
reqGrafanaAdmin := middleware.ReqGrafanaAdmin
reqEditorRole := middleware.ReqEditorRole
reqOrgAdmin := middleware.ReqOrgAdmin
reqOrgAdminFolderAdminOrTeamAdmin := middleware.OrgAdminFolderAdminOrTeamAdmin(hs.SQLStore)
reqOrgAdminFolderAdminOrTeamAdmin := middleware.OrgAdminFolderAdminOrTeamAdmin(hs.SQLStore, hs.dashboardService)
reqCanAccessTeams := middleware.AdminOrEditorAndFeatureEnabled(hs.Cfg.EditorsCanAdmin)
reqSnapshotPublicModeOrSignedIn := middleware.SnapshotPublicModeOrSignedIn(hs.Cfg)
redirectFromLegacyPanelEditURL := middleware.RedirectFromLegacyPanelEditURL(hs.Cfg)
-3
View File
@@ -2,7 +2,6 @@ package api
import (
"errors"
"fmt"
"net/http"
"github.com/grafana/grafana/pkg/api/response"
@@ -42,8 +41,6 @@ func (hs *HTTPServer) SavePublicDashboard(c *models.ReqContext) response.Respons
pdc, err := hs.dashboardService.SavePublicDashboardConfig(c.Req.Context(), &dto)
fmt.Println("err:", err)
if errors.Is(err, models.ErrDashboardNotFound) {
return response.Error(http.StatusNotFound, "dashboard not found", err)
}
+17 -15
View File
@@ -8,13 +8,16 @@ import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboardsnapshots"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDashboardSnapshotAPIEndpoint_singleSnapshot(t *testing.T) {
@@ -29,10 +32,9 @@ func TestDashboardSnapshotAPIEndpoint_singleSnapshot(t *testing.T) {
jsonModel, err := simplejson.NewJson([]byte(`{"id":100}`))
require.NoError(t, err)
viewerRole := models.ROLE_VIEWER
editorRole := models.ROLE_EDITOR
sqlmock := mockstore.NewSQLStoreMock()
aclMockResp := []*models.DashboardAclInfoDTO{}
dashSvc := &dashboards.FakeDashboardService{}
dashSvc.On("GetDashboardAclInfoList", mock.Anything, mock.AnythingOfType("*models.GetDashboardAclInfoListQuery")).Return(nil)
hs := &HTTPServer{DashboardsnapshotsService: &dashboardsnapshots.Service{SQLStore: sqlmock}}
setUpSnapshotTest := func(t *testing.T) *models.DashboardSnapshot {
@@ -47,7 +49,6 @@ func TestDashboardSnapshotAPIEndpoint_singleSnapshot(t *testing.T) {
External: true,
}
sqlmock.ExpectedDashboardSnapshot = mockSnapshotResult
sqlmock.ExpectedDashboardAclInfoList = aclMockResp
sqlmock.ExpectedTeamsByUser = []*models.TeamDTO{}
return mockSnapshotResult
@@ -63,7 +64,7 @@ func TestDashboardSnapshotAPIEndpoint_singleSnapshot(t *testing.T) {
})
mockSnapshotResult.ExternalDeleteUrl = ts.URL
sc.handlerFunc = hs.DeleteDashboardSnapshot
guardian.InitLegacyGuardian(sc.sqlStore)
guardian.InitLegacyGuardian(sc.sqlStore, dashSvc)
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec()
assert.Equal(t, 403, sc.resp.Code)
@@ -100,15 +101,19 @@ func TestDashboardSnapshotAPIEndpoint_singleSnapshot(t *testing.T) {
})
t.Run("When user is editor and dashboard has default ACL", func(t *testing.T) {
aclMockResp = []*models.DashboardAclInfoDTO{
{Role: &viewerRole, Permission: models.PERMISSION_VIEW},
{Role: &editorRole, Permission: models.PERMISSION_EDIT},
}
dashSvc := &dashboards.FakeDashboardService{}
dashSvc.On("GetDashboardAclInfoList", mock.Anything, mock.AnythingOfType("*models.GetDashboardAclInfoListQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardAclInfoListQuery)
q.Result = []*models.DashboardAclInfoDTO{
{Role: &viewerRole, Permission: models.PERMISSION_VIEW},
{Role: &editorRole, Permission: models.PERMISSION_EDIT},
}
}).Return(nil)
loggedInUserScenarioWithRole(t, "Should be able to delete a snapshot when calling DELETE on", "DELETE",
"/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) {
mockSnapshotResult := setUpSnapshotTest(t)
guardian.InitLegacyGuardian(sc.sqlStore, dashSvc)
var externalRequest *http.Request
ts := setupRemoteServer(func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(200)
@@ -130,11 +135,9 @@ func TestDashboardSnapshotAPIEndpoint_singleSnapshot(t *testing.T) {
})
t.Run("When user is editor and creator of the snapshot", func(t *testing.T) {
aclMockResp := []*models.DashboardAclInfoDTO{}
loggedInUserScenarioWithRole(t, "Should be able to delete a snapshot when calling DELETE on",
"DELETE", "/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) {
mockSnapshotResult := setUpSnapshotTest(t)
sqlmock.ExpectedDashboardAclInfoList = aclMockResp
mockSnapshotResult.UserId = testUserID
mockSnapshotResult.External = false
@@ -151,7 +154,6 @@ func TestDashboardSnapshotAPIEndpoint_singleSnapshot(t *testing.T) {
})
t.Run("When deleting an external snapshot", func(t *testing.T) {
aclMockResp = []*models.DashboardAclInfoDTO{}
loggedInUserScenarioWithRole(t,
"Should gracefully delete local snapshot when remote snapshot has already been removed when calling DELETE on",
"DELETE", "/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) {
+76 -62
View File
@@ -133,7 +133,6 @@ func TestDashboardAPIEndpoint(t *testing.T) {
q := args.Get(1).(*models.GetDashboardQuery)
q.Result = fakeDash
}).Return(nil)
mockSQLStore := mockstore.NewSQLStoreMock()
hs := &HTTPServer{
@@ -150,13 +149,14 @@ func TestDashboardAPIEndpoint(t *testing.T) {
setUp := func() {
viewerRole := models.ROLE_VIEWER
editorRole := models.ROLE_EDITOR
aclMockResp := []*models.DashboardAclInfoDTO{
{Role: &viewerRole, Permission: models.PERMISSION_VIEW},
{Role: &editorRole, Permission: models.PERMISSION_EDIT},
}
mockSQLStore.ExpectedDashboardAclInfoList = aclMockResp
guardian.InitLegacyGuardian(mockSQLStore)
dashboardService.On("GetDashboardAclInfoList", mock.Anything, mock.AnythingOfType("*models.GetDashboardAclInfoListQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardAclInfoListQuery)
q.Result = []*models.DashboardAclInfoDTO{
{Role: &viewerRole, Permission: models.PERMISSION_VIEW},
{Role: &editorRole, Permission: models.PERMISSION_EDIT},
}
}).Return(nil)
guardian.InitLegacyGuardian(mockSQLStore, dashboardService)
}
// This tests two scenarios:
@@ -239,6 +239,16 @@ func TestDashboardAPIEndpoint(t *testing.T) {
q := args.Get(1).(*models.GetDashboardQuery)
q.Result = fakeDash
}).Return(nil)
dashboardService.On("GetDashboardAclInfoList", mock.Anything, mock.AnythingOfType("*models.GetDashboardAclInfoListQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardAclInfoListQuery)
q.Result = []*models.DashboardAclInfoDTO{
{
DashboardId: 1,
Permission: models.PERMISSION_EDIT,
UserId: 200,
},
}
}).Return(nil)
mockSQLStore := mockstore.NewSQLStoreMock()
cfg := setting.NewCfg()
@@ -262,17 +272,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
setting.ViewersCanEdit = origCanEdit
})
setting.ViewersCanEdit = false
aclMockResp := []*models.DashboardAclInfoDTO{
{
DashboardId: 1,
Permission: models.PERMISSION_EDIT,
UserId: 200,
},
}
mockSQLStore.ExpectedDashboardAclInfoList = aclMockResp
guardian.InitLegacyGuardian(mockSQLStore)
guardian.InitLegacyGuardian(mockSQLStore, dashboardService)
}
// This tests six scenarios:
@@ -362,13 +362,21 @@ func TestDashboardAPIEndpoint(t *testing.T) {
t.Run("When user is an Org Viewer but has an edit permission", func(t *testing.T) {
role := models.ROLE_VIEWER
mockResult := []*models.DashboardAclInfoDTO{
{OrgId: 1, DashboardId: 2, UserId: 1, Permission: models.PERMISSION_EDIT},
}
setUpInner := func() {
setUp()
mockSQLStore.ExpectedDashboardAclInfoList = mockResult
origCanEdit := setting.ViewersCanEdit
t.Cleanup(func() {
setting.ViewersCanEdit = origCanEdit
})
setting.ViewersCanEdit = false
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardService.On("GetDashboardAclInfoList", mock.Anything, mock.AnythingOfType("*models.GetDashboardAclInfoListQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardAclInfoListQuery)
q.Result = []*models.DashboardAclInfoDTO{
{OrgId: 1, DashboardId: 2, UserId: 1, Permission: models.PERMISSION_EDIT},
}
}).Return(nil)
guardian.InitLegacyGuardian(mockSQLStore, dashboardService)
}
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/uid/abcdefghi",
@@ -417,18 +425,20 @@ func TestDashboardAPIEndpoint(t *testing.T) {
role := models.ROLE_VIEWER
setUpInner := func() {
setUp()
mockResult := []*models.DashboardAclInfoDTO{
{OrgId: 1, DashboardId: 2, UserId: 1, Permission: models.PERMISSION_VIEW},
}
mockSQLStore.ExpectedDashboardAclInfoList = mockResult
origCanEdit := setting.ViewersCanEdit
t.Cleanup(func() {
setting.ViewersCanEdit = origCanEdit
})
setting.ViewersCanEdit = true
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardService.On("GetDashboardAclInfoList", mock.Anything, mock.AnythingOfType("*models.GetDashboardAclInfoListQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardAclInfoListQuery)
q.Result = []*models.DashboardAclInfoDTO{
{OrgId: 1, DashboardId: 2, UserId: 1, Permission: models.PERMISSION_VIEW},
}
}).Return(nil)
guardian.InitLegacyGuardian(mockSQLStore, dashboardService)
}
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
@@ -455,12 +465,20 @@ func TestDashboardAPIEndpoint(t *testing.T) {
role := models.ROLE_VIEWER
setUpInner := func() {
setUp()
origCanEdit := setting.ViewersCanEdit
t.Cleanup(func() {
setting.ViewersCanEdit = origCanEdit
})
setting.ViewersCanEdit = true
mockResult := []*models.DashboardAclInfoDTO{
{OrgId: 1, DashboardId: 2, UserId: 1, Permission: models.PERMISSION_ADMIN},
}
mockSQLStore.ExpectedDashboardAclInfoList = mockResult
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardService.On("GetDashboardAclInfoList", mock.Anything, mock.AnythingOfType("*models.GetDashboardAclInfoListQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardAclInfoListQuery)
q.Result = []*models.DashboardAclInfoDTO{
{OrgId: 1, DashboardId: 2, UserId: 1, Permission: models.PERMISSION_ADMIN},
}
}).Return(nil)
guardian.InitLegacyGuardian(mockSQLStore, dashboardService)
}
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
@@ -506,12 +524,14 @@ func TestDashboardAPIEndpoint(t *testing.T) {
role := models.ROLE_EDITOR
setUpInner := func() {
setUp()
mockResult := []*models.DashboardAclInfoDTO{
{OrgId: 1, DashboardId: 2, UserId: 1, Permission: models.PERMISSION_VIEW},
}
mockSQLStore.ExpectedDashboardAclInfoList = mockResult
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardService.On("GetDashboardAclInfoList", mock.Anything, mock.AnythingOfType("*models.GetDashboardAclInfoListQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardAclInfoListQuery)
q.Result = []*models.DashboardAclInfoDTO{
{OrgId: 1, DashboardId: 2, UserId: 1, Permission: models.PERMISSION_VIEW},
}
}).Return(nil)
guardian.InitLegacyGuardian(mockSQLStore, dashboardService)
}
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
@@ -735,8 +755,9 @@ func TestDashboardAPIEndpoint(t *testing.T) {
}
sqlmock := mockstore.SQLStoreMock{ExpectedDashboardVersions: dashboardvs}
setUp := func() {
mockResult := []*models.DashboardAclInfoDTO{}
sqlmock.ExpectedDashboardAclInfoList = mockResult
dashSvc := dashboards.NewFakeDashboardService(t)
dashSvc.On("GetDashboardAclInfoList", mock.Anything, mock.AnythingOfType("*models.GetDashboardAclInfoListQuery")).Return(nil)
guardian.InitLegacyGuardian(&sqlmock, dashSvc)
}
cmd := dtos.CalculateDiffOptions{
@@ -763,10 +784,8 @@ func TestDashboardAPIEndpoint(t *testing.T) {
t.Run("when user does have permission", func(t *testing.T) {
role := models.ROLE_ADMIN
postDiffScenario(t, "When calling POST on", "/api/dashboards/calculate-diff", "/api/dashboards/calculate-diff", cmd, role, func(sc *scenarioContext) {
setUp()
// This test shouldn't hit GetDashboardAclInfoList, so no setup needed
sc.dashboardVersionService = fakeDashboardVersionService
callPostDashboard(sc)
assert.Equal(t, 200, sc.resp.Code)
@@ -857,40 +876,35 @@ func TestDashboardAPIEndpoint(t *testing.T) {
t.Run("Given provisioned dashboard", func(t *testing.T) {
mockSQLStore := mockstore.NewSQLStoreMock()
setUp := func() {
mockSQLStore.ExpectedDashboardAclInfoList = []*models.DashboardAclInfoDTO{
{OrgId: testOrgID, DashboardId: 1, UserId: testUserID, Permission: models.PERMISSION_EDIT},
}
}
dashboardStore := dashboards.NewFakeDashboardStore(t)
dashboardStore.On("GetProvisionedDataByDashboardID", mock.Anything).Return(&models.DashboardProvisioning{ExternalId: "/dashboard1.json"}, nil).Once()
dashboardService := dashboards.NewFakeDashboardService(t)
dataValue, err := simplejson.NewJson([]byte(`{"id": 1, "editable": true, "style": "dark"}`))
require.NoError(t, err)
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*models.GetDashboardQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardQuery)
q.Result = &models.Dashboard{Id: 1, Data: dataValue}
}).Return(nil)
dashboardService.On("GetDashboardAclInfoList", mock.Anything, mock.AnythingOfType("*models.GetDashboardAclInfoListQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardAclInfoListQuery)
q.Result = []*models.DashboardAclInfoDTO{{OrgId: testOrgID, DashboardId: 1, UserId: testUserID, Permission: models.PERMISSION_EDIT}}
}).Return(nil)
guardian.InitLegacyGuardian(mockSQLStore, dashboardService)
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/uid/dash", "/api/dashboards/uid/:uid", models.ROLE_EDITOR, func(sc *scenarioContext) {
setUp()
fakeProvisioningService := provisioning.NewProvisioningServiceMock(context.Background())
fakeProvisioningService.GetDashboardProvisionerResolvedPathFunc = func(name string) string {
return "/tmp/grafana/dashboards"
}
dashboardStore := &dashboards.FakeDashboardStore{}
defer dashboardStore.AssertExpectations(t)
dashboardStore.On("GetProvisionedDataByDashboardID", mock.Anything).Return(&models.DashboardProvisioning{ExternalId: "/dashboard1.json"}, nil).Once()
dash := getDashboardShouldReturn200WithConfig(t, sc, fakeProvisioningService, dashboardStore, dashboardService)
assert.Equal(t, "../../../dashboard1.json", dash.Meta.ProvisionedExternalId, mockSQLStore)
}, mockSQLStore)
loggedInUserScenarioWithRole(t, "When allowUiUpdates is true and calling GET on", "GET", "/api/dashboards/uid/dash", "/api/dashboards/uid/:uid", models.ROLE_EDITOR, func(sc *scenarioContext) {
setUp()
fakeProvisioningService := provisioning.NewProvisioningServiceMock(context.Background())
fakeProvisioningService.GetDashboardProvisionerResolvedPathFunc = func(name string) string {
return "/tmp/grafana/dashboards"
+1 -1
View File
@@ -670,7 +670,7 @@ func (hs *HTTPServer) buildAdminNavLinks(c *models.ReqContext) []*dtos.NavLink {
func (hs *HTTPServer) editorInAnyFolder(c *models.ReqContext) bool {
hasEditPermissionInFoldersQuery := models.HasEditPermissionInFoldersQuery{SignedInUser: c.SignedInUser}
if err := hs.SQLStore.HasEditPermissionInFolders(c.Req.Context(), &hasEditPermissionInFoldersQuery); err != nil {
if err := hs.dashboardService.HasEditPermissionInFolders(c.Req.Context(), &hasEditPermissionInFoldersQuery); err != nil {
return false
}
return hasEditPermissionInFoldersQuery.Result