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

View File

@ -7,14 +7,17 @@ import (
"github.com/grafana/grafana/pkg/api/routing" "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/dtos"
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/models" "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/guardian"
"github.com/grafana/grafana/pkg/services/search" "github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
var ( var (
@ -46,9 +49,13 @@ func setUp(confs ...setUpConf) *HTTPServer {
aclMockResp = c.aclMockResp aclMockResp = c.aclMockResp
} }
} }
store.ExpectedDashboardAclInfoList = aclMockResp
store.ExpectedTeamsByUser = []*models.TeamDTO{} 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 return hs
} }

View File

@ -1000,15 +1000,18 @@ func TestAPI_MassDeleteAnnotations_AccessControl(t *testing.T) {
func setUpACL() { func setUpACL() {
viewerRole := models.ROLE_VIEWER viewerRole := models.ROLE_VIEWER
editorRole := models.ROLE_EDITOR editorRole := models.ROLE_EDITOR
store := mockstore.NewSQLStoreMock()
aclMockResp := []*models.DashboardAclInfoDTO{ store.ExpectedTeamsByUser = []*models.TeamDTO{}
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: &viewerRole, Permission: models.PERMISSION_VIEW},
{Role: &editorRole, Permission: models.PERMISSION_EDIT}, {Role: &editorRole, Permission: models.PERMISSION_EDIT},
} }
store := mockstore.NewSQLStoreMock() }).Return(nil)
store.ExpectedDashboardAclInfoList = aclMockResp
store.ExpectedTeamsByUser = []*models.TeamDTO{} guardian.InitLegacyGuardian(store, dashSvc)
guardian.InitLegacyGuardian(store)
} }
func setUpRBACGuardian(t *testing.T) { func setUpRBACGuardian(t *testing.T) {

View File

@ -28,7 +28,7 @@ func (hs *HTTPServer) registerRoutes() {
reqGrafanaAdmin := middleware.ReqGrafanaAdmin reqGrafanaAdmin := middleware.ReqGrafanaAdmin
reqEditorRole := middleware.ReqEditorRole reqEditorRole := middleware.ReqEditorRole
reqOrgAdmin := middleware.ReqOrgAdmin reqOrgAdmin := middleware.ReqOrgAdmin
reqOrgAdminFolderAdminOrTeamAdmin := middleware.OrgAdminFolderAdminOrTeamAdmin(hs.SQLStore) reqOrgAdminFolderAdminOrTeamAdmin := middleware.OrgAdminFolderAdminOrTeamAdmin(hs.SQLStore, hs.dashboardService)
reqCanAccessTeams := middleware.AdminOrEditorAndFeatureEnabled(hs.Cfg.EditorsCanAdmin) reqCanAccessTeams := middleware.AdminOrEditorAndFeatureEnabled(hs.Cfg.EditorsCanAdmin)
reqSnapshotPublicModeOrSignedIn := middleware.SnapshotPublicModeOrSignedIn(hs.Cfg) reqSnapshotPublicModeOrSignedIn := middleware.SnapshotPublicModeOrSignedIn(hs.Cfg)
redirectFromLegacyPanelEditURL := middleware.RedirectFromLegacyPanelEditURL(hs.Cfg) redirectFromLegacyPanelEditURL := middleware.RedirectFromLegacyPanelEditURL(hs.Cfg)

View File

@ -2,7 +2,6 @@ package api
import ( import (
"errors" "errors"
"fmt"
"net/http" "net/http"
"github.com/grafana/grafana/pkg/api/response" "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) pdc, err := hs.dashboardService.SavePublicDashboardConfig(c.Req.Context(), &dto)
fmt.Println("err:", err)
if errors.Is(err, models.ErrDashboardNotFound) { if errors.Is(err, models.ErrDashboardNotFound) {
return response.Error(http.StatusNotFound, "dashboard not found", err) return response.Error(http.StatusNotFound, "dashboard not found", err)
} }

View File

@ -8,13 +8,16 @@ import (
"testing" "testing"
"time" "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/components/simplejson"
"github.com/grafana/grafana/pkg/models" "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/dashboardsnapshots"
"github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestDashboardSnapshotAPIEndpoint_singleSnapshot(t *testing.T) { func TestDashboardSnapshotAPIEndpoint_singleSnapshot(t *testing.T) {
@ -29,10 +32,9 @@ func TestDashboardSnapshotAPIEndpoint_singleSnapshot(t *testing.T) {
jsonModel, err := simplejson.NewJson([]byte(`{"id":100}`)) jsonModel, err := simplejson.NewJson([]byte(`{"id":100}`))
require.NoError(t, err) require.NoError(t, err)
viewerRole := models.ROLE_VIEWER
editorRole := models.ROLE_EDITOR
sqlmock := mockstore.NewSQLStoreMock() 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}} hs := &HTTPServer{DashboardsnapshotsService: &dashboardsnapshots.Service{SQLStore: sqlmock}}
setUpSnapshotTest := func(t *testing.T) *models.DashboardSnapshot { setUpSnapshotTest := func(t *testing.T) *models.DashboardSnapshot {
@ -47,7 +49,6 @@ func TestDashboardSnapshotAPIEndpoint_singleSnapshot(t *testing.T) {
External: true, External: true,
} }
sqlmock.ExpectedDashboardSnapshot = mockSnapshotResult sqlmock.ExpectedDashboardSnapshot = mockSnapshotResult
sqlmock.ExpectedDashboardAclInfoList = aclMockResp
sqlmock.ExpectedTeamsByUser = []*models.TeamDTO{} sqlmock.ExpectedTeamsByUser = []*models.TeamDTO{}
return mockSnapshotResult return mockSnapshotResult
@ -63,7 +64,7 @@ func TestDashboardSnapshotAPIEndpoint_singleSnapshot(t *testing.T) {
}) })
mockSnapshotResult.ExternalDeleteUrl = ts.URL mockSnapshotResult.ExternalDeleteUrl = ts.URL
sc.handlerFunc = hs.DeleteDashboardSnapshot sc.handlerFunc = hs.DeleteDashboardSnapshot
guardian.InitLegacyGuardian(sc.sqlStore) guardian.InitLegacyGuardian(sc.sqlStore, dashSvc)
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec() sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec()
assert.Equal(t, 403, sc.resp.Code) 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) { t.Run("When user is editor and dashboard has default ACL", func(t *testing.T) {
aclMockResp = []*models.DashboardAclInfoDTO{ 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: &viewerRole, Permission: models.PERMISSION_VIEW},
{Role: &editorRole, Permission: models.PERMISSION_EDIT}, {Role: &editorRole, Permission: models.PERMISSION_EDIT},
} }
}).Return(nil)
loggedInUserScenarioWithRole(t, "Should be able to delete a snapshot when calling DELETE on", "DELETE", 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) { "/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) {
mockSnapshotResult := setUpSnapshotTest(t) mockSnapshotResult := setUpSnapshotTest(t)
guardian.InitLegacyGuardian(sc.sqlStore, dashSvc)
var externalRequest *http.Request var externalRequest *http.Request
ts := setupRemoteServer(func(rw http.ResponseWriter, req *http.Request) { ts := setupRemoteServer(func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(200) 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) { 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", 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) { "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) {
mockSnapshotResult := setUpSnapshotTest(t) mockSnapshotResult := setUpSnapshotTest(t)
sqlmock.ExpectedDashboardAclInfoList = aclMockResp
mockSnapshotResult.UserId = testUserID mockSnapshotResult.UserId = testUserID
mockSnapshotResult.External = false mockSnapshotResult.External = false
@ -151,7 +154,6 @@ func TestDashboardSnapshotAPIEndpoint_singleSnapshot(t *testing.T) {
}) })
t.Run("When deleting an external snapshot", func(t *testing.T) { t.Run("When deleting an external snapshot", func(t *testing.T) {
aclMockResp = []*models.DashboardAclInfoDTO{}
loggedInUserScenarioWithRole(t, loggedInUserScenarioWithRole(t,
"Should gracefully delete local snapshot when remote snapshot has already been removed when calling DELETE on", "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) { "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) {

View File

@ -133,7 +133,6 @@ func TestDashboardAPIEndpoint(t *testing.T) {
q := args.Get(1).(*models.GetDashboardQuery) q := args.Get(1).(*models.GetDashboardQuery)
q.Result = fakeDash q.Result = fakeDash
}).Return(nil) }).Return(nil)
mockSQLStore := mockstore.NewSQLStoreMock() mockSQLStore := mockstore.NewSQLStoreMock()
hs := &HTTPServer{ hs := &HTTPServer{
@ -150,13 +149,14 @@ func TestDashboardAPIEndpoint(t *testing.T) {
setUp := func() { setUp := func() {
viewerRole := models.ROLE_VIEWER viewerRole := models.ROLE_VIEWER
editorRole := models.ROLE_EDITOR editorRole := models.ROLE_EDITOR
dashboardService.On("GetDashboardAclInfoList", mock.Anything, mock.AnythingOfType("*models.GetDashboardAclInfoListQuery")).Run(func(args mock.Arguments) {
aclMockResp := []*models.DashboardAclInfoDTO{ q := args.Get(1).(*models.GetDashboardAclInfoListQuery)
q.Result = []*models.DashboardAclInfoDTO{
{Role: &viewerRole, Permission: models.PERMISSION_VIEW}, {Role: &viewerRole, Permission: models.PERMISSION_VIEW},
{Role: &editorRole, Permission: models.PERMISSION_EDIT}, {Role: &editorRole, Permission: models.PERMISSION_EDIT},
} }
mockSQLStore.ExpectedDashboardAclInfoList = aclMockResp }).Return(nil)
guardian.InitLegacyGuardian(mockSQLStore) guardian.InitLegacyGuardian(mockSQLStore, dashboardService)
} }
// This tests two scenarios: // This tests two scenarios:
@ -239,6 +239,16 @@ func TestDashboardAPIEndpoint(t *testing.T) {
q := args.Get(1).(*models.GetDashboardQuery) q := args.Get(1).(*models.GetDashboardQuery)
q.Result = fakeDash q.Result = fakeDash
}).Return(nil) }).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() mockSQLStore := mockstore.NewSQLStoreMock()
cfg := setting.NewCfg() cfg := setting.NewCfg()
@ -262,17 +272,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
setting.ViewersCanEdit = origCanEdit setting.ViewersCanEdit = origCanEdit
}) })
setting.ViewersCanEdit = false setting.ViewersCanEdit = false
guardian.InitLegacyGuardian(mockSQLStore, dashboardService)
aclMockResp := []*models.DashboardAclInfoDTO{
{
DashboardId: 1,
Permission: models.PERMISSION_EDIT,
UserId: 200,
},
}
mockSQLStore.ExpectedDashboardAclInfoList = aclMockResp
guardian.InitLegacyGuardian(mockSQLStore)
} }
// This tests six scenarios: // 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) { t.Run("When user is an Org Viewer but has an edit permission", func(t *testing.T) {
role := models.ROLE_VIEWER role := models.ROLE_VIEWER
mockResult := []*models.DashboardAclInfoDTO{ setUpInner := func() {
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}, {OrgId: 1, DashboardId: 2, UserId: 1, Permission: models.PERMISSION_EDIT},
} }
}).Return(nil)
setUpInner := func() { guardian.InitLegacyGuardian(mockSQLStore, dashboardService)
setUp()
mockSQLStore.ExpectedDashboardAclInfoList = mockResult
} }
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/uid/abcdefghi", loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/uid/abcdefghi",
@ -417,18 +425,20 @@ func TestDashboardAPIEndpoint(t *testing.T) {
role := models.ROLE_VIEWER role := models.ROLE_VIEWER
setUpInner := func() { setUpInner := func() {
setUp()
mockResult := []*models.DashboardAclInfoDTO{
{OrgId: 1, DashboardId: 2, UserId: 1, Permission: models.PERMISSION_VIEW},
}
mockSQLStore.ExpectedDashboardAclInfoList = mockResult
origCanEdit := setting.ViewersCanEdit origCanEdit := setting.ViewersCanEdit
t.Cleanup(func() { t.Cleanup(func() {
setting.ViewersCanEdit = origCanEdit setting.ViewersCanEdit = origCanEdit
}) })
setting.ViewersCanEdit = true 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) { 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 role := models.ROLE_VIEWER
setUpInner := func() { setUpInner := func() {
setUp() origCanEdit := setting.ViewersCanEdit
t.Cleanup(func() {
setting.ViewersCanEdit = origCanEdit
})
setting.ViewersCanEdit = true
mockResult := []*models.DashboardAclInfoDTO{ 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}, {OrgId: 1, DashboardId: 2, UserId: 1, Permission: models.PERMISSION_ADMIN},
} }
mockSQLStore.ExpectedDashboardAclInfoList = mockResult }).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) { 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 role := models.ROLE_EDITOR
setUpInner := func() { setUpInner := func() {
setUp() dashboardService := dashboards.NewFakeDashboardService(t)
dashboardService.On("GetDashboardAclInfoList", mock.Anything, mock.AnythingOfType("*models.GetDashboardAclInfoListQuery")).Run(func(args mock.Arguments) {
mockResult := []*models.DashboardAclInfoDTO{ q := args.Get(1).(*models.GetDashboardAclInfoListQuery)
q.Result = []*models.DashboardAclInfoDTO{
{OrgId: 1, DashboardId: 2, UserId: 1, Permission: models.PERMISSION_VIEW}, {OrgId: 1, DashboardId: 2, UserId: 1, Permission: models.PERMISSION_VIEW},
} }
mockSQLStore.ExpectedDashboardAclInfoList = mockResult }).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) { 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} sqlmock := mockstore.SQLStoreMock{ExpectedDashboardVersions: dashboardvs}
setUp := func() { setUp := func() {
mockResult := []*models.DashboardAclInfoDTO{} dashSvc := dashboards.NewFakeDashboardService(t)
sqlmock.ExpectedDashboardAclInfoList = mockResult dashSvc.On("GetDashboardAclInfoList", mock.Anything, mock.AnythingOfType("*models.GetDashboardAclInfoListQuery")).Return(nil)
guardian.InitLegacyGuardian(&sqlmock, dashSvc)
} }
cmd := dtos.CalculateDiffOptions{ cmd := dtos.CalculateDiffOptions{
@ -763,10 +784,8 @@ func TestDashboardAPIEndpoint(t *testing.T) {
t.Run("when user does have permission", func(t *testing.T) { t.Run("when user does have permission", func(t *testing.T) {
role := models.ROLE_ADMIN role := models.ROLE_ADMIN
postDiffScenario(t, "When calling POST on", "/api/dashboards/calculate-diff", "/api/dashboards/calculate-diff", cmd, role, func(sc *scenarioContext) { 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 sc.dashboardVersionService = fakeDashboardVersionService
callPostDashboard(sc) callPostDashboard(sc)
assert.Equal(t, 200, sc.resp.Code) 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) { t.Run("Given provisioned dashboard", func(t *testing.T) {
mockSQLStore := mockstore.NewSQLStoreMock() mockSQLStore := mockstore.NewSQLStoreMock()
setUp := func() { dashboardStore := dashboards.NewFakeDashboardStore(t)
mockSQLStore.ExpectedDashboardAclInfoList = []*models.DashboardAclInfoDTO{ dashboardStore.On("GetProvisionedDataByDashboardID", mock.Anything).Return(&models.DashboardProvisioning{ExternalId: "/dashboard1.json"}, nil).Once()
{OrgId: testOrgID, DashboardId: 1, UserId: testUserID, Permission: models.PERMISSION_EDIT},
} dashboardService := dashboards.NewFakeDashboardService(t)
}
dataValue, err := simplejson.NewJson([]byte(`{"id": 1, "editable": true, "style": "dark"}`)) dataValue, err := simplejson.NewJson([]byte(`{"id": 1, "editable": true, "style": "dark"}`))
require.NoError(t, err) require.NoError(t, err)
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*models.GetDashboardQuery")).Run(func(args mock.Arguments) { dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*models.GetDashboardQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardQuery) q := args.Get(1).(*models.GetDashboardQuery)
q.Result = &models.Dashboard{Id: 1, Data: dataValue} q.Result = &models.Dashboard{Id: 1, Data: dataValue}
}).Return(nil) }).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) { 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 := provisioning.NewProvisioningServiceMock(context.Background())
fakeProvisioningService.GetDashboardProvisionerResolvedPathFunc = func(name string) string { fakeProvisioningService.GetDashboardProvisionerResolvedPathFunc = func(name string) string {
return "/tmp/grafana/dashboards" 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) dash := getDashboardShouldReturn200WithConfig(t, sc, fakeProvisioningService, dashboardStore, dashboardService)
assert.Equal(t, "../../../dashboard1.json", dash.Meta.ProvisionedExternalId, mockSQLStore) assert.Equal(t, "../../../dashboard1.json", dash.Meta.ProvisionedExternalId, mockSQLStore)
}, 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) { 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 := provisioning.NewProvisioningServiceMock(context.Background())
fakeProvisioningService.GetDashboardProvisionerResolvedPathFunc = func(name string) string { fakeProvisioningService.GetDashboardProvisionerResolvedPathFunc = func(name string) string {
return "/tmp/grafana/dashboards" return "/tmp/grafana/dashboards"

View File

@ -670,7 +670,7 @@ func (hs *HTTPServer) buildAdminNavLinks(c *models.ReqContext) []*dtos.NavLink {
func (hs *HTTPServer) editorInAnyFolder(c *models.ReqContext) bool { func (hs *HTTPServer) editorInAnyFolder(c *models.ReqContext) bool {
hasEditPermissionInFoldersQuery := models.HasEditPermissionInFoldersQuery{SignedInUser: c.SignedInUser} 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 false
} }
return hasEditPermissionInFoldersQuery.Result return hasEditPermissionInFoldersQuery.Result

View File

@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/middleware/cookies" "github.com/grafana/grafana/pkg/middleware/cookies"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web" "github.com/grafana/grafana/pkg/web"
@ -191,14 +192,14 @@ func shouldForceLogin(c *models.ReqContext) bool {
return forceLogin return forceLogin
} }
func OrgAdminFolderAdminOrTeamAdmin(ss sqlstore.Store) func(c *models.ReqContext) { func OrgAdminFolderAdminOrTeamAdmin(ss sqlstore.Store, ds dashboards.DashboardService) func(c *models.ReqContext) {
return func(c *models.ReqContext) { return func(c *models.ReqContext) {
if c.OrgRole == models.ROLE_ADMIN { if c.OrgRole == models.ROLE_ADMIN {
return return
} }
hasAdminPermissionInFoldersQuery := models.HasAdminPermissionInFoldersQuery{SignedInUser: c.SignedInUser} hasAdminPermissionInFoldersQuery := models.HasAdminPermissionInFoldersQuery{SignedInUser: c.SignedInUser}
if err := ss.HasAdminPermissionInFolders(c.Req.Context(), &hasAdminPermissionInFoldersQuery); err != nil { if err := ds.HasAdminPermissionInFolders(c.Req.Context(), &hasAdminPermissionInFoldersQuery); err != nil {
c.JsonApiErr(500, "Failed to check if user is a folder admin", err) c.JsonApiErr(500, "Failed to check if user is a folder admin", err)
} }

View File

@ -7,6 +7,7 @@ import (
"time" "time"
"github.com/gosimple/slug" "github.com/gosimple/slug"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
@ -451,14 +452,6 @@ type GetDashboardsQuery struct {
Result []*Dashboard Result []*Dashboard
} }
type GetDashboardPermissionsForUserQuery struct {
DashboardIds []int64
OrgId int64
UserId int64
OrgRole RoleType
Result []*DashboardPermissionForUser
}
type GetDashboardsByPluginIdQuery struct { type GetDashboardsByPluginIdQuery struct {
OrgId int64 OrgId int64
PluginId string PluginId string
@ -477,12 +470,6 @@ type GetDashboardsBySlugQuery struct {
Result []*Dashboard Result []*Dashboard
} }
type DashboardPermissionForUser struct {
DashboardId int64 `json:"dashboardId"`
Permission PermissionType `json:"permission"`
PermissionName string `json:"permissionName"`
}
type DashboardRef struct { type DashboardRef struct {
Uid string Uid string
Slug string Slug string

View File

@ -13,9 +13,12 @@ type DashboardService interface {
DeleteDashboard(ctx context.Context, dashboardId int64, orgId int64) error DeleteDashboard(ctx context.Context, dashboardId int64, orgId int64) error
FindDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) FindDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error)
GetDashboard(ctx context.Context, query *models.GetDashboardQuery) error GetDashboard(ctx context.Context, query *models.GetDashboardQuery) error
GetDashboardAclInfoList(ctx context.Context, query *models.GetDashboardAclInfoListQuery) error
GetDashboards(ctx context.Context, query *models.GetDashboardsQuery) error GetDashboards(ctx context.Context, query *models.GetDashboardsQuery) error
GetDashboardUIDById(ctx context.Context, query *models.GetDashboardRefByIdQuery) error GetDashboardUIDById(ctx context.Context, query *models.GetDashboardRefByIdQuery) error
GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboardConfig, error) GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboardConfig, error)
HasAdminPermissionInFolders(ctx context.Context, query *models.HasAdminPermissionInFoldersQuery) error
HasEditPermissionInFolders(ctx context.Context, query *models.HasEditPermissionInFoldersQuery) error
ImportDashboard(ctx context.Context, dto *SaveDashboardDTO) (*models.Dashboard, error) ImportDashboard(ctx context.Context, dto *SaveDashboardDTO) (*models.Dashboard, error)
MakeUserAdmin(ctx context.Context, orgID int64, userID, dashboardID int64, setViewAndEditPermissions bool) error MakeUserAdmin(ctx context.Context, orgID int64, userID, dashboardID int64, setViewAndEditPermissions bool) error
SaveDashboard(ctx context.Context, dto *SaveDashboardDTO, allowUiUpdate bool) (*models.Dashboard, error) SaveDashboard(ctx context.Context, dto *SaveDashboardDTO, allowUiUpdate bool) (*models.Dashboard, error)
@ -49,6 +52,7 @@ type Store interface {
DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *models.DeleteOrphanedProvisionedDashboardsCommand) error DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *models.DeleteOrphanedProvisionedDashboardsCommand) error
FindDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) FindDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error)
GetDashboard(ctx context.Context, query *models.GetDashboardQuery) error GetDashboard(ctx context.Context, query *models.GetDashboardQuery) error
GetDashboardAclInfoList(ctx context.Context, query *models.GetDashboardAclInfoListQuery) error
GetDashboardUIDById(ctx context.Context, query *models.GetDashboardRefByIdQuery) error GetDashboardUIDById(ctx context.Context, query *models.GetDashboardRefByIdQuery) error
GetDashboards(ctx context.Context, query *models.GetDashboardsQuery) error GetDashboards(ctx context.Context, query *models.GetDashboardsQuery) error
// GetDashboardsByPluginID retrieves dashboards identified by plugin. // GetDashboardsByPluginID retrieves dashboards identified by plugin.
@ -57,6 +61,8 @@ type Store interface {
GetProvisionedDataByDashboardID(dashboardID int64) (*models.DashboardProvisioning, error) GetProvisionedDataByDashboardID(dashboardID int64) (*models.DashboardProvisioning, error)
GetProvisionedDataByDashboardUID(orgID int64, dashboardUID string) (*models.DashboardProvisioning, error) GetProvisionedDataByDashboardUID(orgID int64, dashboardUID string) (*models.DashboardProvisioning, error)
GetPublicDashboardConfig(orgId int64, dashboardUid string) (*models.PublicDashboardConfig, error) GetPublicDashboardConfig(orgId int64, dashboardUid string) (*models.PublicDashboardConfig, error)
HasAdminPermissionInFolders(ctx context.Context, query *models.HasAdminPermissionInFoldersQuery) error
HasEditPermissionInFolders(ctx context.Context, query *models.HasEditPermissionInFoldersQuery) error
// SaveAlerts saves dashboard alerts. // SaveAlerts saves dashboard alerts.
SaveAlerts(ctx context.Context, dashID int64, alerts []*models.Alert) error SaveAlerts(ctx context.Context, dashID int64, alerts []*models.Alert) error
SaveDashboard(cmd models.SaveDashboardCommand) (*models.Dashboard, error) SaveDashboard(cmd models.SaveDashboardCommand) (*models.Dashboard, error)

View File

@ -90,6 +90,20 @@ func (_m *FakeDashboardService) GetDashboard(ctx context.Context, query *models.
return r0 return r0
} }
// GetDashboardAclInfoList provides a mock function with given fields: ctx, query
func (_m *FakeDashboardService) GetDashboardAclInfoList(ctx context.Context, query *models.GetDashboardAclInfoListQuery) error {
ret := _m.Called(ctx, query)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *models.GetDashboardAclInfoListQuery) error); ok {
r0 = rf(ctx, query)
} else {
r0 = ret.Error(0)
}
return r0
}
// GetDashboardUIDById provides a mock function with given fields: ctx, query // GetDashboardUIDById provides a mock function with given fields: ctx, query
func (_m *FakeDashboardService) GetDashboardUIDById(ctx context.Context, query *models.GetDashboardRefByIdQuery) error { func (_m *FakeDashboardService) GetDashboardUIDById(ctx context.Context, query *models.GetDashboardRefByIdQuery) error {
ret := _m.Called(ctx, query) ret := _m.Called(ctx, query)
@ -141,6 +155,34 @@ func (_m *FakeDashboardService) GetPublicDashboardConfig(ctx context.Context, or
return r0, r1 return r0, r1
} }
// HasAdminPermissionInFolders provides a mock function with given fields: ctx, query
func (_m *FakeDashboardService) HasAdminPermissionInFolders(ctx context.Context, query *models.HasAdminPermissionInFoldersQuery) error {
ret := _m.Called(ctx, query)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *models.HasAdminPermissionInFoldersQuery) error); ok {
r0 = rf(ctx, query)
} else {
r0 = ret.Error(0)
}
return r0
}
// HasEditPermissionInFolders provides a mock function with given fields: ctx, query
func (_m *FakeDashboardService) HasEditPermissionInFolders(ctx context.Context, query *models.HasEditPermissionInFoldersQuery) error {
ret := _m.Called(ctx, query)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *models.HasEditPermissionInFoldersQuery) error); ok {
r0 = rf(ctx, query)
} else {
r0 = ret.Error(0)
}
return r0
}
// ImportDashboard provides a mock function with given fields: ctx, dto // ImportDashboard provides a mock function with given fields: ctx, dto
func (_m *FakeDashboardService) ImportDashboard(ctx context.Context, dto *SaveDashboardDTO) (*models.Dashboard, error) { func (_m *FakeDashboardService) ImportDashboard(ctx context.Context, dto *SaveDashboardDTO) (*models.Dashboard, error) {
ret := _m.Called(ctx, dto) ret := _m.Called(ctx, dto)

View File

@ -0,0 +1,150 @@
package database
import (
"context"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/sqlstore"
)
// GetDashboardAclInfoList returns a list of permissions for a dashboard. They can be fetched from three
// different places.
// 1) Permissions for the dashboard
// 2) permissions for its parent folder
// 3) if no specific permissions have been set for the dashboard or its parent folder then get the default permissions
func (d *DashboardStore) GetDashboardAclInfoList(ctx context.Context, query *models.GetDashboardAclInfoListQuery) error {
outerErr := d.sqlStore.WithDbSession(ctx, func(dbSession *sqlstore.DBSession) error {
query.Result = make([]*models.DashboardAclInfoDTO, 0)
falseStr := d.dialect.BooleanStr(false)
if query.DashboardID == 0 {
sql := `SELECT
da.id,
da.org_id,
da.dashboard_id,
da.user_id,
da.team_id,
da.permission,
da.role,
da.created,
da.updated,
'' as user_login,
'' as user_email,
'' as team,
'' as title,
'' as slug,
'' as uid,` +
falseStr + ` AS is_folder,` +
falseStr + ` AS inherited
FROM dashboard_acl as da
WHERE da.dashboard_id = -1`
return dbSession.SQL(sql).Find(&query.Result)
}
rawSQL := `
-- get permissions for the dashboard and its parent folder
SELECT
da.id,
da.org_id,
da.dashboard_id,
da.user_id,
da.team_id,
da.permission,
da.role,
da.created,
da.updated,
u.login AS user_login,
u.email AS user_email,
ug.name AS team,
ug.email AS team_email,
d.title,
d.slug,
d.uid,
d.is_folder,
CASE WHEN (da.dashboard_id = -1 AND d.folder_id > 0) OR da.dashboard_id = d.folder_id THEN ` + d.dialect.BooleanStr(true) + ` ELSE ` + falseStr + ` END AS inherited
FROM dashboard as d
LEFT JOIN dashboard folder on folder.id = d.folder_id
LEFT JOIN dashboard_acl AS da ON
da.dashboard_id = d.id OR
da.dashboard_id = d.folder_id OR
(
-- include default permissions -->
da.org_id = -1 AND (
(folder.id IS NOT NULL AND folder.has_acl = ` + falseStr + `) OR
(folder.id IS NULL AND d.has_acl = ` + falseStr + `)
)
)
LEFT JOIN ` + d.dialect.Quote("user") + ` AS u ON u.id = da.user_id
LEFT JOIN team ug on ug.id = da.team_id
WHERE d.org_id = ? AND d.id = ? AND da.id IS NOT NULL
ORDER BY da.id ASC
`
return dbSession.SQL(rawSQL, query.OrgID, query.DashboardID).Find(&query.Result)
})
if outerErr != nil {
return outerErr
}
for _, p := range query.Result {
p.PermissionName = p.Permission.String()
}
return nil
}
// HasEditPermissionInFolders validates that an user have access to a certain folder
func (d *DashboardStore) HasEditPermissionInFolders(ctx context.Context, query *models.HasEditPermissionInFoldersQuery) error {
return d.sqlStore.WithDbSession(ctx, func(dbSession *sqlstore.DBSession) error {
if query.SignedInUser.HasRole(models.ROLE_EDITOR) {
query.Result = true
return nil
}
builder := &sqlstore.SQLBuilder{}
builder.Write("SELECT COUNT(dashboard.id) AS count FROM dashboard WHERE dashboard.org_id = ? AND dashboard.is_folder = ?",
query.SignedInUser.OrgId, d.dialect.BooleanStr(true))
builder.WriteDashboardPermissionFilter(query.SignedInUser, models.PERMISSION_EDIT)
type folderCount struct {
Count int64
}
resp := make([]*folderCount, 0)
if err := dbSession.SQL(builder.GetSQLString(), builder.GetParams()...).Find(&resp); err != nil {
return err
}
query.Result = len(resp) > 0 && resp[0].Count > 0
return nil
})
}
func (d *DashboardStore) HasAdminPermissionInFolders(ctx context.Context, query *models.HasAdminPermissionInFoldersQuery) error {
return d.sqlStore.WithDbSession(ctx, func(dbSession *sqlstore.DBSession) error {
if query.SignedInUser.HasRole(models.ROLE_ADMIN) {
query.Result = true
return nil
}
builder := &sqlstore.SQLBuilder{}
builder.Write("SELECT COUNT(dashboard.id) AS count FROM dashboard WHERE dashboard.org_id = ? AND dashboard.is_folder = ?", query.SignedInUser.OrgId, d.dialect.BooleanStr(true))
builder.WriteDashboardPermissionFilter(query.SignedInUser, models.PERMISSION_ADMIN)
type folderCount struct {
Count int64
}
resp := make([]*folderCount, 0)
if err := dbSession.SQL(builder.GetSQLString(), builder.GetParams()...).Find(&resp); err != nil {
return err
}
query.Result = len(resp) > 0 && resp[0].Count > 0
return nil
})
}

View File

@ -1,31 +1,28 @@
//go:build integration //go:build integration
// +build integration // +build integration
package permissions package database
import ( import (
"context" "context"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/services/dashboards/database"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
"testing" "testing"
"time"
"github.com/grafana/grafana/pkg/models"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
) )
func TestIntegrationDashboardAclDataAccess(t *testing.T) { func TestIntegrationDashboardAclDataAccess(t *testing.T) {
var sqlStore *sqlstore.SQLStore var sqlStore *sqlstore.SQLStore
var currentUser models.User var currentUser models.User
var savedFolder, childDash *models.Dashboard var savedFolder, childDash *models.Dashboard
var dashboardStore *database.DashboardStore var dashboardStore *DashboardStore
setup := func(t *testing.T) { setup := func(t *testing.T) {
sqlStore = sqlstore.InitTestDB(t) sqlStore = sqlstore.InitTestDB(t)
dashboardStore = database.ProvideDashboardStore(sqlStore) dashboardStore = ProvideDashboardStore(sqlStore)
currentUser = createUser(t, sqlStore, "viewer", "Viewer", false) currentUser = createUser(t, sqlStore, "viewer", "Viewer", false)
savedFolder = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, true, "prod", "webapp") savedFolder = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, true, "prod", "webapp")
childDash = insertTestDashboard(t, dashboardStore, "2 test dash", 1, savedFolder.Id, false, "prod", "webapp") childDash = insertTestDashboard(t, dashboardStore, "2 test dash", 1, savedFolder.Id, false, "prod", "webapp")
@ -45,7 +42,7 @@ func TestIntegrationDashboardAclDataAccess(t *testing.T) {
setup(t) setup(t)
query := models.GetDashboardAclInfoListQuery{DashboardID: savedFolder.Id, OrgID: 1} query := models.GetDashboardAclInfoListQuery{DashboardID: savedFolder.Id, OrgID: 1}
err := sqlStore.GetDashboardAclInfoList(context.Background(), &query) err := dashboardStore.GetDashboardAclInfoList(context.Background(), &query)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, 2, len(query.Result)) require.Equal(t, 2, len(query.Result))
@ -62,7 +59,7 @@ func TestIntegrationDashboardAclDataAccess(t *testing.T) {
setup(t) setup(t)
query := models.GetDashboardAclInfoListQuery{DashboardID: childDash.Id, OrgID: 1} query := models.GetDashboardAclInfoListQuery{DashboardID: childDash.Id, OrgID: 1}
err := sqlStore.GetDashboardAclInfoList(context.Background(), &query) err := dashboardStore.GetDashboardAclInfoList(context.Background(), &query)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, 2, len(query.Result)) require.Equal(t, 2, len(query.Result))
@ -81,7 +78,7 @@ func TestIntegrationDashboardAclDataAccess(t *testing.T) {
require.Nil(t, err) require.Nil(t, err)
query := models.GetDashboardAclInfoListQuery{DashboardID: childDash.Id, OrgID: 1} query := models.GetDashboardAclInfoListQuery{DashboardID: childDash.Id, OrgID: 1}
err = sqlStore.GetDashboardAclInfoList(context.Background(), &query) err = dashboardStore.GetDashboardAclInfoList(context.Background(), &query)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, 0, len(query.Result)) require.Equal(t, 0, len(query.Result))
@ -102,7 +99,7 @@ func TestIntegrationDashboardAclDataAccess(t *testing.T) {
t.Run("When reading dashboard acl should include acl for parent folder", func(t *testing.T) { t.Run("When reading dashboard acl should include acl for parent folder", func(t *testing.T) {
query := models.GetDashboardAclInfoListQuery{DashboardID: childDash.Id, OrgID: 1} query := models.GetDashboardAclInfoListQuery{DashboardID: childDash.Id, OrgID: 1}
err := sqlStore.GetDashboardAclInfoList(context.Background(), &query) err := dashboardStore.GetDashboardAclInfoList(context.Background(), &query)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, 1, len(query.Result)) require.Equal(t, 1, len(query.Result))
@ -121,7 +118,7 @@ func TestIntegrationDashboardAclDataAccess(t *testing.T) {
t.Run("When reading dashboard acl should include acl for parent folder and child", func(t *testing.T) { t.Run("When reading dashboard acl should include acl for parent folder and child", func(t *testing.T) {
query := models.GetDashboardAclInfoListQuery{OrgID: 1, DashboardID: childDash.Id} query := models.GetDashboardAclInfoListQuery{OrgID: 1, DashboardID: childDash.Id}
err := sqlStore.GetDashboardAclInfoList(context.Background(), &query) err := dashboardStore.GetDashboardAclInfoList(context.Background(), &query)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, 2, len(query.Result)) require.Equal(t, 2, len(query.Result))
@ -145,7 +142,7 @@ func TestIntegrationDashboardAclDataAccess(t *testing.T) {
query := models.GetDashboardAclInfoListQuery{OrgID: 1, DashboardID: childDash.Id} query := models.GetDashboardAclInfoListQuery{OrgID: 1, DashboardID: childDash.Id}
err = sqlStore.GetDashboardAclInfoList(context.Background(), &query) err = dashboardStore.GetDashboardAclInfoList(context.Background(), &query)
require.Nil(t, err) require.Nil(t, err)
defaultPermissionsId := int64(-1) defaultPermissionsId := int64(-1)
@ -171,7 +168,7 @@ func TestIntegrationDashboardAclDataAccess(t *testing.T) {
require.Nil(t, err) require.Nil(t, err)
q1 := &models.GetDashboardAclInfoListQuery{DashboardID: savedFolder.Id, OrgID: 1} q1 := &models.GetDashboardAclInfoListQuery{DashboardID: savedFolder.Id, OrgID: 1}
err = sqlStore.GetDashboardAclInfoList(context.Background(), q1) err = dashboardStore.GetDashboardAclInfoList(context.Background(), q1)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, savedFolder.Id, q1.Result[0].DashboardId) require.Equal(t, savedFolder.Id, q1.Result[0].DashboardId)
@ -185,7 +182,7 @@ func TestIntegrationDashboardAclDataAccess(t *testing.T) {
require.Nil(t, err) require.Nil(t, err)
q3 := &models.GetDashboardAclInfoListQuery{DashboardID: savedFolder.Id, OrgID: 1} q3 := &models.GetDashboardAclInfoListQuery{DashboardID: savedFolder.Id, OrgID: 1}
err = sqlStore.GetDashboardAclInfoList(context.Background(), q3) err = dashboardStore.GetDashboardAclInfoList(context.Background(), q3)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, 0, len(q3.Result)) require.Equal(t, 0, len(q3.Result))
}) })
@ -204,7 +201,7 @@ func TestIntegrationDashboardAclDataAccess(t *testing.T) {
require.Nil(t, err) require.Nil(t, err)
q1 := &models.GetDashboardAclInfoListQuery{DashboardID: savedFolder.Id, OrgID: 1} q1 := &models.GetDashboardAclInfoListQuery{DashboardID: savedFolder.Id, OrgID: 1}
err = sqlStore.GetDashboardAclInfoList(context.Background(), q1) err = dashboardStore.GetDashboardAclInfoList(context.Background(), q1)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, savedFolder.Id, q1.Result[0].DashboardId) require.Equal(t, savedFolder.Id, q1.Result[0].DashboardId)
require.Equal(t, models.PERMISSION_EDIT, q1.Result[0].Permission) require.Equal(t, models.PERMISSION_EDIT, q1.Result[0].Permission)
@ -224,7 +221,7 @@ func TestIntegrationDashboardAclDataAccess(t *testing.T) {
require.Nil(t, err) require.Nil(t, err)
q3 := &models.GetDashboardAclInfoListQuery{DashboardID: savedFolder.Id, OrgID: 1} q3 := &models.GetDashboardAclInfoListQuery{DashboardID: savedFolder.Id, OrgID: 1}
err = sqlStore.GetDashboardAclInfoList(context.Background(), q3) err = dashboardStore.GetDashboardAclInfoList(context.Background(), q3)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, 1, len(q3.Result)) require.Equal(t, 1, len(q3.Result))
require.Equal(t, savedFolder.Id, q3.Result[0].DashboardId) require.Equal(t, savedFolder.Id, q3.Result[0].DashboardId)
@ -236,11 +233,11 @@ func TestIntegrationDashboardAclDataAccess(t *testing.T) {
t.Run("Default permissions for root folder dashboards", func(t *testing.T) { t.Run("Default permissions for root folder dashboards", func(t *testing.T) {
setup(t) setup(t)
var rootFolderId int64 = 0 var rootFolderId int64 = 0
sqlStore := sqlstore.InitTestDB(t) //sqlStore := sqlstore.InitTestDB(t)
query := models.GetDashboardAclInfoListQuery{DashboardID: rootFolderId, OrgID: 1} query := models.GetDashboardAclInfoListQuery{DashboardID: rootFolderId, OrgID: 1}
err := sqlStore.GetDashboardAclInfoList(context.Background(), &query) err := dashboardStore.GetDashboardAclInfoList(context.Background(), &query)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, 2, len(query.Result)) require.Equal(t, 2, len(query.Result))
@ -268,39 +265,3 @@ func createUser(t *testing.T, sqlStore *sqlstore.SQLStore, name string, role str
require.Equal(t, models.RoleType(role), q1.Result[0].Role) require.Equal(t, models.RoleType(role), q1.Result[0].Role)
return *currentUser return *currentUser
} }
func insertTestDashboard(t *testing.T, dashboardStore *database.DashboardStore, title string, orgId int64,
folderId int64, isFolder bool, tags ...interface{}) *models.Dashboard {
t.Helper()
cmd := models.SaveDashboardCommand{
OrgId: orgId,
FolderId: folderId,
IsFolder: isFolder,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": title,
"tags": tags,
}),
}
dash, err := dashboardStore.SaveDashboard(cmd)
require.NoError(t, err)
require.NotNil(t, dash)
dash.Data.Set("id", dash.Id)
dash.Data.Set("uid", dash.Uid)
return dash
}
func updateDashboardAcl(t *testing.T, dashboardStore *database.DashboardStore, dashboardID int64,
items ...models.DashboardAcl) error {
t.Helper()
var itemPtrs []*models.DashboardAcl
for _, it := range items {
item := it
item.Created = time.Now()
item.Updated = time.Now()
itemPtrs = append(itemPtrs, &item)
}
return dashboardStore.UpdateDashboardACL(context.Background(), dashboardID, itemPtrs)
}

View File

@ -310,24 +310,6 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) {
require.Equal(t, query.Result[1].ID, folder2.Id) require.Equal(t, query.Result[1].ID, folder2.Id)
}) })
t.Run("should have write access to all folders and dashboards", func(t *testing.T) {
query := models.GetDashboardPermissionsForUserQuery{
DashboardIds: []int64{folder1.Id, folder2.Id},
OrgId: 1,
UserId: adminUser.Id,
OrgRole: models.ROLE_ADMIN,
}
err := sqlStore.GetDashboardPermissionsForUser(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, len(query.Result), 2)
require.Equal(t, query.Result[0].DashboardId, folder1.Id)
require.Equal(t, query.Result[0].Permission, models.PERMISSION_ADMIN)
require.Equal(t, query.Result[1].DashboardId, folder2.Id)
require.Equal(t, query.Result[1].Permission, models.PERMISSION_ADMIN)
})
t.Run("should have edit permission in folders", func(t *testing.T) { t.Run("should have edit permission in folders", func(t *testing.T) {
query := &models.HasEditPermissionInFoldersQuery{ query := &models.HasEditPermissionInFoldersQuery{
SignedInUser: &models.SignedInUser{UserId: adminUser.Id, OrgId: 1, OrgRole: models.ROLE_ADMIN}, SignedInUser: &models.SignedInUser{UserId: adminUser.Id, OrgId: 1, OrgRole: models.ROLE_ADMIN},
@ -363,24 +345,6 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) {
require.Equal(t, query.Result[1].ID, folder2.Id) require.Equal(t, query.Result[1].ID, folder2.Id)
}) })
t.Run("should have edit access to folders with default ACL", func(t *testing.T) {
query := models.GetDashboardPermissionsForUserQuery{
DashboardIds: []int64{folder1.Id, folder2.Id},
OrgId: 1,
UserId: editorUser.Id,
OrgRole: models.ROLE_EDITOR,
}
err := sqlStore.GetDashboardPermissionsForUser(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, len(query.Result), 2)
require.Equal(t, query.Result[0].DashboardId, folder1.Id)
require.Equal(t, query.Result[0].Permission, models.PERMISSION_EDIT)
require.Equal(t, query.Result[1].DashboardId, folder2.Id)
require.Equal(t, query.Result[1].Permission, models.PERMISSION_EDIT)
})
t.Run("Should have write access to one dashboard folder if default role changed to view for one folder", func(t *testing.T) { t.Run("Should have write access to one dashboard folder if default role changed to view for one folder", func(t *testing.T) {
err := updateDashboardAcl(t, dashboardStore, folder1.Id, models.DashboardAcl{ err := updateDashboardAcl(t, dashboardStore, folder1.Id, models.DashboardAcl{
DashboardID: folder1.Id, OrgID: 1, UserID: editorUser.Id, Permission: models.PERMISSION_VIEW, DashboardID: folder1.Id, OrgID: 1, UserID: editorUser.Id, Permission: models.PERMISSION_VIEW,
@ -427,26 +391,6 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) {
require.Equal(t, len(query.Result), 0) require.Equal(t, len(query.Result), 0)
}) })
t.Run("should have view access to folders with default ACL", func(t *testing.T) {
setup3()
query := models.GetDashboardPermissionsForUserQuery{
DashboardIds: []int64{folder1.Id, folder2.Id},
OrgId: 1,
UserId: viewerUser.Id,
OrgRole: models.ROLE_VIEWER,
}
err := sqlStore.GetDashboardPermissionsForUser(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, len(query.Result), 2)
require.Equal(t, query.Result[0].DashboardId, folder1.Id)
require.Equal(t, query.Result[0].Permission, models.PERMISSION_VIEW)
require.Equal(t, query.Result[1].DashboardId, folder2.Id)
require.Equal(t, query.Result[1].Permission, models.PERMISSION_VIEW)
})
t.Run("Should be able to get one dashboard folder if default role changed to edit for one folder", func(t *testing.T) { t.Run("Should be able to get one dashboard folder if default role changed to edit for one folder", func(t *testing.T) {
err := updateDashboardAcl(t, dashboardStore, folder1.Id, models.DashboardAcl{ err := updateDashboardAcl(t, dashboardStore, folder1.Id, models.DashboardAcl{
DashboardID: folder1.Id, OrgID: 1, UserID: viewerUser.Id, Permission: models.PERMISSION_EDIT, DashboardID: folder1.Id, OrgID: 1, UserID: viewerUser.Id, Permission: models.PERMISSION_EDIT,

View File

@ -586,3 +586,15 @@ func makeQueryResult(query *models.FindPersistedDashboardsQuery, res []dashboard
} }
} }
} }
func (dr *DashboardServiceImpl) GetDashboardAclInfoList(ctx context.Context, query *models.GetDashboardAclInfoListQuery) error {
return dr.dashboardStore.GetDashboardAclInfoList(ctx, query)
}
func (dr *DashboardServiceImpl) HasAdminPermissionInFolders(ctx context.Context, query *models.HasAdminPermissionInFoldersQuery) error {
return dr.dashboardStore.HasAdminPermissionInFolders(ctx, query)
}
func (dr *DashboardServiceImpl) HasEditPermissionInFolders(ctx context.Context, query *models.HasEditPermissionInFoldersQuery) error {
return dr.dashboardStore.HasEditPermissionInFolders(ctx, query)
}

View File

@ -815,8 +815,14 @@ func permissionScenario(t *testing.T, desc string, canSave bool, fn permissionSc
t.Run(desc, func(t *testing.T) { t.Run(desc, func(t *testing.T) {
sqlStore := sqlstore.InitTestDB(t) sqlStore := sqlstore.InitTestDB(t)
guardian.InitLegacyGuardian(sqlStore)
dashboardStore := database.ProvideDashboardStore(sqlStore) dashboardStore := database.ProvideDashboardStore(sqlStore)
service := ProvideDashboardService(
&setting.Cfg{}, dashboardStore, &dummyDashAlertExtractor{},
featuremgmt.WithFeatures(),
accesscontrolmock.NewMockedPermissionsService(),
accesscontrolmock.NewMockedPermissionsService(),
)
guardian.InitLegacyGuardian(sqlStore, service)
savedFolder := saveTestFolder(t, "Saved folder", testOrgID, sqlStore) savedFolder := saveTestFolder(t, "Saved folder", testOrgID, sqlStore)
savedDashInFolder := saveTestDashboard(t, "Saved dash in folder", testOrgID, savedFolder.Id, sqlStore) savedDashInFolder := saveTestDashboard(t, "Saved dash in folder", testOrgID, savedFolder.Id, sqlStore)

View File

@ -81,6 +81,20 @@ func (_m *FakeDashboardStore) GetDashboard(ctx context.Context, query *models.Ge
return r0 return r0
} }
// GetDashboardAclInfoList provides a mock function with given fields: ctx, query
func (_m *FakeDashboardStore) GetDashboardAclInfoList(ctx context.Context, query *models.GetDashboardAclInfoListQuery) error {
ret := _m.Called(ctx, query)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *models.GetDashboardAclInfoListQuery) error); ok {
r0 = rf(ctx, query)
} else {
r0 = ret.Error(0)
}
return r0
}
// GetDashboardUIDById provides a mock function with given fields: ctx, query // GetDashboardUIDById provides a mock function with given fields: ctx, query
func (_m *FakeDashboardStore) GetDashboardUIDById(ctx context.Context, query *models.GetDashboardRefByIdQuery) error { func (_m *FakeDashboardStore) GetDashboardUIDById(ctx context.Context, query *models.GetDashboardRefByIdQuery) error {
ret := _m.Called(ctx, query) ret := _m.Called(ctx, query)
@ -284,6 +298,34 @@ func (_m *FakeDashboardStore) GetPublicDashboardConfig(orgId int64, dashboardUid
return r0, r1 return r0, r1
} }
// HasAdminPermissionInFolders provides a mock function with given fields: ctx, query
func (_m *FakeDashboardStore) HasAdminPermissionInFolders(ctx context.Context, query *models.HasAdminPermissionInFoldersQuery) error {
ret := _m.Called(ctx, query)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *models.HasAdminPermissionInFoldersQuery) error); ok {
r0 = rf(ctx, query)
} else {
r0 = ret.Error(0)
}
return r0
}
// HasEditPermissionInFolders provides a mock function with given fields: ctx, query
func (_m *FakeDashboardStore) HasEditPermissionInFolders(ctx context.Context, query *models.HasEditPermissionInFoldersQuery) error {
ret := _m.Called(ctx, query)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *models.HasEditPermissionInFoldersQuery) error); ok {
r0 = rf(ctx, query)
} else {
r0 = ret.Error(0)
}
return r0
}
// SaveAlerts provides a mock function with given fields: ctx, dashID, alerts // SaveAlerts provides a mock function with given fields: ctx, dashID, alerts
func (_m *FakeDashboardStore) SaveAlerts(ctx context.Context, dashID int64, alerts []*models.Alert) error { func (_m *FakeDashboardStore) SaveAlerts(ctx context.Context, dashID int64, alerts []*models.Alert) error {
ret := _m.Called(ctx, dashID, alerts) ret := _m.Called(ctx, dashID, alerts)
@ -367,20 +409,6 @@ func (_m *FakeDashboardStore) SavePublicDashboardConfig(cmd models.SavePublicDas
return r0, r1 return r0, r1
} }
// SearchDashboards provides a mock function with given fields: ctx, query
func (_m *FakeDashboardStore) SearchDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) error {
ret := _m.Called(ctx, query)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *models.FindPersistedDashboardsQuery) error); ok {
r0 = rf(ctx, query)
} else {
r0 = ret.Error(0)
}
return r0
}
// UnprovisionDashboard provides a mock function with given fields: ctx, id // UnprovisionDashboard provides a mock function with given fields: ctx, id
func (_m *FakeDashboardStore) UnprovisionDashboard(ctx context.Context, id int64) error { func (_m *FakeDashboardStore) UnprovisionDashboard(ctx context.Context, id int64) error {
ret := _m.Called(ctx, id) ret := _m.Called(ctx, id)

View File

@ -6,6 +6,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
@ -44,6 +45,7 @@ type dashboardGuardianImpl struct {
log log.Logger log log.Logger
ctx context.Context ctx context.Context
store sqlstore.Store store sqlstore.Store
dashboardService dashboards.DashboardService
} }
// New factory for creating a new dashboard guardian instance // New factory for creating a new dashboard guardian instance
@ -52,7 +54,7 @@ var New = func(ctx context.Context, dashId int64, orgId int64, user *models.Sign
panic("no guardian factory implementation provided") panic("no guardian factory implementation provided")
} }
func newDashboardGuardian(ctx context.Context, dashId int64, orgId int64, user *models.SignedInUser, store sqlstore.Store) *dashboardGuardianImpl { func newDashboardGuardian(ctx context.Context, dashId int64, orgId int64, user *models.SignedInUser, store sqlstore.Store, dashSvc dashboards.DashboardService) *dashboardGuardianImpl {
return &dashboardGuardianImpl{ return &dashboardGuardianImpl{
user: user, user: user,
dashId: dashId, dashId: dashId,
@ -60,6 +62,7 @@ func newDashboardGuardian(ctx context.Context, dashId int64, orgId int64, user *
log: log.New("dashboard.permissions"), log: log.New("dashboard.permissions"),
ctx: ctx, ctx: ctx,
store: store, store: store,
dashboardService: dashSvc,
} }
} }
@ -222,7 +225,7 @@ func (g *dashboardGuardianImpl) GetAcl() ([]*models.DashboardAclInfoDTO, error)
} }
query := models.GetDashboardAclInfoListQuery{DashboardID: g.dashId, OrgID: g.orgId} query := models.GetDashboardAclInfoListQuery{DashboardID: g.dashId, OrgID: g.orgId}
if err := g.store.GetDashboardAclInfoList(g.ctx, &query); err != nil { if err := g.dashboardService.GetDashboardAclInfoList(g.ctx, &query); err != nil {
return nil, err return nil, err
} }
g.acl = query.Result g.acl = query.Result

View File

@ -7,11 +7,13 @@ import (
"runtime" "runtime"
"testing" "testing"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/require"
) )
const ( const (
@ -683,11 +685,15 @@ func (sc *scenarioContext) verifyUpdateChildDashboardPermissionsWithOverrideShou
func TestGuardianGetHiddenACL(t *testing.T) { func TestGuardianGetHiddenACL(t *testing.T) {
t.Run("Get hidden ACL tests", func(t *testing.T) { t.Run("Get hidden ACL tests", func(t *testing.T) {
store := mockstore.NewSQLStoreMock() store := mockstore.NewSQLStoreMock()
store.ExpectedDashboardAclInfoList = []*models.DashboardAclInfoDTO{ dashSvc := dashboards.NewFakeDashboardService(t)
dashSvc.On("GetDashboardAclInfoList", mock.Anything, mock.AnythingOfType("*models.GetDashboardAclInfoListQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardAclInfoListQuery)
q.Result = []*models.DashboardAclInfoDTO{
{Inherited: false, UserId: 1, UserLogin: "user1", Permission: models.PERMISSION_EDIT}, {Inherited: false, UserId: 1, UserLogin: "user1", Permission: models.PERMISSION_EDIT},
{Inherited: false, UserId: 2, UserLogin: "user2", Permission: models.PERMISSION_ADMIN}, {Inherited: false, UserId: 2, UserLogin: "user2", Permission: models.PERMISSION_ADMIN},
{Inherited: true, UserId: 3, UserLogin: "user3", Permission: models.PERMISSION_VIEW}, {Inherited: true, UserId: 3, UserLogin: "user3", Permission: models.PERMISSION_VIEW},
} }
}).Return(nil)
cfg := setting.NewCfg() cfg := setting.NewCfg()
cfg.HiddenUsers = map[string]struct{}{"user2": {}} cfg.HiddenUsers = map[string]struct{}{"user2": {}}
@ -698,7 +704,7 @@ func TestGuardianGetHiddenACL(t *testing.T) {
UserId: 1, UserId: 1,
Login: "user1", Login: "user1",
} }
g := newDashboardGuardian(context.Background(), dashboardID, orgID, user, store) g := newDashboardGuardian(context.Background(), dashboardID, orgID, user, store, dashSvc)
hiddenACL, err := g.GetHiddenACL(cfg) hiddenACL, err := g.GetHiddenACL(cfg)
require.NoError(t, err) require.NoError(t, err)
@ -714,7 +720,7 @@ func TestGuardianGetHiddenACL(t *testing.T) {
Login: "user1", Login: "user1",
IsGrafanaAdmin: true, IsGrafanaAdmin: true,
} }
g := newDashboardGuardian(context.Background(), dashboardID, orgID, user, store) g := newDashboardGuardian(context.Background(), dashboardID, orgID, user, store, &dashboards.FakeDashboardService{})
hiddenACL, err := g.GetHiddenACL(cfg) hiddenACL, err := g.GetHiddenACL(cfg)
require.NoError(t, err) require.NoError(t, err)
@ -727,8 +733,10 @@ func TestGuardianGetHiddenACL(t *testing.T) {
func TestGuardianGetAclWithoutDuplicates(t *testing.T) { func TestGuardianGetAclWithoutDuplicates(t *testing.T) {
t.Run("Get hidden ACL tests", func(t *testing.T) { t.Run("Get hidden ACL tests", func(t *testing.T) {
store := mockstore.NewSQLStoreMock() store := mockstore.NewSQLStoreMock()
dashSvc := dashboards.NewFakeDashboardService(t)
store.ExpectedDashboardAclInfoList = []*models.DashboardAclInfoDTO{ dashSvc.On("GetDashboardAclInfoList", mock.Anything, mock.AnythingOfType("*models.GetDashboardAclInfoListQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardAclInfoListQuery)
q.Result = []*models.DashboardAclInfoDTO{
{Inherited: true, UserId: 3, UserLogin: "user3", Permission: models.PERMISSION_EDIT}, {Inherited: true, UserId: 3, UserLogin: "user3", Permission: models.PERMISSION_EDIT},
{Inherited: false, UserId: 3, UserLogin: "user3", Permission: models.PERMISSION_VIEW}, {Inherited: false, UserId: 3, UserLogin: "user3", Permission: models.PERMISSION_VIEW},
{Inherited: false, UserId: 2, UserLogin: "user2", Permission: models.PERMISSION_ADMIN}, {Inherited: false, UserId: 2, UserLogin: "user2", Permission: models.PERMISSION_ADMIN},
@ -738,6 +746,7 @@ func TestGuardianGetAclWithoutDuplicates(t *testing.T) {
{Inherited: true, UserId: 6, UserLogin: "user6", Permission: models.PERMISSION_VIEW}, {Inherited: true, UserId: 6, UserLogin: "user6", Permission: models.PERMISSION_VIEW},
{Inherited: false, UserId: 6, UserLogin: "user6", Permission: models.PERMISSION_EDIT}, {Inherited: false, UserId: 6, UserLogin: "user6", Permission: models.PERMISSION_EDIT},
} }
}).Return(nil)
t.Run("Should get acl without duplicates", func(t *testing.T) { t.Run("Should get acl without duplicates", func(t *testing.T) {
user := &models.SignedInUser{ user := &models.SignedInUser{
@ -745,7 +754,7 @@ func TestGuardianGetAclWithoutDuplicates(t *testing.T) {
UserId: 1, UserId: 1,
Login: "user1", Login: "user1",
} }
g := newDashboardGuardian(context.Background(), dashboardID, orgID, user, store) g := newDashboardGuardian(context.Background(), dashboardID, orgID, user, store, dashSvc)
acl, err := g.GetACLWithoutDuplicates() acl, err := g.GetACLWithoutDuplicates()
require.NoError(t, err) require.NoError(t, err)

View File

@ -8,8 +8,10 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
) )
@ -38,7 +40,7 @@ func orgRoleScenario(desc string, t *testing.T, role models.RoleType, fn scenari
OrgRole: role, OrgRole: role,
} }
store := mockstore.NewSQLStoreMock() store := mockstore.NewSQLStoreMock()
guard := newDashboardGuardian(context.Background(), dashboardID, orgID, user, store) guard := newDashboardGuardian(context.Background(), dashboardID, orgID, user, store, &dashboards.FakeDashboardService{})
sc := &scenarioContext{ sc := &scenarioContext{
t: t, t: t,
@ -60,7 +62,7 @@ func apiKeyScenario(desc string, t *testing.T, role models.RoleType, fn scenario
ApiKeyId: 10, ApiKeyId: 10,
} }
store := mockstore.NewSQLStoreMock() store := mockstore.NewSQLStoreMock()
guard := newDashboardGuardian(context.Background(), dashboardID, orgID, user, store) guard := newDashboardGuardian(context.Background(), dashboardID, orgID, user, store, &dashboards.FakeDashboardService{})
sc := &scenarioContext{ sc := &scenarioContext{
t: t, t: t,
orgRoleScenario: desc, orgRoleScenario: desc,
@ -77,7 +79,6 @@ func permissionScenario(desc string, dashboardID int64, sc *scenarioContext,
permissions []*models.DashboardAclInfoDTO, fn scenarioFunc) { permissions []*models.DashboardAclInfoDTO, fn scenarioFunc) {
sc.t.Run(desc, func(t *testing.T) { sc.t.Run(desc, func(t *testing.T) {
store := mockstore.NewSQLStoreMock() store := mockstore.NewSQLStoreMock()
store.ExpectedDashboardAclInfoList = permissions
teams := []*models.TeamDTO{} teams := []*models.TeamDTO{}
for _, p := range permissions { for _, p := range permissions {
@ -87,8 +88,14 @@ func permissionScenario(desc string, dashboardID int64, sc *scenarioContext,
} }
store.ExpectedTeamsByUser = teams store.ExpectedTeamsByUser = teams
dashSvc := dashboards.NewFakeDashboardService(t)
dashSvc.On("GetDashboardAclInfoList", mock.Anything, mock.AnythingOfType("*models.GetDashboardAclInfoListQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardAclInfoListQuery)
q.Result = permissions
}).Return(nil)
sc.permissionScenario = desc sc.permissionScenario = desc
sc.g = newDashboardGuardian(context.Background(), dashboardID, sc.givenUser.OrgId, sc.givenUser, store) sc.g = newDashboardGuardian(context.Background(), dashboardID, sc.givenUser.OrgId, sc.givenUser, store, dashSvc)
sc.givenDashboardID = dashboardID sc.givenDashboardID = dashboardID
sc.givenPermissions = permissions sc.givenPermissions = permissions
sc.givenTeams = teams sc.givenTeams = teams

View File

@ -20,14 +20,14 @@ func ProvideService(
// TODO: Fix this hack, see https://github.com/grafana/grafana-enterprise/issues/2935 // TODO: Fix this hack, see https://github.com/grafana/grafana-enterprise/issues/2935
InitAccessControlGuardian(store, ac, folderPermissionsService, dashboardPermissionsService, dashboardService) InitAccessControlGuardian(store, ac, folderPermissionsService, dashboardPermissionsService, dashboardService)
} else { } else {
InitLegacyGuardian(store) InitLegacyGuardian(store, dashboardService)
} }
return &Provider{} return &Provider{}
} }
func InitLegacyGuardian(store sqlstore.Store) { func InitLegacyGuardian(store sqlstore.Store, dashSvc dashboards.DashboardService) {
New = func(ctx context.Context, dashId int64, orgId int64, user *models.SignedInUser) DashboardGuardian { New = func(ctx context.Context, dashId int64, orgId int64, user *models.SignedInUser) DashboardGuardian {
return newDashboardGuardian(ctx, dashId, orgId, user, store) return newDashboardGuardian(ctx, dashId, orgId, user, store, dashSvc)
} }
} }

View File

@ -296,7 +296,7 @@ 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 := mockstore.NewSQLStoreMock() store := mockstore.NewSQLStoreMock()
guardian.InitLegacyGuardian(store) guardian.InitLegacyGuardian(store, &dashboards.FakeDashboardService{})
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")
@ -322,18 +322,17 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
orgID := int64(1) orgID := int64(1)
role := models.ROLE_ADMIN role := models.ROLE_ADMIN
sqlStore := sqlstore.InitTestDB(t) sqlStore := sqlstore.InitTestDB(t)
guardian.InitLegacyGuardian(sqlStore)
dashboardStore := database.ProvideDashboardStore(sqlStore) dashboardStore := database.ProvideDashboardStore(sqlStore)
features := featuremgmt.WithFeatures() features := featuremgmt.WithFeatures()
cfg := setting.NewCfg() cfg := setting.NewCfg()
cfg.IsFeatureToggleEnabled = features.IsEnabled cfg.IsFeatureToggleEnabled = features.IsEnabled
folderPermissions := acmock.NewMockedPermissionsService() folderPermissions := acmock.NewMockedPermissionsService()
dashboardPermissions := acmock.NewMockedPermissionsService() dashboardPermissions := acmock.NewMockedPermissionsService()
dashboardService := dashboardservice.ProvideDashboardService( dashboardService := dashboardservice.ProvideDashboardService(
cfg, dashboardStore, nil, cfg, dashboardStore, nil,
features, folderPermissions, dashboardPermissions, features, folderPermissions, dashboardPermissions,
) )
guardian.InitLegacyGuardian(sqlStore, dashboardService)
ac := acmock.New() ac := acmock.New()
service := LibraryElementService{ service := LibraryElementService{
Cfg: cfg, Cfg: cfg,

View File

@ -1429,7 +1429,7 @@ func updateFolderACL(t *testing.T, dashboardStore *database.DashboardStore, fold
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 := mockstore.NewSQLStoreMock() store := mockstore.NewSQLStoreMock()
guardian.InitLegacyGuardian(store) guardian.InitLegacyGuardian(store, &dashboards.FakeDashboardService{})
t.Helper() t.Helper()
testScenario(t, desc, func(t *testing.T, sc scenarioContext) { testScenario(t, desc, func(t *testing.T, sc scenarioContext) {

View File

@ -2,7 +2,6 @@ package sqlstore
import ( import (
"context" "context"
"strings"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
@ -42,78 +41,6 @@ func (ss *SQLStore) GetDashboardTags(ctx context.Context, query *models.GetDashb
}) })
} }
// GetDashboardPermissionsForUser returns the maximum permission the specified user has for a dashboard(s)
// The function takes in a list of dashboard ids and the user id and role
func (ss *SQLStore) GetDashboardPermissionsForUser(ctx context.Context, query *models.GetDashboardPermissionsForUserQuery) error {
return ss.WithDbSession(ctx, func(dbSession *DBSession) error {
if len(query.DashboardIds) == 0 {
return models.ErrCommandValidationFailed
}
if query.OrgRole == models.ROLE_ADMIN {
var permissions = make([]*models.DashboardPermissionForUser, 0)
for _, d := range query.DashboardIds {
permissions = append(permissions, &models.DashboardPermissionForUser{
DashboardId: d,
Permission: models.PERMISSION_ADMIN,
PermissionName: models.PERMISSION_ADMIN.String(),
})
}
query.Result = permissions
return nil
}
params := make([]interface{}, 0)
// check dashboards that have ACLs via user id, team id or role
sql := `SELECT d.id AS dashboard_id, MAX(COALESCE(da.permission, pt.permission)) AS permission
FROM dashboard AS d
LEFT JOIN dashboard_acl as da on d.folder_id = da.dashboard_id or d.id = da.dashboard_id
LEFT JOIN team_member as ugm on ugm.team_id = da.team_id
LEFT JOIN org_user ou ON ou.role = da.role AND ou.user_id = ?
`
params = append(params, query.UserId)
// check the user's role for dashboards that do not have hasAcl set
sql += `LEFT JOIN org_user ouRole ON ouRole.user_id = ? AND ouRole.org_id = ?`
params = append(params, query.UserId)
params = append(params, query.OrgId)
sql += `
LEFT JOIN (SELECT 1 AS permission, 'Viewer' AS role
UNION SELECT 2 AS permission, 'Editor' AS role
UNION SELECT 4 AS permission, 'Admin' AS role) pt ON ouRole.role = pt.role
WHERE
d.Id IN (?` + strings.Repeat(",?", len(query.DashboardIds)-1) + `) `
for _, id := range query.DashboardIds {
params = append(params, id)
}
sql += ` AND
d.org_id = ? AND
(
(d.has_acl = ? AND (da.user_id = ? OR ugm.user_id = ? OR ou.id IS NOT NULL))
OR (d.has_acl = ? AND ouRole.id IS NOT NULL)
)
group by d.id
order by d.id asc`
params = append(params, query.OrgId)
params = append(params, dialect.BooleanStr(true))
params = append(params, query.UserId)
params = append(params, query.UserId)
params = append(params, dialect.BooleanStr(false))
err := dbSession.SQL(sql, params...).Find(&query.Result)
for _, p := range query.Result {
p.PermissionName = p.Permission.String()
}
return err
})
}
// HasEditPermissionInFolders validates that an user have access to a certain folder // HasEditPermissionInFolders validates that an user have access to a certain folder
func (ss *SQLStore) HasEditPermissionInFolders(ctx context.Context, query *models.HasEditPermissionInFoldersQuery) error { func (ss *SQLStore) HasEditPermissionInFolders(ctx context.Context, query *models.HasEditPermissionInFoldersQuery) error {
return ss.WithDbSession(ctx, func(dbSession *DBSession) error { return ss.WithDbSession(ctx, func(dbSession *DBSession) error {

View File

@ -1,94 +0,0 @@
package sqlstore
import (
"context"
"github.com/grafana/grafana/pkg/models"
)
// GetDashboardAclInfoList returns a list of permissions for a dashboard. They can be fetched from three
// different places.
// 1) Permissions for the dashboard
// 2) permissions for its parent folder
// 3) if no specific permissions have been set for the dashboard or its parent folder then get the default permissions
func (ss *SQLStore) GetDashboardAclInfoList(ctx context.Context, query *models.GetDashboardAclInfoListQuery) error {
outerErr := ss.WithDbSession(ctx, func(dbSession *DBSession) error {
query.Result = make([]*models.DashboardAclInfoDTO, 0)
falseStr := dialect.BooleanStr(false)
if query.DashboardID == 0 {
sql := `SELECT
da.id,
da.org_id,
da.dashboard_id,
da.user_id,
da.team_id,
da.permission,
da.role,
da.created,
da.updated,
'' as user_login,
'' as user_email,
'' as team,
'' as title,
'' as slug,
'' as uid,` +
falseStr + ` AS is_folder,` +
falseStr + ` AS inherited
FROM dashboard_acl as da
WHERE da.dashboard_id = -1`
return dbSession.SQL(sql).Find(&query.Result)
}
rawSQL := `
-- get permissions for the dashboard and its parent folder
SELECT
da.id,
da.org_id,
da.dashboard_id,
da.user_id,
da.team_id,
da.permission,
da.role,
da.created,
da.updated,
u.login AS user_login,
u.email AS user_email,
ug.name AS team,
ug.email AS team_email,
d.title,
d.slug,
d.uid,
d.is_folder,
CASE WHEN (da.dashboard_id = -1 AND d.folder_id > 0) OR da.dashboard_id = d.folder_id THEN ` + dialect.BooleanStr(true) + ` ELSE ` + falseStr + ` END AS inherited
FROM dashboard as d
LEFT JOIN dashboard folder on folder.id = d.folder_id
LEFT JOIN dashboard_acl AS da ON
da.dashboard_id = d.id OR
da.dashboard_id = d.folder_id OR
(
-- include default permissions -->
da.org_id = -1 AND (
(folder.id IS NOT NULL AND folder.has_acl = ` + falseStr + `) OR
(folder.id IS NULL AND d.has_acl = ` + falseStr + `)
)
)
LEFT JOIN ` + dialect.Quote("user") + ` AS u ON u.id = da.user_id
LEFT JOIN team ug on ug.id = da.team_id
WHERE d.org_id = ? AND d.id = ? AND da.id IS NOT NULL
ORDER BY da.id ASC
`
return dbSession.SQL(rawSQL, query.OrgID, query.DashboardID).Find(&query.Result)
})
if outerErr != nil {
return outerErr
}
for _, p := range query.Result {
p.PermissionName = p.Permission.String()
}
return nil
}

View File

@ -429,15 +429,6 @@ func (m *SQLStoreMock) RemoveOrgUser(ctx context.Context, cmd *models.RemoveOrgU
return testData.Response return testData.Response
} }
func (m *SQLStoreMock) SaveDashboard(cmd models.SaveDashboardCommand) (*models.Dashboard, error) {
return nil, m.ExpectedError
}
func (m SQLStoreMock) SearchDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) error {
query.Result = m.ExpectedPersistedDashboards
return m.ExpectedError
}
func (m *SQLStoreMock) GetDashboardTags(ctx context.Context, query *models.GetDashboardTagsQuery) error { func (m *SQLStoreMock) GetDashboardTags(ctx context.Context, query *models.GetDashboardTagsQuery) error {
return nil // TODO: Implement return nil // TODO: Implement
} }
@ -447,10 +438,6 @@ func (m *SQLStoreMock) GetDashboards(ctx context.Context, query *models.GetDashb
return m.ExpectedError return m.ExpectedError
} }
func (m *SQLStoreMock) GetDashboardUIDById(ctx context.Context, query *models.GetDashboardRefByIdQuery) error {
return m.ExpectedError
}
func (m SQLStoreMock) GetDataSource(ctx context.Context, query *models.GetDataSourceQuery) error { func (m SQLStoreMock) GetDataSource(ctx context.Context, query *models.GetDataSourceQuery) error {
query.Result = m.ExpectedDatasource query.Result = m.ExpectedDatasource
return m.ExpectedError return m.ExpectedError
@ -614,10 +601,6 @@ func (m *SQLStoreMock) HasAdminPermissionInFolders(ctx context.Context, query *m
return m.ExpectedError return m.ExpectedError
} }
func (m *SQLStoreMock) GetDashboardPermissionsForUser(ctx context.Context, query *models.GetDashboardPermissionsForUserQuery) error {
return m.ExpectedError
}
func (m *SQLStoreMock) IsAdminOfTeams(ctx context.Context, query *models.IsAdminOfTeamsQuery) error { func (m *SQLStoreMock) IsAdminOfTeams(ctx context.Context, query *models.IsAdminOfTeamsQuery) error {
return m.ExpectedError return m.ExpectedError
} }

View File

@ -9,12 +9,13 @@ import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
"github.com/stretchr/testify/require"
) )
func TestIntegrationAccountDataAccess(t *testing.T) { func TestIntegrationAccountDataAccess(t *testing.T) {
@ -301,7 +302,6 @@ func TestIntegrationAccountDataAccess(t *testing.T) {
require.Equal(t, query.Result.Name, "ac2 name") require.Equal(t, query.Result.Name, "ac2 name")
require.Equal(t, query.Result.Login, "ac2") require.Equal(t, query.Result.Login, "ac2")
require.Equal(t, query.Result.OrgName, "ac1@test.com") require.Equal(t, query.Result.OrgName, "ac1@test.com")
// require.Equal(t, query.Result.OrgRole, "Viewer")
}) })
t.Run("Should set last org as current when removing user from current", func(t *testing.T) { t.Run("Should set last org as current when removing user from current", func(t *testing.T) {
@ -390,7 +390,7 @@ func TestIntegrationAccountDataAccess(t *testing.T) {
t.Run("Should remove dependent permissions for deleted org user", func(t *testing.T) { t.Run("Should remove dependent permissions for deleted org user", func(t *testing.T) {
permQuery := &models.GetDashboardAclInfoListQuery{DashboardID: dash1.Id, OrgID: ac1.OrgId} permQuery := &models.GetDashboardAclInfoListQuery{DashboardID: dash1.Id, OrgID: ac1.OrgId}
err = sqlStore.GetDashboardAclInfoList(context.Background(), permQuery) err = getDashboardAclInfoList(sqlStore, permQuery)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, len(permQuery.Result), 0) require.Equal(t, len(permQuery.Result), 0)
@ -399,7 +399,7 @@ func TestIntegrationAccountDataAccess(t *testing.T) {
t.Run("Should not remove dashboard permissions for same user in another org", func(t *testing.T) { t.Run("Should not remove dashboard permissions for same user in another org", func(t *testing.T) {
permQuery := &models.GetDashboardAclInfoListQuery{DashboardID: dash2.Id, OrgID: ac3.OrgId} permQuery := &models.GetDashboardAclInfoListQuery{DashboardID: dash2.Id, OrgID: ac3.OrgId}
err = sqlStore.GetDashboardAclInfoList(context.Background(), permQuery) err = getDashboardAclInfoList(sqlStore, permQuery)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, len(permQuery.Result), 1) require.Equal(t, len(permQuery.Result), 1)
@ -455,6 +455,7 @@ func insertTestDashboard(t *testing.T, sqlStore *SQLStore, title string, orgId i
Message: cmd.Message, Message: cmd.Message,
Data: dash.Data, Data: dash.Data,
} }
require.NoError(t, err)
if affectedRows, err := sess.Insert(dashVersion); err != nil { if affectedRows, err := sess.Insert(dashVersion); err != nil {
return err return err
@ -502,3 +503,88 @@ func updateDashboardAcl(t *testing.T, sqlStore *SQLStore, dashboardID int64, ite
}) })
return err return err
} }
// This function was copied from pkg/services/dashboards/database to circumvent
// import cycles. When this org-related code is refactored into a service the
// tests can the real GetDashboardAclInfoList functions
func getDashboardAclInfoList(s *SQLStore, query *models.GetDashboardAclInfoListQuery) error {
outerErr := s.WithDbSession(context.Background(), func(dbSession *DBSession) error {
query.Result = make([]*models.DashboardAclInfoDTO, 0)
falseStr := dialect.BooleanStr(false)
if query.DashboardID == 0 {
sql := `SELECT
da.id,
da.org_id,
da.dashboard_id,
da.user_id,
da.team_id,
da.permission,
da.role,
da.created,
da.updated,
'' as user_login,
'' as user_email,
'' as team,
'' as title,
'' as slug,
'' as uid,` +
falseStr + ` AS is_folder,` +
falseStr + ` AS inherited
FROM dashboard_acl as da
WHERE da.dashboard_id = -1`
return dbSession.SQL(sql).Find(&query.Result)
}
rawSQL := `
-- get permissions for the dashboard and its parent folder
SELECT
da.id,
da.org_id,
da.dashboard_id,
da.user_id,
da.team_id,
da.permission,
da.role,
da.created,
da.updated,
u.login AS user_login,
u.email AS user_email,
ug.name AS team,
ug.email AS team_email,
d.title,
d.slug,
d.uid,
d.is_folder,
CASE WHEN (da.dashboard_id = -1 AND d.folder_id > 0) OR da.dashboard_id = d.folder_id THEN ` + dialect.BooleanStr(true) + ` ELSE ` + falseStr + ` END AS inherited
FROM dashboard as d
LEFT JOIN dashboard folder on folder.id = d.folder_id
LEFT JOIN dashboard_acl AS da ON
da.dashboard_id = d.id OR
da.dashboard_id = d.folder_id OR
(
-- include default permissions -->
da.org_id = -1 AND (
(folder.id IS NOT NULL AND folder.has_acl = ` + falseStr + `) OR
(folder.id IS NULL AND d.has_acl = ` + falseStr + `)
)
)
LEFT JOIN ` + dialect.Quote("user") + ` AS u ON u.id = da.user_id
LEFT JOIN team ug on ug.id = da.team_id
WHERE d.org_id = ? AND d.id = ? AND da.id IS NOT NULL
ORDER BY da.id ASC
`
return dbSession.SQL(rawSQL, query.OrgID, query.DashboardID).Find(&query.Result)
})
if outerErr != nil {
return outerErr
}
for _, p := range query.Result {
p.PermissionName = p.Permission.String()
}
return nil
}

View File

@ -16,7 +16,6 @@ type Store interface {
CreateDashboardSnapshot(ctx context.Context, cmd *models.CreateDashboardSnapshotCommand) error CreateDashboardSnapshot(ctx context.Context, cmd *models.CreateDashboardSnapshotCommand) error
DeleteDashboardSnapshot(ctx context.Context, cmd *models.DeleteDashboardSnapshotCommand) error DeleteDashboardSnapshot(ctx context.Context, cmd *models.DeleteDashboardSnapshotCommand) error
GetDashboardSnapshot(ctx context.Context, query *models.GetDashboardSnapshotQuery) error GetDashboardSnapshot(ctx context.Context, query *models.GetDashboardSnapshotQuery) error
HasEditPermissionInFolders(ctx context.Context, query *models.HasEditPermissionInFoldersQuery) error
SearchDashboardSnapshots(ctx context.Context, query *models.GetDashboardSnapshotsQuery) error SearchDashboardSnapshots(ctx context.Context, query *models.GetDashboardSnapshotsQuery) error
GetOrgByName(name string) (*models.Org, error) GetOrgByName(name string) (*models.Org, error)
CreateOrg(ctx context.Context, cmd *models.CreateOrgCommand) error CreateOrg(ctx context.Context, cmd *models.CreateOrgCommand) error
@ -75,7 +74,6 @@ type Store interface {
WithTransactionalDbSession(ctx context.Context, callback DBTransactionFunc) error WithTransactionalDbSession(ctx context.Context, callback DBTransactionFunc) error
InTransaction(ctx context.Context, fn func(ctx context.Context) error) error InTransaction(ctx context.Context, fn func(ctx context.Context) error) error
GetDashboardVersions(ctx context.Context, query *models.GetDashboardVersionsQuery) error GetDashboardVersions(ctx context.Context, query *models.GetDashboardVersionsQuery) error
GetDashboardAclInfoList(ctx context.Context, query *models.GetDashboardAclInfoListQuery) error
CreatePlaylist(ctx context.Context, cmd *models.CreatePlaylistCommand) error CreatePlaylist(ctx context.Context, cmd *models.CreatePlaylistCommand) error
UpdatePlaylist(ctx context.Context, cmd *models.UpdatePlaylistCommand) error UpdatePlaylist(ctx context.Context, cmd *models.UpdatePlaylistCommand) error
GetPlaylist(ctx context.Context, query *models.GetPlaylistByIdQuery) error GetPlaylist(ctx context.Context, query *models.GetPlaylistByIdQuery) error
@ -134,7 +132,5 @@ type Store interface {
ExpireOldUserInvites(ctx context.Context, cmd *models.ExpireTempUsersCommand) error ExpireOldUserInvites(ctx context.Context, cmd *models.ExpireTempUsersCommand) error
GetDBHealthQuery(ctx context.Context, query *models.GetDBHealthQuery) error GetDBHealthQuery(ctx context.Context, query *models.GetDBHealthQuery) error
SearchOrgs(ctx context.Context, query *models.SearchOrgsQuery) error SearchOrgs(ctx context.Context, query *models.SearchOrgsQuery) error
HasAdminPermissionInFolders(ctx context.Context, query *models.HasAdminPermissionInFoldersQuery) error
GetDashboardPermissionsForUser(ctx context.Context, query *models.GetDashboardPermissionsForUserQuery) error
IsAdminOfTeams(ctx context.Context, query *models.IsAdminOfTeamsQuery) error IsAdminOfTeams(ctx context.Context, query *models.IsAdminOfTeamsQuery) error
} }

View File

@ -296,7 +296,7 @@ func TestIntegrationTeamCommandsAndQueries(t *testing.T) {
require.Equal(t, err, models.ErrTeamNotFound) require.Equal(t, err, models.ErrTeamNotFound)
permQuery := &models.GetDashboardAclInfoListQuery{DashboardID: 1, OrgID: testOrgID} permQuery := &models.GetDashboardAclInfoListQuery{DashboardID: 1, OrgID: testOrgID}
err = sqlStore.GetDashboardAclInfoList(context.Background(), permQuery) err = getDashboardAclInfoList(sqlStore, permQuery)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, len(permQuery.Result), 0) require.Equal(t, len(permQuery.Result), 0)

View File

@ -261,7 +261,7 @@ func TestIntegrationUserDataAccess(t *testing.T) {
require.Len(t, query1.Result, 1) require.Len(t, query1.Result, 1)
permQuery := &models.GetDashboardAclInfoListQuery{DashboardID: 1, OrgID: users[0].OrgId} permQuery := &models.GetDashboardAclInfoListQuery{DashboardID: 1, OrgID: users[0].OrgId}
err = ss.GetDashboardAclInfoList(context.Background(), permQuery) err = getDashboardAclInfoList(ss, permQuery)
require.Nil(t, err) require.Nil(t, err)
require.Len(t, permQuery.Result, 0) require.Len(t, permQuery.Result, 0)
@ -335,7 +335,7 @@ func TestIntegrationUserDataAccess(t *testing.T) {
require.Len(t, query2.Result, 1) require.Len(t, query2.Result, 1)
permQuery = &models.GetDashboardAclInfoListQuery{DashboardID: 1, OrgID: users[0].OrgId} permQuery = &models.GetDashboardAclInfoListQuery{DashboardID: 1, OrgID: users[0].OrgId}
err = ss.GetDashboardAclInfoList(context.Background(), permQuery) err = getDashboardAclInfoList(ss, permQuery)
require.Nil(t, err) require.Nil(t, err)
require.Len(t, permQuery.Result, 0) require.Len(t, permQuery.Result, 0)