From 192a81d07f077b43d178a01e6dac5eb11d42961c Mon Sep 17 00:00:00 2001 From: Stephanie Hingtgen Date: Thu, 23 Jan 2025 09:30:14 -0700 Subject: [PATCH] Folders: Fix guardian to use folder service (#99339) --- pkg/api/dashboard_snapshot_test.go | 3 +- pkg/api/dashboard_test.go | 6 +- pkg/api/folder_bench_test.go | 2 +- .../dashboards/service/dashboard_service.go | 20 ++- .../dashboard_service_integration_test.go | 3 +- pkg/services/folder/folderimpl/folder.go | 14 +- .../guardian/accesscontrol_guardian.go | 141 ++++++++++-------- .../guardian/accesscontrol_guardian_test.go | 3 +- pkg/services/guardian/guardian.go | 28 ++-- pkg/services/guardian/provider.go | 20 +-- pkg/services/libraryelements/guard.go | 13 +- .../libraryelements_get_all_test.go | 4 +- .../libraryelements_patch_test.go | 6 +- .../libraryelements_permissions_test.go | 21 +-- .../libraryelements/libraryelements_test.go | 62 ++++---- .../librarypanels/librarypanels_test.go | 8 +- 16 files changed, 207 insertions(+), 147 deletions(-) diff --git a/pkg/api/dashboard_snapshot_test.go b/pkg/api/dashboard_snapshot_test.go index 2f27909020e..24dcd3ef4ec 100644 --- a/pkg/api/dashboard_snapshot_test.go +++ b/pkg/api/dashboard_snapshot_test.go @@ -15,6 +15,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/db/dbtest" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" "github.com/grafana/grafana/pkg/services/dashboards" @@ -40,7 +41,7 @@ func TestHTTPServer_DeleteDashboardSnapshot(t *testing.T) { hs.DashboardService = svc hs.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) - guardian.InitAccessControlGuardian(hs.Cfg, hs.AccessControl, hs.DashboardService) + guardian.InitAccessControlGuardian(hs.Cfg, hs.AccessControl, hs.DashboardService, hs.folderService, log.NewNopLogger()) }) } diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index 5d130a1ddd3..0efb1a167ee 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -151,7 +151,7 @@ func TestHTTPServer_GetDashboard_AccessControl(t *testing.T) { hs.starService = startest.NewStarServiceFake() hs.dashboardProvisioningService = mockDashboardProvisioningService{} - guardian.InitAccessControlGuardian(hs.Cfg, hs.AccessControl, hs.DashboardService) + guardian.InitAccessControlGuardian(hs.Cfg, hs.AccessControl, hs.DashboardService, hs.folderService, log.NewNopLogger()) }) } @@ -279,7 +279,7 @@ func TestHTTPServer_DeleteDashboardByUID_AccessControl(t *testing.T) { license.On("FeatureEnabled", publicdashboardModels.FeaturePublicDashboardsEmailSharing).Return(false) hs.PublicDashboardsApi = api.ProvideApi(pubDashService, nil, hs.AccessControl, featuremgmt.WithFeatures(), middleware, hs.Cfg, license) - guardian.InitAccessControlGuardian(hs.Cfg, hs.AccessControl, hs.DashboardService) + guardian.InitAccessControlGuardian(hs.Cfg, hs.AccessControl, hs.DashboardService, hs.folderService, log.NewNopLogger()) }) } deleteDashboard := func(server *webtest.Server, permissions []accesscontrol.Permission) (*http.Response, error) { @@ -330,7 +330,7 @@ func TestHTTPServer_GetDashboardVersions_AccessControl(t *testing.T) { ExpectedDashboardVersion: &dashver.DashboardVersionDTO{}, } - guardian.InitAccessControlGuardian(hs.Cfg, hs.AccessControl, hs.DashboardService) + guardian.InitAccessControlGuardian(hs.Cfg, hs.AccessControl, hs.DashboardService, hs.folderService, log.NewNopLogger()) }) } diff --git a/pkg/api/folder_bench_test.go b/pkg/api/folder_bench_test.go index 0ca06bdd658..8192ff6fef5 100644 --- a/pkg/api/folder_bench_test.go +++ b/pkg/api/folder_bench_test.go @@ -495,7 +495,7 @@ func setupServer(b testing.TB, sc benchScenario, features featuremgmt.FeatureTog } hs.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) - guardian.InitAccessControlGuardian(hs.Cfg, hs.AccessControl, hs.DashboardService) + guardian.InitAccessControlGuardian(hs.Cfg, hs.AccessControl, hs.DashboardService, hs.folderService, log.NewNopLogger()) m.Get("/api/folders", hs.GetFolders) m.Get("/api/search", hs.Search) diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index f68ed03495d..d5af358d86f 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -56,6 +56,7 @@ var ( provisionerPermissions = []accesscontrol.Permission{ {Action: dashboards.ActionFoldersCreate, Scope: dashboards.ScopeFoldersAll}, {Action: dashboards.ActionFoldersWrite, Scope: dashboards.ScopeFoldersAll}, + {Action: dashboards.ActionFoldersRead, Scope: dashboards.ScopeFoldersAll}, {Action: dashboards.ActionDashboardsCreate, Scope: dashboards.ScopeFoldersAll}, {Action: dashboards.ActionDashboardsWrite, Scope: dashboards.ScopeFoldersAll}, {Action: datasources.ActionRead, Scope: datasources.ScopeAll}, @@ -611,13 +612,28 @@ func getGuardianForSavePermissionCheck(ctx context.Context, d *dashboards.Dashbo if newDashboard { // if it's a new dashboard/folder check the parent folder permissions metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Dashboard).Inc() - // nolint:staticcheck - guard, err := guardian.New(ctx, d.FolderID, d.OrgID, user) + guard, err := guardian.NewByFolder(ctx, &folder.Folder{ + ID: d.FolderID, // nolint:staticcheck + OrgID: d.OrgID, + }, d.OrgID, user) if err != nil { return nil, err } return guard, nil } + + if d.IsFolder { + guard, err := guardian.NewByFolder(ctx, &folder.Folder{ + ID: d.ID, // nolint:staticcheck + UID: d.UID, + OrgID: d.OrgID, + }, d.OrgID, user) + if err != nil { + return nil, err + } + return guard, nil + } + guard, err := guardian.NewByDashboard(ctx, d, d.OrgID, user) if err != nil { return nil, err diff --git a/pkg/services/dashboards/service/dashboard_service_integration_test.go b/pkg/services/dashboards/service/dashboard_service_integration_test.go index 860e0be4cf6..95f840294c9 100644 --- a/pkg/services/dashboards/service/dashboard_service_integration_test.go +++ b/pkg/services/dashboards/service/dashboard_service_integration_test.go @@ -12,6 +12,7 @@ import ( "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/actest" @@ -896,7 +897,7 @@ func permissionScenario(t *testing.T, desc string, canSave bool, fn permissionSc ) dashboardService.RegisterDashboardPermissions(dashboardPermissions) require.NoError(t, err) - guardian.InitAccessControlGuardian(cfg, ac, dashboardService) + guardian.InitAccessControlGuardian(cfg, ac, dashboardService, folderService, log.NewNopLogger()) savedFolder := saveTestFolder(t, "Saved folder", testOrgID, sqlStore) savedDashInFolder := saveTestDashboard(t, "Saved dash in folder", testOrgID, savedFolder.UID, sqlStore) diff --git a/pkg/services/folder/folderimpl/folder.go b/pkg/services/folder/folderimpl/folder.go index 85664cda390..b9f8c31b9a9 100644 --- a/pkg/services/folder/folderimpl/folder.go +++ b/pkg/services/folder/folderimpl/folder.go @@ -392,7 +392,7 @@ func (s *Service) GetChildrenLegacy(ctx context.Context, q *folder.GetChildrenQu // we only need to check access to the folder // if the parent is accessible then the subfolders are accessible as well (due to inheritance) - g, err := guardian.NewByUID(ctx, q.UID, q.OrgID, q.SignedInUser) + g, err := guardian.NewByFolderUID(ctx, q.UID, q.OrgID, q.SignedInUser) if err != nil { return nil, err } @@ -941,7 +941,7 @@ func (s *Service) DeleteLegacy(ctx context.Context, cmd *folder.DeleteFolderComm return folder.ErrBadRequest.Errorf("invalid orgID") } - guard, err := guardian.NewByUID(ctx, cmd.UID, cmd.OrgID, cmd.SignedInUser) + guard, err := guardian.NewByFolderUID(ctx, cmd.UID, cmd.OrgID, cmd.SignedInUser) if err != nil { return err } @@ -1414,13 +1414,19 @@ func getGuardianForSavePermissionCheck(ctx context.Context, d *dashboards.Dashbo // if it's a new dashboard/folder check the parent folder permissions metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Folder).Inc() // nolint:staticcheck - guard, err := guardian.New(ctx, d.FolderID, d.OrgID, user) + guard, err := guardian.NewByFolder(ctx, &folder.Folder{ + ID: d.FolderID, // nolint:staticcheck + OrgID: d.OrgID, + }, d.OrgID, user) if err != nil { return nil, err } return guard, nil } - guard, err := guardian.NewByDashboard(ctx, d, d.OrgID, user) + guard, err := guardian.NewByFolder(ctx, &folder.Folder{ + UID: d.UID, + OrgID: d.OrgID, + }, d.OrgID, user) if err != nil { return nil, err } diff --git a/pkg/services/guardian/accesscontrol_guardian.go b/pkg/services/guardian/accesscontrol_guardian.go index de32d370ae2..c521fa9221e 100644 --- a/pkg/services/guardian/accesscontrol_guardian.go +++ b/pkg/services/guardian/accesscontrol_guardian.go @@ -18,6 +18,7 @@ var _ DashboardGuardian = new(accessControlDashboardGuardian) func NewAccessControlDashboardGuardian( ctx context.Context, cfg *setting.Cfg, dashboardId int64, user identity.Requester, ac accesscontrol.AccessControl, dashboardService dashboards.DashboardService, + foldersService folder.Service, logger log.Logger, ) (DashboardGuardian, error) { var dashboard *dashboards.Dashboard if dashboardId != 0 { @@ -37,6 +38,7 @@ func NewAccessControlDashboardGuardian( } if dashboard != nil && dashboard.IsFolder { + logger.Info("using dashboard guardian for folder", "folder", dashboard.UID) return &accessControlFolderGuardian{ accessControlBaseGuardian: accessControlBaseGuardian{ ctx: ctx, @@ -58,68 +60,22 @@ func NewAccessControlDashboardGuardian( user: user, ac: ac, dashboardService: dashboardService, - }, - dashboard: dashboard, - }, nil -} - -// NewAccessControlDashboardGuardianByDashboard creates a dashboard guardian by the provided dashboardUID. -func NewAccessControlDashboardGuardianByUID( - ctx context.Context, cfg *setting.Cfg, dashboardUID string, user identity.Requester, - ac accesscontrol.AccessControl, dashboardService dashboards.DashboardService, -) (DashboardGuardian, error) { - var dashboard *dashboards.Dashboard - if dashboardUID != "" { - q := &dashboards.GetDashboardQuery{ - UID: dashboardUID, - OrgID: user.GetOrgID(), - } - - qResult, err := dashboardService.GetDashboard(ctx, q) - if err != nil { - if errors.Is(err, dashboards.ErrDashboardNotFound) { - return nil, ErrGuardianDashboardNotFound.Errorf("failed to get dashboard by UID: %w", err) - } - return nil, ErrGuardianGetDashboardFailure.Errorf("failed to get dashboard by UID: %w", err) - } - dashboard = qResult - } - - if dashboard != nil && dashboard.IsFolder { - return &accessControlFolderGuardian{ - accessControlBaseGuardian: accessControlBaseGuardian{ - ctx: ctx, - cfg: cfg, - log: log.New("folder.permissions"), - user: user, - ac: ac, - dashboardService: dashboardService, - }, - folder: dashboards.FromDashboard(dashboard), - }, nil - } - - return &accessControlDashboardGuardian{ - accessControlBaseGuardian: accessControlBaseGuardian{ - cfg: cfg, - ctx: ctx, - log: log.New("dashboard.permissions"), - user: user, - ac: ac, - dashboardService: dashboardService, + folderService: foldersService, }, dashboard: dashboard, }, nil } // NewAccessControlDashboardGuardianByDashboard creates a dashboard guardian by the provided dashboard. -// This constructor should be preferred over the other two if the dashboard in available +// This constructor should be preferred over the other two if the dashboard is available // since it avoids querying the database for fetching the dashboard. func NewAccessControlDashboardGuardianByDashboard( ctx context.Context, cfg *setting.Cfg, dashboard *dashboards.Dashboard, user identity.Requester, - ac accesscontrol.AccessControl, dashboardService dashboards.DashboardService, + ac accesscontrol.AccessControl, dashboardService dashboards.DashboardService, folderService folder.Service, + logger log.Logger, ) (DashboardGuardian, error) { if dashboard != nil && dashboard.IsFolder { + logger.Info("using by dashboard guardian for folder", "folder", dashboard.UID) return &accessControlFolderGuardian{ accessControlBaseGuardian: accessControlBaseGuardian{ ctx: ctx, @@ -128,6 +84,7 @@ func NewAccessControlDashboardGuardianByDashboard( user: user, ac: ac, dashboardService: dashboardService, + folderService: folderService, }, folder: dashboards.FromDashboard(dashboard), }, nil @@ -141,16 +98,35 @@ func NewAccessControlDashboardGuardianByDashboard( user: user, ac: ac, dashboardService: dashboardService, + folderService: folderService, }, dashboard: dashboard, }, nil } -// NewAccessControlFolderGuardian creates a folder guardian by the provided folder. -func NewAccessControlFolderGuardian( - ctx context.Context, cfg *setting.Cfg, f *folder.Folder, user identity.Requester, - ac accesscontrol.AccessControl, dashboardService dashboards.DashboardService, +// NewAccessControlFolderGuardianByUID creates a folder guardian by the provided folderUID. +func NewAccessControlFolderGuardianByUID( + ctx context.Context, cfg *setting.Cfg, folderUID string, user identity.Requester, + ac accesscontrol.AccessControl, dashboardService dashboards.DashboardService, foldersService folder.Service, ) (DashboardGuardian, error) { + var f *folder.Folder + if folderUID != "" { + q := &folder.GetFolderQuery{ + UID: &folderUID, + OrgID: user.GetOrgID(), + SignedInUser: user, + } + + qResult, err := foldersService.Get(ctx, q) + if err != nil { + if errors.Is(err, dashboards.ErrFolderNotFound) { + return nil, ErrGuardianFolderNotFound.Errorf("failed to get folder by UID: %w", err) + } + return nil, ErrGuardianGetFolderFailure.Errorf("failed to get folder by UID: %w", err) + } + f = qResult + } + return &accessControlFolderGuardian{ accessControlBaseGuardian: accessControlBaseGuardian{ ctx: ctx, @@ -159,6 +135,44 @@ func NewAccessControlFolderGuardian( user: user, ac: ac, dashboardService: dashboardService, + folderService: foldersService, + }, + folder: f, + }, nil +} + +// NewAccessControlFolderGuardian creates a folder guardian by the provided folder. +func NewAccessControlFolderGuardian( + ctx context.Context, cfg *setting.Cfg, f *folder.Folder, user identity.Requester, + ac accesscontrol.AccessControl, orgID int64, dashboardService dashboards.DashboardService, + folderService folder.Service, +) (DashboardGuardian, error) { + if f.UID == "" { // nolint:staticcheck + query := &folder.GetFolderQuery{ + ID: &f.ID, // nolint:staticcheck + OrgID: orgID, + SignedInUser: user, + } + + folder, err := folderService.Get(ctx, query) + if err != nil { + if errors.Is(err, dashboards.ErrFolderNotFound) { + return nil, ErrGuardianFolderNotFound.Errorf("failed to get folder: %w", err) + } + return nil, ErrGuardianGetFolderFailure.Errorf("failed to get folder: %w", err) + } + f = folder + } + + return &accessControlFolderGuardian{ + accessControlBaseGuardian: accessControlBaseGuardian{ + ctx: ctx, + cfg: cfg, + log: log.New("folder.permissions"), + user: user, + ac: ac, + dashboardService: dashboardService, + folderService: folderService, }, folder: f, }, nil @@ -171,6 +185,7 @@ type accessControlBaseGuardian struct { user identity.Requester ac accesscontrol.AccessControl dashboardService dashboards.DashboardService + folderService folder.Service } type accessControlDashboardGuardian struct { @@ -353,24 +368,24 @@ func (a *accessControlFolderGuardian) evaluate(evaluator accesscontrol.Evaluator return ok, err } -func (a *accessControlDashboardGuardian) loadParentFolder(folderID int64) (*dashboards.Dashboard, error) { +func (a *accessControlDashboardGuardian) loadParentFolder(folderID int64) (*folder.Folder, error) { if folderID == 0 { - return &dashboards.Dashboard{UID: accesscontrol.GeneralFolderUID}, nil + return &folder.Folder{UID: accesscontrol.GeneralFolderUID, OrgID: a.user.GetOrgID()}, nil } - folderQuery := &dashboards.GetDashboardQuery{ID: folderID, OrgID: a.user.GetOrgID()} - folderQueryResult, err := a.dashboardService.GetDashboard(a.ctx, folderQuery) + folderQuery := &folder.GetFolderQuery{ID: &folderID, OrgID: a.user.GetOrgID(), SignedInUser: a.user} + folderQueryResult, err := a.folderService.Get(a.ctx, folderQuery) if err != nil { return nil, err } return folderQueryResult, nil } -func (a *accessControlFolderGuardian) loadParentFolder(folderID int64) (*dashboards.Dashboard, error) { +func (a *accessControlFolderGuardian) loadParentFolder(folderID int64) (*folder.Folder, error) { if folderID == 0 { - return &dashboards.Dashboard{UID: accesscontrol.GeneralFolderUID}, nil + return &folder.Folder{UID: accesscontrol.GeneralFolderUID, OrgID: a.user.GetOrgID()}, nil } - folderQuery := &dashboards.GetDashboardQuery{ID: folderID, OrgID: a.user.GetOrgID()} - folderQueryResult, err := a.dashboardService.GetDashboard(a.ctx, folderQuery) + folderQuery := &folder.GetFolderQuery{ID: &folderID, OrgID: a.user.GetOrgID(), SignedInUser: a.user} + folderQueryResult, err := a.folderService.Get(a.ctx, folderQuery) if err != nil { return nil, err } diff --git a/pkg/services/guardian/accesscontrol_guardian_test.go b/pkg/services/guardian/accesscontrol_guardian_test.go index 8d24b282395..59ac7d49828 100644 --- a/pkg/services/guardian/accesscontrol_guardian_test.go +++ b/pkg/services/guardian/accesscontrol_guardian_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" "github.com/grafana/grafana/pkg/services/dashboards" @@ -976,7 +977,7 @@ func setupAccessControlGuardianTest( userPermissions[orgID][p.Action] = append(userPermissions[orgID][p.Action], p.Scope) } - g, err := NewAccessControlDashboardGuardianByDashboard(context.Background(), cfg, d, &user.SignedInUser{OrgID: orgID, Permissions: userPermissions}, ac, fakeDashboardService) + g, err := NewAccessControlDashboardGuardianByDashboard(context.Background(), cfg, d, &user.SignedInUser{OrgID: orgID, Permissions: userPermissions}, ac, fakeDashboardService, folderSvc, log.NewNopLogger()) require.NoError(t, err) return g } diff --git a/pkg/services/guardian/guardian.go b/pkg/services/guardian/guardian.go index e094674d07e..7100b7ffacc 100644 --- a/pkg/services/guardian/guardian.go +++ b/pkg/services/guardian/guardian.go @@ -15,6 +15,7 @@ var ( ErrGuardianGetDashboardFailure = errutil.Internal("guardian.getDashboardFailure", errutil.WithPublicMessage("Failed to get dashboard")) ErrGuardianDashboardNotFound = errutil.NotFound("guardian.dashboardNotFound") ErrGuardianFolderNotFound = errutil.NotFound("guardian.folderNotFound") + ErrGuardianGetFolderFailure = errutil.Internal("guardian.getFolderFailure", errutil.WithPublicMessage("Failed to get folder")) ) // DashboardGuardian to be used for guard against operations without access on dashboard and acl @@ -33,18 +34,18 @@ var New = func(ctx context.Context, dashId int64, orgId int64, user identity.Req panic("no guardian factory implementation provided") } -// NewByUID factory for creating a new dashboard guardian instance -// When using access control this function is replaced on startup and the AccessControlDashboardGuardian is returned -var NewByUID = func(ctx context.Context, dashUID string, orgId int64, user identity.Requester) (DashboardGuardian, error) { - panic("no guardian factory implementation provided") -} - // NewByDashboard factory for creating a new dashboard guardian instance // When using access control this function is replaced on startup and the AccessControlDashboardGuardian is returned var NewByDashboard = func(ctx context.Context, dash *dashboards.Dashboard, orgId int64, user identity.Requester) (DashboardGuardian, error) { panic("no guardian factory implementation provided") } +// NewByFolderUID factory for creating a new folder guardian instance +// When using access control this function is replaced on startup and the AccessControlDashboardGuardian is returned +var NewByFolderUID = func(ctx context.Context, folderUID string, orgId int64, user identity.Requester) (DashboardGuardian, error) { + panic("no guardian factory implementation provided") +} + // NewByFolder factory for creating a new folder guardian instance // When using access control this function is replaced on startup and the AccessControlDashboardGuardian is returned var NewByFolder = func(ctx context.Context, f *folder.Folder, orgId int64, user identity.Requester) (DashboardGuardian, error) { @@ -107,14 +108,6 @@ func MockDashboardGuardian(mock *FakeDashboardGuardian) { mock.User = user return mock, nil } - - NewByUID = func(_ context.Context, dashUID string, orgId int64, user identity.Requester) (DashboardGuardian, error) { - mock.OrgID = orgId - mock.DashUID = dashUID - mock.User = user - return mock, nil - } - NewByDashboard = func(_ context.Context, dash *dashboards.Dashboard, orgId int64, user identity.Requester) (DashboardGuardian, error) { mock.OrgID = orgId mock.DashUID = dash.UID @@ -123,6 +116,13 @@ func MockDashboardGuardian(mock *FakeDashboardGuardian) { return mock, nil } + NewByFolderUID = func(_ context.Context, folderUID string, orgId int64, user identity.Requester) (DashboardGuardian, error) { + mock.OrgID = orgId + mock.DashUID = folderUID + mock.User = user + return mock, nil + } + NewByFolder = func(_ context.Context, f *folder.Folder, orgId int64, user identity.Requester) (DashboardGuardian, error) { mock.OrgID = orgId mock.DashUID = f.UID diff --git a/pkg/services/guardian/provider.go b/pkg/services/guardian/provider.go index ad07caf0695..7e3902f1e5a 100644 --- a/pkg/services/guardian/provider.go +++ b/pkg/services/guardian/provider.go @@ -4,6 +4,7 @@ import ( "context" "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/folder" @@ -16,28 +17,29 @@ type Provider struct{} func ProvideService( cfg *setting.Cfg, ac accesscontrol.AccessControl, dashboardService dashboards.DashboardService, teamService team.Service, + folderService folder.Service, ) *Provider { // TODO: Fix this hack, see https://github.com/grafana/grafana-enterprise/issues/2935 - InitAccessControlGuardian(cfg, ac, dashboardService) + InitAccessControlGuardian(cfg, ac, dashboardService, folderService, log.New("guardian")) return &Provider{} } func InitAccessControlGuardian( - cfg *setting.Cfg, ac accesscontrol.AccessControl, dashboardService dashboards.DashboardService, + cfg *setting.Cfg, ac accesscontrol.AccessControl, dashboardService dashboards.DashboardService, folderService folder.Service, logger log.Logger, ) { New = func(ctx context.Context, dashId int64, orgId int64, user identity.Requester) (DashboardGuardian, error) { - return NewAccessControlDashboardGuardian(ctx, cfg, dashId, user, ac, dashboardService) - } - - NewByUID = func(ctx context.Context, dashUID string, orgId int64, user identity.Requester) (DashboardGuardian, error) { - return NewAccessControlDashboardGuardianByUID(ctx, cfg, dashUID, user, ac, dashboardService) + return NewAccessControlDashboardGuardian(ctx, cfg, dashId, user, ac, dashboardService, folderService, logger) } NewByDashboard = func(ctx context.Context, dash *dashboards.Dashboard, orgId int64, user identity.Requester) (DashboardGuardian, error) { - return NewAccessControlDashboardGuardianByDashboard(ctx, cfg, dash, user, ac, dashboardService) + return NewAccessControlDashboardGuardianByDashboard(ctx, cfg, dash, user, ac, dashboardService, folderService, logger) + } + + NewByFolderUID = func(ctx context.Context, folderUID string, orgId int64, user identity.Requester) (DashboardGuardian, error) { + return NewAccessControlFolderGuardianByUID(ctx, cfg, folderUID, user, ac, dashboardService, folderService) } NewByFolder = func(ctx context.Context, f *folder.Folder, orgId int64, user identity.Requester) (DashboardGuardian, error) { - return NewAccessControlFolderGuardian(ctx, cfg, f, user, ac, dashboardService) + return NewAccessControlFolderGuardian(ctx, cfg, f, user, ac, orgId, dashboardService, folderService) } } diff --git a/pkg/services/libraryelements/guard.go b/pkg/services/libraryelements/guard.go index aa4f5d484fd..737d961f8c8 100644 --- a/pkg/services/libraryelements/guard.go +++ b/pkg/services/libraryelements/guard.go @@ -6,6 +6,7 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/identity" "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/guardian" "github.com/grafana/grafana/pkg/services/libraryelements/model" "github.com/grafana/grafana/pkg/services/org" @@ -41,7 +42,7 @@ func (l *LibraryElementService) requireEditPermissionsOnFolderUID(ctx context.Co return dashboards.ErrFolderAccessDenied } - g, err := guardian.NewByUID(ctx, folderUID, user.GetOrgID(), user) + g, err := guardian.NewByFolderUID(ctx, folderUID, user.GetOrgID(), user) if err != nil { return err } @@ -67,7 +68,10 @@ func (l *LibraryElementService) requireEditPermissionsOnFolder(ctx context.Conte return dashboards.ErrFolderAccessDenied } - g, err := guardian.New(ctx, folderID, user.GetOrgID(), user) + g, err := guardian.NewByFolder(ctx, &folder.Folder{ + ID: folderID, + OrgID: user.GetOrgID(), + }, user.GetOrgID(), user) if err != nil { return err } @@ -88,7 +92,10 @@ func (l *LibraryElementService) requireViewPermissionsOnFolder(ctx context.Conte return nil } - g, err := guardian.New(ctx, folderID, user.GetOrgID(), user) + g, err := guardian.NewByFolder(ctx, &folder.Folder{ + ID: folderID, + OrgID: user.GetOrgID(), + }, user.GetOrgID(), user) if err != nil { return err } diff --git a/pkg/services/libraryelements/libraryelements_get_all_test.go b/pkg/services/libraryelements/libraryelements_get_all_test.go index 29cc7819fca..5be5ff4dede 100644 --- a/pkg/services/libraryelements/libraryelements_get_all_test.go +++ b/pkg/services/libraryelements/libraryelements_get_all_test.go @@ -539,7 +539,7 @@ func TestGetAllLibraryElements(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and folderFilterUIDs is set to existing folders, it should succeed and the result should be correct", func(t *testing.T, sc scenarioContext) { - newFolder := createFolder(t, sc, "NewFolder") + newFolder := createFolder(t, sc, "NewFolder", nil) // nolint:staticcheck command := getCreatePanelCommand(newFolder.ID, newFolder.UID, "Text - Library Panel2") sc.reqContext.Req.Body = mockRequestBody(command) @@ -608,7 +608,7 @@ func TestGetAllLibraryElements(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and folderFilter is set to a nonexistent folders, it should succeed and the result should be correct", func(t *testing.T, sc scenarioContext) { - newFolder := createFolder(t, sc, "NewFolder") + newFolder := createFolder(t, sc, "NewFolder", nil) // nolint:staticcheck command := getCreatePanelCommand(newFolder.ID, sc.folder.UID, "Text - Library Panel2") sc.reqContext.Req.Body = mockRequestBody(command) diff --git a/pkg/services/libraryelements/libraryelements_patch_test.go b/pkg/services/libraryelements/libraryelements_patch_test.go index e59a19e29b6..8e7dce686ea 100644 --- a/pkg/services/libraryelements/libraryelements_patch_test.go +++ b/pkg/services/libraryelements/libraryelements_patch_test.go @@ -24,7 +24,7 @@ func TestPatchLibraryElement(t *testing.T) { scenarioWithPanel(t, "When an admin tries to patch a library panel that exists, it should succeed", func(t *testing.T, sc scenarioContext) { - newFolder := createFolder(t, sc, "NewFolder") + newFolder := createFolder(t, sc, "NewFolder", nil) cmd := model.PatchLibraryElementCommand{ FolderID: newFolder.ID, // nolint:staticcheck FolderUID: &newFolder.UID, @@ -91,7 +91,7 @@ func TestPatchLibraryElement(t *testing.T) { scenarioWithPanel(t, "When an admin tries to patch a library panel with folder only, it should change folder successfully and return correct result", func(t *testing.T, sc scenarioContext) { - newFolder := createFolder(t, sc, "NewFolder") + newFolder := createFolder(t, sc, "NewFolder", nil) cmd := model.PatchLibraryElementCommand{ FolderID: newFolder.ID, // nolint:staticcheck FolderUID: &newFolder.UID, @@ -335,7 +335,7 @@ func TestPatchLibraryElement(t *testing.T) { scenarioWithPanel(t, "When an admin tries to patch a library panel with a folder where a library panel with the same name already exists, it should fail", func(t *testing.T, sc scenarioContext) { - newFolder := createFolder(t, sc, "NewFolder") + newFolder := createFolder(t, sc, "NewFolder", nil) // nolint:staticcheck command := getCreatePanelCommand(newFolder.ID, newFolder.UID, "Text - Library Panel") sc.ctx.Req.Body = mockRequestBody(command) diff --git a/pkg/services/libraryelements/libraryelements_permissions_test.go b/pkg/services/libraryelements/libraryelements_permissions_test.go index a07092269ad..f9af4ed15ad 100644 --- a/pkg/services/libraryelements/libraryelements_permissions_test.go +++ b/pkg/services/libraryelements/libraryelements_permissions_test.go @@ -38,7 +38,7 @@ func TestLibraryElementPermissionsGeneralFolder(t *testing.T) { testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it to the General folder, it should return correct status", testCase.role), func(t *testing.T, sc scenarioContext) { - folder := createFolder(t, sc, "Folder") + folder := createFolder(t, sc, "Folder", nil) // nolint:staticcheck command := getCreatePanelCommand(folder.ID, folder.UID, "Library Panel Name") sc.reqContext.Req.Body = mockRequestBody(command) @@ -56,7 +56,7 @@ func TestLibraryElementPermissionsGeneralFolder(t *testing.T) { testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it from the General folder, it should return correct status", testCase.role), func(t *testing.T, sc scenarioContext) { - folder := createFolder(t, sc, "Folder") + folder := createFolder(t, sc, "Folder", nil) command := getCreatePanelCommand(0, "", "Library Panel Name") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) @@ -178,7 +178,7 @@ func TestLibraryElementCreatePermissions(t *testing.T) { for _, testCase := range accessCases { testScenario(t, testCase.desc, func(t *testing.T, sc scenarioContext) { - folder := createFolder(t, sc, "Folder") + folder := createFolder(t, sc, "Folder", nil) sc.reqContext.SignedInUser.Permissions = map[int64]map[string][]string{ 1: testCase.permissions, } @@ -235,14 +235,14 @@ func TestLibraryElementPatchPermissions(t *testing.T) { for _, testCase := range accessCases { testScenario(t, testCase.desc, func(t *testing.T, sc scenarioContext) { - fromFolder := createFolder(t, sc, "FromFolder") + fromFolder := createFolder(t, sc, "FromFolder", nil) // nolint:staticcheck command := getCreatePanelCommand(fromFolder.ID, fromFolder.UID, "Library Panel Name") sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) result := validateAndUnMarshalResponse(t, resp) - toFolder := createFolder(t, sc, "ToFolder") + toFolder := createFolder(t, sc, "ToFolder", nil) sc.reqContext.SignedInUser.Permissions = map[int64]map[string][]string{ 1: testCase.permissions, @@ -268,6 +268,7 @@ func TestLibraryElementDeletePermissions(t *testing.T) { desc: "can delete library elements when granted write access to the correct folder", permissions: map[string][]string{ dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersProvider.GetResourceScopeUID("Folder")}, + dashboards.ActionFoldersRead: {dashboards.ScopeFoldersProvider.GetResourceScopeUID("Folder")}, }, status: http.StatusOK, }, @@ -275,6 +276,7 @@ func TestLibraryElementDeletePermissions(t *testing.T) { desc: "can delete library elements when granted write access to all folders", permissions: map[string][]string{ dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersProvider.GetResourceAllScope()}, + dashboards.ActionFoldersRead: {dashboards.ScopeFoldersProvider.GetResourceAllScope()}, }, status: http.StatusOK, }, @@ -282,6 +284,7 @@ func TestLibraryElementDeletePermissions(t *testing.T) { desc: "can't delete library elements when granted write access to the wrong folder", permissions: map[string][]string{ dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersProvider.GetResourceScopeUID("Other_folder")}, + dashboards.ActionFoldersRead: {dashboards.ScopeFoldersProvider.GetResourceScopeUID("Other_folder")}, }, status: http.StatusForbidden, }, @@ -297,7 +300,7 @@ func TestLibraryElementDeletePermissions(t *testing.T) { for _, testCase := range accessCases { testScenario(t, testCase.desc, func(t *testing.T, sc scenarioContext) { - folder := createFolder(t, sc, "Folder") + folder := createFolder(t, sc, "Folder", sc.service.folderService) // nolint:staticcheck command := getCreatePanelCommand(folder.ID, folder.UID, "Library Panel Name") sc.reqContext.Req.Body = mockRequestBody(command) @@ -327,7 +330,7 @@ func TestLibraryElementsWithMissingFolders(t *testing.T) { testScenario(t, "When a user tries to patch a library panel by moving it to a folder that doesn't exist, it should fail", func(t *testing.T, sc scenarioContext) { - folder := createFolder(t, sc, "Folder") + folder := createFolder(t, sc, "Folder", nil) // nolint:staticcheck command := getCreatePanelCommand(folder.ID, folder.UID, "Library Panel Name") sc.reqContext.Req.Body = mockRequestBody(command) @@ -368,7 +371,7 @@ func TestLibraryElementsGetPermissions(t *testing.T) { for _, testCase := range getCases { testScenario(t, testCase.desc, func(t *testing.T, sc scenarioContext) { - folder := createFolder(t, sc, "Folder") + folder := createFolder(t, sc, "Folder", nil) // nolint:staticcheck cmd := getCreatePanelCommand(folder.ID, folder.UID, "Library Panel") sc.reqContext.Req.Body = mockRequestBody(cmd) @@ -419,7 +422,7 @@ func TestLibraryElementsGetAllPermissions(t *testing.T) { testScenario(t, testCase.desc, func(t *testing.T, sc scenarioContext) { for i := 1; i <= 2; i++ { - folder := createFolder(t, sc, fmt.Sprintf("Folder%d", i)) + folder := createFolder(t, sc, fmt.Sprintf("Folder%d", i), nil) // nolint:staticcheck cmd := getCreatePanelCommand(folder.ID, folder.UID, fmt.Sprintf("Library Panel %d", i)) sc.reqContext.Req.Body = mockRequestBody(cmd) diff --git a/pkg/services/libraryelements/libraryelements_test.go b/pkg/services/libraryelements/libraryelements_test.go index 6dcdacb026c..9debfc717d5 100644 --- a/pkg/services/libraryelements/libraryelements_test.go +++ b/pkg/services/libraryelements/libraryelements_test.go @@ -32,7 +32,6 @@ import ( "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/folder/foldertest" "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/libraryelements/model" "github.com/grafana/grafana/pkg/services/org" @@ -99,7 +98,7 @@ func TestDeleteLibraryPanelsInFolder(t *testing.T) { scenarioWithPanel(t, "When an admin tries to delete a folder uid that doesn't exist, it should fail", func(t *testing.T, sc scenarioContext) { err := sc.service.DeleteLibraryElementsInFolder(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, sc.folder.UID+"xxxx") - require.EqualError(t, err, dashboards.ErrFolderNotFound.Error()) + require.EqualError(t, err, guardian.ErrGuardianFolderNotFound.Errorf("failed to get folder by UID: %w", dashboards.ErrFolderNotFound).Error()) }) scenarioWithPanel(t, "When an admin tries to delete a folder that contains disconnected elements, it should delete all disconnected elements too", @@ -300,17 +299,19 @@ func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash require.NoError(t, err) ac := actest.FakeAccessControl{ExpectedEvaluate: true} folderPermissions := acmock.NewMockedPermissionsService() + folderPermissions.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil) dashboardPermissions := acmock.NewMockedPermissionsService() dashboardPermissions.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil) folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) - var expectedFolder *folder.Folder - if dash.FolderUID != "" || dash.FolderID != 0 { // nolint:staticcheck - expectedFolder = &folder.Folder{ID: folderID, UID: folderUID} - } + fStore := folderimpl.ProvideStore(sqlStore) + folderSvc := folderimpl.ProvideService(fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, + folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), cfg, nil, tracing.InitializeTracerForTest()) + _, err = folderSvc.Create(context.Background(), &folder.CreateFolderCommand{UID: folderUID, SignedInUser: &user, Title: folderUID + "-title"}) + require.NoError(t, err) service, err := dashboardservice.ProvideDashboardServiceImpl( cfg, dashboardStore, folderStore, features, folderPermissions, ac, - &foldertest.FakeService{ExpectedFolder: expectedFolder}, + folderSvc, folder.NewFakeStore(), nil, nil, @@ -327,22 +328,24 @@ func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash return dashboard } -func createFolder(t *testing.T, sc scenarioContext, title string) *folder.Folder { +func createFolder(t *testing.T, sc scenarioContext, title string, folderSvc folder.Service) *folder.Folder { t.Helper() - features := featuremgmt.WithFeatures() - cfg := setting.NewCfg() - ac := actest.FakeAccessControl{ExpectedEvaluate: true} - dashboardStore, err := database.ProvideDashboardStore(sc.sqlStore, cfg, features, tagimpl.ProvideService(sc.sqlStore)) - require.NoError(t, err) + if folderSvc == nil { + features := featuremgmt.WithFeatures() + cfg := setting.NewCfg() + ac := actest.FakeAccessControl{ExpectedEvaluate: true} + dashboardStore, err := database.ProvideDashboardStore(sc.sqlStore, cfg, features, tagimpl.ProvideService(sc.sqlStore)) + require.NoError(t, err) - folderStore := folderimpl.ProvideDashboardFolderStore(sc.sqlStore) - store := folderimpl.ProvideStore(sc.sqlStore) - s := folderimpl.ProvideService(store, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, sc.sqlStore, - features, supportbundlestest.NewFakeBundleService(), cfg, nil, tracing.InitializeTracerForTest()) - t.Logf("Creating folder with title and UID %q", title) + folderStore := folderimpl.ProvideDashboardFolderStore(sc.sqlStore) + store := folderimpl.ProvideStore(sc.sqlStore) + folderSvc = folderimpl.ProvideService(store, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, sc.sqlStore, + features, supportbundlestest.NewFakeBundleService(), cfg, nil, tracing.InitializeTracerForTest()) + t.Logf("Creating folder with title and UID %q", title) + } ctx := identity.WithRequester(context.Background(), &sc.user) - folder, err := s.Create(ctx, &folder.CreateFolderCommand{ + folder, err := folderSvc.Create(ctx, &folder.CreateFolderCommand{ OrgID: sc.user.OrgID, Title: title, UID: title, SignedInUser: &sc.user, }) require.NoError(t, err) @@ -399,15 +402,18 @@ func scenarioWithPanel(t *testing.T, desc string, fn func(t *testing.T, sc scena folderPermissions := acmock.NewMockedPermissionsService() dashboardPermissions := acmock.NewMockedPermissionsService() folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) + fStore := folderimpl.ProvideStore(sqlStore) + folderSvc := folderimpl.ProvideService(fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, + folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), cfg, nil, tracing.InitializeTracerForTest()) dashboardService, svcErr := dashboardservice.ProvideDashboardServiceImpl( cfg, dashboardStore, folderStore, features, folderPermissions, ac, - foldertest.NewFakeService(), folder.NewFakeStore(), + folderSvc, fStore, nil, nil, nil, nil, quotaService, nil, ) require.NoError(t, svcErr) dashboardService.RegisterDashboardPermissions(dashboardPermissions) - guardian.InitAccessControlGuardian(cfg, ac, dashboardService) + guardian.InitAccessControlGuardian(cfg, ac, dashboardService, folderSvc, log.NewNopLogger()) testScenario(t, desc, func(t *testing.T, sc scenarioContext) { // nolint:staticcheck @@ -462,23 +468,23 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo folderPermissions.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil) dashboardPermissions := acmock.NewMockedPermissionsService() folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) + fStore := folderimpl.ProvideStore(sqlStore) + folderSvc := folderimpl.ProvideService(fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, + folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), cfg, nil, tracing.InitializeTracerForTest()) dashService, dashSvcErr := dashboardservice.ProvideDashboardServiceImpl( cfg, dashboardStore, folderStore, features, folderPermissions, ac, - foldertest.NewFakeService(), folder.NewFakeStore(), + folderSvc, fStore, nil, nil, nil, nil, quotaService, nil, ) require.NoError(t, dashSvcErr) dashService.RegisterDashboardPermissions(dashboardPermissions) - guardian.InitAccessControlGuardian(cfg, ac, dashService) - fStore := folderimpl.ProvideStore(sqlStore) - folderSrv := folderimpl.ProvideService(fStore, ac, bus.ProvideBus(tracer), dashboardStore, folderStore, sqlStore, - features, supportbundlestest.NewFakeBundleService(), cfg, nil, tracing.InitializeTracerForTest()) + guardian.InitAccessControlGuardian(cfg, ac, dashService, folderSvc, log.NewNopLogger()) service := LibraryElementService{ Cfg: cfg, features: featuremgmt.WithFeatures(), SQLStore: sqlStore, - folderService: folderSrv, + folderService: folderSvc, } // deliberate difference between signed in user and user in db to make it crystal clear @@ -510,7 +516,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo }, } - sc.folder = createFolder(t, sc, "ScenarioFolder") + sc.folder = createFolder(t, sc, "ScenarioFolder", folderSvc) fn(t, sc) }) diff --git a/pkg/services/librarypanels/librarypanels_test.go b/pkg/services/librarypanels/librarypanels_test.go index 80baeead8d2..9c030cc9061 100644 --- a/pkg/services/librarypanels/librarypanels_test.go +++ b/pkg/services/librarypanels/librarypanels_test.go @@ -15,6 +15,7 @@ import ( "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/slugify" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/kinds/librarypanel" @@ -823,18 +824,19 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo ac := actest.FakeAccessControl{ExpectedEvaluate: true} dashStore := &dashboards.FakeDashboardStore{} - dashStore.On("GetDashboard", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{ID: 1}, nil) folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) dashPermissionService := acmock.NewMockedPermissionsService() + folderSvc := foldertest.NewFakeService() + folderSvc.ExpectedFolder = &folder.Folder{ID: 1} dashService, err := dashboardservice.ProvideDashboardServiceImpl( cfg, dashStore, folderStore, features, acmock.NewMockedPermissionsService(), ac, - foldertest.NewFakeService(), folder.NewFakeStore(), + folderSvc, folder.NewFakeStore(), nil, nil, nil, nil, quotaService, nil, ) require.NoError(t, err) dashService.RegisterDashboardPermissions(dashPermissionService) - guardian.InitAccessControlGuardian(cfg, ac, dashService) + guardian.InitAccessControlGuardian(cfg, ac, dashService, folderSvc, log.NewNopLogger()) dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, features, tagimpl.ProvideService(sqlStore)) require.NoError(t, err)