sqlstore split: dashboard permissions (#49962)

* backend/sqlstore split: remove unused GetDashboardPermissionsForUser from sqlstore
* remove debugging line
* backend/sqlstore: move dashboard permission related functions to dashboard service
This commit is contained in:
Kristin Laemmert
2022-06-01 14:16:26 -04:00
committed by GitHub
parent bb94681d5a
commit 2edfbb7767
30 changed files with 557 additions and 481 deletions

View File

@ -7,14 +7,17 @@ import (
"github.com/grafana/grafana/pkg/api/routing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var (
@ -46,9 +49,13 @@ func setUp(confs ...setUpConf) *HTTPServer {
aclMockResp = c.aclMockResp
}
}
store.ExpectedDashboardAclInfoList = aclMockResp
store.ExpectedTeamsByUser = []*models.TeamDTO{}
guardian.InitLegacyGuardian(store)
dashSvc := &dashboards.FakeDashboardService{}
dashSvc.On("GetDashboardAclInfoList", mock.Anything, mock.AnythingOfType("*models.GetDashboardAclInfoListQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardAclInfoListQuery)
q.Result = aclMockResp
}).Return(nil)
guardian.InitLegacyGuardian(store, dashSvc)
return hs
}

View File

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

View File

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

View File

@ -2,7 +2,6 @@ package api
import (
"errors"
"fmt"
"net/http"
"github.com/grafana/grafana/pkg/api/response"
@ -42,8 +41,6 @@ func (hs *HTTPServer) SavePublicDashboard(c *models.ReqContext) response.Respons
pdc, err := hs.dashboardService.SavePublicDashboardConfig(c.Req.Context(), &dto)
fmt.Println("err:", err)
if errors.Is(err, models.ErrDashboardNotFound) {
return response.Error(http.StatusNotFound, "dashboard not found", err)
}

View File

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

View File

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

View File

@ -670,7 +670,7 @@ func (hs *HTTPServer) buildAdminNavLinks(c *models.ReqContext) []*dtos.NavLink {
func (hs *HTTPServer) editorInAnyFolder(c *models.ReqContext) bool {
hasEditPermissionInFoldersQuery := models.HasEditPermissionInFoldersQuery{SignedInUser: c.SignedInUser}
if err := hs.SQLStore.HasEditPermissionInFolders(c.Req.Context(), &hasEditPermissionInFoldersQuery); err != nil {
if err := hs.dashboardService.HasEditPermissionInFolders(c.Req.Context(), &hasEditPermissionInFoldersQuery); err != nil {
return false
}
return hasEditPermissionInFoldersQuery.Result

View File

@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/middleware/cookies"
"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/setting"
"github.com/grafana/grafana/pkg/web"
@ -191,14 +192,14 @@ func shouldForceLogin(c *models.ReqContext) bool {
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) {
if c.OrgRole == models.ROLE_ADMIN {
return
}
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)
}

View File

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

View File

@ -13,9 +13,12 @@ type DashboardService interface {
DeleteDashboard(ctx context.Context, dashboardId int64, orgId int64) error
FindDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, 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
GetDashboardUIDById(ctx context.Context, query *models.GetDashboardRefByIdQuery) 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)
MakeUserAdmin(ctx context.Context, orgID int64, userID, dashboardID int64, setViewAndEditPermissions bool) 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
FindDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, 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
GetDashboards(ctx context.Context, query *models.GetDashboardsQuery) error
// GetDashboardsByPluginID retrieves dashboards identified by plugin.
@ -57,6 +61,8 @@ type Store interface {
GetProvisionedDataByDashboardID(dashboardID int64) (*models.DashboardProvisioning, error)
GetProvisionedDataByDashboardUID(orgID int64, dashboardUID string) (*models.DashboardProvisioning, 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(ctx context.Context, dashID int64, alerts []*models.Alert) error
SaveDashboard(cmd models.SaveDashboardCommand) (*models.Dashboard, error)

View File

@ -90,6 +90,20 @@ func (_m *FakeDashboardService) GetDashboard(ctx context.Context, query *models.
return r0
}
// 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
func (_m *FakeDashboardService) GetDashboardUIDById(ctx context.Context, query *models.GetDashboardRefByIdQuery) error {
ret := _m.Called(ctx, query)
@ -141,6 +155,34 @@ func (_m *FakeDashboardService) GetPublicDashboardConfig(ctx context.Context, or
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
func (_m *FakeDashboardService) ImportDashboard(ctx context.Context, dto *SaveDashboardDTO) (*models.Dashboard, error) {
ret := _m.Called(ctx, dto)

View File

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

View File

@ -1,31 +1,28 @@
//go:build integration
// +build integration
package permissions
package database
import (
"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"
"time"
"github.com/grafana/grafana/pkg/models"
"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) {
var sqlStore *sqlstore.SQLStore
var currentUser models.User
var savedFolder, childDash *models.Dashboard
var dashboardStore *database.DashboardStore
var dashboardStore *DashboardStore
setup := func(t *testing.T) {
sqlStore = sqlstore.InitTestDB(t)
dashboardStore = database.ProvideDashboardStore(sqlStore)
dashboardStore = ProvideDashboardStore(sqlStore)
currentUser = createUser(t, sqlStore, "viewer", "Viewer", false)
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")
@ -45,7 +42,7 @@ func TestIntegrationDashboardAclDataAccess(t *testing.T) {
setup(t)
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.Equal(t, 2, len(query.Result))
@ -62,7 +59,7 @@ func TestIntegrationDashboardAclDataAccess(t *testing.T) {
setup(t)
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.Equal(t, 2, len(query.Result))
@ -81,7 +78,7 @@ func TestIntegrationDashboardAclDataAccess(t *testing.T) {
require.Nil(t, err)
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.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) {
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.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) {
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.Equal(t, 2, len(query.Result))
@ -145,7 +142,7 @@ func TestIntegrationDashboardAclDataAccess(t *testing.T) {
query := models.GetDashboardAclInfoListQuery{OrgID: 1, DashboardID: childDash.Id}
err = sqlStore.GetDashboardAclInfoList(context.Background(), &query)
err = dashboardStore.GetDashboardAclInfoList(context.Background(), &query)
require.Nil(t, err)
defaultPermissionsId := int64(-1)
@ -171,7 +168,7 @@ func TestIntegrationDashboardAclDataAccess(t *testing.T) {
require.Nil(t, err)
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.Equal(t, savedFolder.Id, q1.Result[0].DashboardId)
@ -185,7 +182,7 @@ func TestIntegrationDashboardAclDataAccess(t *testing.T) {
require.Nil(t, err)
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.Equal(t, 0, len(q3.Result))
})
@ -204,7 +201,7 @@ func TestIntegrationDashboardAclDataAccess(t *testing.T) {
require.Nil(t, err)
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.Equal(t, savedFolder.Id, q1.Result[0].DashboardId)
require.Equal(t, models.PERMISSION_EDIT, q1.Result[0].Permission)
@ -224,7 +221,7 @@ func TestIntegrationDashboardAclDataAccess(t *testing.T) {
require.Nil(t, err)
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.Equal(t, 1, len(q3.Result))
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) {
setup(t)
var rootFolderId int64 = 0
sqlStore := sqlstore.InitTestDB(t)
//sqlStore := sqlstore.InitTestDB(t)
query := models.GetDashboardAclInfoListQuery{DashboardID: rootFolderId, OrgID: 1}
err := sqlStore.GetDashboardAclInfoList(context.Background(), &query)
err := dashboardStore.GetDashboardAclInfoList(context.Background(), &query)
require.Nil(t, err)
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)
return *currentUser
}
func insertTestDashboard(t *testing.T, dashboardStore *database.DashboardStore, title string, orgId int64,
folderId int64, isFolder bool, tags ...interface{}) *models.Dashboard {
t.Helper()
cmd := models.SaveDashboardCommand{
OrgId: orgId,
FolderId: folderId,
IsFolder: isFolder,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": title,
"tags": tags,
}),
}
dash, err := dashboardStore.SaveDashboard(cmd)
require.NoError(t, err)
require.NotNil(t, dash)
dash.Data.Set("id", dash.Id)
dash.Data.Set("uid", dash.Uid)
return dash
}
func updateDashboardAcl(t *testing.T, dashboardStore *database.DashboardStore, dashboardID int64,
items ...models.DashboardAcl) error {
t.Helper()
var itemPtrs []*models.DashboardAcl
for _, it := range items {
item := it
item.Created = time.Now()
item.Updated = time.Now()
itemPtrs = append(itemPtrs, &item)
}
return dashboardStore.UpdateDashboardACL(context.Background(), dashboardID, itemPtrs)
}

View File

@ -310,24 +310,6 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) {
require.Equal(t, query.Result[1].ID, folder2.Id)
})
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) {
query := &models.HasEditPermissionInFoldersQuery{
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)
})
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) {
err := updateDashboardAcl(t, dashboardStore, folder1.Id, models.DashboardAcl{
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)
})
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) {
err := updateDashboardAcl(t, dashboardStore, folder1.Id, models.DashboardAcl{
DashboardID: folder1.Id, OrgID: 1, UserID: viewerUser.Id, Permission: models.PERMISSION_EDIT,

View File

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

View File

@ -815,8 +815,14 @@ func permissionScenario(t *testing.T, desc string, canSave bool, fn permissionSc
t.Run(desc, func(t *testing.T) {
sqlStore := sqlstore.InitTestDB(t)
guardian.InitLegacyGuardian(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)
savedDashInFolder := saveTestDashboard(t, "Saved dash in folder", testOrgID, savedFolder.Id, sqlStore)

View File

@ -81,6 +81,20 @@ func (_m *FakeDashboardStore) GetDashboard(ctx context.Context, query *models.Ge
return r0
}
// 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
func (_m *FakeDashboardStore) GetDashboardUIDById(ctx context.Context, query *models.GetDashboardRefByIdQuery) error {
ret := _m.Called(ctx, query)
@ -284,6 +298,34 @@ func (_m *FakeDashboardStore) GetPublicDashboardConfig(orgId int64, dashboardUid
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
func (_m *FakeDashboardStore) SaveAlerts(ctx context.Context, dashID int64, alerts []*models.Alert) error {
ret := _m.Called(ctx, dashID, alerts)
@ -367,20 +409,6 @@ func (_m *FakeDashboardStore) SavePublicDashboardConfig(cmd models.SavePublicDas
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
func (_m *FakeDashboardStore) UnprovisionDashboard(ctx context.Context, id int64) error {
ret := _m.Called(ctx, id)

View File

@ -6,6 +6,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"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/setting"
)
@ -44,6 +45,7 @@ type dashboardGuardianImpl struct {
log log.Logger
ctx context.Context
store sqlstore.Store
dashboardService dashboards.DashboardService
}
// 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")
}
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{
user: user,
dashId: dashId,
@ -60,6 +62,7 @@ func newDashboardGuardian(ctx context.Context, dashId int64, orgId int64, user *
log: log.New("dashboard.permissions"),
ctx: ctx,
store: store,
dashboardService: dashSvc,
}
}
@ -222,7 +225,7 @@ func (g *dashboardGuardianImpl) GetAcl() ([]*models.DashboardAclInfoDTO, error)
}
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
}
g.acl = query.Result

View File

@ -7,11 +7,13 @@ import (
"runtime"
"testing"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"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/setting"
"github.com/stretchr/testify/require"
)
const (
@ -683,11 +685,15 @@ func (sc *scenarioContext) verifyUpdateChildDashboardPermissionsWithOverrideShou
func TestGuardianGetHiddenACL(t *testing.T) {
t.Run("Get hidden ACL tests", func(t *testing.T) {
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: 2, UserLogin: "user2", Permission: models.PERMISSION_ADMIN},
{Inherited: true, UserId: 3, UserLogin: "user3", Permission: models.PERMISSION_VIEW},
}
}).Return(nil)
cfg := setting.NewCfg()
cfg.HiddenUsers = map[string]struct{}{"user2": {}}
@ -698,7 +704,7 @@ func TestGuardianGetHiddenACL(t *testing.T) {
UserId: 1,
Login: "user1",
}
g := newDashboardGuardian(context.Background(), dashboardID, orgID, user, store)
g := newDashboardGuardian(context.Background(), dashboardID, orgID, user, store, dashSvc)
hiddenACL, err := g.GetHiddenACL(cfg)
require.NoError(t, err)
@ -714,7 +720,7 @@ func TestGuardianGetHiddenACL(t *testing.T) {
Login: "user1",
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)
require.NoError(t, err)
@ -727,8 +733,10 @@ func TestGuardianGetHiddenACL(t *testing.T) {
func TestGuardianGetAclWithoutDuplicates(t *testing.T) {
t.Run("Get hidden ACL tests", func(t *testing.T) {
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: true, UserId: 3, UserLogin: "user3", Permission: models.PERMISSION_EDIT},
{Inherited: false, UserId: 3, UserLogin: "user3", Permission: models.PERMISSION_VIEW},
{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: false, UserId: 6, UserLogin: "user6", Permission: models.PERMISSION_EDIT},
}
}).Return(nil)
t.Run("Should get acl without duplicates", func(t *testing.T) {
user := &models.SignedInUser{
@ -745,7 +754,7 @@ func TestGuardianGetAclWithoutDuplicates(t *testing.T) {
UserId: 1,
Login: "user1",
}
g := newDashboardGuardian(context.Background(), dashboardID, orgID, user, store)
g := newDashboardGuardian(context.Background(), dashboardID, orgID, user, store, dashSvc)
acl, err := g.GetACLWithoutDuplicates()
require.NoError(t, err)

View File

@ -8,8 +8,10 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"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,
}
store := mockstore.NewSQLStoreMock()
guard := newDashboardGuardian(context.Background(), dashboardID, orgID, user, store)
guard := newDashboardGuardian(context.Background(), dashboardID, orgID, user, store, &dashboards.FakeDashboardService{})
sc := &scenarioContext{
t: t,
@ -60,7 +62,7 @@ func apiKeyScenario(desc string, t *testing.T, role models.RoleType, fn scenario
ApiKeyId: 10,
}
store := mockstore.NewSQLStoreMock()
guard := newDashboardGuardian(context.Background(), dashboardID, orgID, user, store)
guard := newDashboardGuardian(context.Background(), dashboardID, orgID, user, store, &dashboards.FakeDashboardService{})
sc := &scenarioContext{
t: t,
orgRoleScenario: desc,
@ -77,7 +79,6 @@ func permissionScenario(desc string, dashboardID int64, sc *scenarioContext,
permissions []*models.DashboardAclInfoDTO, fn scenarioFunc) {
sc.t.Run(desc, func(t *testing.T) {
store := mockstore.NewSQLStoreMock()
store.ExpectedDashboardAclInfoList = permissions
teams := []*models.TeamDTO{}
for _, p := range permissions {
@ -87,8 +88,14 @@ func permissionScenario(desc string, dashboardID int64, sc *scenarioContext,
}
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.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.givenPermissions = permissions
sc.givenTeams = teams

View File

@ -20,14 +20,14 @@ func ProvideService(
// TODO: Fix this hack, see https://github.com/grafana/grafana-enterprise/issues/2935
InitAccessControlGuardian(store, ac, folderPermissionsService, dashboardPermissionsService, dashboardService)
} else {
InitLegacyGuardian(store)
InitLegacyGuardian(store, dashboardService)
}
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 {
return newDashboardGuardian(ctx, dashId, orgId, user, store)
return newDashboardGuardian(ctx, dashId, orgId, user, store, dashSvc)
}
}

View File

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

View File

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

View File

@ -2,7 +2,6 @@ package sqlstore
import (
"context"
"strings"
"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
func (ss *SQLStore) HasEditPermissionInFolders(ctx context.Context, query *models.HasEditPermissionInFoldersQuery) error {
return ss.WithDbSession(ctx, func(dbSession *DBSession) error {

View File

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

View File

@ -429,15 +429,6 @@ func (m *SQLStoreMock) RemoveOrgUser(ctx context.Context, cmd *models.RemoveOrgU
return testData.Response
}
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 {
return nil // TODO: Implement
}
@ -447,10 +438,6 @@ func (m *SQLStoreMock) GetDashboards(ctx context.Context, query *models.GetDashb
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 {
query.Result = m.ExpectedDatasource
return m.ExpectedError
@ -614,10 +601,6 @@ func (m *SQLStoreMock) HasAdminPermissionInFolders(ctx context.Context, query *m
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 {
return m.ExpectedError
}

View File

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

View File

@ -16,7 +16,6 @@ type Store interface {
CreateDashboardSnapshot(ctx context.Context, cmd *models.CreateDashboardSnapshotCommand) error
DeleteDashboardSnapshot(ctx context.Context, cmd *models.DeleteDashboardSnapshotCommand) 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
GetOrgByName(name string) (*models.Org, error)
CreateOrg(ctx context.Context, cmd *models.CreateOrgCommand) error
@ -75,7 +74,6 @@ type Store interface {
WithTransactionalDbSession(ctx context.Context, callback DBTransactionFunc) error
InTransaction(ctx context.Context, fn func(ctx context.Context) error) 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
UpdatePlaylist(ctx context.Context, cmd *models.UpdatePlaylistCommand) error
GetPlaylist(ctx context.Context, query *models.GetPlaylistByIdQuery) error
@ -134,7 +132,5 @@ type Store interface {
ExpireOldUserInvites(ctx context.Context, cmd *models.ExpireTempUsersCommand) error
GetDBHealthQuery(ctx context.Context, query *models.GetDBHealthQuery) 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
}

View File

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

View File

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