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:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user