mirror of https://github.com/grafana/grafana
Revert "RBAC: remove dashboard ACL logic from dash store and service (#78130)"
This reverts commit dd54931147
.
pull/78203/head
parent
384db8e0ca
commit
8057b9298d
@ -0,0 +1,103 @@ |
||||
package database |
||||
|
||||
import ( |
||||
"context" |
||||
|
||||
"github.com/grafana/grafana/pkg/infra/db" |
||||
"github.com/grafana/grafana/pkg/services/dashboards" |
||||
) |
||||
|
||||
// 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 *dashboards.GetDashboardACLInfoListQuery) ([]*dashboards.DashboardACLInfoDTO, error) { |
||||
queryResult := make([]*dashboards.DashboardACLInfoDTO, 0) |
||||
outerErr := d.store.WithDbSession(ctx, func(dbSession *db.Session) error { |
||||
falseStr := d.store.GetDialect().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(&queryResult) |
||||
} |
||||
|
||||
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.store.GetDialect().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.store.GetDialect().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(&queryResult) |
||||
}) |
||||
|
||||
if outerErr != nil { |
||||
return nil, outerErr |
||||
} |
||||
|
||||
for _, p := range queryResult { |
||||
p.PermissionName = p.Permission.String() |
||||
} |
||||
|
||||
return queryResult, nil |
||||
} |
||||
|
||||
func (d *dashboardStore) DeleteACLByUser(ctx context.Context, userID int64) error { |
||||
return d.store.WithTransactionalDbSession(ctx, func(sess *db.Session) error { |
||||
var rawSQL = "DELETE FROM dashboard_acl WHERE user_id = ?" |
||||
_, err := sess.Exec(rawSQL, userID) |
||||
return err |
||||
}) |
||||
} |
@ -0,0 +1,299 @@ |
||||
package database |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"testing" |
||||
"time" |
||||
|
||||
"github.com/stretchr/testify/require" |
||||
|
||||
"github.com/grafana/grafana/pkg/infra/db" |
||||
"github.com/grafana/grafana/pkg/services/dashboards" |
||||
"github.com/grafana/grafana/pkg/services/org" |
||||
"github.com/grafana/grafana/pkg/services/org/orgimpl" |
||||
"github.com/grafana/grafana/pkg/services/quota/quotaimpl" |
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest" |
||||
"github.com/grafana/grafana/pkg/services/sqlstore" |
||||
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" |
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl" |
||||
"github.com/grafana/grafana/pkg/services/team/teamimpl" |
||||
"github.com/grafana/grafana/pkg/services/user" |
||||
"github.com/grafana/grafana/pkg/services/user/userimpl" |
||||
) |
||||
|
||||
func TestIntegrationDashboardACLDataAccess(t *testing.T) { |
||||
if testing.Short() { |
||||
t.Skip("skipping integration test") |
||||
} |
||||
var sqlStore *sqlstore.SQLStore |
||||
var currentUser user.User |
||||
var savedFolder, childDash *dashboards.Dashboard |
||||
var dashboardStore dashboards.Store |
||||
|
||||
setup := func(t *testing.T) int64 { |
||||
sqlStore = db.InitTestDB(t) |
||||
quotaService := quotatest.New(false, nil) |
||||
var err error |
||||
dashboardStore, err = ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore), quotaService) |
||||
require.NoError(t, err) |
||||
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, savedFolder.UID, false, "prod", "webapp") |
||||
return currentUser.OrgID |
||||
} |
||||
|
||||
t.Run("Dashboard permission with userId and teamId set to 0", func(t *testing.T) { |
||||
orgID := setup(t) |
||||
err := updateDashboardACL(t, dashboardStore, savedFolder.ID, dashboards.DashboardACL{ |
||||
OrgID: orgID, |
||||
DashboardID: savedFolder.ID, |
||||
Permission: dashboards.PERMISSION_EDIT, |
||||
}) |
||||
require.Equal(t, dashboards.ErrDashboardACLInfoMissing, err) |
||||
}) |
||||
|
||||
t.Run("Folder acl should include default acl", func(t *testing.T) { |
||||
orgID := setup(t) |
||||
query := dashboards.GetDashboardACLInfoListQuery{DashboardID: savedFolder.ID, OrgID: orgID} |
||||
|
||||
queryResult, err := dashboardStore.GetDashboardACLInfoList(context.Background(), &query) |
||||
require.Nil(t, err) |
||||
|
||||
require.Equal(t, 2, len(queryResult)) |
||||
defaultPermissionsId := int64(-1) |
||||
require.Equal(t, defaultPermissionsId, queryResult[0].DashboardID) |
||||
require.Equal(t, org.RoleViewer, *queryResult[0].Role) |
||||
require.False(t, queryResult[0].Inherited) |
||||
require.Equal(t, defaultPermissionsId, queryResult[1].DashboardID) |
||||
require.Equal(t, org.RoleEditor, *queryResult[1].Role) |
||||
require.False(t, queryResult[1].Inherited) |
||||
}) |
||||
|
||||
t.Run("Dashboard acl should include acl for parent folder", func(t *testing.T) { |
||||
orgID := setup(t) |
||||
query := dashboards.GetDashboardACLInfoListQuery{DashboardID: childDash.ID, OrgID: orgID} |
||||
|
||||
queryResult, err := dashboardStore.GetDashboardACLInfoList(context.Background(), &query) |
||||
require.Nil(t, err) |
||||
|
||||
require.Equal(t, 2, len(queryResult)) |
||||
defaultPermissionsId := int64(-1) |
||||
require.Equal(t, defaultPermissionsId, queryResult[0].DashboardID) |
||||
require.Equal(t, org.RoleViewer, *queryResult[0].Role) |
||||
require.True(t, queryResult[0].Inherited) |
||||
require.Equal(t, defaultPermissionsId, queryResult[1].DashboardID) |
||||
require.Equal(t, org.RoleEditor, *queryResult[1].Role) |
||||
require.True(t, queryResult[1].Inherited) |
||||
}) |
||||
|
||||
t.Run("Folder with removed default permissions returns no acl items", func(t *testing.T) { |
||||
orgID := setup(t) |
||||
err := dashboardStore.UpdateDashboardACL(context.Background(), savedFolder.ID, nil) |
||||
require.Nil(t, err) |
||||
|
||||
query := dashboards.GetDashboardACLInfoListQuery{DashboardID: childDash.ID, OrgID: orgID} |
||||
queryResult, err := dashboardStore.GetDashboardACLInfoList(context.Background(), &query) |
||||
require.Nil(t, err) |
||||
|
||||
require.Equal(t, 0, len(queryResult)) |
||||
}) |
||||
|
||||
t.Run("Given a dashboard folder and a user", func(t *testing.T) { |
||||
t.Run("Given dashboard folder permission", func(t *testing.T) { |
||||
orgID := setup(t) |
||||
err := updateDashboardACL(t, dashboardStore, savedFolder.ID, dashboards.DashboardACL{ |
||||
OrgID: orgID, |
||||
UserID: currentUser.ID, |
||||
DashboardID: savedFolder.ID, |
||||
Permission: dashboards.PERMISSION_EDIT, |
||||
}) |
||||
require.Nil(t, err) |
||||
|
||||
t.Run("When reading dashboard acl should include acl for parent folder", func(t *testing.T) { |
||||
query := dashboards.GetDashboardACLInfoListQuery{DashboardID: childDash.ID, OrgID: orgID} |
||||
|
||||
queryResult, err := dashboardStore.GetDashboardACLInfoList(context.Background(), &query) |
||||
require.Nil(t, err) |
||||
|
||||
require.Equal(t, 1, len(queryResult)) |
||||
require.Equal(t, savedFolder.ID, queryResult[0].DashboardID) |
||||
}) |
||||
|
||||
t.Run("Given child dashboard permission", func(t *testing.T) { |
||||
err := updateDashboardACL(t, dashboardStore, childDash.ID, dashboards.DashboardACL{ |
||||
OrgID: orgID, |
||||
UserID: currentUser.ID, |
||||
DashboardID: childDash.ID, |
||||
Permission: dashboards.PERMISSION_EDIT, |
||||
}) |
||||
require.Nil(t, err) |
||||
|
||||
t.Run("When reading dashboard acl should include acl for parent folder and child", func(t *testing.T) { |
||||
query := dashboards.GetDashboardACLInfoListQuery{OrgID: orgID, DashboardID: childDash.ID} |
||||
|
||||
queryResult, err := dashboardStore.GetDashboardACLInfoList(context.Background(), &query) |
||||
require.Nil(t, err) |
||||
|
||||
require.Equal(t, 2, len(queryResult)) |
||||
require.Equal(t, savedFolder.ID, queryResult[0].DashboardID) |
||||
require.True(t, queryResult[0].Inherited) |
||||
require.Equal(t, childDash.ID, queryResult[1].DashboardID) |
||||
require.False(t, queryResult[1].Inherited) |
||||
}) |
||||
}) |
||||
}) |
||||
|
||||
t.Run("Reading dashboard acl should include default acl for parent folder and the child acl", func(t *testing.T) { |
||||
orgID := setup(t) |
||||
err := updateDashboardACL(t, dashboardStore, childDash.ID, dashboards.DashboardACL{ |
||||
OrgID: 1, |
||||
UserID: currentUser.ID, |
||||
DashboardID: childDash.ID, |
||||
Permission: dashboards.PERMISSION_EDIT, |
||||
}) |
||||
require.Nil(t, err) |
||||
|
||||
query := dashboards.GetDashboardACLInfoListQuery{OrgID: orgID, DashboardID: childDash.ID} |
||||
|
||||
queryResult, err := dashboardStore.GetDashboardACLInfoList(context.Background(), &query) |
||||
require.Nil(t, err) |
||||
|
||||
defaultPermissionsId := int64(-1) |
||||
require.Equal(t, 3, len(queryResult)) |
||||
require.Equal(t, defaultPermissionsId, queryResult[0].DashboardID) |
||||
require.Equal(t, org.RoleViewer, *queryResult[0].Role) |
||||
require.True(t, queryResult[0].Inherited) |
||||
require.Equal(t, defaultPermissionsId, queryResult[1].DashboardID) |
||||
require.Equal(t, org.RoleEditor, *queryResult[1].Role) |
||||
require.True(t, queryResult[1].Inherited) |
||||
require.Equal(t, childDash.ID, queryResult[2].DashboardID) |
||||
require.False(t, queryResult[2].Inherited) |
||||
}) |
||||
|
||||
t.Run("Add and delete dashboard permission", func(t *testing.T) { |
||||
orgID := setup(t) |
||||
err := updateDashboardACL(t, dashboardStore, savedFolder.ID, dashboards.DashboardACL{ |
||||
OrgID: 1, |
||||
UserID: currentUser.ID, |
||||
DashboardID: savedFolder.ID, |
||||
Permission: dashboards.PERMISSION_EDIT, |
||||
}) |
||||
require.Nil(t, err) |
||||
|
||||
q1 := &dashboards.GetDashboardACLInfoListQuery{DashboardID: savedFolder.ID, OrgID: orgID} |
||||
q1Result, err := dashboardStore.GetDashboardACLInfoList(context.Background(), q1) |
||||
require.Nil(t, err) |
||||
|
||||
require.Equal(t, savedFolder.ID, q1Result[0].DashboardID) |
||||
require.Equal(t, dashboards.PERMISSION_EDIT, q1Result[0].Permission) |
||||
require.Equal(t, "Edit", q1Result[0].PermissionName) |
||||
require.Equal(t, currentUser.ID, q1Result[0].UserID) |
||||
require.Equal(t, currentUser.Login, q1Result[0].UserLogin) |
||||
require.Equal(t, currentUser.Email, q1Result[0].UserEmail) |
||||
|
||||
err = updateDashboardACL(t, dashboardStore, savedFolder.ID) |
||||
require.Nil(t, err) |
||||
|
||||
q3 := &dashboards.GetDashboardACLInfoListQuery{DashboardID: savedFolder.ID, OrgID: orgID} |
||||
q3Result, err := dashboardStore.GetDashboardACLInfoList(context.Background(), q3) |
||||
require.Nil(t, err) |
||||
require.Equal(t, 0, len(q3Result)) |
||||
}) |
||||
|
||||
t.Run("Should be able to add a user permission for a team", func(t *testing.T) { |
||||
orgID := setup(t) |
||||
teamSvc := teamimpl.ProvideService(sqlStore, sqlStore.Cfg) |
||||
team1, err := teamSvc.CreateTeam("group1 name", "", 1) |
||||
require.Nil(t, err) |
||||
|
||||
err = updateDashboardACL(t, dashboardStore, savedFolder.ID, dashboards.DashboardACL{ |
||||
OrgID: 1, |
||||
TeamID: team1.ID, |
||||
DashboardID: savedFolder.ID, |
||||
Permission: dashboards.PERMISSION_EDIT, |
||||
}) |
||||
require.Nil(t, err) |
||||
|
||||
q1 := &dashboards.GetDashboardACLInfoListQuery{DashboardID: savedFolder.ID, OrgID: orgID} |
||||
q1Result, err := dashboardStore.GetDashboardACLInfoList(context.Background(), q1) |
||||
require.Nil(t, err) |
||||
require.Equal(t, savedFolder.ID, q1Result[0].DashboardID) |
||||
require.Equal(t, dashboards.PERMISSION_EDIT, q1Result[0].Permission) |
||||
require.Equal(t, team1.ID, q1Result[0].TeamID) |
||||
}) |
||||
|
||||
t.Run("Should be able to update an existing permission for a team", func(t *testing.T) { |
||||
orgID := setup(t) |
||||
teamSvc := teamimpl.ProvideService(sqlStore, sqlStore.Cfg) |
||||
team1, err := teamSvc.CreateTeam("group1 name", "", 1) |
||||
require.Nil(t, err) |
||||
err = updateDashboardACL(t, dashboardStore, savedFolder.ID, dashboards.DashboardACL{ |
||||
OrgID: 1, |
||||
TeamID: team1.ID, |
||||
DashboardID: savedFolder.ID, |
||||
Permission: dashboards.PERMISSION_ADMIN, |
||||
}) |
||||
require.Nil(t, err) |
||||
|
||||
q3 := &dashboards.GetDashboardACLInfoListQuery{DashboardID: savedFolder.ID, OrgID: orgID} |
||||
q3Result, err := dashboardStore.GetDashboardACLInfoList(context.Background(), q3) |
||||
require.Nil(t, err) |
||||
require.Equal(t, 1, len(q3Result)) |
||||
require.Equal(t, savedFolder.ID, q3Result[0].DashboardID) |
||||
require.Equal(t, dashboards.PERMISSION_ADMIN, q3Result[0].Permission) |
||||
require.Equal(t, team1.ID, q3Result[0].TeamID) |
||||
}) |
||||
}) |
||||
|
||||
t.Run("Default permissions for root folder dashboards", func(t *testing.T) { |
||||
orgID := setup(t) |
||||
var rootFolderId int64 = 0 |
||||
//sqlStore := db.InitTestDB(t)
|
||||
|
||||
query := dashboards.GetDashboardACLInfoListQuery{DashboardID: rootFolderId, OrgID: orgID} |
||||
|
||||
queryResult, err := dashboardStore.GetDashboardACLInfoList(context.Background(), &query) |
||||
require.Nil(t, err) |
||||
|
||||
require.Equal(t, 2, len(queryResult)) |
||||
defaultPermissionsId := int64(-1) |
||||
require.Equal(t, defaultPermissionsId, queryResult[0].DashboardID) |
||||
require.Equal(t, org.RoleViewer, *queryResult[0].Role) |
||||
require.False(t, queryResult[0].Inherited) |
||||
require.Equal(t, defaultPermissionsId, queryResult[1].DashboardID) |
||||
require.Equal(t, org.RoleEditor, *queryResult[1].Role) |
||||
require.False(t, queryResult[1].Inherited) |
||||
}) |
||||
|
||||
t.Run("Delete acl by user", func(t *testing.T) { |
||||
setup(t) |
||||
err := dashboardStore.DeleteACLByUser(context.Background(), currentUser.ID) |
||||
require.NoError(t, err) |
||||
}) |
||||
} |
||||
|
||||
func createUser(t *testing.T, sqlStore *sqlstore.SQLStore, name string, role string, isAdmin bool) user.User { |
||||
t.Helper() |
||||
sqlStore.Cfg.AutoAssignOrg = true |
||||
sqlStore.Cfg.AutoAssignOrgId = 1 |
||||
sqlStore.Cfg.AutoAssignOrgRole = role |
||||
|
||||
qs := quotaimpl.ProvideService(sqlStore, sqlStore.Cfg) |
||||
orgService, err := orgimpl.ProvideService(sqlStore, sqlStore.Cfg, qs) |
||||
require.NoError(t, err) |
||||
usrSvc, err := userimpl.ProvideService(sqlStore, orgService, sqlStore.Cfg, nil, nil, qs, supportbundlestest.NewFakeBundleService()) |
||||
require.NoError(t, err) |
||||
|
||||
o, err := orgService.CreateWithMember(context.Background(), &org.CreateOrgCommand{Name: fmt.Sprintf("test org %d", time.Now().UnixNano())}) |
||||
require.NoError(t, err) |
||||
|
||||
currentUserCmd := user.CreateUserCommand{Login: name, Email: name + "@test.com", Name: "a " + name, IsAdmin: isAdmin, OrgID: o.ID} |
||||
currentUser, err := usrSvc.Create(context.Background(), ¤tUserCmd) |
||||
require.NoError(t, err) |
||||
orgs, err := orgService.GetUserOrgList(context.Background(), &org.GetUserOrgListQuery{UserID: currentUser.ID}) |
||||
require.NoError(t, err) |
||||
require.Equal(t, org.RoleType(role), orgs[0].Role) |
||||
require.Equal(t, o.ID, orgs[0].OrgID) |
||||
return *currentUser |
||||
} |
Loading…
Reference in new issue