mirror of
https://github.com/grafana/grafana.git
synced 2025-09-18 11:23:07 +08:00
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:
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
150
pkg/services/dashboards/database/acl.go
Normal file
150
pkg/services/dashboards/database/acl.go
Normal 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
|
||||||
|
})
|
||||||
|
}
|
@ -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)
|
|
||||||
}
|
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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) {
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
Reference in New Issue
Block a user