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:
co-authored by
Ieva
Emil Tullstedt
parent
2648fcb833
commit
988a120d6d
@@ -2,6 +2,7 @@ package annotationsimpl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -10,14 +11,20 @@ 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/log"
|
||||
"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/annotations"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
dashboardstore "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/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||
@@ -495,7 +502,7 @@ func TestIntegrationAnnotationListingWithRBAC(t *testing.T) {
|
||||
sql := db.InitTestDB(t)
|
||||
|
||||
var maximumTagsLength int64 = 60
|
||||
repo := xormRepositoryImpl{db: sql, cfg: setting.NewCfg(), log: log.New("annotation.test"), tagService: tagimpl.ProvideService(sql, sql.Cfg), maximumTagsLength: maximumTagsLength}
|
||||
repo := xormRepositoryImpl{db: sql, cfg: setting.NewCfg(), log: log.New("annotation.test"), tagService: tagimpl.ProvideService(sql, sql.Cfg), maximumTagsLength: maximumTagsLength, features: featuremgmt.WithFeatures()}
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := dashboardstore.ProvideDashboardStore(sql, sql.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sql, sql.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
@@ -550,7 +557,7 @@ func TestIntegrationAnnotationListingWithRBAC(t *testing.T) {
|
||||
UserID: 1,
|
||||
OrgID: 1,
|
||||
}
|
||||
role := setupRBACRole(t, repo, user)
|
||||
role := setupRBACRole(t, sql, user)
|
||||
|
||||
type testStruct struct {
|
||||
description string
|
||||
@@ -611,7 +618,7 @@ func TestIntegrationAnnotationListingWithRBAC(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
user.Permissions = map[int64]map[string][]string{1: tc.permissions}
|
||||
setupRBACPermission(t, repo, role, user)
|
||||
setupRBACPermission(t, sql, role, user)
|
||||
|
||||
results, err := repo.Get(context.Background(), &annotations.ItemQuery{
|
||||
OrgID: 1,
|
||||
@@ -630,10 +637,163 @@ func TestIntegrationAnnotationListingWithRBAC(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func setupRBACRole(t *testing.T, repo xormRepositoryImpl, user *user.SignedInUser) *accesscontrol.Role {
|
||||
func TestIntegrationAnnotationListingWithInheritedRBAC(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
|
||||
orgID := int64(1)
|
||||
permissions := []accesscontrol.Permission{
|
||||
{
|
||||
Action: dashboards.ActionFoldersCreate,
|
||||
}, {
|
||||
Action: dashboards.ActionFoldersWrite,
|
||||
Scope: dashboards.ScopeFoldersAll,
|
||||
},
|
||||
}
|
||||
usr := &user.SignedInUser{
|
||||
UserID: 1,
|
||||
OrgID: orgID,
|
||||
Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(permissions)},
|
||||
}
|
||||
|
||||
var role *accesscontrol.Role
|
||||
|
||||
dashboardIDs := make([]int64, 0, folder.MaxNestedFolderDepth+1)
|
||||
annotationsTexts := make([]string, 0, folder.MaxNestedFolderDepth+1)
|
||||
|
||||
setupFolderStructure := func() *sqlstore.SQLStore {
|
||||
db := db.InitTestDB(t)
|
||||
|
||||
// enable nested folders so that the folder table is populated for all the tests
|
||||
features := featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)
|
||||
|
||||
origNewGuardian := guardian.New
|
||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanViewValue: true, CanSaveValue: true})
|
||||
t.Cleanup(func() {
|
||||
guardian.New = origNewGuardian
|
||||
})
|
||||
|
||||
// dashboard store commands that should be called.
|
||||
dashStore, err := dashboardstore.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)
|
||||
|
||||
var maximumTagsLength int64 = 60
|
||||
repo := xormRepositoryImpl{db: db, cfg: setting.NewCfg(), log: log.New("annotation.test"), tagService: tagimpl.ProvideService(db, db.Cfg), maximumTagsLength: maximumTagsLength, features: features}
|
||||
|
||||
parentUID := ""
|
||||
for i := 0; ; i++ {
|
||||
uid := fmt.Sprintf("f%d", i)
|
||||
f, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{
|
||||
UID: uid,
|
||||
OrgID: orgID,
|
||||
Title: uid,
|
||||
SignedInUser: usr,
|
||||
ParentUID: parentUID,
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, folder.ErrMaximumDepthReached) {
|
||||
break
|
||||
}
|
||||
|
||||
t.Log("unexpected error", "error", err)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
dashInFolder := dashboards.SaveDashboardCommand{
|
||||
UserID: usr.UserID,
|
||||
OrgID: orgID,
|
||||
IsFolder: false,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"title": fmt.Sprintf("Dashboard under %s", f.UID),
|
||||
}),
|
||||
FolderID: f.ID,
|
||||
FolderUID: f.UID,
|
||||
}
|
||||
dashboard, err := dashStore.SaveDashboard(context.Background(), dashInFolder)
|
||||
require.NoError(t, err)
|
||||
|
||||
dashboardIDs = append(dashboardIDs, dashboard.ID)
|
||||
|
||||
parentUID = f.UID
|
||||
|
||||
annotationTxt := fmt.Sprintf("annotation %d", i)
|
||||
dash1Annotation := &annotations.Item{
|
||||
OrgID: orgID,
|
||||
DashboardID: dashboard.ID,
|
||||
Epoch: 10,
|
||||
Text: annotationTxt,
|
||||
}
|
||||
err = repo.Add(context.Background(), dash1Annotation)
|
||||
require.NoError(t, err)
|
||||
|
||||
annotationsTexts = append(annotationsTexts, annotationTxt)
|
||||
}
|
||||
|
||||
role = setupRBACRole(t, db, usr)
|
||||
return db
|
||||
}
|
||||
|
||||
db := setupFolderStructure()
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
features featuremgmt.FeatureToggles
|
||||
permissions map[string][]string
|
||||
expectedAnnotationText []string
|
||||
expectedError bool
|
||||
}{
|
||||
{
|
||||
desc: "Should find only annotations from dashboards under folders that user can read",
|
||||
features: featuremgmt.WithFeatures(),
|
||||
permissions: map[string][]string{
|
||||
accesscontrol.ActionAnnotationsRead: {accesscontrol.ScopeAnnotationsTypeDashboard},
|
||||
dashboards.ActionDashboardsRead: {"folders:uid:f0"},
|
||||
},
|
||||
expectedAnnotationText: annotationsTexts[:1],
|
||||
},
|
||||
{
|
||||
desc: "Should find only annotations from dashboards under inherited folders if nested folder are enabled",
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders),
|
||||
permissions: map[string][]string{
|
||||
accesscontrol.ActionAnnotationsRead: {accesscontrol.ScopeAnnotationsTypeDashboard},
|
||||
dashboards.ActionDashboardsRead: {"folders:uid:f0"},
|
||||
},
|
||||
expectedAnnotationText: annotationsTexts[:],
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
var maximumTagsLength int64 = 60
|
||||
repo := xormRepositoryImpl{db: db, cfg: setting.NewCfg(), log: log.New("annotation.test"), tagService: tagimpl.ProvideService(db, db.Cfg), maximumTagsLength: maximumTagsLength, features: tc.features}
|
||||
|
||||
usr.Permissions = map[int64]map[string][]string{1: tc.permissions}
|
||||
setupRBACPermission(t, db, role, usr)
|
||||
|
||||
results, err := repo.Get(context.Background(), &annotations.ItemQuery{
|
||||
OrgID: 1,
|
||||
SignedInUser: usr,
|
||||
})
|
||||
if tc.expectedError {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, len(tc.expectedAnnotationText))
|
||||
for _, r := range results {
|
||||
assert.Contains(t, tc.expectedAnnotationText, r.Text)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func setupRBACRole(t *testing.T, db *sqlstore.SQLStore, user *user.SignedInUser) *accesscontrol.Role {
|
||||
t.Helper()
|
||||
var role *accesscontrol.Role
|
||||
err := repo.db.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
err := db.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
role = &accesscontrol.Role{
|
||||
OrgID: user.OrgID,
|
||||
UID: "test_role",
|
||||
@@ -662,9 +822,9 @@ func setupRBACRole(t *testing.T, repo xormRepositoryImpl, user *user.SignedInUse
|
||||
return role
|
||||
}
|
||||
|
||||
func setupRBACPermission(t *testing.T, repo xormRepositoryImpl, role *accesscontrol.Role, user *user.SignedInUser) {
|
||||
func setupRBACPermission(t *testing.T, db *sqlstore.SQLStore, role *accesscontrol.Role, user *user.SignedInUser) {
|
||||
t.Helper()
|
||||
err := repo.db.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user