mirror of https://github.com/grafana/grafana.git
				
				
				
			Zanzana: Evaluate permissions alongside with RBAC engine (#90064)
* Zanzana: Evaluate permissions if feature flag enabled * Fix tests * adjust logs * fix spelling * remove unused * only evaluate implemented resources * refactor
This commit is contained in:
		
							parent
							
								
									48e6e9a36c
								
							
						
					
					
						commit
						87d86e81ce
					
				|  | @ -15,6 +15,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/annotations" | ||||
| 	"github.com/grafana/grafana/pkg/services/annotations/annotationstest" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	"github.com/grafana/grafana/pkg/services/dashboards" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
| 	"github.com/grafana/grafana/pkg/services/folder" | ||||
|  | @ -401,7 +402,7 @@ func TestAPI_Annotations(t *testing.T) { | |||
| 				folderDB.On("GetFolderByID", mock.Anything, mock.Anything, mock.Anything).Return(&folder.Folder{UID: folderUID, ID: 1}, nil) | ||||
| 				hs.DashboardService = dashService | ||||
| 				hs.folderService = folderService | ||||
| 				hs.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) | ||||
| 				hs.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()) | ||||
| 				hs.AccessControl.RegisterScopeAttributeResolver(AnnotationTypeScopeResolver(hs.annotationsRepo, hs.Features, dashService, folderService)) | ||||
| 				hs.AccessControl.RegisterScopeAttributeResolver(dashboards.NewDashboardIDScopeResolver(folderDB, dashService, folderService)) | ||||
| 			}) | ||||
|  |  | |||
|  | @ -26,6 +26,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/services/auth/authtest" | ||||
| 	"github.com/grafana/grafana/pkg/services/authn" | ||||
| 	"github.com/grafana/grafana/pkg/services/authn/authntest" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	"github.com/grafana/grafana/pkg/services/contexthandler" | ||||
| 	contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" | ||||
| 	dashver "github.com/grafana/grafana/pkg/services/dashboardversion" | ||||
|  | @ -269,7 +270,7 @@ func setupSimpleHTTPServer(features featuremgmt.FeatureToggles) *HTTPServer { | |||
| 		Cfg:             cfg, | ||||
| 		Features:        features, | ||||
| 		License:         &licensing.OSSLicensingService{}, | ||||
| 		AccessControl:   acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), | ||||
| 		AccessControl:   acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()), | ||||
| 		annotationsRepo: annotationstest.NewFakeAnnotationsRepo(), | ||||
| 		authInfoService: &authinfotest.FakeService{ | ||||
| 			ExpectedLabels: map[int64]string{int64(1): login.GetAuthProviderLabel(login.LDAPAuthModule)}, | ||||
|  | @ -312,7 +313,7 @@ func SetupAPITestServer(t *testing.T, opts ...APITestServerOption) *webtest.Serv | |||
| 	} | ||||
| 
 | ||||
| 	if hs.AccessControl == nil { | ||||
| 		hs.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) | ||||
| 		hs.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()) | ||||
| 	} | ||||
| 
 | ||||
| 	hs.registerRoutes() | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/infra/db/dbtest" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	"github.com/grafana/grafana/pkg/services/dashboards" | ||||
| 	"github.com/grafana/grafana/pkg/services/dashboardsnapshots" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
|  | @ -39,7 +40,7 @@ func TestHTTPServer_DeleteDashboardSnapshot(t *testing.T) { | |||
| 
 | ||||
| 			hs.DashboardService = svc | ||||
| 
 | ||||
| 			hs.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) | ||||
| 			hs.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()) | ||||
| 			guardian.InitAccessControlGuardian(hs.Cfg, hs.AccessControl, hs.DashboardService) | ||||
| 		}) | ||||
| 	} | ||||
|  |  | |||
|  | @ -30,6 +30,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/actest" | ||||
| 	accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" | ||||
| 	"github.com/grafana/grafana/pkg/services/annotations/annotationstest" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" | ||||
| 	"github.com/grafana/grafana/pkg/services/dashboards" | ||||
| 	"github.com/grafana/grafana/pkg/services/dashboards/database" | ||||
|  | @ -130,7 +131,7 @@ func newTestLive(t *testing.T, store db.DB) *live.GrafanaLive { | |||
| 		nil, | ||||
| 		&usagestats.UsageStatsMock{T: t}, | ||||
| 		nil, | ||||
| 		features, acimpl.ProvideAccessControl(features), &dashboards.FakeDashboardService{}, annotationstest.NewFakeAnnotationsRepo(), nil) | ||||
| 		features, acimpl.ProvideAccessControl(features, zanzana.NewNoopClient()), &dashboards.FakeDashboardService{}, annotationstest.NewFakeAnnotationsRepo(), nil) | ||||
| 	require.NoError(t, err) | ||||
| 	return gLive | ||||
| } | ||||
|  | @ -147,7 +148,7 @@ func TestHTTPServer_GetDashboard_AccessControl(t *testing.T) { | |||
| 			hs.DashboardService = dashSvc | ||||
| 
 | ||||
| 			hs.Cfg = setting.NewCfg() | ||||
| 			hs.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) | ||||
| 			hs.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()) | ||||
| 			hs.starService = startest.NewStarServiceFake() | ||||
| 			hs.dashboardProvisioningService = mockDashboardProvisioningService{} | ||||
| 
 | ||||
|  | @ -266,7 +267,7 @@ func TestHTTPServer_DeleteDashboardByUID_AccessControl(t *testing.T) { | |||
| 			hs.DashboardService = dashSvc | ||||
| 
 | ||||
| 			hs.Cfg = setting.NewCfg() | ||||
| 			hs.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) | ||||
| 			hs.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()) | ||||
| 			hs.starService = startest.NewStarServiceFake() | ||||
| 
 | ||||
| 			hs.LibraryPanelService = &mockLibraryPanelService{} | ||||
|  | @ -322,7 +323,7 @@ func TestHTTPServer_GetDashboardVersions_AccessControl(t *testing.T) { | |||
| 			hs.DashboardService = dashSvc | ||||
| 
 | ||||
| 			hs.Cfg = setting.NewCfg() | ||||
| 			hs.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) | ||||
| 			hs.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()) | ||||
| 			hs.starService = startest.NewStarServiceFake() | ||||
| 
 | ||||
| 			hs.dashboardVersionService = &dashvertest.FakeDashboardVersionService{ | ||||
|  |  | |||
|  | @ -19,6 +19,7 @@ import ( | |||
| 	ac "github.com/grafana/grafana/pkg/services/accesscontrol" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/actest" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" | ||||
| 	"github.com/grafana/grafana/pkg/services/datasources" | ||||
| 	"github.com/grafana/grafana/pkg/services/datasources/guardian" | ||||
|  | @ -115,7 +116,7 @@ func TestAddDataSource_URLWithoutProtocol(t *testing.T) { | |||
| 			expectedDatasource: &datasources.DataSource{}, | ||||
| 		}, | ||||
| 		Cfg:                  setting.NewCfg(), | ||||
| 		AccessControl:        acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), | ||||
| 		AccessControl:        acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()), | ||||
| 		accesscontrolService: actest.FakeService{}, | ||||
| 	} | ||||
| 
 | ||||
|  | @ -332,7 +333,7 @@ func TestUpdateDataSource_URLWithoutProtocol(t *testing.T) { | |||
| 			expectedDatasource: &datasources.DataSource{}, | ||||
| 		}, | ||||
| 		Cfg:                  setting.NewCfg(), | ||||
| 		AccessControl:        acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), | ||||
| 		AccessControl:        acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()), | ||||
| 		accesscontrolService: actest.FakeService{}, | ||||
| 	} | ||||
| 
 | ||||
|  | @ -365,7 +366,7 @@ func TestUpdateDataSourceByID_DataSourceNameExists(t *testing.T) { | |||
| 			}, | ||||
| 		}, | ||||
| 		Cfg:                  setting.NewCfg(), | ||||
| 		AccessControl:        acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), | ||||
| 		AccessControl:        acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()), | ||||
| 		accesscontrolService: actest.FakeService{}, | ||||
| 		Live:                 newTestLive(t, nil), | ||||
| 	} | ||||
|  |  | |||
|  | @ -456,7 +456,7 @@ func setupServer(b testing.TB, sc benchScenario, features featuremgmt.FeatureTog | |||
| 
 | ||||
| 	folderStore := folderimpl.ProvideDashboardFolderStore(sc.db) | ||||
| 
 | ||||
| 	ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) | ||||
| 	ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()) | ||||
| 	folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, sc.db, features, supportbundlestest.NewFakeBundleService(), nil) | ||||
| 
 | ||||
| 	cfg := setting.NewCfg() | ||||
|  | @ -494,7 +494,7 @@ func setupServer(b testing.TB, sc benchScenario, features featuremgmt.FeatureTog | |||
| 		DashboardService: dashboardSvc, | ||||
| 	} | ||||
| 
 | ||||
| 	hs.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) | ||||
| 	hs.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()) | ||||
| 	guardian.InitAccessControlGuardian(hs.Cfg, hs.AccessControl, hs.DashboardService) | ||||
| 
 | ||||
| 	m.Get("/api/folders", hs.GetFolders) | ||||
|  |  | |||
|  | @ -32,6 +32,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/actest" | ||||
| 	"github.com/grafana/grafana/pkg/services/authn" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" | ||||
| 	"github.com/grafana/grafana/pkg/services/datasources" | ||||
| 	datasourceservice "github.com/grafana/grafana/pkg/services/datasources/service" | ||||
|  | @ -844,7 +845,7 @@ func getDatasourceProxiedRequest(t *testing.T, ctx *contextmodel.ReqContext, cfg | |||
| 	secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger")) | ||||
| 	features := featuremgmt.WithFeatures() | ||||
| 	quotaService := quotatest.New(false, nil) | ||||
| 	dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, features, acimpl.ProvideAccessControl(features), | ||||
| 	dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, features, acimpl.ProvideAccessControl(features, zanzana.NewNoopClient()), | ||||
| 		&actest.FakePermissionsService{}, quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, | ||||
| 		plugincontext.ProvideBaseService(cfg, pluginconfig.NewFakePluginRequestConfigProvider())) | ||||
| 	require.NoError(t, err) | ||||
|  | @ -966,7 +967,7 @@ func runDatasourceAuthTest(t *testing.T, secretsService secrets.Service, secrets | |||
| 	var routes []*plugins.Route | ||||
| 	features := featuremgmt.WithFeatures() | ||||
| 	quotaService := quotatest.New(false, nil) | ||||
| 	dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, features, acimpl.ProvideAccessControl(features), | ||||
| 	dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, features, acimpl.ProvideAccessControl(features, zanzana.NewNoopClient()), | ||||
| 		&actest.FakePermissionsService{}, quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, | ||||
| 		plugincontext.ProvideBaseService(cfg, pluginconfig.NewFakePluginRequestConfigProvider())) | ||||
| 	require.NoError(t, err) | ||||
|  | @ -1022,7 +1023,7 @@ func setupDSProxyTest(t *testing.T, ctx *contextmodel.ReqContext, ds *datasource | |||
| 	secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore()) | ||||
| 	secretsStore := secretskvs.NewSQLSecretsKVStore(dbtest.NewFakeDB(), secretsService, log.NewNopLogger()) | ||||
| 	features := featuremgmt.WithFeatures() | ||||
| 	dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, features, acimpl.ProvideAccessControl(features), | ||||
| 	dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, features, acimpl.ProvideAccessControl(features, zanzana.NewNoopClient()), | ||||
| 		&actest.FakePermissionsService{}, quotatest.New(false, nil), &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, | ||||
| 		plugincontext.ProvideBaseService(cfg, pluginconfig.NewFakePluginRequestConfigProvider())) | ||||
| 	require.NoError(t, err) | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/plugins" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/authn" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
| 	"github.com/grafana/grafana/pkg/services/org" | ||||
|  | @ -265,7 +266,7 @@ func TestPluginProxy(t *testing.T) { | |||
| 			SecureJSONData: map[string][]byte{}, | ||||
| 		} | ||||
| 		cfg := &setting.Cfg{} | ||||
| 		proxy, err := NewPluginProxy(ps, routes, ctx, "", cfg, secretsService, tracing.InitializeTracerForTest(), &http.Transport{}, acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), featuremgmt.WithFeatures()) | ||||
| 		proxy, err := NewPluginProxy(ps, routes, ctx, "", cfg, secretsService, tracing.InitializeTracerForTest(), &http.Transport{}, acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()), featuremgmt.WithFeatures()) | ||||
| 		require.NoError(t, err) | ||||
| 		proxy.HandleRequest() | ||||
| 
 | ||||
|  | @ -421,7 +422,7 @@ func TestPluginProxyRoutes(t *testing.T) { | |||
| 				SecureJSONData: map[string][]byte{}, | ||||
| 			} | ||||
| 			cfg := &setting.Cfg{} | ||||
| 			proxy, err := NewPluginProxy(ps, testRoutes, ctx, tc.proxyPath, cfg, secretsService, tracing.InitializeTracerForTest(), &http.Transport{}, acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), featuremgmt.WithFeatures(tc.withFeatures...)) | ||||
| 			proxy, err := NewPluginProxy(ps, testRoutes, ctx, tc.proxyPath, cfg, secretsService, tracing.InitializeTracerForTest(), &http.Transport{}, acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()), featuremgmt.WithFeatures(tc.withFeatures...)) | ||||
| 			require.NoError(t, err) | ||||
| 			proxy.HandleRequest() | ||||
| 
 | ||||
|  | @ -536,7 +537,7 @@ func TestPluginProxyRoutesAccessControl(t *testing.T) { | |||
| 				SecureJSONData: map[string][]byte{}, | ||||
| 			} | ||||
| 			cfg := &setting.Cfg{} | ||||
| 			proxy, err := NewPluginProxy(ps, testRoutes, ctx, tc.proxyPath, cfg, secretsService, tracing.InitializeTracerForTest(), &http.Transport{}, acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), featuremgmt.WithFeatures(featuremgmt.FlagAccessControlOnCall)) | ||||
| 			proxy, err := NewPluginProxy(ps, testRoutes, ctx, tc.proxyPath, cfg, secretsService, tracing.InitializeTracerForTest(), &http.Transport{}, acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()), featuremgmt.WithFeatures(featuremgmt.FlagAccessControlOnCall)) | ||||
| 			require.NoError(t, err) | ||||
| 			proxy.HandleRequest() | ||||
| 
 | ||||
|  | @ -567,7 +568,7 @@ func getPluginProxiedRequest(t *testing.T, ps *pluginsettings.DTO, secretsServic | |||
| 			ReqRole: org.RoleEditor, | ||||
| 		} | ||||
| 	} | ||||
| 	proxy, err := NewPluginProxy(ps, []*plugins.Route{}, ctx, "", cfg, secretsService, tracing.InitializeTracerForTest(), &http.Transport{}, acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), featuremgmt.WithFeatures()) | ||||
| 	proxy, err := NewPluginProxy(ps, []*plugins.Route{}, ctx, "", cfg, secretsService, tracing.InitializeTracerForTest(), &http.Transport{}, acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()), featuremgmt.WithFeatures()) | ||||
| 	require.NoError(t, err) | ||||
| 
 | ||||
| 	req, err := http.NewRequest(http.MethodGet, "/api/plugin-proxy/grafana-simple-app/api/v4/alerts", nil) | ||||
|  |  | |||
|  | @ -34,6 +34,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/actest" | ||||
| 	"github.com/grafana/grafana/pkg/services/authn" | ||||
| 	"github.com/grafana/grafana/pkg/services/authn/authntest" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
| 	"github.com/grafana/grafana/pkg/services/org" | ||||
|  | @ -746,7 +747,7 @@ func TestHTTPServer_hasPluginRequestedPermissions(t *testing.T) { | |||
| 			} | ||||
| 			hs.log = logger | ||||
| 			hs.accesscontrolService = actest.FakeService{} | ||||
| 			hs.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) | ||||
| 			hs.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()) | ||||
| 
 | ||||
| 			expectedIdentity := &authn.Identity{ | ||||
| 				OrgID:       tt.orgID, | ||||
|  |  | |||
|  | @ -28,6 +28,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" | ||||
| 	"github.com/grafana/grafana/pkg/services/auth/idtest" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
| 	"github.com/grafana/grafana/pkg/services/login" | ||||
|  | @ -60,7 +61,7 @@ func TestUserAPIEndpoint_userLoggedIn(t *testing.T) { | |||
| 	hs := &HTTPServer{ | ||||
| 		Cfg:           settings, | ||||
| 		SQLStore:      sqlStore, | ||||
| 		AccessControl: acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), | ||||
| 		AccessControl: acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()), | ||||
| 	} | ||||
| 
 | ||||
| 	mockResult := user.SearchUserQueryResult{ | ||||
|  |  | |||
|  | @ -23,6 +23,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/infra/tracing" | ||||
| 	"github.com/grafana/grafana/pkg/infra/usagestats" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
| 	"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" | ||||
| 	"github.com/grafana/grafana/pkg/setting" | ||||
|  | @ -247,7 +248,7 @@ func createService(t *testing.T, sqlStore db.DB, withDB bool) *UsageStats { | |||
| 		kvstore.ProvideService(sqlStore), | ||||
| 		routing.NewRouteRegister(), | ||||
| 		tracing.InitializeTracerForTest(), | ||||
| 		acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), | ||||
| 		acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()), | ||||
| 		supportbundlestest.NewFakeBundleService(), | ||||
| 	) | ||||
| 
 | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/login/social" | ||||
| 	"github.com/grafana/grafana/pkg/login/social/connectors" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
| 	"github.com/grafana/grafana/pkg/services/licensing" | ||||
| 	secretsfake "github.com/grafana/grafana/pkg/services/secrets/fakes" | ||||
|  | @ -67,7 +68,7 @@ func TestSocialService_ProvideService(t *testing.T) { | |||
| 	cfg.Raw = iniFile | ||||
| 
 | ||||
| 	secrets := secretsfake.NewMockService(t) | ||||
| 	accessControl := acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) | ||||
| 	accessControl := acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()) | ||||
| 	sqlStore := db.InitTestDB(t) | ||||
| 
 | ||||
| 	ssoSettingsSvc := ssosettingsimpl.ProvideService( | ||||
|  | @ -179,7 +180,7 @@ func TestSocialService_ProvideService_GrafanaComGrafanaNet(t *testing.T) { | |||
| 
 | ||||
| 	cfg := setting.NewCfg() | ||||
| 	secrets := secretsfake.NewMockService(t) | ||||
| 	accessControl := acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) | ||||
| 	accessControl := acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()) | ||||
| 	sqlStore := db.InitTestDB(t) | ||||
| 
 | ||||
| 	ssoSettingsSvc := ssosettingsimpl.ProvideService( | ||||
|  |  | |||
|  | @ -6,15 +6,16 @@ import ( | |||
| 	"fmt" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"go.opentelemetry.io/otel" | ||||
| 	"go.opentelemetry.io/otel/attribute" | ||||
| 	"go.opentelemetry.io/otel/trace" | ||||
| 
 | ||||
| 	"github.com/grafana/grafana/pkg/apimachinery/identity" | ||||
| 	"github.com/grafana/grafana/pkg/registry" | ||||
| 	"github.com/grafana/grafana/pkg/services/authn" | ||||
| 	contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" | ||||
| 	"github.com/grafana/grafana/pkg/services/org" | ||||
| 	"github.com/grafana/grafana/pkg/services/user" | ||||
| 	"go.opentelemetry.io/otel" | ||||
| 	"go.opentelemetry.io/otel/attribute" | ||||
| 	"go.opentelemetry.io/otel/trace" | ||||
| ) | ||||
| 
 | ||||
| var tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/accesscontrol") | ||||
|  |  | |||
|  | @ -3,32 +3,52 @@ package acimpl | |||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"time" | ||||
| 
 | ||||
| 	openfgav1 "github.com/openfga/api/proto/openfga/v1" | ||||
| 	"github.com/prometheus/client_golang/prometheus" | ||||
| 
 | ||||
| 	"github.com/grafana/grafana/pkg/apimachinery/identity" | ||||
| 	"github.com/grafana/grafana/pkg/infra/log" | ||||
| 	"github.com/grafana/grafana/pkg/infra/metrics" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	errAccessNotImplemented = errors.New("access control not implemented for resource") | ||||
| ) | ||||
| 
 | ||||
| var _ accesscontrol.AccessControl = new(AccessControl) | ||||
| 
 | ||||
| func ProvideAccessControl(features featuremgmt.FeatureToggles) *AccessControl { | ||||
| func ProvideAccessControl(features featuremgmt.FeatureToggles, zclient zanzana.Client) *AccessControl { | ||||
| 	logger := log.New("accesscontrol") | ||||
| 	return &AccessControl{ | ||||
| 		features, logger, accesscontrol.NewResolvers(logger), | ||||
| 		features, logger, accesscontrol.NewResolvers(logger), zclient, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func ProvideAccessControlTest() *AccessControl { | ||||
| 	return ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()) | ||||
| } | ||||
| 
 | ||||
| type AccessControl struct { | ||||
| 	features  featuremgmt.FeatureToggles | ||||
| 	log       log.Logger | ||||
| 	resolvers accesscontrol.Resolvers | ||||
| 	zclient   zanzana.Client | ||||
| } | ||||
| 
 | ||||
| func (a *AccessControl) Evaluate(ctx context.Context, user identity.Requester, evaluator accesscontrol.Evaluator) (bool, error) { | ||||
| 	if a.features.IsEnabledGlobally(featuremgmt.FlagZanzana) { | ||||
| 		return a.evaluateCompare(ctx, user, evaluator) | ||||
| 	} | ||||
| 
 | ||||
| 	return a.evaluate(ctx, user, evaluator) | ||||
| } | ||||
| 
 | ||||
| func (a *AccessControl) evaluate(ctx context.Context, user identity.Requester, evaluator accesscontrol.Evaluator) (bool, error) { | ||||
| 	timer := prometheus.NewTimer(metrics.MAccessEvaluationsSummary) | ||||
| 	defer timer.ObserveDuration() | ||||
| 	metrics.MAccessEvaluationCount.Inc() | ||||
|  | @ -66,6 +86,87 @@ func (a *AccessControl) Evaluate(ctx context.Context, user identity.Requester, e | |||
| 	return resolvedEvaluator.Evaluate(permissions), nil | ||||
| } | ||||
| 
 | ||||
| func (a *AccessControl) evaluateZanzana(ctx context.Context, user identity.Requester, evaluator accesscontrol.Evaluator) (bool, error) { | ||||
| 	eval, err := evaluator.MutateScopes(ctx, a.resolvers.GetScopeAttributeMutator(user.GetOrgID())) | ||||
| 	if err != nil { | ||||
| 		if !errors.Is(err, accesscontrol.ErrResolverNotFound) { | ||||
| 			return false, err | ||||
| 		} | ||||
| 		eval = evaluator | ||||
| 	} | ||||
| 
 | ||||
| 	return eval.EvaluateCustom(func(action, scope string) (bool, error) { | ||||
| 		kind, _, identifier := accesscontrol.SplitScope(scope) | ||||
| 		key, ok := zanzana.TranslateToTuple(user.GetUID().String(), action, kind, identifier, user.GetOrgID()) | ||||
| 		if !ok { | ||||
| 			// unsupported translation
 | ||||
| 			return false, errAccessNotImplemented | ||||
| 		} | ||||
| 
 | ||||
| 		res, err := a.zclient.Check(ctx, &openfgav1.CheckRequest{ | ||||
| 			TupleKey: &openfgav1.CheckRequestTupleKey{ | ||||
| 				User:     key.User, | ||||
| 				Relation: key.Relation, | ||||
| 				Object:   key.Object, | ||||
| 			}, | ||||
| 		}) | ||||
| 
 | ||||
| 		if err != nil { | ||||
| 			return false, err | ||||
| 		} | ||||
| 
 | ||||
| 		return res.Allowed, nil | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| type evalResult struct { | ||||
| 	runner   string | ||||
| 	decision bool | ||||
| 	err      error | ||||
| 	duration time.Duration | ||||
| } | ||||
| 
 | ||||
| // evaluateCompare run RBAC and zanzana checks in parallel and then compare result
 | ||||
| func (a *AccessControl) evaluateCompare(ctx context.Context, user identity.Requester, evaluator accesscontrol.Evaluator) (bool, error) { | ||||
| 	res := make(chan evalResult, 2) | ||||
| 	go func() { | ||||
| 		start := time.Now() | ||||
| 		hasAccess, err := a.evaluateZanzana(ctx, user, evaluator) | ||||
| 		res <- evalResult{"zanzana", hasAccess, err, time.Since(start)} | ||||
| 	}() | ||||
| 
 | ||||
| 	go func() { | ||||
| 		start := time.Now() | ||||
| 		hasAccess, err := a.evaluate(ctx, user, evaluator) | ||||
| 		res <- evalResult{"grafana", hasAccess, err, time.Since(start)} | ||||
| 	}() | ||||
| 	first, second := <-res, <-res | ||||
| 	close(res) | ||||
| 
 | ||||
| 	if second.runner == "grafana" { | ||||
| 		first, second = second, first | ||||
| 	} | ||||
| 
 | ||||
| 	if !errors.Is(second.err, errAccessNotImplemented) { | ||||
| 		if second.err != nil { | ||||
| 			a.log.Error("zanzana evaluation failed", "error", second.err) | ||||
| 		} else if first.decision != second.decision { | ||||
| 			a.log.Warn( | ||||
| 				"zanzana evaluation result does not match grafana", | ||||
| 				"grafana_decision", first.decision, | ||||
| 				"zanana_decision", second.decision, | ||||
| 				"grafana_ms", first.duration, | ||||
| 				"zanzana_ms", second.duration, | ||||
| 				"eval", evaluator.GoString(), | ||||
| 			) | ||||
| 		} else { | ||||
| 			a.log.Debug("zanzana evaluation is correct", "grafana_ms", first.duration, "zanzana_ms", second.duration) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return first.decision, first.err | ||||
| } | ||||
| 
 | ||||
| func (a *AccessControl) RegisterScopeAttributeResolver(prefix string, resolver accesscontrol.ScopeAttributeResolver) { | ||||
| 	a.resolvers.AddScopeAttributeResolver(prefix, resolver) | ||||
| } | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ import ( | |||
| 
 | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
| 	"github.com/grafana/grafana/pkg/services/user" | ||||
| ) | ||||
|  | @ -65,7 +66,7 @@ func TestAccessControl_Evaluate(t *testing.T) { | |||
| 
 | ||||
| 	for _, tt := range tests { | ||||
| 		t.Run(tt.desc, func(t *testing.T) { | ||||
| 			ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets)) | ||||
| 			ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets), zanzana.NewNoopClient()) | ||||
| 
 | ||||
| 			if tt.scopeResolver != nil { | ||||
| 				ac.RegisterScopeAttributeResolver(tt.resolverPrefix, tt.scopeResolver) | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/actest" | ||||
| 	"github.com/grafana/grafana/pkg/services/authn" | ||||
| 	"github.com/grafana/grafana/pkg/services/authn/authntest" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
| 	"github.com/grafana/grafana/pkg/services/team" | ||||
|  | @ -24,7 +25,7 @@ import ( | |||
| ) | ||||
| 
 | ||||
| func TestAuthorizeInOrgMiddleware(t *testing.T) { | ||||
| 	ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) | ||||
| 	ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()) | ||||
| 
 | ||||
| 	// Define test cases
 | ||||
| 	testCases := []struct { | ||||
|  |  | |||
|  | @ -11,9 +11,13 @@ import ( | |||
| 
 | ||||
| var logger = log.New("accesscontrol.evaluator") | ||||
| 
 | ||||
| type CheckerFn func(action string, scope string) (bool, error) | ||||
| 
 | ||||
| type Evaluator interface { | ||||
| 	// Evaluate permissions that are grouped by action
 | ||||
| 	Evaluate(permissions map[string][]string) bool | ||||
| 	// EvaluateCustom allows to perform evaluation with custom check function
 | ||||
| 	EvaluateCustom(fn CheckerFn) (bool, error) | ||||
| 	// MutateScopes executes a sequence of ScopeModifier functions on all embedded scopes of an evaluator and returns a new Evaluator
 | ||||
| 	MutateScopes(ctx context.Context, mutate ScopeAttributeMutator) (Evaluator, error) | ||||
| 	// String returns a string representation of permission required by the evaluator
 | ||||
|  | @ -80,6 +84,25 @@ func match(scope, target string) bool { | |||
| 	return scope == target | ||||
| } | ||||
| 
 | ||||
| func (p permissionEvaluator) EvaluateCustom(fn CheckerFn) (bool, error) { | ||||
| 	if len(p.Scopes) == 0 { | ||||
| 		return fn(p.Action, "") | ||||
| 	} | ||||
| 
 | ||||
| 	for _, target := range p.Scopes { | ||||
| 		matches, err := fn(p.Action, target) | ||||
| 		if err != nil { | ||||
| 			return false, err | ||||
| 		} | ||||
| 
 | ||||
| 		if matches { | ||||
| 			return true, nil | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return false, nil | ||||
| } | ||||
| 
 | ||||
| func (p permissionEvaluator) MutateScopes(ctx context.Context, mutate ScopeAttributeMutator) (Evaluator, error) { | ||||
| 	if p.Scopes == nil { | ||||
| 		return EvalPermission(p.Action), nil | ||||
|  | @ -135,6 +158,19 @@ func (a allEvaluator) Evaluate(permissions map[string][]string) bool { | |||
| 	return true | ||||
| } | ||||
| 
 | ||||
| func (a allEvaluator) EvaluateCustom(fn CheckerFn) (bool, error) { | ||||
| 	for _, e := range a.allOf { | ||||
| 		allowed, err := e.EvaluateCustom(fn) | ||||
| 		if err != nil { | ||||
| 			return false, err | ||||
| 		} | ||||
| 		if !allowed { | ||||
| 			return false, nil | ||||
| 		} | ||||
| 	} | ||||
| 	return true, nil | ||||
| } | ||||
| 
 | ||||
| func (a allEvaluator) MutateScopes(ctx context.Context, mutate ScopeAttributeMutator) (Evaluator, error) { | ||||
| 	resolved := false | ||||
| 	modified := make([]Evaluator, 0, len(a.allOf)) | ||||
|  | @ -195,6 +231,19 @@ func (a anyEvaluator) Evaluate(permissions map[string][]string) bool { | |||
| 	return false | ||||
| } | ||||
| 
 | ||||
| func (a anyEvaluator) EvaluateCustom(fn CheckerFn) (bool, error) { | ||||
| 	for _, e := range a.anyOf { | ||||
| 		allowed, err := e.EvaluateCustom(fn) | ||||
| 		if err != nil { | ||||
| 			return false, err | ||||
| 		} | ||||
| 		if allowed { | ||||
| 			return true, nil | ||||
| 		} | ||||
| 	} | ||||
| 	return false, nil | ||||
| } | ||||
| 
 | ||||
| func (a anyEvaluator) MutateScopes(ctx context.Context, mutate ScopeAttributeMutator) (Evaluator, error) { | ||||
| 	resolved := false | ||||
| 	modified := make([]Evaluator, 0, len(a.anyOf)) | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ import ( | |||
| 	"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/authz/zanzana" | ||||
| 	"github.com/grafana/grafana/pkg/services/contexthandler/ctxkey" | ||||
| 	contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
|  | @ -25,7 +26,7 @@ type middlewareTestCase struct { | |||
| } | ||||
| 
 | ||||
| func TestMiddleware(t *testing.T) { | ||||
| 	ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) | ||||
| 	ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()) | ||||
| 
 | ||||
| 	tests := []middlewareTestCase{ | ||||
| 		{ | ||||
|  | @ -81,7 +82,7 @@ func TestMiddleware_forceLogin(t *testing.T) { | |||
| 		{url: "/endpoint"}, | ||||
| 	} | ||||
| 
 | ||||
| 	ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) | ||||
| 	ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()) | ||||
| 
 | ||||
| 	for _, tc := range tests { | ||||
| 		t.Run(tc.url, func(t *testing.T) { | ||||
|  |  | |||
|  | @ -214,19 +214,7 @@ func (p Permission) OSSPermission() Permission { | |||
| 
 | ||||
| // SplitScope returns kind, attribute and Identifier
 | ||||
| func (p Permission) SplitScope() (string, string, string) { | ||||
| 	if p.Scope == "" { | ||||
| 		return "", "", "" | ||||
| 	} | ||||
| 
 | ||||
| 	fragments := strings.Split(p.Scope, ":") | ||||
| 	switch l := len(fragments); l { | ||||
| 	case 1: // Splitting a wildcard scope "*" -> kind: "*"; attribute: "*"; identifier: "*"
 | ||||
| 		return fragments[0], fragments[0], fragments[0] | ||||
| 	case 2: // Splitting a wildcard scope with specified kind "dashboards:*" -> kind: "dashboards"; attribute: "*"; identifier: "*"
 | ||||
| 		return fragments[0], fragments[1], fragments[1] | ||||
| 	default: // Splitting a scope with all fields specified "dashboards:uid:my_dash" -> kind: "dashboards"; attribute: "uid"; identifier: "my_dash"
 | ||||
| 		return fragments[0], fragments[1], strings.Join(fragments[2:], ":") | ||||
| 	} | ||||
| 	return SplitScope(p.Scope) | ||||
| } | ||||
| 
 | ||||
| type GetUserPermissionsQuery struct { | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/actest" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
| 	"github.com/grafana/grafana/pkg/services/licensing/licensingtest" | ||||
| 	"github.com/grafana/grafana/pkg/services/org/orgimpl" | ||||
|  | @ -289,7 +290,7 @@ func TestService_RegisterActionSets(t *testing.T) { | |||
| 			if tt.actionSetsEnabled { | ||||
| 				features = featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets) | ||||
| 			} | ||||
| 			ac := acimpl.ProvideAccessControl(features) | ||||
| 			ac := acimpl.ProvideAccessControl(features, zanzana.NewNoopClient()) | ||||
| 			actionSets := NewActionSetService() | ||||
| 			_, err := New( | ||||
| 				setting.NewCfg(), tt.options, features, routing.NewRouteRegister(), licensingtest.NewFakeLicensing(), | ||||
|  | @ -335,7 +336,7 @@ func setupTestEnvironment(t *testing.T, ops Options) (*Service, user.Service, te | |||
| 	license := licensingtest.NewFakeLicensing() | ||||
| 	license.On("FeatureEnabled", "accesscontrol.enforcement").Return(true).Maybe() | ||||
| 	acService := &actest.FakeService{} | ||||
| 	ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) | ||||
| 	ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()) | ||||
| 	service, err := New( | ||||
| 		cfg, ops, featuremgmt.WithFeatures(), routing.NewRouteRegister(), license, | ||||
| 		ac, acService, sql, teamSvc, userSvc, NewActionSetService(), | ||||
|  |  | |||
|  | @ -10,6 +10,23 @@ const ( | |||
| 	maxPrefixParts = 2 | ||||
| ) | ||||
| 
 | ||||
| // SplitScope returns kind, attribute and Identifier
 | ||||
| func SplitScope(scope string) (string, string, string) { | ||||
| 	if scope == "" { | ||||
| 		return "", "", "" | ||||
| 	} | ||||
| 
 | ||||
| 	fragments := strings.Split(scope, ":") | ||||
| 	switch l := len(fragments); l { | ||||
| 	case 1: // Splitting a wildcard scope "*" -> kind: "*"; attribute: "*"; identifier: "*"
 | ||||
| 		return fragments[0], fragments[0], fragments[0] | ||||
| 	case 2: // Splitting a wildcard scope with specified kind "dashboards:*" -> kind: "dashboards"; attribute: "*"; identifier: "*"
 | ||||
| 		return fragments[0], fragments[1], fragments[1] | ||||
| 	default: // Splitting a scope with all fields specified "dashboards:uid:my_dash" -> kind: "dashboards"; attribute: "uid"; identifier: "my_dash"
 | ||||
| 		return fragments[0], fragments[1], strings.Join(fragments[2:], ":") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func ParseScopeID(scope string) (int64, error) { | ||||
| 	id, err := strconv.ParseInt(ScopeSuffix(scope), 10, 64) | ||||
| 	if err != nil { | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/annotations" | ||||
| 	"github.com/grafana/grafana/pkg/services/annotations/testutil" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	"github.com/grafana/grafana/pkg/services/dashboards" | ||||
| 	dashboardstore "github.com/grafana/grafana/pkg/services/dashboards/database" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
|  | @ -225,7 +226,7 @@ func TestIntegrationAnnotationListingWithInheritedRBAC(t *testing.T) { | |||
| 			guardian.New = origNewGuardian | ||||
| 		}) | ||||
| 
 | ||||
| 		ac := acimpl.ProvideAccessControl(features) | ||||
| 		ac := acimpl.ProvideAccessControl(features, zanzana.NewNoopClient()) | ||||
| 		folderSvc := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderimpl.ProvideDashboardFolderStore(sql), sql, features, supportbundlestest.NewFakeBundleService(), nil) | ||||
| 
 | ||||
| 		cfg.AnnotationMaximumTagsLength = 60 | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/bus" | ||||
| 	"github.com/grafana/grafana/pkg/infra/db" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	"github.com/grafana/grafana/pkg/services/correlations" | ||||
| 	"github.com/grafana/grafana/pkg/services/datasources" | ||||
| 	fakeDatasources "github.com/grafana/grafana/pkg/services/datasources/fakes" | ||||
|  | @ -20,6 +21,6 @@ func New(db db.DB, cfg *setting.Cfg, bus bus.Bus) *correlations.CorrelationsServ | |||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	correlationsSvc, _ := correlations.ProvideService(db, routing.NewRouteRegister(), ds, acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), bus, quotatest.New(false, nil), cfg) | ||||
| 	correlationsSvc, _ := correlations.ProvideService(db, routing.NewRouteRegister(), ds, acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()), bus, quotatest.New(false, nil), cfg) | ||||
| 	return correlationsSvc | ||||
| } | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/infra/db" | ||||
| 	"github.com/grafana/grafana/pkg/infra/tracing" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	"github.com/grafana/grafana/pkg/services/dashboards" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
| 	"github.com/grafana/grafana/pkg/services/folder" | ||||
|  | @ -827,7 +828,7 @@ func TestIntegrationFindDashboardsByTitle(t *testing.T) { | |||
| 	orgID := int64(1) | ||||
| 	insertTestDashboard(t, dashboardStore, "dashboard under general", orgID, 0, "", false) | ||||
| 
 | ||||
| 	ac := acimpl.ProvideAccessControl(features) | ||||
| 	ac := acimpl.ProvideAccessControl(features, zanzana.NewNoopClient()) | ||||
| 	folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) | ||||
| 	folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil) | ||||
| 
 | ||||
|  | @ -944,7 +945,7 @@ func TestIntegrationFindDashboardsByFolder(t *testing.T) { | |||
| 	orgID := int64(1) | ||||
| 	insertTestDashboard(t, dashboardStore, "dashboard under general", orgID, 0, "", false) | ||||
| 
 | ||||
| 	ac := acimpl.ProvideAccessControl(features) | ||||
| 	ac := acimpl.ProvideAccessControl(features, zanzana.NewNoopClient()) | ||||
| 	folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) | ||||
| 	folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil) | ||||
| 
 | ||||
|  |  | |||
|  | @ -27,6 +27,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/actest" | ||||
| 	acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	"github.com/grafana/grafana/pkg/services/dashboards" | ||||
| 	"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess" | ||||
| 	"github.com/grafana/grafana/pkg/services/dashboards/database" | ||||
|  | @ -97,7 +98,7 @@ func TestIntegrationFolderService(t *testing.T) { | |||
| 			features:             features, | ||||
| 			bus:                  bus.ProvideBus(tracing.InitializeTracerForTest()), | ||||
| 			db:                   db, | ||||
| 			accessControl:        acimpl.ProvideAccessControl(features), | ||||
| 			accessControl:        acimpl.ProvideAccessControl(features, zanzana.NewNoopClient()), | ||||
| 			metrics:              newFoldersMetrics(nil), | ||||
| 			registry:             make(map[string]folder.RegistryService), | ||||
| 		} | ||||
|  | @ -427,7 +428,7 @@ func TestIntegrationNestedFolderService(t *testing.T) { | |||
| 	nestedFolderStore := ProvideStore(db) | ||||
| 
 | ||||
| 	b := bus.ProvideBus(tracing.InitializeTracerForTest()) | ||||
| 	ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) | ||||
| 	ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()) | ||||
| 
 | ||||
| 	serviceWithFlagOn := &Service{ | ||||
| 		log:                  slog.New(logtest.NewTestHandler(t)).With("logger", "test-folder-service"), | ||||
|  | @ -803,7 +804,7 @@ func TestNestedFolderServiceFeatureToggle(t *testing.T) { | |||
| 		dashboardStore:       &dashStore, | ||||
| 		dashboardFolderStore: dashboardFolderStore, | ||||
| 		features:             featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders), | ||||
| 		accessControl:        acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), | ||||
| 		accessControl:        acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()), | ||||
| 		metrics:              newFoldersMetrics(nil), | ||||
| 	} | ||||
| 	t.Run("create folder", func(t *testing.T) { | ||||
|  | @ -839,7 +840,7 @@ func TestFolderServiceDualWrite(t *testing.T) { | |||
| 		dashboardStore:       dashStore, | ||||
| 		dashboardFolderStore: dashboardFolderStore, | ||||
| 		features:             featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders), | ||||
| 		accessControl:        acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), | ||||
| 		accessControl:        acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()), | ||||
| 		metrics:              newFoldersMetrics(nil), | ||||
| 		bus:                  bus.ProvideBus(tracing.InitializeTracerForTest()), | ||||
| 	} | ||||
|  | @ -903,7 +904,7 @@ func TestNestedFolderService(t *testing.T) { | |||
| 			features := featuremgmt.WithFeatures() | ||||
| 
 | ||||
| 			db, _ := sqlstore.InitTestDB(t) | ||||
| 			folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, features, acimpl.ProvideAccessControl(features), db) | ||||
| 			folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, features, acimpl.ProvideAccessControl(features, zanzana.NewNoopClient()), db) | ||||
| 			_, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{ | ||||
| 				OrgID:        orgID, | ||||
| 				Title:        dash.Title, | ||||
|  | @ -937,7 +938,7 @@ func TestNestedFolderService(t *testing.T) { | |||
| 			features := featuremgmt.WithFeatures("nestedFolders") | ||||
| 
 | ||||
| 			db, _ := sqlstore.InitTestDB(t) | ||||
| 			folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, features, acimpl.ProvideAccessControl(features), db) | ||||
| 			folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, features, acimpl.ProvideAccessControl(features, zanzana.NewNoopClient()), db) | ||||
| 			_, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{ | ||||
| 				OrgID:        orgID, | ||||
| 				Title:        dash.Title, | ||||
|  | @ -969,7 +970,7 @@ func TestNestedFolderService(t *testing.T) { | |||
| 			dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand")).Return(&dashboards.Dashboard{}, nil) | ||||
| 
 | ||||
| 			features := featuremgmt.WithFeatures("nestedFolders") | ||||
| 			folderSvc := setup(t, dashStore, nil, nil, features, acimpl.ProvideAccessControl(features), dbtest.NewFakeDB()) | ||||
| 			folderSvc := setup(t, dashStore, nil, nil, features, acimpl.ProvideAccessControl(features, zanzana.NewNoopClient()), dbtest.NewFakeDB()) | ||||
| 			_, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{ | ||||
| 				OrgID:        orgID, | ||||
| 				Title:        dash.Title, | ||||
|  | @ -1005,7 +1006,7 @@ func TestNestedFolderService(t *testing.T) { | |||
| 			nestedFolderStore := NewFakeStore() | ||||
| 			db, _ := sqlstore.InitTestDB(t) | ||||
| 			features := featuremgmt.WithFeatures("nestedFolders") | ||||
| 			folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, features, acimpl.ProvideAccessControl(features), db) | ||||
| 			folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, features, acimpl.ProvideAccessControl(features, zanzana.NewNoopClient()), db) | ||||
| 			_, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{ | ||||
| 				OrgID:        orgID, | ||||
| 				Title:        dash.Title, | ||||
|  | @ -1141,7 +1142,7 @@ func TestNestedFolderService(t *testing.T) { | |||
| 			nestedFolderUser.Permissions[orgID] = map[string][]string{dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersProvider.GetResourceScopeUID("wrong_uid")}} | ||||
| 
 | ||||
| 			features := featuremgmt.WithFeatures("nestedFolders") | ||||
| 			folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, features, acimpl.ProvideAccessControl(features), dbtest.NewFakeDB()) | ||||
| 			folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, features, acimpl.ProvideAccessControl(features, zanzana.NewNoopClient()), dbtest.NewFakeDB()) | ||||
| 			_, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder", OrgID: orgID, SignedInUser: nestedFolderUser}) | ||||
| 			require.ErrorIs(t, err, dashboards.ErrFolderAccessDenied) | ||||
| 		}) | ||||
|  | @ -1162,7 +1163,7 @@ func TestNestedFolderService(t *testing.T) { | |||
| 			nestedFolderUser.Permissions[orgID] = map[string][]string{dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersProvider.GetResourceScopeUID("newFolder")}} | ||||
| 
 | ||||
| 			features := featuremgmt.WithFeatures("nestedFolders") | ||||
| 			folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, features, acimpl.ProvideAccessControl(features), dbtest.NewFakeDB()) | ||||
| 			folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, features, acimpl.ProvideAccessControl(features, zanzana.NewNoopClient()), dbtest.NewFakeDB()) | ||||
| 			_, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder", OrgID: orgID, SignedInUser: nestedFolderUser}) | ||||
| 			require.NoError(t, err) | ||||
| 			// the folder is set inside InTransaction() but the fake one is called
 | ||||
|  | @ -1174,7 +1175,7 @@ func TestNestedFolderService(t *testing.T) { | |||
| 			nestedFolderUser.Permissions[orgID] = map[string][]string{dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersProvider.GetResourceAllScope()}} | ||||
| 
 | ||||
| 			features := featuremgmt.WithFeatures("nestedFolders") | ||||
| 			folderSvc := setup(t, &dashboards.FakeDashboardStore{}, foldertest.NewFakeFolderStore(t), NewFakeStore(), features, acimpl.ProvideAccessControl(features), dbtest.NewFakeDB()) | ||||
| 			folderSvc := setup(t, &dashboards.FakeDashboardStore{}, foldertest.NewFakeFolderStore(t), NewFakeStore(), features, acimpl.ProvideAccessControl(features, zanzana.NewNoopClient()), dbtest.NewFakeDB()) | ||||
| 			_, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: accesscontrol.K6FolderUID, NewParentUID: "newFolder", OrgID: orgID, SignedInUser: nestedFolderUser}) | ||||
| 			require.Error(t, err, folder.ErrBadRequest) | ||||
| 		}) | ||||
|  | @ -1192,7 +1193,7 @@ func TestNestedFolderService(t *testing.T) { | |||
| 			} | ||||
| 
 | ||||
| 			features := featuremgmt.WithFeatures("nestedFolders") | ||||
| 			folderSvc := setup(t, &dashboards.FakeDashboardStore{}, foldertest.NewFakeFolderStore(t), nestedFolderStore, features, acimpl.ProvideAccessControl(features), dbtest.NewFakeDB()) | ||||
| 			folderSvc := setup(t, &dashboards.FakeDashboardStore{}, foldertest.NewFakeFolderStore(t), nestedFolderStore, features, acimpl.ProvideAccessControl(features, zanzana.NewNoopClient()), dbtest.NewFakeDB()) | ||||
| 			_, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: childUID, NewParentUID: "newFolder", OrgID: orgID, SignedInUser: nestedFolderUser}) | ||||
| 			require.Error(t, err, folder.ErrBadRequest) | ||||
| 		}) | ||||
|  | @ -1208,7 +1209,7 @@ func TestNestedFolderService(t *testing.T) { | |||
| 			nestedFolderUser.Permissions[orgID] = map[string][]string{dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersProvider.GetResourceScopeUID("")}} | ||||
| 
 | ||||
| 			features := featuremgmt.WithFeatures("nestedFolders") | ||||
| 			folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, features, acimpl.ProvideAccessControl(features), dbtest.NewFakeDB()) | ||||
| 			folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, features, acimpl.ProvideAccessControl(features, zanzana.NewNoopClient()), dbtest.NewFakeDB()) | ||||
| 			_, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "", OrgID: orgID, SignedInUser: nestedFolderUser}) | ||||
| 			require.Error(t, err, dashboards.ErrFolderAccessDenied) | ||||
| 		}) | ||||
|  | @ -1229,7 +1230,7 @@ func TestNestedFolderService(t *testing.T) { | |||
| 			nestedFolderUser.Permissions[orgID] = map[string][]string{dashboards.ActionFoldersCreate: {}} | ||||
| 
 | ||||
| 			features := featuremgmt.WithFeatures("nestedFolders") | ||||
| 			folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, features, acimpl.ProvideAccessControl(features), dbtest.NewFakeDB()) | ||||
| 			folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, features, acimpl.ProvideAccessControl(features, zanzana.NewNoopClient()), dbtest.NewFakeDB()) | ||||
| 			_, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "", OrgID: orgID, SignedInUser: nestedFolderUser}) | ||||
| 			require.NoError(t, err) | ||||
| 			// the folder is set inside InTransaction() but the fake one is called
 | ||||
|  | @ -1387,7 +1388,7 @@ func TestIntegrationNestedFolderSharedWithMe(t *testing.T) { | |||
| 	nestedFolderStore := ProvideStore(db) | ||||
| 
 | ||||
| 	b := bus.ProvideBus(tracing.InitializeTracerForTest()) | ||||
| 	ac := acimpl.ProvideAccessControl(featuresFlagOn) | ||||
| 	ac := acimpl.ProvideAccessControl(featuresFlagOn, zanzana.NewNoopClient()) | ||||
| 
 | ||||
| 	serviceWithFlagOn := &Service{ | ||||
| 		log:                  slog.New(logtest.NewTestHandler(t)).With("logger", "test-folder-service"), | ||||
|  | @ -1807,7 +1808,7 @@ func TestFolderServiceGetFolder(t *testing.T) { | |||
| 		nestedFolderStore := ProvideStore(db) | ||||
| 
 | ||||
| 		b := bus.ProvideBus(tracing.InitializeTracerForTest()) | ||||
| 		ac := acimpl.ProvideAccessControl(featuresFlagOff) | ||||
| 		ac := acimpl.ProvideAccessControl(featuresFlagOff, zanzana.NewNoopClient()) | ||||
| 
 | ||||
| 		return Service{ | ||||
| 			log:                  slog.New(logtest.NewTestHandler(t)).With("logger", "test-folder-service"), | ||||
|  | @ -1889,7 +1890,7 @@ func TestFolderServiceGetFolders(t *testing.T) { | |||
| 	nestedFolderStore := ProvideStore(db) | ||||
| 
 | ||||
| 	b := bus.ProvideBus(tracing.InitializeTracerForTest()) | ||||
| 	ac := acimpl.ProvideAccessControl(featuresFlagOff) | ||||
| 	ac := acimpl.ProvideAccessControl(featuresFlagOff, zanzana.NewNoopClient()) | ||||
| 
 | ||||
| 	serviceWithFlagOff := &Service{ | ||||
| 		log:                  slog.New(logtest.NewTestHandler(t)).With("logger", "test-folder-service"), | ||||
|  | @ -1973,7 +1974,7 @@ func TestGetChildrenFilterByPermission(t *testing.T) { | |||
| 	nestedFolderStore := ProvideStore(db) | ||||
| 
 | ||||
| 	b := bus.ProvideBus(tracing.InitializeTracerForTest()) | ||||
| 	ac := acimpl.ProvideAccessControl(featuresFlagOff) | ||||
| 	ac := acimpl.ProvideAccessControl(featuresFlagOff, zanzana.NewNoopClient()) | ||||
| 
 | ||||
| 	features := featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders) | ||||
| 
 | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ import ( | |||
| 
 | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	"github.com/grafana/grafana/pkg/services/dashboards" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
| 	"github.com/grafana/grafana/pkg/services/folder/foldertest" | ||||
|  | @ -956,7 +957,7 @@ func setupAccessControlGuardianTest( | |||
| 	fakeDashboardService := dashboards.NewFakeDashboardService(t) | ||||
| 	fakeDashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardQuery")).Maybe().Return(d, nil) | ||||
| 
 | ||||
| 	ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) | ||||
| 	ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()) | ||||
| 	folderSvc := foldertest.NewFakeService() | ||||
| 
 | ||||
| 	folderStore := foldertest.NewFakeFolderStore(t) | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/auth/authtest" | ||||
| 	"github.com/grafana/grafana/pkg/services/authn/authntest" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
| 	"github.com/grafana/grafana/pkg/services/ldap" | ||||
| 	"github.com/grafana/grafana/pkg/services/ldap/multildap" | ||||
|  | @ -67,7 +68,7 @@ func setupAPITest(t *testing.T, opts ...func(a *Service)) (*Service, *webtest.Se | |||
| 
 | ||||
| 	a := ProvideService(cfg, | ||||
| 		router, | ||||
| 		acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), | ||||
| 		acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()), | ||||
| 		usertest.NewUserServiceFake(), | ||||
| 		&authinfotest.FakeService{}, | ||||
| 		ldap.ProvideGroupsService(), | ||||
|  |  | |||
|  | @ -25,6 +25,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/actest" | ||||
| 	acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" | ||||
| 	"github.com/grafana/grafana/pkg/services/dashboards" | ||||
| 	"github.com/grafana/grafana/pkg/services/dashboards/database" | ||||
|  | @ -444,7 +445,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo | |||
| 		quotaService := quotatest.New(false, nil) | ||||
| 		dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, features, tagimpl.ProvideService(sqlStore), quotaService) | ||||
| 		require.NoError(t, err) | ||||
| 		ac := acimpl.ProvideAccessControl(features) | ||||
| 		ac := acimpl.ProvideAccessControl(features, zanzana.NewNoopClient()) | ||||
| 		folderPermissions := acmock.NewMockedPermissionsService() | ||||
| 		folderPermissions.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil) | ||||
| 		dashboardPermissions := acmock.NewMockedPermissionsService() | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/infra/usagestats" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/annotations/annotationstest" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	"github.com/grafana/grafana/pkg/services/dashboards" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
| 	"github.com/grafana/grafana/pkg/setting" | ||||
|  | @ -36,7 +37,7 @@ func Test_provideLiveService_RedisUnavailable(t *testing.T) { | |||
| 		nil, | ||||
| 		&usagestats.UsageStatsMock{T: t}, | ||||
| 		nil, | ||||
| 		featuremgmt.WithFeatures(), acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), &dashboards.FakeDashboardService{}, annotationstest.NewFakeAnnotationsRepo(), nil) | ||||
| 		featuremgmt.WithFeatures(), acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()), &dashboards.FakeDashboardService{}, annotationstest.NewFakeAnnotationsRepo(), nil) | ||||
| 
 | ||||
| 	// Proceeds without live HA if redis is unavaialble
 | ||||
| 	require.NoError(t, err) | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ import ( | |||
| 	ac "github.com/grafana/grafana/pkg/services/accesscontrol" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" | ||||
| 	"github.com/grafana/grafana/pkg/services/datasources" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
|  | @ -434,7 +435,7 @@ func TestAddAppLinksAccessControl(t *testing.T) { | |||
| 	service := ServiceImpl{ | ||||
| 		log:            log.New("navtree"), | ||||
| 		cfg:            cfg, | ||||
| 		accessControl:  acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), | ||||
| 		accessControl:  acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()), | ||||
| 		pluginSettings: &pluginSettings, | ||||
| 		features:       featuremgmt.WithFeatures(), | ||||
| 		pluginStore: &pluginstore.FakePluginStore{ | ||||
|  |  | |||
|  | @ -15,6 +15,8 @@ import ( | |||
| 	"github.com/stretchr/testify/require" | ||||
| 
 | ||||
| 	alertingNotify "github.com/grafana/alerting/notify" | ||||
| 
 | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol" | ||||
| 
 | ||||
| 	"github.com/grafana/grafana/pkg/api/response" | ||||
|  | @ -631,9 +633,9 @@ func createSut(t *testing.T) AlertmanagerSrv { | |||
| 	} | ||||
| 	mam := createMultiOrgAlertmanager(t, configs) | ||||
| 	log := log.NewNopLogger() | ||||
| 	ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) | ||||
| 	ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()) | ||||
| 	ruleStore := ngfakes.NewRuleStore(t) | ||||
| 	ruleAuthzService := accesscontrol.NewRuleService(acimpl.ProvideAccessControl(featuremgmt.WithFeatures())) | ||||
| 	ruleAuthzService := accesscontrol.NewRuleService(acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient())) | ||||
| 	return AlertmanagerSrv{ | ||||
| 		mam:        mam, | ||||
| 		crypto:     mam.Crypto, | ||||
|  |  | |||
|  | @ -14,11 +14,13 @@ import ( | |||
| 	"github.com/stretchr/testify/require" | ||||
| 
 | ||||
| 	alertingModels "github.com/grafana/alerting/models" | ||||
| 
 | ||||
| 	"github.com/grafana/grafana-plugin-sdk-go/data" | ||||
| 
 | ||||
| 	"github.com/grafana/grafana/pkg/expr" | ||||
| 	"github.com/grafana/grafana/pkg/infra/log" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" | ||||
| 	"github.com/grafana/grafana/pkg/services/datasources" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
|  | @ -558,7 +560,7 @@ func TestRouteGetRuleStatuses(t *testing.T) { | |||
| 			log:     log.NewNopLogger(), | ||||
| 			manager: fakeAIM, | ||||
| 			store:   ruleStore, | ||||
| 			authz:   accesscontrol.NewRuleService(acimpl.ProvideAccessControl(featuremgmt.WithFeatures())), | ||||
| 			authz:   accesscontrol.NewRuleService(acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient())), | ||||
| 		} | ||||
| 
 | ||||
| 		permissions := createPermissionsForRules(slices.Concat(rulesInGroup1, rulesInGroup2, rulesInGroup3), orgID) | ||||
|  | @ -673,7 +675,7 @@ func TestRouteGetRuleStatuses(t *testing.T) { | |||
| 				log:     log.NewNopLogger(), | ||||
| 				manager: fakeAIM, | ||||
| 				store:   ruleStore, | ||||
| 				authz:   accesscontrol.NewRuleService(acimpl.ProvideAccessControl(featuremgmt.WithFeatures())), | ||||
| 				authz:   accesscontrol.NewRuleService(acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient())), | ||||
| 			} | ||||
| 
 | ||||
| 			c := &contextmodel.ReqContext{Context: &web.Context{Req: req}, SignedInUser: &user.SignedInUser{OrgID: orgID, Permissions: createPermissionsForRules(rules, orgID)}} | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/infra/log" | ||||
| 	ac "github.com/grafana/grafana/pkg/services/accesscontrol" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" | ||||
| 	"github.com/grafana/grafana/pkg/services/dashboards" | ||||
| 	"github.com/grafana/grafana/pkg/services/datasources" | ||||
|  | @ -649,7 +650,7 @@ func createService(store *fakes.RuleStore) *RulerSrv { | |||
| 		cfg: &setting.UnifiedAlertingSettings{ | ||||
| 			BaseInterval: 10 * time.Second, | ||||
| 		}, | ||||
| 		authz:          accesscontrol.NewRuleService(acimpl.ProvideAccessControl(featuremgmt.WithFeatures())), | ||||
| 		authz:          accesscontrol.NewRuleService(acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient())), | ||||
| 		amConfigStore:  &fakeAMRefresher{}, | ||||
| 		amRefresher:    &fakeAMRefresher{}, | ||||
| 		featureManager: featuremgmt.WithFeatures(), | ||||
|  |  | |||
|  | @ -6,12 +6,15 @@ import ( | |||
| 	"fmt" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/stretchr/testify/require" | ||||
| 
 | ||||
| 	"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/services/accesscontrol" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/actest" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
| 	"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" | ||||
| 	"github.com/grafana/grafana/pkg/services/ngalert/models" | ||||
|  | @ -20,7 +23,6 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/services/secrets/database" | ||||
| 	"github.com/grafana/grafana/pkg/services/secrets/manager" | ||||
| 	"github.com/grafana/grafana/pkg/services/user" | ||||
| 	"github.com/stretchr/testify/require" | ||||
| ) | ||||
| 
 | ||||
| func TestReceiverService_GetReceiver(t *testing.T) { | ||||
|  | @ -72,7 +74,7 @@ func TestReceiverService_GetReceivers(t *testing.T) { | |||
| func TestReceiverService_DecryptRedact(t *testing.T) { | ||||
| 	sqlStore := db.InitTestDB(t) | ||||
| 	secretsService := manager.SetupTestService(t, database.ProvideSecretsStore(sqlStore)) | ||||
| 	ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) | ||||
| 	ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()) | ||||
| 
 | ||||
| 	getMethods := []string{"single", "multi"} | ||||
| 
 | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/actest" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
| 	"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" | ||||
| 	"github.com/grafana/grafana/pkg/services/ngalert/models" | ||||
|  | @ -261,7 +262,7 @@ func TestContactPointServiceDecryptRedact(t *testing.T) { | |||
| 	secretsService := manager.SetupTestService(t, database.ProvideSecretsStore(db.InitTestDB(t))) | ||||
| 	receiverServiceWithAC := func(ecp *ContactPointService) *notifier.ReceiverService { | ||||
| 		return notifier.NewReceiverService( | ||||
| 			acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), | ||||
| 			acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()), | ||||
| 			// Get won't use the sut's config store, so we can use a different one here.
 | ||||
| 			fakes.NewFakeAlertmanagerConfigStore(createEncryptedConfig(t, secretsService)), | ||||
| 			ecp.provenanceStore, | ||||
|  |  | |||
|  | @ -7,9 +7,10 @@ import ( | |||
| 	"net/http/httptest" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/stretchr/testify/require" | ||||
| 
 | ||||
| 	"github.com/grafana/grafana-plugin-sdk-go/backend" | ||||
| 	"github.com/grafana/grafana-plugin-sdk-go/data" | ||||
| 	"github.com/stretchr/testify/require" | ||||
| 
 | ||||
| 	"github.com/grafana/grafana/pkg/api/routing" | ||||
| 	"github.com/grafana/grafana/pkg/infra/db" | ||||
|  | @ -17,6 +18,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/infra/log" | ||||
| 	"github.com/grafana/grafana/pkg/plugins" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	"github.com/grafana/grafana/pkg/services/contexthandler/ctxkey" | ||||
| 	contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" | ||||
| 	"github.com/grafana/grafana/pkg/services/datasources" | ||||
|  | @ -55,7 +57,7 @@ func setupTestServer( | |||
| 	// build router to register routes
 | ||||
| 	rr := routing.NewRouteRegister() | ||||
| 
 | ||||
| 	ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) | ||||
| 	ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()) | ||||
| 
 | ||||
| 	// build mux
 | ||||
| 	m := web.New() | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/apimachinery/errutil" | ||||
| 	"github.com/grafana/grafana/pkg/components/simplejson" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	"github.com/grafana/grafana/pkg/services/dashboards" | ||||
| 	dashboardsDB "github.com/grafana/grafana/pkg/services/dashboards/database" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
|  | @ -1482,7 +1483,7 @@ func TestPublicDashboardServiceImpl_ListPublicDashboards(t *testing.T) { | |||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) | ||||
| 	ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()) | ||||
| 
 | ||||
| 	for _, tt := range testCases { | ||||
| 		t.Run(tt.name, func(t *testing.T) { | ||||
|  |  | |||
|  | @ -22,6 +22,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/services/apikey/apikeyimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/auth" | ||||
| 	"github.com/grafana/grafana/pkg/services/auth/authimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	"github.com/grafana/grafana/pkg/services/dashboards" | ||||
| 	dashboardStore "github.com/grafana/grafana/pkg/services/dashboards/database" | ||||
| 	"github.com/grafana/grafana/pkg/services/datasources" | ||||
|  | @ -494,7 +495,7 @@ func setupEnv(t *testing.T, sqlStore db.DB, cfg *setting.Cfg, b bus.Bus, quotaSe | |||
| 	require.NoError(t, err) | ||||
| 	m := metrics.NewNGAlert(prometheus.NewRegistry()) | ||||
| 
 | ||||
| 	ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) | ||||
| 	ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()) | ||||
| 	ruleStore, err := ngstore.ProvideDBStore(cfg, featuremgmt.WithFeatures(), sqlStore, &foldertest.FakeService{}, &dashboards.FakeDashboardService{}, ac) | ||||
| 	require.NoError(t, err) | ||||
| 	_, err = ngalert.ProvideService( | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/actest" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
| 	"github.com/grafana/grafana/pkg/services/org" | ||||
| 	"github.com/grafana/grafana/pkg/services/serviceaccounts" | ||||
|  | @ -306,7 +307,7 @@ func setupTests(t *testing.T, opts ...func(a *ServiceAccountsAPI)) *webtest.Serv | |||
| 		cfg:                  cfg, | ||||
| 		service:              &satests.FakeServiceAccountService{}, | ||||
| 		accesscontrolService: &actest.FakeService{}, | ||||
| 		accesscontrol:        acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), | ||||
| 		accesscontrol:        acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()), | ||||
| 		RouterRegister:       routing.NewRouteRegister(), | ||||
| 		log:                  log.NewNopLogger(), | ||||
| 		permissionService:    &actest.FakePermissionsService{}, | ||||
|  |  | |||
|  | @ -19,6 +19,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/login/social" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
| 	"github.com/grafana/grafana/pkg/services/org" | ||||
| 	"github.com/grafana/grafana/pkg/services/ssosettings" | ||||
|  | @ -564,7 +565,7 @@ func setupTests(t *testing.T, service ssosettings.Service) *webtest.Server { | |||
| 	api := &Api{ | ||||
| 		Log:                logger, | ||||
| 		RouteRegister:      routing.NewRouteRegister(), | ||||
| 		AccessControl:      acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), | ||||
| 		AccessControl:      acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()), | ||||
| 		SSOSettingsService: service, | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -21,6 +21,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/login/social" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
| 	"github.com/grafana/grafana/pkg/services/licensing/licensingtest" | ||||
| 	secretsFakes "github.com/grafana/grafana/pkg/services/secrets/fakes" | ||||
|  | @ -1858,7 +1859,7 @@ func setupTestEnv(t *testing.T, isLicensingEnabled, keepFallbackStratergies, sam | |||
| 	store := ssosettingstests.NewFakeStore() | ||||
| 	fallbackStrategy := ssosettingstests.NewFakeFallbackStrategy() | ||||
| 	secrets := secretsFakes.NewMockService(t) | ||||
| 	accessControl := acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) | ||||
| 	accessControl := acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()) | ||||
| 	reloadables := make(map[string]ssosettings.Reloadable) | ||||
| 
 | ||||
| 	fallbackStrategy.ExpectedIsMatch = true | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/actest" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	"github.com/grafana/grafana/pkg/services/dashboards" | ||||
| 	"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
|  | @ -37,7 +38,7 @@ func SetupAPITestServer(t *testing.T, opts ...func(a *TeamAPI)) *webtest.Server | |||
| 	a := ProvideTeamAPI(router, | ||||
| 		teamtest.NewFakeService(), | ||||
| 		actest.FakeService{}, | ||||
| 		acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), | ||||
| 		acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()), | ||||
| 		&actest.FakePermissionsService{}, | ||||
| 		&usertest.FakeUserService{}, | ||||
| 		&licensing.OSSLicensingService{}, | ||||
|  | @ -259,7 +260,7 @@ func Test_getTeamMembershipUpdates(t *testing.T) { | |||
| 			tapi := ProvideTeamAPI(routing.NewRouteRegister(), | ||||
| 				teamSvc, | ||||
| 				actest.FakeService{}, | ||||
| 				acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), | ||||
| 				acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()), | ||||
| 				&actest.FakePermissionsService{}, | ||||
| 				userService, | ||||
| 				&licensing.OSSLicensingService{}, | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions" | ||||
| 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | ||||
| 	"github.com/grafana/grafana/pkg/services/dashboards" | ||||
| 	"github.com/grafana/grafana/pkg/services/featuremgmt" | ||||
| 	"github.com/grafana/grafana/pkg/services/folder/foldertest" | ||||
|  | @ -348,7 +349,7 @@ func TestIntegrationTimeIntervalProvisioning(t *testing.T) { | |||
| 	adminClient := adminK8sClient.NotificationsV0alpha1().TimeIntervals("default") | ||||
| 
 | ||||
| 	env := helper.GetEnv() | ||||
| 	ac := acimpl.ProvideAccessControl(env.FeatureToggles) | ||||
| 	ac := acimpl.ProvideAccessControl(env.FeatureToggles, zanzana.NewNoopClient()) | ||||
| 	db, err := store.ProvideDBStore(env.Cfg, env.FeatureToggles, env.SQLStore, &foldertest.FakeService{}, &dashboards.FakeDashboardService{}, ac) | ||||
| 	require.NoError(t, err) | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue