diff --git a/pkg/services/accesscontrol/acimpl/service.go b/pkg/services/accesscontrol/acimpl/service.go index a3da99b7d1b..c0d6f4b7814 100644 --- a/pkg/services/accesscontrol/acimpl/service.go +++ b/pkg/services/accesscontrol/acimpl/service.go @@ -35,11 +35,9 @@ func ProvideService(cfg *setting.Cfg, store db.DB, routeRegister routing.RouteRe accessControl accesscontrol.AccessControl, features *featuremgmt.FeatureManager) (*Service, error) { service := ProvideOSSService(cfg, database.ProvideService(store), cache, features) - if !accesscontrol.IsDisabled(cfg) { - api.NewAccessControlAPI(routeRegister, accessControl, service, features).RegisterAPIEndpoints() - if err := accesscontrol.DeclareFixedRoles(service, cfg); err != nil { - return nil, err - } + api.NewAccessControlAPI(routeRegister, accessControl, service, features).RegisterAPIEndpoints() + if err := accesscontrol.DeclareFixedRoles(service, cfg); err != nil { + return nil, err } return service, nil diff --git a/pkg/services/accesscontrol/actest/common.go b/pkg/services/accesscontrol/actest/common.go index 5aa71efe08c..6be44a69119 100644 --- a/pkg/services/accesscontrol/actest/common.go +++ b/pkg/services/accesscontrol/actest/common.go @@ -1,6 +1,17 @@ package actest -import "sync" +import ( + "context" + "sync" + "testing" + "time" + + "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/services/user" + "github.com/stretchr/testify/require" +) const Concurrency = 10 const BatchSize = 1000 @@ -59,3 +70,64 @@ func ConcurrentBatch(workers, count, size int, eachFn func(start, end int) error } return nil } + +// creates a role, connected it to user and store all permission from the user in database +func AddUserPermissionToDB(t testing.TB, db db.DB, user *user.SignedInUser) { + t.Helper() + err := db.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error { + var oldRole accesscontrol.Role + hadOldRole, err := sess.SQL("SELECT * FROM role where uid = 'test_role'").Get(&oldRole) + if err != nil { + return err + } + + _, err = sess.Exec("DELETE FROM role WHERE uid = 'test_role'") + require.NoError(t, err) + + role := &accesscontrol.Role{ + OrgID: user.OrgID, + UID: "test_role", + Name: "test:role", + Updated: time.Now(), + Created: time.Now(), + } + + if _, err := sess.Insert(role); err != nil { + return err + } + + _, err = sess.Insert(accesscontrol.UserRole{ + OrgID: role.OrgID, + RoleID: role.ID, + UserID: user.UserID, + Created: time.Now(), + }) + require.NoError(t, err) + + if hadOldRole { + if _, err := sess.Exec("DELETE FROM permission WHERE role_id = ?", oldRole.ID); err != nil { + return err + } + } + + var permissions []accesscontrol.Permission + for action, scopes := range user.Permissions[user.OrgID] { + for _, scope := range scopes { + p := accesscontrol.Permission{ + RoleID: role.ID, Action: action, Scope: scope, Created: time.Now(), Updated: time.Now(), + } + //p.Kind, p.Attribute, p.Identifier = p.SplitScope() + + permissions = append(permissions, p) + } + } + + if _, err := sess.InsertMulti(&permissions); err != nil { + return err + } + + return nil + }) + + require.NoError(t, err) +} diff --git a/pkg/services/dashboards/database/database.go b/pkg/services/dashboards/database/database.go index a3656d648b5..fddcfea6c78 100644 --- a/pkg/services/dashboards/database/database.go +++ b/pkg/services/dashboards/database/database.go @@ -960,26 +960,13 @@ func (d *dashboardStore) GetDashboards(ctx context.Context, query *dashboards.Ge } func (d *dashboardStore) FindDashboards(ctx context.Context, query *dashboards.FindPersistedDashboardsQuery) ([]dashboards.DashboardSearchProjection, error) { - filters := []interface{}{ - permissions.DashboardPermissionFilter{ - OrgRole: query.SignedInUser.OrgRole, - OrgId: query.SignedInUser.OrgID, - Dialect: d.store.GetDialect(), - UserId: query.SignedInUser.UserID, - PermissionLevel: query.Permission, - }, + recursiveQueriesAreSupported, err := d.store.RecursiveQueriesAreSupported() + if err != nil { + return nil, err } - if !ac.IsDisabled(d.cfg) { - recursiveQueriesAreSupported, err := d.store.RecursiveQueriesAreSupported() - if err != nil { - return nil, err - } - - // if access control is enabled, overwrite the filters so far - filters = []interface{}{ - permissions.NewAccessControlDashboardPermissionFilter(query.SignedInUser, query.Permission, query.Type, d.features, recursiveQueriesAreSupported), - } + filters := []interface{}{ + permissions.NewAccessControlDashboardPermissionFilter(query.SignedInUser, query.Permission, query.Type, d.features, recursiveQueriesAreSupported), } for _, filter := range query.Sort.Filter { @@ -1031,7 +1018,7 @@ func (d *dashboardStore) FindDashboards(ctx context.Context, query *dashboards.F sql, params := sb.ToSQL(limit, page) - err := d.store.WithDbSession(ctx, func(sess *db.Session) error { + err = d.store.WithDbSession(ctx, func(sess *db.Session) error { return sess.SQL(sql, params...).Find(&res) }) diff --git a/pkg/services/dashboards/database/database_folder_test.go b/pkg/services/dashboards/database/database_folder_test.go index ec1d4bbf0c0..7a69a168d03 100644 --- a/pkg/services/dashboards/database/database_folder_test.go +++ b/pkg/services/dashboards/database/database_folder_test.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -15,6 +14,7 @@ import ( "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/accesscontrol/actest" "github.com/grafana/grafana/pkg/services/accesscontrol/mock" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -40,7 +40,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { t.Run("Testing DB", func(t *testing.T) { var sqlStore *sqlstore.SQLStore var flder, dashInRoot, childDash *dashboards.Dashboard - var currentUser user.User + var currentUser *user.SignedInUser var dashboardStore dashboards.Store setup := func() { @@ -54,16 +54,24 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { dashInRoot = insertTestDashboard(t, dashboardStore, "test dash 67", 1, 0, false, "prod", "webapp") childDash = insertTestDashboard(t, dashboardStore, "test dash 23", 1, flder.ID, false, "prod", "webapp") insertTestDashboard(t, dashboardStore, "test dash 45", 1, flder.ID, false, "prod") - currentUser = createUser(t, sqlStore, "viewer", "Viewer", false) + currentUser = &user.SignedInUser{ + UserID: 1, + OrgID: 1, + OrgRole: org.RoleViewer, + } } t.Run("Given one dashboard folder with two dashboards and one dashboard in the root folder", func(t *testing.T) { setup() - t.Run("and no acls are set", func(t *testing.T) { - t.Run("should return all dashboards", func(t *testing.T) { + t.Run("and user can read folders and dashboards", func(t *testing.T) { + currentUser.Permissions = map[int64]map[string][]string{1: {dashboards.ActionDashboardsRead: []string{dashboards.ScopeDashboardsAll}, + dashboards.ActionFoldersRead: []string{dashboards.ScopeFoldersAll}}} + actest.AddUserPermissionToDB(t, sqlStore, currentUser) + + t.Run("should return all dashboards and folders", func(t *testing.T) { query := &dashboards.FindPersistedDashboardsQuery{ - SignedInUser: &user.SignedInUser{UserID: currentUser.ID, OrgID: 1, OrgRole: org.RoleViewer}, + SignedInUser: currentUser, OrgId: 1, DashboardIds: []int64{flder.ID, dashInRoot.ID}, } @@ -75,120 +83,54 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { }) }) - t.Run("and acl is set for dashboard folder", func(t *testing.T) { - var otherUser int64 = 999 - err := updateDashboardACL(t, dashboardStore, flder.ID, dashboards.DashboardACL{ - DashboardID: flder.ID, - OrgID: 1, - UserID: otherUser, - Permission: dashboards.PERMISSION_EDIT, - }) - require.NoError(t, err) + t.Run("and user can only read dashboards", func(t *testing.T) { + currentUser.Permissions = map[int64]map[string][]string{1: {dashboards.ActionDashboardsRead: []string{dashboards.ScopeDashboardsAll}}} + actest.AddUserPermissionToDB(t, sqlStore, currentUser) t.Run("should not return folder", func(t *testing.T) { query := &dashboards.FindPersistedDashboardsQuery{ - SignedInUser: &user.SignedInUser{UserID: currentUser.ID, OrgID: 1, OrgRole: org.RoleViewer}, - OrgId: 1, DashboardIds: []int64{flder.ID, dashInRoot.ID}, + SignedInUser: currentUser, + OrgId: 1, + DashboardIds: []int64{flder.ID, dashInRoot.ID}, } hits, err := testSearchDashboards(dashboardStore, query) require.NoError(t, err) - require.Equal(t, len(hits), 1) + require.Equal(t, 1, len(hits)) require.Equal(t, hits[0].ID, dashInRoot.ID) }) - - t.Run("when the user is given permission", func(t *testing.T) { - err := updateDashboardACL(t, dashboardStore, flder.ID, dashboards.DashboardACL{ - DashboardID: flder.ID, OrgID: 1, UserID: currentUser.ID, Permission: dashboards.PERMISSION_EDIT, - }) - require.NoError(t, err) - - t.Run("should be able to access folder", func(t *testing.T) { - query := &dashboards.FindPersistedDashboardsQuery{ - SignedInUser: &user.SignedInUser{UserID: currentUser.ID, OrgID: 1, OrgRole: org.RoleViewer}, - OrgId: 1, - DashboardIds: []int64{flder.ID, dashInRoot.ID}, - } - hits, err := testSearchDashboards(dashboardStore, query) - require.NoError(t, err) - require.Equal(t, len(hits), 2) - require.Equal(t, hits[0].ID, flder.ID) - require.Equal(t, hits[1].ID, dashInRoot.ID) - }) - }) - - t.Run("when the user is an admin", func(t *testing.T) { - t.Run("should be able to access folder", func(t *testing.T) { - query := &dashboards.FindPersistedDashboardsQuery{ - SignedInUser: &user.SignedInUser{ - UserID: currentUser.ID, - OrgID: 1, - OrgRole: org.RoleAdmin, - }, - OrgId: 1, - DashboardIds: []int64{flder.ID, dashInRoot.ID}, - } - hits, err := testSearchDashboards(dashboardStore, query) - require.NoError(t, err) - require.Equal(t, len(hits), 2) - require.Equal(t, hits[0].ID, flder.ID) - require.Equal(t, hits[1].ID, dashInRoot.ID) - }) - }) }) - t.Run("and acl is set for dashboard child and folder has all permissions removed", func(t *testing.T) { - var otherUser int64 = 999 - err := updateDashboardACL(t, dashboardStore, flder.ID) - require.NoError(t, err) - err = updateDashboardACL(t, dashboardStore, childDash.ID, dashboards.DashboardACL{ - DashboardID: flder.ID, OrgID: 1, UserID: otherUser, Permission: dashboards.PERMISSION_EDIT, - }) - require.NoError(t, err) + t.Run("and permissions are set for dashboard child and folder has all permissions removed", func(t *testing.T) { + currentUser.Permissions = map[int64]map[string][]string{1: {dashboards.ActionDashboardsRead: {dashboards.ScopeDashboardsProvider.GetResourceScopeUID(dashInRoot.UID)}}} + actest.AddUserPermissionToDB(t, sqlStore, currentUser) t.Run("should not return folder or child", func(t *testing.T) { query := &dashboards.FindPersistedDashboardsQuery{ - SignedInUser: &user.SignedInUser{UserID: currentUser.ID, OrgID: 1, OrgRole: org.RoleViewer}, OrgId: 1, DashboardIds: []int64{flder.ID, childDash.ID, dashInRoot.ID}, + SignedInUser: currentUser, + DashboardIds: []int64{flder.ID, childDash.ID, dashInRoot.ID}, } hits, err := testSearchDashboards(dashboardStore, query) require.NoError(t, err) - require.Equal(t, len(hits), 1) + require.Equal(t, 1, len(hits)) require.Equal(t, hits[0].ID, dashInRoot.ID) }) t.Run("when the user is given permission to child", func(t *testing.T) { - err := updateDashboardACL(t, dashboardStore, childDash.ID, dashboards.DashboardACL{ - DashboardID: childDash.ID, OrgID: 1, UserID: currentUser.ID, Permission: dashboards.PERMISSION_EDIT, - }) - require.NoError(t, err) + currentUser.Permissions = map[int64]map[string][]string{1: {dashboards.ActionDashboardsRead: {dashboards.ScopeDashboardsAll}}} + actest.AddUserPermissionToDB(t, sqlStore, currentUser) t.Run("should be able to search for child dashboard but not folder", func(t *testing.T) { - query := &dashboards.FindPersistedDashboardsQuery{SignedInUser: &user.SignedInUser{UserID: currentUser.ID, OrgID: 1, OrgRole: org.RoleViewer}, OrgId: 1, DashboardIds: []int64{flder.ID, childDash.ID, dashInRoot.ID}} - hits, err := testSearchDashboards(dashboardStore, query) - require.NoError(t, err) - require.Equal(t, len(hits), 2) - require.Equal(t, hits[0].ID, childDash.ID) - require.Equal(t, hits[1].ID, dashInRoot.ID) - }) - }) - - t.Run("when the user is an admin", func(t *testing.T) { - t.Run("should be able to search for child dash and folder", func(t *testing.T) { query := &dashboards.FindPersistedDashboardsQuery{ - SignedInUser: &user.SignedInUser{ - UserID: currentUser.ID, - OrgID: 1, - OrgRole: org.RoleAdmin, - }, + SignedInUser: currentUser, OrgId: 1, - DashboardIds: []int64{flder.ID, dashInRoot.ID, childDash.ID}, + DashboardIds: []int64{flder.ID, childDash.ID, dashInRoot.ID}, } hits, err := testSearchDashboards(dashboardStore, query) require.NoError(t, err) - require.Equal(t, len(hits), 3) - require.Equal(t, hits[0].ID, flder.ID) - require.Equal(t, hits[1].ID, childDash.ID) - require.Equal(t, hits[2].ID, dashInRoot.ID) + require.Equal(t, 2, len(hits)) + require.Equal(t, hits[0].ID, childDash.ID) + require.Equal(t, hits[1].ID, dashInRoot.ID) }) }) }) @@ -197,13 +139,14 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { t.Run("Given two dashboard folders with one dashboard each and one dashboard in the root folder", func(t *testing.T) { var sqlStore *sqlstore.SQLStore var folder1, folder2, dashInRoot, childDash1, childDash2 *dashboards.Dashboard - var currentUser user.User var rootFolderId int64 = 0 + var currentUser *user.SignedInUser setup2 := func() { sqlStore = db.InitTestDB(t) quotaService := quotatest.New(false, nil) - dashboardStore, err := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService) + var err error + dashboardStore, err = ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService) require.NoError(t, err) folder1 = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, true, "prod") folder2 = insertTestDashboard(t, dashboardStore, "2 test dash folder", 1, 0, true, "prod") @@ -211,18 +154,26 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { childDash1 = insertTestDashboard(t, dashboardStore, "child dash 1", 1, folder1.ID, false, "prod") childDash2 = insertTestDashboard(t, dashboardStore, "child dash 2", 1, folder2.ID, false, "prod") - currentUser = createUser(t, sqlStore, "viewer", "Viewer", false) + currentUser = &user.SignedInUser{ + UserID: 1, + OrgID: 1, + OrgRole: org.RoleViewer, + } } setup2() t.Run("and one folder is expanded, the other collapsed", func(t *testing.T) { + currentUser.Permissions = map[int64]map[string][]string{1: {dashboards.ActionDashboardsRead: {dashboards.ScopeDashboardsAll}, dashboards.ActionFoldersRead: []string{dashboards.ScopeFoldersAll}}} + actest.AddUserPermissionToDB(t, sqlStore, currentUser) + t.Run("should return dashboards in root and expanded folder", func(t *testing.T) { query := &dashboards.FindPersistedDashboardsQuery{ FolderIds: []int64{ - rootFolderId, folder1.ID}, SignedInUser: &user.SignedInUser{UserID: currentUser.ID, - OrgID: 1, OrgRole: org.RoleViewer, + rootFolderId, + folder1.ID, }, - OrgId: 1, + SignedInUser: currentUser, + OrgId: 1, } hits, err := testSearchDashboards(dashboardStore, query) require.NoError(t, err) @@ -235,18 +186,14 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { }) t.Run("and acl is set for one dashboard folder", func(t *testing.T) { - const otherUser int64 = 999 - err := updateDashboardACL(t, dashboardStore, folder1.ID, dashboards.DashboardACL{ - DashboardID: folder1.ID, OrgID: 1, UserID: otherUser, Permission: dashboards.PERMISSION_EDIT, - }) - require.NoError(t, err) - t.Run("and a dashboard is moved from folder without acl to the folder with an acl", func(t *testing.T) { moveDashboard(t, dashboardStore, 1, childDash2.Data, folder1.ID) + currentUser.Permissions = map[int64]map[string][]string{1: {dashboards.ActionDashboardsRead: {dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder2.UID), dashboards.ScopeDashboardsProvider.GetResourceScopeUID(dashInRoot.UID)}}} + actest.AddUserPermissionToDB(t, sqlStore, currentUser) t.Run("should not return folder with acl or its children", func(t *testing.T) { query := &dashboards.FindPersistedDashboardsQuery{ - SignedInUser: &user.SignedInUser{UserID: currentUser.ID, OrgID: 1, OrgRole: org.RoleViewer}, + SignedInUser: currentUser, OrgId: 1, DashboardIds: []int64{folder1.ID, childDash1.ID, childDash2.ID, dashInRoot.ID}, } @@ -259,10 +206,12 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { t.Run("and a dashboard is moved from folder with acl to the folder without an acl", func(t *testing.T) { setup2() moveDashboard(t, dashboardStore, 1, childDash1.Data, folder2.ID) + currentUser.Permissions = map[int64]map[string][]string{1: {dashboards.ActionDashboardsRead: {dashboards.ScopeDashboardsProvider.GetResourceScopeUID(dashInRoot.UID), dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder2.UID)}, dashboards.ActionFoldersRead: {dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder2.UID)}}} + actest.AddUserPermissionToDB(t, sqlStore, currentUser) t.Run("should return folder without acl and its children", func(t *testing.T) { query := &dashboards.FindPersistedDashboardsQuery{ - SignedInUser: &user.SignedInUser{UserID: currentUser.ID, OrgID: 1, OrgRole: org.RoleViewer}, + SignedInUser: currentUser, OrgId: 1, DashboardIds: []int64{folder2.ID, childDash1.ID, childDash2.ID, dashInRoot.ID}, } @@ -275,127 +224,6 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { require.Equal(t, hits[3].ID, dashInRoot.ID) }) }) - - t.Run("and a dashboard with an acl is moved to the folder without an acl", func(t *testing.T) { - err := updateDashboardACL(t, dashboardStore, childDash1.ID, dashboards.DashboardACL{ - DashboardID: childDash1.ID, OrgID: 1, UserID: otherUser, Permission: dashboards.PERMISSION_EDIT, - }) - require.NoError(t, err) - - moveDashboard(t, dashboardStore, 1, childDash1.Data, folder2.ID) - - t.Run("should return folder without acl but not the dashboard with acl", func(t *testing.T) { - query := &dashboards.FindPersistedDashboardsQuery{ - SignedInUser: &user.SignedInUser{UserID: currentUser.ID, OrgID: 1, OrgRole: org.RoleViewer}, - OrgId: 1, - DashboardIds: []int64{folder2.ID, childDash1.ID, childDash2.ID, dashInRoot.ID}, - } - hits, err := testSearchDashboards(dashboardStore, query) - require.NoError(t, err) - require.Equal(t, len(hits), 4) - require.Equal(t, hits[0].ID, folder2.ID) - require.Equal(t, hits[1].ID, childDash1.ID) - require.Equal(t, hits[2].ID, childDash2.ID) - require.Equal(t, hits[3].ID, dashInRoot.ID) - }) - }) - }) - }) - - t.Run("Given two dashboard folders", func(t *testing.T) { - var sqlStore *sqlstore.SQLStore - var folder1, folder2 *dashboards.Dashboard - var adminUser, editorUser, viewerUser user.User - - setup3 := func() { - sqlStore = db.InitTestDB(t) - quotaService := quotatest.New(false, nil) - dashboardStore, err := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService) - require.NoError(t, err) - folder1 = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, true, "prod") - folder2 = insertTestDashboard(t, dashboardStore, "2 test dash folder", 1, 0, true, "prod") - insertTestDashboard(t, dashboardStore, "folder in another org", 2, 0, true, "prod") - - adminUser = createUser(t, sqlStore, "admin", "Admin", true) - editorUser = createUser(t, sqlStore, "editor", "Editor", false) - viewerUser = createUser(t, sqlStore, "viewer", "Viewer", false) - } - - setup3() - t.Run("Admin users", func(t *testing.T) { - t.Run("Should have write access to all dashboard folders in their org", func(t *testing.T) { - query := dashboards.FindPersistedDashboardsQuery{ - OrgId: 1, - SignedInUser: &user.SignedInUser{UserID: adminUser.ID, OrgRole: org.RoleAdmin, OrgID: 1}, - Permission: dashboards.PERMISSION_VIEW, - Type: "dash-folder", - } - - hits, err := testSearchDashboards(dashboardStore, &query) - require.NoError(t, err) - - require.Equal(t, len(hits), 2) - require.Equal(t, hits[0].ID, folder1.ID) - require.Equal(t, hits[1].ID, folder2.ID) - }) - }) - - t.Run("Editor users", func(t *testing.T) { - query := dashboards.FindPersistedDashboardsQuery{ - OrgId: 1, - SignedInUser: &user.SignedInUser{UserID: editorUser.ID, OrgRole: org.RoleEditor, OrgID: 1}, - Permission: dashboards.PERMISSION_EDIT, - } - - t.Run("Should have write access to all dashboard folders with default ACL", func(t *testing.T) { - hits, err := testSearchDashboards(dashboardStore, &query) - require.NoError(t, err) - - require.Equal(t, len(hits), 2) - require.Equal(t, hits[0].ID, folder1.ID) - require.Equal(t, hits[1].ID, folder2.ID) - }) - - 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, dashboards.DashboardACL{ - DashboardID: folder1.ID, OrgID: 1, UserID: editorUser.ID, Permission: dashboards.PERMISSION_VIEW, - }) - require.NoError(t, err) - - hits, err := testSearchDashboards(dashboardStore, &query) - require.NoError(t, err) - - require.Equal(t, len(hits), 1) - require.Equal(t, hits[0].ID, folder2.ID) - }) - }) - - t.Run("Viewer users", func(t *testing.T) { - query := dashboards.FindPersistedDashboardsQuery{ - OrgId: 1, - SignedInUser: &user.SignedInUser{UserID: viewerUser.ID, OrgRole: org.RoleViewer, OrgID: 1}, - Permission: dashboards.PERMISSION_EDIT, - } - - t.Run("Should have no write access to any dashboard folders with default ACL", func(t *testing.T) { - hits, err := testSearchDashboards(dashboardStore, &query) - require.NoError(t, err) - - require.Equal(t, len(hits), 0) - }) - - 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, dashboards.DashboardACL{ - DashboardID: folder1.ID, OrgID: 1, UserID: viewerUser.ID, Permission: dashboards.PERMISSION_EDIT, - }) - require.NoError(t, err) - - hits, err := testSearchDashboards(dashboardStore, &query) - require.NoError(t, err) - - require.Equal(t, len(hits), 1) - require.Equal(t, hits[0].ID, folder1.ID) - }) }) }) }) @@ -415,8 +243,7 @@ func TestIntegrationDashboardInheritedFolderRBAC(t *testing.T) { dashInParentTitle = "dashboard in parent" dashInSubfolderTitle = "dashboard in subfolder" ) - var viewer user.SignedInUser - var role *accesscontrol.Role + var viewer *user.SignedInUser setup := func() { sqlStore = db.InitTestDB(t) @@ -431,7 +258,7 @@ func TestIntegrationDashboardInheritedFolderRBAC(t *testing.T) { require.NoError(t, err) usr := createUser(t, sqlStore, "viewer", "Viewer", false) - viewer = user.SignedInUser{ + viewer = &user.SignedInUser{ UserID: usr.ID, OrgID: usr.OrgID, OrgRole: org.RoleViewer, @@ -530,8 +357,6 @@ func TestIntegrationDashboardInheritedFolderRBAC(t *testing.T) { } _, err = dashboardWriteStore.SaveDashboard(context.Background(), saveDashboardCmd) require.NoError(t, err) - - role = setupRBACRole(t, *sqlStore, &viewer) } setup() @@ -593,10 +418,10 @@ func TestIntegrationDashboardInheritedFolderRBAC(t *testing.T) { require.NoError(t, err) viewer.Permissions = map[int64]map[string][]string{viewer.OrgID: tc.permissions} - setupRBACPermission(t, *sqlStore, role, &viewer) + actest.AddUserPermissionToDB(t, sqlStore, viewer) query := &dashboards.FindPersistedDashboardsQuery{ - SignedInUser: &viewer, + SignedInUser: viewer, OrgId: viewer.OrgID, } @@ -626,61 +451,3 @@ func moveDashboard(t *testing.T, dashboardStore dashboards.Store, orgId int64, d return dash } - -func setupRBACRole(t *testing.T, db sqlstore.SQLStore, user *user.SignedInUser) *accesscontrol.Role { - t.Helper() - var role *accesscontrol.Role - err := db.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error { - role = &accesscontrol.Role{ - OrgID: user.OrgID, - UID: "test_role", - Name: "test:role", - Updated: time.Now(), - Created: time.Now(), - } - _, err := sess.Insert(role) - if err != nil { - return err - } - - _, err = sess.Insert(accesscontrol.UserRole{ - OrgID: role.OrgID, - RoleID: role.ID, - UserID: user.UserID, - Created: time.Now(), - }) - if err != nil { - return err - } - return nil - }) - - require.NoError(t, err) - return role -} - -func setupRBACPermission(t *testing.T, db sqlstore.SQLStore, role *accesscontrol.Role, user *user.SignedInUser) { - t.Helper() - err := db.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error { - if _, err := sess.Exec("DELETE FROM permission WHERE role_id = ?", role.ID); err != nil { - return err - } - - var acPermission []accesscontrol.Permission - for action, scopes := range user.Permissions[user.OrgID] { - for _, scope := range scopes { - acPermission = append(acPermission, accesscontrol.Permission{ - RoleID: role.ID, Action: action, Scope: scope, Created: time.Now(), Updated: time.Now(), - }) - } - } - - if _, err := sess.InsertMulti(&acPermission); err != nil { - return err - } - - return nil - }) - - require.NoError(t, err) -} diff --git a/pkg/services/publicdashboards/database/database_test.go b/pkg/services/publicdashboards/database/database_test.go index eb3edaa469b..5cb4259bf10 100644 --- a/pkg/services/publicdashboards/database/database_test.go +++ b/pkg/services/publicdashboards/database/database_test.go @@ -4,13 +4,13 @@ import ( "context" "fmt" - "strings" "testing" "time" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/accesscontrol/actest" "github.com/grafana/grafana/pkg/services/dashboards" dashboardsDB "github.com/grafana/grafana/pkg/services/dashboards/database" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -85,11 +85,12 @@ func TestIntegrationListPublicDashboard(t *testing.T) { {Action: dashboards.ActionDashboardsRead, Scope: fmt.Sprintf("dashboards:uid:%s", cDash.UID)}, } - err := insertPermissions(sqlStore, orgId, "viewer", permissions) - require.NoError(t, err) + usr := &user.SignedInUser{UserID: 1, OrgID: orgId, Permissions: map[int64]map[string][]string{orgId: accesscontrol.GroupScopesByAction(permissions)}} + + actest.AddUserPermissionToDB(t, sqlStore, usr) query := &PublicDashboardListQuery{ - User: &user.SignedInUser{UserID: 1, OrgID: orgId, Permissions: map[int64]map[string][]string{orgId: accesscontrol.GroupScopesByAction(permissions)}}, + User: usr, OrgID: orgId, Page: 1, Limit: 50, @@ -113,11 +114,12 @@ func TestIntegrationListPublicDashboard(t *testing.T) { {Action: dashboards.ActionDashboardsRead, Scope: fmt.Sprintf("dashboards:uid:%s", cDash.UID)}, } - err := insertPermissions(sqlStore, orgId, "viewer", permissions) - require.NoError(t, err) + usr := &user.SignedInUser{UserID: 1, OrgID: orgId, Permissions: map[int64]map[string][]string{orgId: accesscontrol.GroupScopesByAction(permissions)}} + + actest.AddUserPermissionToDB(t, sqlStore, usr) query := &PublicDashboardListQuery{ - User: &user.SignedInUser{UserID: 1, OrgID: orgId, Permissions: map[int64]map[string][]string{orgId: accesscontrol.GroupScopesByAction(permissions)}}, + User: usr, OrgID: orgId, Page: 1, Limit: 50, @@ -140,11 +142,12 @@ func TestIntegrationListPublicDashboard(t *testing.T) { {Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:another-dashboard-2-uid"}, } - err := insertPermissions(sqlStore, orgId, "viewer", permissions) - require.NoError(t, err) + usr := &user.SignedInUser{UserID: 1, OrgID: orgId, Permissions: map[int64]map[string][]string{orgId: accesscontrol.GroupScopesByAction(permissions)}} + + actest.AddUserPermissionToDB(t, sqlStore, usr) query := &PublicDashboardListQuery{ - User: &user.SignedInUser{UserID: 1, OrgID: orgId, Permissions: map[int64]map[string][]string{orgId: accesscontrol.GroupScopesByAction(permissions)}}, + User: usr, OrgID: orgId, Page: 1, Limit: 50, @@ -917,50 +920,3 @@ func insertPublicDashboard(t *testing.T, publicdashboardStore *PublicDashboardSt return pubdash } - -func insertPermissions(sqlStore *sqlstore.SQLStore, orgId int64, role string, permissions []accesscontrol.Permission) error { - return sqlStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error { - newRole := &accesscontrol.Role{ - OrgID: orgId, - UID: fmt.Sprintf("basic_%s", role), - Name: fmt.Sprintf("basic:%s", role), - Updated: time.Now(), - Created: time.Now(), - } - _, err := sess.Insert(newRole) - if err != nil { - return err - } - - _, err = sess.Insert(accesscontrol.BuiltinRole{ - OrgID: orgId, - RoleID: newRole.ID, - Role: strings.ToUpper(role[:1]) + role[1:], - Created: time.Now(), - Updated: time.Now(), - }) - if err != nil { - return err - } - - for i := range permissions { - permissions[i].RoleID = newRole.ID - permissions[i].Created = time.Now() - permissions[i].Updated = time.Now() - } - - _, err = sess.InsertMulti(&permissions) - if err != nil { - return err - } - - _, err = sess.Insert(accesscontrol.UserRole{ - OrgID: orgId, - RoleID: newRole.ID, - UserID: 1, - Created: time.Now(), - }) - - return err - }) -} diff --git a/pkg/services/searchV2/auth.go b/pkg/services/searchV2/auth.go index fab9913b217..af561c069d5 100644 --- a/pkg/services/searchV2/auth.go +++ b/pkg/services/searchV2/auth.go @@ -8,7 +8,6 @@ import ( "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/folder" - "github.com/grafana/grafana/pkg/services/sqlstore/permissions" "github.com/grafana/grafana/pkg/services/user" ) @@ -29,68 +28,25 @@ type simpleAuthService struct { logger log.Logger } -type dashIdQueryResult struct { - UID string `xorm:"uid"` -} - func (a *simpleAuthService) GetDashboardReadFilter(ctx context.Context, orgID int64, user *user.SignedInUser) (ResourceFilter, error) { - if !a.ac.IsDisabled() { - canReadDashboard, canReadFolder := accesscontrol.Checker(user, dashboards.ActionDashboardsRead), accesscontrol.Checker(user, dashboards.ActionFoldersRead) - return func(kind entityKind, uid, parent string) bool { - if kind == entityKindFolder { - scopes, err := dashboards.GetInheritedScopes(ctx, orgID, uid, a.folderService) - if err != nil { - a.logger.Debug("could not retrieve inherited folder scopes:", "err", err) - } - scopes = append(scopes, dashboards.ScopeFoldersProvider.GetResourceScopeUID(uid)) - return canReadFolder(scopes...) - } else if kind == entityKindDashboard { - scopes, err := dashboards.GetInheritedScopes(ctx, orgID, parent, a.folderService) - if err != nil { - a.logger.Debug("could not retrieve inherited folder scopes:", "err", err) - } - scopes = append(scopes, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(uid)) - scopes = append(scopes, dashboards.ScopeFoldersProvider.GetResourceScopeUID(parent)) - return canReadDashboard(scopes...) + canReadDashboard, canReadFolder := accesscontrol.Checker(user, dashboards.ActionDashboardsRead), accesscontrol.Checker(user, dashboards.ActionFoldersRead) + return func(kind entityKind, uid, parent string) bool { + if kind == entityKindFolder { + scopes, err := dashboards.GetInheritedScopes(ctx, orgID, uid, a.folderService) + if err != nil { + a.logger.Debug("could not retrieve inherited folder scopes:", "err", err) } - return false - }, nil - } - - filter := permissions.DashboardPermissionFilter{ - OrgRole: user.OrgRole, - OrgId: user.OrgID, - Dialect: a.sql.GetDialect(), - UserId: user.UserID, - PermissionLevel: dashboards.PERMISSION_VIEW, - } - rows := make([]*dashIdQueryResult, 0) - - err := a.sql.WithDbSession(context.Background(), func(sess *db.Session) error { - sql, params := filter.Where() - sess.Table("dashboard"). - Where(sql, params...). - Where("org_id = ?", user.OrgID). - Cols("uid") - - err := sess.Find(&rows) - if err != nil { - return err + scopes = append(scopes, dashboards.ScopeFoldersProvider.GetResourceScopeUID(uid)) + return canReadFolder(scopes...) + } else if kind == entityKindDashboard { + scopes, err := dashboards.GetInheritedScopes(ctx, orgID, parent, a.folderService) + if err != nil { + a.logger.Debug("could not retrieve inherited folder scopes:", "err", err) + } + scopes = append(scopes, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(uid)) + scopes = append(scopes, dashboards.ScopeFoldersProvider.GetResourceScopeUID(parent)) + return canReadDashboard(scopes...) } - - return nil - }) - - if err != nil { - return nil, err - } - - uids := make(map[string]bool, len(rows)+1) - for i := 0; i < len(rows); i++ { - uids[rows[i].UID] = true - } - - return func(_ entityKind, uid, _ string) bool { - return uids[uid] - }, err + return false + }, nil } diff --git a/pkg/services/sqlstore/permissions/dashboard.go b/pkg/services/sqlstore/permissions/dashboard.go index d12085c6f0c..85a00c5cfed 100644 --- a/pkg/services/sqlstore/permissions/dashboard.go +++ b/pkg/services/sqlstore/permissions/dashboard.go @@ -10,8 +10,6 @@ import ( "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/login" - "github.com/grafana/grafana/pkg/services/org" - "github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" "github.com/grafana/grafana/pkg/services/user" ) @@ -19,73 +17,6 @@ import ( // maximum possible capacity for recursive queries array: one query for folder and one for dashboard actions const maximumRecursiveQueries = 2 -type DashboardPermissionFilter struct { - OrgRole org.RoleType - Dialect migrator.Dialect - UserId int64 - OrgId int64 - PermissionLevel dashboards.PermissionType -} - -func (d DashboardPermissionFilter) Where() (string, []interface{}) { - if d.OrgRole == org.RoleAdmin { - return "", nil - } - - okRoles := []interface{}{d.OrgRole} - if d.OrgRole == org.RoleEditor { - okRoles = append(okRoles, org.RoleViewer) - } - - falseStr := d.Dialect.BooleanStr(false) - - sql := `( - dashboard.id IN ( - SELECT distinct DashboardId from ( - SELECT d.id AS DashboardId - FROM dashboard AS d - LEFT JOIN dashboard_acl AS da ON - da.dashboard_id = d.id OR - da.dashboard_id = d.folder_id - WHERE - d.org_id = ? AND - da.permission >= ? AND - ( - da.user_id = ? OR - da.team_id IN (SELECT team_id from team_member AS tm WHERE tm.user_id = ?) OR - da.role IN (?` + strings.Repeat(",?", len(okRoles)-1) + `) - ) - UNION - SELECT d.id AS DashboardId - FROM dashboard AS d - LEFT JOIN dashboard AS folder on folder.id = d.folder_id - LEFT JOIN dashboard_acl AS da ON - ( - -- 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 + `) - ) - ) - WHERE - d.org_id = ? AND - da.permission >= ? AND - ( - da.user_id = ? OR - da.role IN (?` + strings.Repeat(",?", len(okRoles)-1) + `) - ) - ) AS a - ) - ) - ` - - params := []interface{}{d.OrgId, d.PermissionLevel, d.UserId, d.UserId} - params = append(params, okRoles...) - params = append(params, d.OrgId, d.PermissionLevel, d.UserId) - params = append(params, okRoles...) - return sql, params -} - type clause struct { string params []interface{} diff --git a/pkg/services/sqlstore/searchstore/search_test.go b/pkg/services/sqlstore/searchstore/search_test.go index b092579ab17..86e8bab6cad 100644 --- a/pkg/services/sqlstore/searchstore/search_test.go +++ b/pkg/services/sqlstore/searchstore/search_test.go @@ -113,43 +113,6 @@ func TestBuilder_Pagination(t *testing.T) { assert.Equal(t, "P", resPg2[0].Title, "page 2 should start with the 16th dashboard") } -func TestBuilder_Permissions(t *testing.T) { - user := &user.SignedInUser{ - UserID: 1, - OrgID: 1, - OrgRole: org.RoleViewer, - } - - store := setupTestEnvironment(t) - createDashboards(t, store, 0, 1, user.OrgID) - - level := dashboards.PERMISSION_EDIT - - builder := &searchstore.Builder{ - Filters: []interface{}{ - searchstore.OrgFilter{OrgId: user.OrgID}, - searchstore.TitleSorter{}, - permissions.DashboardPermissionFilter{ - Dialect: store.GetDialect(), - OrgRole: user.OrgRole, - OrgId: user.OrgID, - UserId: user.UserID, - PermissionLevel: level, - }, - }, - Dialect: store.GetDialect(), - } - - res := []dashboards.DashboardSearchProjection{} - err := store.WithDbSession(context.Background(), func(sess *db.Session) error { - sql, params := builder.ToSQL(limit, page) - return sess.SQL(sql, params...).Find(&res) - }) - require.NoError(t, err) - - assert.Len(t, res, 0) -} - func TestBuilder_RBAC(t *testing.T) { testsCases := []struct { desc string