Search v1: Add support for inherited folder permissions if nested folders are enabled (#63275)

* Add features dependency to SQLBuilder

* Add features dependency to AccessControlDashboardPermissionFilter

* Add test for folder inheritance

* Dashboard permissions: Return recursive query

* Recursive query for inherited folders

* Modify search builder

* Adjust db.SQLBuilder

* Pass flag to SQLbuilder if CTEs are supported

* Add support for mysql < 8.0

* Add benchmarking for search with nested folders

* Set features to AlertStore

* Update pkg/infra/db/sqlbuilder.go

Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>

* Set features to LibraryElementService

* SQLBuilder tests with nested folder flag set

* Apply suggestion from code review

Co-authored-by: IevaVasiljeva <ieva.vasiljeva@grafana.com>
Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>
This commit is contained in:
Sofia Papagiannaki
2023-04-06 11:16:15 +03:00
committed by GitHub
co-authored by Ieva Emil Tullstedt
parent 2648fcb833
commit 988a120d6d
24 changed files with 1487 additions and 105 deletions
@@ -9,14 +9,24 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"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/mock"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/database"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/permissions"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/user"
)
@@ -129,13 +139,18 @@ func TestIntegration_DashboardPermissionFilter(t *testing.T) {
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
store := setupTest(t, 10, 100, tt.permissions)
recursiveQueriesAreSupported, err := store.RecursiveQueriesAreSupported()
require.NoError(t, err)
usr := &user.SignedInUser{OrgID: 1, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}
filter := permissions.NewAccessControlDashboardPermissionFilter(usr, tt.permission, tt.queryType)
filter := permissions.NewAccessControlDashboardPermissionFilter(usr, tt.permission, tt.queryType, featuremgmt.WithFeatures(), recursiveQueriesAreSupported)
var result int
err := store.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
err = store.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
q, params := filter.Where()
_, err := sess.SQL("SELECT COUNT(*) FROM dashboard WHERE "+q, params...).Get(&result)
recQry, recQryParams := filter.With()
params = append(recQryParams, params...)
_, err := sess.SQL(recQry+"\nSELECT COUNT(*) FROM dashboard WHERE "+q, params...).Get(&result)
return err
})
require.NoError(t, err)
@@ -145,7 +160,115 @@ func TestIntegration_DashboardPermissionFilter(t *testing.T) {
}
}
func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) {
testCases := []struct {
desc string
queryType string
permission dashboards.PermissionType
permissions []accesscontrol.Permission
expectedResult []string
features featuremgmt.FeatureToggles
}{
{
desc: "Should be able to view dashboards under inherited folders if nested folders are enabled",
queryType: searchstore.TypeDashboard,
permission: dashboards.PERMISSION_VIEW,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:parent"},
},
features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders),
expectedResult: []string{"dashboard under parent folder", "dashboard under subfolder"},
},
{
desc: "Should not be able to view dashboards under inherited folders if nested folders are not enabled",
queryType: searchstore.TypeDashboard,
permission: dashboards.PERMISSION_VIEW,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:parent"},
},
features: featuremgmt.WithFeatures(),
expectedResult: []string{"dashboard under parent folder"},
},
{
desc: "Should be able to view inherited folders if nested folders are enabled",
queryType: searchstore.TypeFolder,
permission: dashboards.PERMISSION_VIEW,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:parent"},
},
features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders),
expectedResult: []string{"parent", "subfolder"},
},
{
desc: "Should not be able to view inherited folders if nested folders are not enabled",
queryType: searchstore.TypeFolder,
permission: dashboards.PERMISSION_VIEW,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:parent"},
},
features: featuremgmt.WithFeatures(),
expectedResult: []string{"parent"},
},
{
desc: "Should be able to view inherited dashboards and folders if nested folders are enabled",
permission: dashboards.PERMISSION_VIEW,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:parent"},
{Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:parent"},
},
features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders),
expectedResult: []string{"parent", "subfolder", "dashboard under parent folder", "dashboard under subfolder"},
},
{
desc: "Should not be able to view inherited dashboards and folders if nested folders are not enabled",
permission: dashboards.PERMISSION_VIEW,
permissions: []accesscontrol.Permission{
{Action: dashboards.ActionFoldersRead, Scope: "folders:uid:parent"},
{Action: dashboards.ActionDashboardsRead, Scope: "folders:uid:parent"},
},
features: featuremgmt.WithFeatures(),
expectedResult: []string{"parent", "dashboard under parent folder"},
},
}
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanViewValue: true, CanSaveValue: true})
t.Cleanup(func() {
guardian.New = origNewGuardian
})
var orgID int64 = 1
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
tc.permissions = append(tc.permissions, accesscontrol.Permission{
Action: dashboards.ActionFoldersCreate,
}, accesscontrol.Permission{
Action: dashboards.ActionFoldersWrite,
Scope: dashboards.ScopeFoldersAll,
})
usr := &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(tc.permissions)}}
db := setupNestedTest(t, usr, tc.permissions, orgID, tc.features)
recursiveQueriesAreSupported, err := db.RecursiveQueriesAreSupported()
require.NoError(t, err)
filter := permissions.NewAccessControlDashboardPermissionFilter(usr, tc.permission, tc.queryType, tc.features, recursiveQueriesAreSupported)
var result []string
err = db.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
q, params := filter.Where()
recQry, recQryParams := filter.With()
params = append(recQryParams, params...)
err := sess.SQL(recQry+"\nSELECT title FROM dashboard WHERE "+q, params...).Find(&result)
return err
})
require.NoError(t, err)
assert.Equal(t, tc.expectedResult, result)
})
}
}
func setupTest(t *testing.T, numFolders, numDashboards int, permissions []accesscontrol.Permission) db.DB {
t.Helper()
store := db.InitTestDB(t)
err := store.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
dashes := make([]dashboards.Dashboard, 0, numFolders+numDashboards)
@@ -227,3 +350,95 @@ func setupTest(t *testing.T, numFolders, numDashboards int, permissions []access
require.NoError(t, err)
return store
}
func setupNestedTest(t *testing.T, usr *user.SignedInUser, perms []accesscontrol.Permission, orgID int64, features featuremgmt.FeatureToggles) db.DB {
t.Helper()
db := sqlstore.InitTestDB(t)
// dashboard store commands that should be called.
dashStore, err := database.ProvideDashboardStore(db, db.Cfg, features, tagimpl.ProvideService(db, db.Cfg), quotatest.New(false, nil))
require.NoError(t, err)
folderSvc := folderimpl.ProvideService(mock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), db.Cfg, dashStore, folderimpl.ProvideDashboardFolderStore(db), db, features)
// create parent folder
parent, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{
UID: "parent",
OrgID: orgID,
Title: "parent",
SignedInUser: usr,
})
require.NoError(t, err)
// create subfolder
subfolder, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{
UID: "subfolder",
ParentUID: "parent",
OrgID: orgID,
Title: "subfolder",
SignedInUser: usr,
})
require.NoError(t, err)
// create dashboard under parent folder
_, err = dashStore.SaveDashboard(context.Background(), dashboards.SaveDashboardCommand{
OrgID: orgID,
FolderID: parent.ID,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"title": "dashboard under parent folder",
}),
})
require.NoError(t, err)
// create dashboard under subfolder
_, err = dashStore.SaveDashboard(context.Background(), dashboards.SaveDashboardCommand{
OrgID: orgID,
FolderID: subfolder.ID,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"title": "dashboard under subfolder",
}),
})
require.NoError(t, err)
err = db.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
role := &accesscontrol.Role{
OrgID: 0,
UID: "basic_viewer",
Name: "basic:viewer",
Updated: time.Now(),
Created: time.Now(),
}
_, err = sess.Insert(role)
if err != nil {
return err
}
_, err = sess.Insert(accesscontrol.BuiltinRole{
OrgID: 0,
RoleID: role.ID,
Role: "Viewer",
Created: time.Now(),
Updated: time.Now(),
})
if err != nil {
return err
}
for i := range perms {
perms[i].RoleID = role.ID
perms[i].Created = time.Now()
perms[i].Updated = time.Now()
}
if len(perms) > 0 {
_, err = sess.InsertMulti(&perms)
if err != nil {
return err
}
}
return nil
})
require.NoError(t, err)
return db
}