Folders: Set folder creation permission as part of legacy create (#94040)

* Add folder store to dashboard permissions
* Include folder store in annotation scope resolver
* Add folder store when initialising library elements
* Include folder store in search v2 service initialisation
* Include folder store in GetInheritedScopes
* Add folder store to folder permissions provider
* Include cfg, folder permissions in folder service
* Move setting of folder permissions for folder service create method
pull/94065/head
Arati R. 9 months ago committed by GitHub
parent 2a73b89374
commit e399fe6d09
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      pkg/api/annotations.go
  2. 7
      pkg/api/annotations_test.go
  3. 2
      pkg/api/dashboard_test.go
  4. 38
      pkg/api/folder.go
  5. 9
      pkg/api/folder_bench_test.go
  6. 3
      pkg/api/http_server.go
  7. 4
      pkg/services/accesscontrol/ossaccesscontrol/dashboard.go
  8. 4
      pkg/services/accesscontrol/ossaccesscontrol/folder.go
  9. 83
      pkg/services/accesscontrol/ossaccesscontrol/testutil/testutil.go
  10. 6
      pkg/services/annotations/annotationsimpl/annotations_test.go
  11. 41
      pkg/services/dashboards/accesscontrol.go
  12. 34
      pkg/services/dashboards/accesscontrol_test.go
  13. 4
      pkg/services/dashboards/database/database_folder_test.go
  14. 10
      pkg/services/dashboards/database/database_test.go
  15. 4
      pkg/services/dashboards/service/dashboard_service.go
  16. 49
      pkg/services/folder/folderimpl/folder.go
  17. 22
      pkg/services/folder/folderimpl/folder_test.go
  18. 9
      pkg/services/guardian/accesscontrol_guardian_test.go
  19. 4
      pkg/services/libraryelements/accesscontrol.go
  20. 4
      pkg/services/libraryelements/libraryelements.go
  21. 5
      pkg/services/libraryelements/libraryelements_test.go
  22. 10
      pkg/services/librarypanels/librarypanels_test.go
  23. 4
      pkg/services/ngalert/api/api_provisioning_test.go
  24. 4
      pkg/services/ngalert/provisioning/alert_rules_test.go
  25. 3
      pkg/services/ngalert/testutil/testutil.go
  26. 12
      pkg/services/searchV2/auth.go
  27. 4
      pkg/services/searchV2/index_test.go
  28. 10
      pkg/services/searchV2/service.go
  29. 4
      pkg/services/searchV2/service_bench_test.go
  30. 6
      pkg/services/sqlstore/permissions/dashboard_test.go
  31. 3
      pkg/services/sqlstore/permissions/dashboards_bench_test.go

@ -587,7 +587,7 @@ func (hs *HTTPServer) GetAnnotationTags(c *contextmodel.ReqContext) response.Res
// where <type> is the type of annotation with id <id>.
// If annotationPermissionUpdate feature toggle is enabled, dashboard annotation scope will be resolved to the corresponding
// dashboard and folder scopes (eg, "dashboards:uid:<annotation_dashboard_uid>", "folders:uid:<parent_folder_uid>" etc).
func AnnotationTypeScopeResolver(annotationsRepo annotations.Repository, features featuremgmt.FeatureToggles, dashSvc dashboards.DashboardService, folderSvc folder.Service) (string, accesscontrol.ScopeAttributeResolver) {
func AnnotationTypeScopeResolver(annotationsRepo annotations.Repository, features featuremgmt.FeatureToggles, dashSvc dashboards.DashboardService, folderStore folder.Store) (string, accesscontrol.ScopeAttributeResolver) {
prefix := accesscontrol.ScopeAnnotationsProvider.GetResourceScope("")
return prefix, accesscontrol.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, initialScope string) ([]string, error) {
scopeParts := strings.Split(initialScope, ":")
@ -649,7 +649,7 @@ func AnnotationTypeScopeResolver(annotationsRepo annotations.Repository, feature
// Append dashboard parent scopes if dashboard is in a folder or the general scope if dashboard is not in a folder
if dashboard.FolderUID != "" {
scopes = append(scopes, dashboards.ScopeFoldersProvider.GetResourceScopeUID(dashboard.FolderUID))
inheritedScopes, err := dashboards.GetInheritedScopes(ctx, orgID, dashboard.FolderUID, folderSvc)
inheritedScopes, err := dashboards.GetInheritedScopes(ctx, orgID, dashboard.FolderUID, folderStore)
if err != nil {
return nil, err
}

@ -397,14 +397,15 @@ func TestAPI_Annotations(t *testing.T) {
dashService := &dashboards.FakeDashboardService{}
dashService.On("GetDashboard", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{UID: dashUID, FolderUID: folderUID, FolderID: 1}, nil)
folderService := &foldertest.FakeService{}
fStore := folder.NewFakeStore()
folderService.ExpectedFolder = &folder.Folder{UID: folderUID, ID: 1}
folderDB := &foldertest.FakeFolderStore{}
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(), zanzana.NewNoopClient())
hs.AccessControl.RegisterScopeAttributeResolver(AnnotationTypeScopeResolver(hs.annotationsRepo, hs.Features, dashService, folderService))
hs.AccessControl.RegisterScopeAttributeResolver(dashboards.NewDashboardIDScopeResolver(folderDB, dashService, folderService))
hs.AccessControl.RegisterScopeAttributeResolver(AnnotationTypeScopeResolver(hs.annotationsRepo, hs.Features, dashService, fStore))
hs.AccessControl.RegisterScopeAttributeResolver(dashboards.NewDashboardIDScopeResolver(folderDB, dashService, fStore))
})
var body io.Reader
if tt.body != "" {
@ -504,7 +505,7 @@ func TestService_AnnotationTypeScopeResolver(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
features := featuremgmt.WithFeatures(tc.featureToggles...)
prefix, resolver := AnnotationTypeScopeResolver(fakeAnnoRepo, features, dashSvc, &foldertest.FakeService{})
prefix, resolver := AnnotationTypeScopeResolver(fakeAnnoRepo, features, dashSvc, folder.NewFakeStore())
require.Equal(t, "annotations:id:", prefix)
resolved, err := resolver.Resolve(context.Background(), 1, tc.given)

@ -836,7 +836,7 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr
db := db.InitTestDB(t)
fStore := folderimpl.ProvideStore(db)
folderSvc := folderimpl.ProvideService(fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()),
dashboardStore, folderStore, db, features,
dashboardStore, folderStore, db, features, cfg, folderPermissions,
supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
if dashboardService == nil {
dashboardService, err = service.ProvideDashboardServiceImpl(

@ -1,7 +1,6 @@
package api
import (
"context"
"errors"
"net/http"
"strconv"
@ -12,12 +11,10 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"github.com/grafana/authlib/claims"
"github.com/grafana/grafana/pkg/api/apierrors"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/apimachinery/identity"
folderalpha1 "github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
"github.com/grafana/grafana/pkg/infra/metrics"
internalfolders "github.com/grafana/grafana/pkg/registry/apis/folders"
@ -31,7 +28,6 @@ import (
"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"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/util/errhttp"
@ -219,10 +215,6 @@ func (hs *HTTPServer) CreateFolder(c *contextmodel.ReqContext) response.Response
return apierrors.ToFolderErrorResponse(err)
}
if err := hs.setDefaultFolderPermissions(c.Req.Context(), cmd.OrgID, cmd.SignedInUser, folder); err != nil {
hs.log.Error("Could not set the default folder permissions", "folder", folder.Title, "user", cmd.SignedInUser, "error", err)
}
// Clear permission cache for the user who's created the folder, so that new permissions are fetched for their next call
// Required for cases when caller wants to immediately interact with the newly created object
hs.accesscontrolService.ClearUserPermissionCache(c.SignedInUser)
@ -236,36 +228,6 @@ func (hs *HTTPServer) CreateFolder(c *contextmodel.ReqContext) response.Response
return response.JSON(http.StatusOK, folderDTO)
}
func (hs *HTTPServer) setDefaultFolderPermissions(ctx context.Context, orgID int64, user identity.Requester, folder *folder.Folder) error {
if !hs.Cfg.RBAC.PermissionsOnCreation("folder") {
return nil
}
var permissions []accesscontrol.SetResourcePermissionCommand
if user.IsIdentityType(claims.TypeUser) {
userID, err := user.GetInternalID()
if err != nil {
return err
}
permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{
UserID: userID, Permission: dashboardaccess.PERMISSION_ADMIN.String(),
})
}
isNested := folder.ParentUID != ""
if !isNested || !hs.Features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) {
permissions = append(permissions, []accesscontrol.SetResourcePermissionCommand{
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
}...)
}
_, err := hs.folderPermissionsService.SetPermissions(ctx, orgID, folder.UID, permissions...)
return err
}
// swagger:route POST /folders/{folder_uid}/move folders moveFolder
//
// Move folder.

@ -464,14 +464,15 @@ func setupServer(b testing.TB, sc benchScenario, features featuremgmt.FeatureTog
features, tracing.InitializeTracerForTest(), zanzana.NewNoopClient(), sc.db, permreg.ProvidePermissionRegistry(),
)
fStore := folderimpl.ProvideStore(sc.db)
folderServiceWithFlagOn := folderimpl.ProvideService(fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore,
folderStore, sc.db, features, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
folderPermissions, err := ossaccesscontrol.ProvideFolderPermissions(
cfg, features, routing.NewRouteRegister(), sc.db, ac, license, &dashboards.FakeDashboardStore{}, folderServiceWithFlagOn, acSvc, sc.teamSvc, sc.userSvc, actionSets)
cfg, features, routing.NewRouteRegister(), sc.db, ac, license, &dashboards.FakeDashboardStore{}, fStore, acSvc, sc.teamSvc, sc.userSvc, actionSets)
require.NoError(b, err)
folderServiceWithFlagOn := folderimpl.ProvideService(fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore,
folderStore, sc.db, features, cfg, folderPermissions, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
dashboardPermissions, err := ossaccesscontrol.ProvideDashboardPermissions(
cfg, features, routing.NewRouteRegister(), sc.db, ac, license, &dashboards.FakeDashboardStore{}, folderServiceWithFlagOn, acSvc, sc.teamSvc, sc.userSvc, actionSets)
cfg, features, routing.NewRouteRegister(), sc.db, ac, license, &dashboards.FakeDashboardStore{}, fStore, acSvc, sc.teamSvc, sc.userSvc, actionSets)
require.NoError(b, err)
dashboardSvc, err := dashboardservice.ProvideDashboardServiceImpl(

@ -253,6 +253,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
authInfoService login.AuthInfoService, storageService store.StorageService,
notificationService notifications.Service, dashboardService dashboards.DashboardService,
dashboardProvisioningService dashboards.DashboardProvisioningService, folderService folder.Service,
folderStore folder.Store,
dsGuardian guardian.DatasourceGuardianProvider,
dashboardsnapshotsService dashboardsnapshots.Service, pluginSettings pluginSettings.Service,
avatarCacheServer *avatar.AvatarCacheServer, preferenceService pref.Service,
@ -379,7 +380,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
hs.registerRoutes()
// Register access control scope resolver for annotations
hs.AccessControl.RegisterScopeAttributeResolver(AnnotationTypeScopeResolver(hs.annotationsRepo, features, dashboardService, folderService))
hs.AccessControl.RegisterScopeAttributeResolver(AnnotationTypeScopeResolver(hs.annotationsRepo, features, dashboardService, folderStore))
if err := hs.declareFixedRoles(); err != nil {
return nil, err

@ -93,7 +93,7 @@ func registerDashboardRoles(cfg *setting.Cfg, features featuremgmt.FeatureToggle
func ProvideDashboardPermissions(
cfg *setting.Cfg, features featuremgmt.FeatureToggles, router routing.RouteRegister, sql db.DB, ac accesscontrol.AccessControl,
license licensing.Licensing, dashboardStore dashboards.Store, folderService folder.Service, service accesscontrol.Service,
license licensing.Licensing, dashboardStore dashboards.Store, folderStore folder.Store, service accesscontrol.Service,
teamService team.Service, userService user.Service, actionSetService resourcepermissions.ActionSetService,
) (*DashboardPermissionsService, error) {
getDashboard := func(ctx context.Context, orgID int64, resourceID string) (*dashboards.Dashboard, error) {
@ -145,7 +145,7 @@ func ProvideDashboardPermissions(
}
parentScope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(queryResult.UID)
nestedScopes, err := dashboards.GetInheritedScopes(ctx, orgID, queryResult.UID, folderService)
nestedScopes, err := dashboards.GetInheritedScopes(ctx, orgID, queryResult.UID, folderStore)
if err != nil {
return nil, err
}

@ -84,7 +84,7 @@ func registerFolderRoles(cfg *setting.Cfg, features featuremgmt.FeatureToggles,
func ProvideFolderPermissions(
cfg *setting.Cfg, features featuremgmt.FeatureToggles, router routing.RouteRegister, sql db.DB, accesscontrol accesscontrol.AccessControl,
license licensing.Licensing, dashboardStore dashboards.Store, folderService folder.Service, service accesscontrol.Service,
license licensing.Licensing, dashboardStore dashboards.Store, folderStore folder.Store, service accesscontrol.Service,
teamService team.Service, userService user.Service, actionSetService resourcepermissions.ActionSetService,
) (*FolderPermissionsService, error) {
if err := registerFolderRoles(cfg, features, service); err != nil {
@ -111,7 +111,7 @@ func ProvideFolderPermissions(
return nil
},
InheritedScopesSolver: func(ctx context.Context, orgID int64, resourceID string) ([]string, error) {
return dashboards.GetInheritedScopes(ctx, orgID, resourceID, folderService)
return dashboards.GetInheritedScopes(ctx, orgID, resourceID, folderStore)
},
Assignments: resourcepermissions.Assignments{
Users: true,

@ -0,0 +1,83 @@
package testutil
import (
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
acdb "github.com/grafana/grafana/pkg/services/accesscontrol/database"
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/permreg"
"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/folderimpl"
"github.com/grafana/grafana/pkg/services/licensing/licensingtest"
"github.com/grafana/grafana/pkg/services/org/orgimpl"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/supportbundles/bundleregistry"
"github.com/grafana/grafana/pkg/services/team/teamimpl"
"github.com/grafana/grafana/pkg/services/user/userimpl"
"github.com/grafana/grafana/pkg/setting"
)
func ProvideFolderPermissions(
features featuremgmt.FeatureToggles,
cfg *setting.Cfg,
sqlStore *sqlstore.SQLStore,
) (*ossaccesscontrol.FolderPermissionsService, error) {
actionSets := resourcepermissions.NewActionSetService(features)
acSvc := acimpl.ProvideOSSService(
cfg, acdb.ProvideService(sqlStore), actionSets, localcache.ProvideService(),
features, tracing.InitializeTracerForTest(), zanzana.NewNoopClient(), sqlStore, permreg.ProvidePermissionRegistry(),
)
license := licensingtest.NewFakeLicensing()
license.On("FeatureEnabled", "accesscontrol.enforcement").Return(true).Maybe()
ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient())
fStore := folderimpl.ProvideStore(sqlStore)
quotaService := quotatest.New(false, nil)
orgService, err := orgimpl.ProvideService(sqlStore, cfg, quotaService)
if err != nil {
return nil, err
}
teamSvc, err := teamimpl.ProvideService(sqlStore, cfg, tracing.InitializeTracerForTest())
if err != nil {
return nil, err
}
cache := localcache.ProvideService()
userSvc, err := userimpl.ProvideService(
sqlStore,
orgService,
cfg,
teamSvc,
cache,
tracing.InitializeTracerForTest(),
quotaService,
bundleregistry.ProvideService(),
)
if err != nil {
return nil, err
}
return ossaccesscontrol.ProvideFolderPermissions(
cfg,
features,
routing.NewRouteRegister(),
sqlStore,
ac,
license,
&dashboards.FakeDashboardStore{},
fStore,
acSvc,
teamSvc,
userSvc,
actionSets,
)
}

@ -16,6 +16,7 @@ import (
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
ftestutil "github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol/testutil"
"github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/annotations/testutil"
"github.com/grafana/grafana/pkg/services/authz/zanzana"
@ -227,10 +228,11 @@ func TestIntegrationAnnotationListingWithInheritedRBAC(t *testing.T) {
})
ac := acimpl.ProvideAccessControl(features, zanzana.NewNoopClient())
folderPermissions, err := ftestutil.ProvideFolderPermissions(features, cfg, sql)
require.NoError(t, err)
fStore := folderimpl.ProvideStore(sql)
folderSvc := folderimpl.ProvideService(fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore,
folderimpl.ProvideDashboardFolderStore(sql), sql, features, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
folderimpl.ProvideDashboardFolderStore(sql), sql, features, cfg, folderPermissions, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
cfg.AnnotationMaximumTagsLength = 60
store := NewXormStore(cfg, log.New("annotation.test"), sql, tagService)

@ -43,7 +43,7 @@ var (
)
// NewFolderNameScopeResolver provides an ScopeAttributeResolver that is able to convert a scope prefixed with "folders:name:" into an uid based scope.
func NewFolderNameScopeResolver(folderDB folder.FolderStore, folderSvc folder.Service) (string, ac.ScopeAttributeResolver) {
func NewFolderNameScopeResolver(folderDB folder.FolderStore, folderStore folder.Store) (string, ac.ScopeAttributeResolver) {
prefix := ScopeFoldersProvider.GetResourceScopeName("")
return prefix, ac.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) {
ctx, span := tracer.Start(ctx, "dashboards.NewFolderNameScopeResolver")
@ -63,7 +63,7 @@ func NewFolderNameScopeResolver(folderDB folder.FolderStore, folderSvc folder.Se
return nil, err
}
result, err := GetInheritedScopes(ctx, folder.OrgID, folder.UID, folderSvc)
result, err := GetInheritedScopes(ctx, folder.OrgID, folder.UID, folderStore)
if err != nil {
return nil, err
}
@ -74,7 +74,7 @@ func NewFolderNameScopeResolver(folderDB folder.FolderStore, folderSvc folder.Se
}
// NewFolderIDScopeResolver provides an ScopeAttributeResolver that is able to convert a scope prefixed with "folders:id:" into an uid based scope.
func NewFolderIDScopeResolver(folderDB folder.FolderStore, folderSvc folder.Service) (string, ac.ScopeAttributeResolver) {
func NewFolderIDScopeResolver(folderDB folder.FolderStore, folderStore folder.Store) (string, ac.ScopeAttributeResolver) {
prefix := ScopeFoldersProvider.GetResourceScope("")
return prefix, ac.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) {
ctx, span := tracer.Start(ctx, "dashboards.NewFolderIDScopeResolver")
@ -98,7 +98,7 @@ func NewFolderIDScopeResolver(folderDB folder.FolderStore, folderSvc folder.Serv
return nil, err
}
result, err := GetInheritedScopes(ctx, folder.OrgID, folder.UID, folderSvc)
result, err := GetInheritedScopes(ctx, folder.OrgID, folder.UID, folderStore)
if err != nil {
return nil, err
}
@ -110,7 +110,7 @@ func NewFolderIDScopeResolver(folderDB folder.FolderStore, folderSvc folder.Serv
// NewFolderUIDScopeResolver provides an ScopeAttributeResolver that is able to convert a scope prefixed with "folders:uid:"
// into uid based scopes for folder and its parents
func NewFolderUIDScopeResolver(folderSvc folder.Service) (string, ac.ScopeAttributeResolver) {
func NewFolderUIDScopeResolver(folderStore folder.Store) (string, ac.ScopeAttributeResolver) {
prefix := ScopeFoldersProvider.GetResourceScopeUID("")
return prefix, ac.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) {
ctx, span := tracer.Start(ctx, "dashboards.NewFolderUIDScopeResolver")
@ -125,7 +125,7 @@ func NewFolderUIDScopeResolver(folderSvc folder.Service) (string, ac.ScopeAttrib
return nil, err
}
inheritedScopes, err := GetInheritedScopes(ctx, orgID, uid, folderSvc)
inheritedScopes, err := GetInheritedScopes(ctx, orgID, uid, folderStore)
if err != nil {
return nil, err
}
@ -135,7 +135,7 @@ func NewFolderUIDScopeResolver(folderSvc folder.Service) (string, ac.ScopeAttrib
// NewDashboardIDScopeResolver provides an ScopeAttributeResolver that is able to convert a scope prefixed with "dashboards:id:"
// into uid based scopes for both dashboard and folder
func NewDashboardIDScopeResolver(folderDB folder.FolderStore, ds DashboardService, folderSvc folder.Service) (string, ac.ScopeAttributeResolver) {
func NewDashboardIDScopeResolver(folderDB folder.FolderStore, ds DashboardService, folderStore folder.Store) (string, ac.ScopeAttributeResolver) {
prefix := ScopeDashboardsProvider.GetResourceScope("")
return prefix, ac.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) {
ctx, span := tracer.Start(ctx, "dashboards.NewDashboardIDScopeResolver")
@ -155,13 +155,13 @@ func NewDashboardIDScopeResolver(folderDB folder.FolderStore, ds DashboardServic
return nil, err
}
return resolveDashboardScope(ctx, folderDB, orgID, dashboard, folderSvc)
return resolveDashboardScope(ctx, folderDB, orgID, dashboard, folderStore)
})
}
// NewDashboardUIDScopeResolver provides an ScopeAttributeResolver that is able to convert a scope prefixed with "dashboards:uid:"
// into uid based scopes for both dashboard and folder
func NewDashboardUIDScopeResolver(folderDB folder.FolderStore, ds DashboardService, folderSvc folder.Service) (string, ac.ScopeAttributeResolver) {
func NewDashboardUIDScopeResolver(folderDB folder.FolderStore, ds DashboardService, folderStore folder.Store) (string, ac.ScopeAttributeResolver) {
prefix := ScopeDashboardsProvider.GetResourceScopeUID("")
return prefix, ac.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) {
ctx, span := tracer.Start(ctx, "dashboards.NewDashboardUIDScopeResolver")
@ -181,11 +181,11 @@ func NewDashboardUIDScopeResolver(folderDB folder.FolderStore, ds DashboardServi
return nil, err
}
return resolveDashboardScope(ctx, folderDB, orgID, dashboard, folderSvc)
return resolveDashboardScope(ctx, folderDB, orgID, dashboard, folderStore)
})
}
func resolveDashboardScope(ctx context.Context, folderDB folder.FolderStore, orgID int64, dashboard *Dashboard, folderSvc folder.Service) ([]string, error) {
func resolveDashboardScope(ctx context.Context, folderDB folder.FolderStore, orgID int64, dashboard *Dashboard, folderStore folder.Store) ([]string, error) {
ctx, span := tracer.Start(ctx, "dashboards.resolveDashboardScope")
span.End()
@ -208,7 +208,7 @@ func resolveDashboardScope(ctx context.Context, folderDB folder.FolderStore, org
folderUID = folder.UID
}
result, err := GetInheritedScopes(ctx, orgID, folderUID, folderSvc)
result, err := GetInheritedScopes(ctx, orgID, folderUID, folderStore)
if err != nil {
return nil, err
}
@ -223,17 +223,24 @@ func resolveDashboardScope(ctx context.Context, folderDB folder.FolderStore, org
return result, nil
}
func GetInheritedScopes(ctx context.Context, orgID int64, folderUID string, folderSvc folder.Service) ([]string, error) {
func GetInheritedScopes(ctx context.Context, orgID int64, folderUID string, folderStore folder.Store) ([]string, error) {
ctx, span := tracer.Start(ctx, "dashboards.GetInheritedScopes")
span.End()
if folderUID == ac.GeneralFolderUID {
return nil, nil
}
ancestors, err := folderSvc.GetParents(ctx, folder.GetParentsQuery{
UID: folderUID,
OrgID: orgID,
})
var ancestors []*folder.Folder
var err error
if folderUID == folder.SharedWithMeFolderUID {
ancestors = []*folder.Folder{&folder.SharedWithMeFolder}
} else {
ancestors, err = folderStore.GetParents(ctx, folder.GetParentsQuery{
UID: folderUID,
OrgID: orgID,
})
}
if err != nil {
if errors.Is(err, folder.ErrFolderNotFound) {

@ -18,7 +18,7 @@ import (
func TestNewFolderNameScopeResolver(t *testing.T) {
t.Run("prefix should be expected", func(t *testing.T) {
prefix, _ := NewFolderNameScopeResolver(foldertest.NewFakeFolderStore(t), foldertest.NewFakeService())
prefix, _ := NewFolderNameScopeResolver(foldertest.NewFakeFolderStore(t), folder.NewFakeStore())
require.Equal(t, "folders:name:", prefix)
})
@ -31,7 +31,7 @@ func TestNewFolderNameScopeResolver(t *testing.T) {
scope := "folders:name:" + title
_, resolver := NewFolderNameScopeResolver(folderStore, foldertest.NewFakeService())
_, resolver := NewFolderNameScopeResolver(folderStore, folder.NewFakeStore())
resolvedScopes, err := resolver.Resolve(context.Background(), orgId, scope)
require.NoError(t, err)
require.Len(t, resolvedScopes, 1)
@ -49,8 +49,8 @@ func TestNewFolderNameScopeResolver(t *testing.T) {
scope := "folders:name:" + title
folderSvc := foldertest.NewFakeService()
folderSvc.ExpectedFolders = []*folder.Folder{
fStore := folder.NewFakeStore()
fStore.ExpectedParentFolders = []*folder.Folder{
{
UID: "parent",
},
@ -58,7 +58,7 @@ func TestNewFolderNameScopeResolver(t *testing.T) {
UID: "grandparent",
},
}
_, resolver := NewFolderNameScopeResolver(folderStore, folderSvc)
_, resolver := NewFolderNameScopeResolver(folderStore, fStore)
resolvedScopes, err := resolver.Resolve(context.Background(), orgId, scope)
require.NoError(t, err)
@ -75,20 +75,20 @@ func TestNewFolderNameScopeResolver(t *testing.T) {
folderStore.AssertCalled(t, "GetFolderByTitle", mock.Anything, orgId, title, mock.Anything)
})
t.Run("resolver should fail if input scope is not expected", func(t *testing.T) {
_, resolver := NewFolderNameScopeResolver(foldertest.NewFakeFolderStore(t), foldertest.NewFakeService())
_, resolver := NewFolderNameScopeResolver(foldertest.NewFakeFolderStore(t), folder.NewFakeStore())
_, err := resolver.Resolve(context.Background(), rand.Int63(), "folders:id:123")
require.ErrorIs(t, err, ac.ErrInvalidScope)
})
t.Run("resolver should fail if resource of input scope is empty", func(t *testing.T) {
_, resolver := NewFolderNameScopeResolver(foldertest.NewFakeFolderStore(t), foldertest.NewFakeService())
_, resolver := NewFolderNameScopeResolver(foldertest.NewFakeFolderStore(t), folder.NewFakeStore())
_, err := resolver.Resolve(context.Background(), rand.Int63(), "folders:name:")
require.ErrorIs(t, err, ac.ErrInvalidScope)
})
t.Run("returns 'not found' if folder does not exist", func(t *testing.T) {
folderStore := foldertest.NewFakeFolderStore(t)
_, resolver := NewFolderNameScopeResolver(folderStore, foldertest.NewFakeService())
_, resolver := NewFolderNameScopeResolver(folderStore, folder.NewFakeStore())
orgId := rand.Int63()
folderStore.On("GetFolderByTitle", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, ErrDashboardNotFound).Once()
@ -103,12 +103,12 @@ func TestNewFolderNameScopeResolver(t *testing.T) {
func TestNewFolderIDScopeResolver(t *testing.T) {
t.Run("prefix should be expected", func(t *testing.T) {
prefix, _ := NewFolderIDScopeResolver(foldertest.NewFakeFolderStore(t), foldertest.NewFakeService())
prefix, _ := NewFolderIDScopeResolver(foldertest.NewFakeFolderStore(t), folder.NewFakeStore())
require.Equal(t, "folders:id:", prefix)
})
t.Run("resolver should fail if input scope is not expected", func(t *testing.T) {
_, resolver := NewFolderIDScopeResolver(foldertest.NewFakeFolderStore(t), foldertest.NewFakeService())
_, resolver := NewFolderIDScopeResolver(foldertest.NewFakeFolderStore(t), folder.NewFakeStore())
_, err := resolver.Resolve(context.Background(), rand.Int63(), "folders:uid:123")
require.ErrorIs(t, err, ac.ErrInvalidScope)
@ -118,7 +118,7 @@ func TestNewFolderIDScopeResolver(t *testing.T) {
var (
orgId = rand.Int63()
scope = "folders:id:0"
_, resolver = NewFolderIDScopeResolver(foldertest.NewFakeFolderStore(t), foldertest.NewFakeService())
_, resolver = NewFolderIDScopeResolver(foldertest.NewFakeFolderStore(t), folder.NewFakeStore())
)
resolved, err := resolver.Resolve(context.Background(), orgId, scope)
@ -129,7 +129,7 @@ func TestNewFolderIDScopeResolver(t *testing.T) {
})
t.Run("resolver should fail if resource of input scope is empty", func(t *testing.T) {
_, resolver := NewFolderIDScopeResolver(foldertest.NewFakeFolderStore(t), foldertest.NewFakeService())
_, resolver := NewFolderIDScopeResolver(foldertest.NewFakeFolderStore(t), folder.NewFakeStore())
_, err := resolver.Resolve(context.Background(), rand.Int63(), "folders:id:")
require.ErrorIs(t, err, ac.ErrInvalidScope)
@ -137,7 +137,7 @@ func TestNewFolderIDScopeResolver(t *testing.T) {
t.Run("returns 'not found' if folder does not exist", func(t *testing.T) {
folderStore := foldertest.NewFakeFolderStore(t)
folderStore.On("GetFolderByID", mock.Anything, mock.Anything, mock.Anything).Return(nil, ErrDashboardNotFound).Once()
_, resolver := NewFolderIDScopeResolver(folderStore, foldertest.NewFakeService())
_, resolver := NewFolderIDScopeResolver(folderStore, folder.NewFakeStore())
orgId := rand.Int63()
scope := "folders:id:10"
@ -149,12 +149,12 @@ func TestNewFolderIDScopeResolver(t *testing.T) {
func TestNewDashboardIDScopeResolver(t *testing.T) {
t.Run("prefix should be expected", func(t *testing.T) {
prefix, _ := NewDashboardIDScopeResolver(foldertest.NewFakeFolderStore(t), &FakeDashboardService{}, foldertest.NewFakeService())
prefix, _ := NewDashboardIDScopeResolver(foldertest.NewFakeFolderStore(t), &FakeDashboardService{}, folder.NewFakeStore())
require.Equal(t, "dashboards:id:", prefix)
})
t.Run("resolver should fail if input scope is not expected", func(t *testing.T) {
_, resolver := NewDashboardIDScopeResolver(foldertest.NewFakeFolderStore(t), &FakeDashboardService{}, foldertest.NewFakeService())
_, resolver := NewDashboardIDScopeResolver(foldertest.NewFakeFolderStore(t), &FakeDashboardService{}, folder.NewFakeStore())
_, err := resolver.Resolve(context.Background(), rand.Int63(), "dashboards:uid:123")
require.ErrorIs(t, err, ac.ErrInvalidScope)
})
@ -162,12 +162,12 @@ func TestNewDashboardIDScopeResolver(t *testing.T) {
func TestNewDashboardUIDScopeResolver(t *testing.T) {
t.Run("prefix should be expected", func(t *testing.T) {
prefix, _ := NewDashboardUIDScopeResolver(foldertest.NewFakeFolderStore(t), &FakeDashboardService{}, foldertest.NewFakeService())
prefix, _ := NewDashboardUIDScopeResolver(foldertest.NewFakeFolderStore(t), &FakeDashboardService{}, folder.NewFakeStore())
require.Equal(t, "dashboards:uid:", prefix)
})
t.Run("resolver should fail if input scope is not expected", func(t *testing.T) {
_, resolver := NewDashboardUIDScopeResolver(foldertest.NewFakeFolderStore(t), &FakeDashboardService{}, foldertest.NewFakeService())
_, resolver := NewDashboardUIDScopeResolver(foldertest.NewFakeFolderStore(t), &FakeDashboardService{}, folder.NewFakeStore())
_, err := resolver.Resolve(context.Background(), rand.Int63(), "dashboards:id:123")
require.ErrorIs(t, err, ac.ErrInvalidScope)
})

@ -303,8 +303,10 @@ func TestIntegrationDashboardInheritedFolderRBAC(t *testing.T) {
guardian.New = origNewGuardian
})
folderPermissions := mock.NewMockedPermissionsService()
folderStore := folderimpl.ProvideStore(sqlStore)
folderSvc := folderimpl.ProvideService(folderStore, mock.New(), bus.ProvideBus(tracer), dashboardWriteStore, folderimpl.ProvideDashboardFolderStore(sqlStore), sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
folderSvc := folderimpl.ProvideService(folderStore, mock.New(), bus.ProvideBus(tracer), dashboardWriteStore, folderimpl.ProvideDashboardFolderStore(sqlStore),
sqlStore, features, cfg, folderPermissions, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
parentUID := ""
for i := 0; ; i++ {

@ -16,6 +16,8 @@ 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/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol/testutil"
"github.com/grafana/grafana/pkg/services/authz/zanzana"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
@ -829,10 +831,11 @@ func TestIntegrationFindDashboardsByTitle(t *testing.T) {
insertTestDashboard(t, dashboardStore, "dashboard under general", orgID, 0, "", false)
ac := acimpl.ProvideAccessControl(features, zanzana.NewNoopClient())
folderPermissions := mock.NewMockedPermissionsService()
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
fStore := folderimpl.ProvideStore(sqlStore)
folderServiceWithFlagOn := folderimpl.ProvideService(fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore,
folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
folderStore, sqlStore, features, cfg, folderPermissions, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
user := &user.SignedInUser{
OrgID: 1,
@ -951,8 +954,11 @@ func TestIntegrationFindDashboardsByFolder(t *testing.T) {
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
fStore := folderimpl.ProvideStore(sqlStore)
folderPermissions, err := testutil.ProvideFolderPermissions(features, cfg, sqlStore)
require.NoError(t, err)
folderServiceWithFlagOn := folderimpl.ProvideService(fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore,
folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
folderStore, sqlStore, features, cfg, folderPermissions, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
user := &user.SignedInUser{
OrgID: 1,

@ -81,8 +81,8 @@ func ProvideDashboardServiceImpl(
metrics: newDashboardsMetrics(r),
}
ac.RegisterScopeAttributeResolver(dashboards.NewDashboardIDScopeResolver(folderStore, dashSvc, folderSvc))
ac.RegisterScopeAttributeResolver(dashboards.NewDashboardUIDScopeResolver(folderStore, dashSvc, folderSvc))
ac.RegisterScopeAttributeResolver(dashboards.NewDashboardIDScopeResolver(folderStore, dashSvc, fStore))
ac.RegisterScopeAttributeResolver(dashboards.NewDashboardUIDScopeResolver(folderStore, dashSvc, fStore))
if err := folderSvc.RegisterService(dashSvc); err != nil {
return nil, err

@ -11,6 +11,7 @@ import (
"sync"
"time"
"github.com/grafana/authlib/claims"
"github.com/grafana/dskit/concurrency"
"github.com/prometheus/client_golang/prometheus"
"go.opentelemetry.io/otel/attribute"
@ -29,11 +30,13 @@ import (
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/services/store/entity"
"github.com/grafana/grafana/pkg/services/supportbundles"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
@ -46,6 +49,8 @@ type Service struct {
dashboardStore dashboards.Store
dashboardFolderStore folder.FolderStore
features featuremgmt.FeatureToggles
cfg *setting.Cfg
folderPermissions accesscontrol.FolderPermissionsService
accessControl accesscontrol.AccessControl
// bus is currently used to publish event in case of folder full path change.
// For example when a folder is moved to another folder or when a folder is renamed.
@ -65,6 +70,8 @@ func ProvideService(
folderStore folder.FolderStore,
db db.DB, // DB for the (new) nested folder store
features featuremgmt.FeatureToggles,
cfg *setting.Cfg,
folderPermissions accesscontrol.FolderPermissionsService,
supportBundles supportbundles.Service,
r prometheus.Registerer,
tracer tracing.Tracer,
@ -75,6 +82,8 @@ func ProvideService(
dashboardFolderStore: folderStore,
store: store,
features: features,
cfg: cfg,
folderPermissions: folderPermissions,
accessControl: ac,
bus: bus,
db: db,
@ -86,9 +95,9 @@ func ProvideService(
supportBundles.RegisterSupportItemCollector(srv.supportBundleCollector())
ac.RegisterScopeAttributeResolver(dashboards.NewFolderNameScopeResolver(folderStore, srv))
ac.RegisterScopeAttributeResolver(dashboards.NewFolderIDScopeResolver(folderStore, srv))
ac.RegisterScopeAttributeResolver(dashboards.NewFolderUIDScopeResolver(srv))
ac.RegisterScopeAttributeResolver(dashboards.NewFolderNameScopeResolver(folderStore, store))
ac.RegisterScopeAttributeResolver(dashboards.NewFolderIDScopeResolver(folderStore, store))
ac.RegisterScopeAttributeResolver(dashboards.NewFolderUIDScopeResolver(store))
return srv
}
@ -658,9 +667,43 @@ func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (
if nestedFolder != nil && nestedFolder.ParentUID != "" {
f.ParentUID = nestedFolder.ParentUID
}
if err = s.setDefaultFolderPermissions(ctx, cmd.OrgID, user, f); err != nil {
return nil, err
}
return f, nil
}
func (s *Service) setDefaultFolderPermissions(ctx context.Context, orgID int64, user identity.Requester, folder *folder.Folder) error {
if !s.cfg.RBAC.PermissionsOnCreation("folder") {
return nil
}
var permissions []accesscontrol.SetResourcePermissionCommand
if user.IsIdentityType(claims.TypeUser) {
userID, err := user.GetInternalID()
if err != nil {
return err
}
permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{
UserID: userID, Permission: dashboardaccess.PERMISSION_ADMIN.String(),
})
}
isNested := folder.ParentUID != ""
if !isNested || !s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) {
permissions = append(permissions, []accesscontrol.SetResourcePermissionCommand{
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
}...)
}
_, err := s.folderPermissions.SetPermissions(ctx, orgID, folder.UID, permissions...)
return err
}
func (s *Service) Update(ctx context.Context, cmd *folder.UpdateFolderCommand) (*folder.Folder, error) {
ctx, span := s.tracer.Start(ctx, "folder.Update")
defer span.End()

@ -62,10 +62,11 @@ func TestIntegrationProvideFolderService(t *testing.T) {
}
t.Run("should register scope resolvers", func(t *testing.T) {
ac := acmock.New()
db, _ := db.InitTestDBWithCfg(t)
db, cfg := db.InitTestDBWithCfg(t)
folderPermissions := acmock.NewMockedPermissionsService()
store := ProvideStore(db)
ProvideService(store, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), nil, nil, db,
featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
featuremgmt.WithFeatures(), cfg, folderPermissions, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
require.Len(t, ac.Calls.RegisterAttributeScopeResolver, 3)
})
@ -97,6 +98,7 @@ func TestIntegrationFolderService(t *testing.T) {
dashboardFolderStore: folderStore,
store: nestedFolderStore,
features: features,
cfg: cfg,
bus: bus.ProvideBus(tracing.InitializeTracerForTest()),
db: db,
accessControl: acimpl.ProvideAccessControl(features, zanzana.NewNoopClient()),
@ -439,6 +441,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
dashboardFolderStore: folderStore,
store: nestedFolderStore,
features: featuresFlagOn,
cfg: cfg,
bus: b,
db: db,
accessControl: ac,
@ -494,7 +497,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOn, db, serviceWithFlagOn, dashSrv, ac)
require.NoError(t, err)
elementService := libraryelements.ProvideService(cfg, db, routeRegister, serviceWithFlagOn, featuresFlagOn, ac)
elementService := libraryelements.ProvideService(cfg, db, routeRegister, serviceWithFlagOn, serviceWithFlagOn.store, featuresFlagOn, ac)
lps, err := librarypanels.ProvideService(cfg, db, routeRegister, elementService, serviceWithFlagOn)
require.NoError(t, err)
@ -554,6 +557,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
dashboardFolderStore: folderStore,
store: nestedFolderStore,
features: featuresFlagOff,
cfg: cfg,
bus: b,
db: db,
registry: make(map[string]folder.RegistryService),
@ -576,7 +580,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOff, db, serviceWithFlagOff, dashSrv, ac)
require.NoError(t, err)
elementService := libraryelements.ProvideService(cfg, db, routeRegister, serviceWithFlagOff, featuresFlagOff, ac)
elementService := libraryelements.ProvideService(cfg, db, routeRegister, serviceWithFlagOff, serviceWithFlagOff.store, featuresFlagOff, ac)
lps, err := librarypanels.ProvideService(cfg, db, routeRegister, elementService, serviceWithFlagOff)
require.NoError(t, err)
@ -632,6 +636,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
log: slog.New(logtest.NewTestHandler(t)).With("logger", "test-folder-service"),
dashboardFolderStore: folderStore,
features: featuresFlagOff,
cfg: cfg,
bus: b,
db: db,
registry: make(map[string]folder.RegistryService),
@ -705,7 +710,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
CanEditValue: true,
})
elementService := libraryelements.ProvideService(cfg, db, routeRegister, tc.service, tc.featuresFlag, ac)
elementService := libraryelements.ProvideService(cfg, db, routeRegister, tc.service, tc.service.store, tc.featuresFlag, ac)
lps, err := librarypanels.ProvideService(cfg, db, routeRegister, elementService, tc.service)
require.NoError(t, err)
@ -810,6 +815,7 @@ func TestNestedFolderServiceFeatureToggle(t *testing.T) {
dashboardStore: &dashStore,
dashboardFolderStore: dashboardFolderStore,
features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders),
cfg: setting.NewCfg(),
accessControl: acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()),
metrics: newFoldersMetrics(nil),
tracer: tracing.InitializeTracerForTest(),
@ -847,6 +853,7 @@ func TestFolderServiceDualWrite(t *testing.T) {
dashboardStore: dashStore,
dashboardFolderStore: dashboardFolderStore,
features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders),
cfg: cfg,
accessControl: acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()),
metrics: newFoldersMetrics(nil),
tracer: tracing.InitializeTracerForTest(),
@ -1479,6 +1486,7 @@ func TestIntegrationNestedFolderSharedWithMe(t *testing.T) {
dashboardFolderStore: folderStore,
store: nestedFolderStore,
features: featuresFlagOn,
cfg: cfg,
bus: b,
db: db,
accessControl: ac,
@ -1901,6 +1909,7 @@ func TestFolderServiceGetFolder(t *testing.T) {
dashboardFolderStore: folderStore,
store: nestedFolderStore,
features: features,
cfg: cfg,
bus: b,
db: db,
accessControl: ac,
@ -1983,6 +1992,7 @@ func TestFolderServiceGetFolders(t *testing.T) {
dashboardFolderStore: folderStore,
store: nestedFolderStore,
features: featuresFlagOff,
cfg: cfg,
bus: b,
db: db,
accessControl: ac,
@ -2070,6 +2080,7 @@ func TestGetChildrenFilterByPermission(t *testing.T) {
dashboardFolderStore: folderStore,
store: nestedFolderStore,
features: features,
cfg: cfg,
bus: b,
db: db,
accessControl: ac,
@ -2533,6 +2544,7 @@ func setup(t *testing.T, dashStore dashboards.Store, dashboardFolderStore folder
dashboardFolderStore: dashboardFolderStore,
store: nestedFolderStore,
features: features,
cfg: setting.NewCfg(),
accessControl: ac,
db: db,
metrics: newFoldersMetrics(nil),

@ -14,6 +14,7 @@ import (
"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"
"github.com/grafana/grafana/pkg/services/folder/foldertest"
"github.com/grafana/grafana/pkg/services/licensing/licensingtest"
"github.com/grafana/grafana/pkg/services/user"
@ -958,13 +959,13 @@ func setupAccessControlGuardianTest(
fakeDashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardQuery")).Maybe().Return(d, nil)
ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient())
folderSvc := foldertest.NewFakeService()
fStore := folder.NewFakeStore()
folderStore := foldertest.NewFakeFolderStore(t)
ac.RegisterScopeAttributeResolver(dashboards.NewDashboardUIDScopeResolver(folderStore, fakeDashboardService, folderSvc))
ac.RegisterScopeAttributeResolver(dashboards.NewFolderUIDScopeResolver(folderSvc))
ac.RegisterScopeAttributeResolver(dashboards.NewFolderIDScopeResolver(folderStore, folderSvc))
ac.RegisterScopeAttributeResolver(dashboards.NewDashboardUIDScopeResolver(folderStore, fakeDashboardService, fStore))
ac.RegisterScopeAttributeResolver(dashboards.NewFolderUIDScopeResolver(fStore))
ac.RegisterScopeAttributeResolver(dashboards.NewFolderIDScopeResolver(folderStore, fStore))
license := licensingtest.NewFakeLicensing()
license.On("FeatureEnabled", "accesscontrol.enforcement").Return(true).Maybe()

@ -35,7 +35,7 @@ var (
// LibraryPanelUIDScopeResolver provides a ScopeAttributeResolver that is able to convert a scope prefixed with "library.panels:uid:"
// into uid based scopes for a library panel and its associated folder hierarchy
func LibraryPanelUIDScopeResolver(l *LibraryElementService, folderSvc folder.Service) (string, ac.ScopeAttributeResolver) {
func LibraryPanelUIDScopeResolver(l *LibraryElementService, folderStore folder.Store) (string, ac.ScopeAttributeResolver) {
prefix := ScopeLibraryPanelsProvider.GetResourceScopeUID("")
return prefix, ac.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) {
if !strings.HasPrefix(scope, prefix) {
@ -60,7 +60,7 @@ func LibraryPanelUIDScopeResolver(l *LibraryElementService, folderSvc folder.Ser
return nil, err
}
inheritedScopes, err := dashboards.GetInheritedScopes(ctx, orgID, libElDTO.FolderUID, folderSvc)
inheritedScopes, err := dashboards.GetInheritedScopes(ctx, orgID, libElDTO.FolderUID, folderStore)
if err != nil {
return nil, err
}

@ -15,7 +15,7 @@ import (
"github.com/grafana/grafana/pkg/setting"
)
func ProvideService(cfg *setting.Cfg, sqlStore db.DB, routeRegister routing.RouteRegister, folderService folder.Service, features featuremgmt.FeatureToggles, ac accesscontrol.AccessControl) *LibraryElementService {
func ProvideService(cfg *setting.Cfg, sqlStore db.DB, routeRegister routing.RouteRegister, folderService folder.Service, folderStore folder.Store, features featuremgmt.FeatureToggles, ac accesscontrol.AccessControl) *LibraryElementService {
l := &LibraryElementService{
Cfg: cfg,
SQLStore: sqlStore,
@ -27,7 +27,7 @@ func ProvideService(cfg *setting.Cfg, sqlStore db.DB, routeRegister routing.Rout
}
l.registerAPIEndpoints()
ac.RegisterScopeAttributeResolver(LibraryPanelUIDScopeResolver(l, l.folderService))
ac.RegisterScopeAttributeResolver(LibraryPanelUIDScopeResolver(l, folderStore))
return l
}

@ -324,6 +324,7 @@ func createFolder(t *testing.T, sc scenarioContext, title string) *folder.Folder
features := featuremgmt.WithFeatures()
cfg := setting.NewCfg()
ac := actest.FakeAccessControl{ExpectedEvaluate: true}
folderPermissions := acmock.NewMockedPermissionsService()
quotaService := quotatest.New(false, nil)
dashboardStore, err := database.ProvideDashboardStore(sc.sqlStore, cfg, features, tagimpl.ProvideService(sc.sqlStore), quotaService)
require.NoError(t, err)
@ -331,7 +332,7 @@ func createFolder(t *testing.T, sc scenarioContext, title string) *folder.Folder
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(), nil, tracing.InitializeTracerForTest())
features, cfg, folderPermissions, supportbundlestest.NewFakeBundleService(), 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{
@ -463,7 +464,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
guardian.InitAccessControlGuardian(cfg, ac, dashService)
fStore := folderimpl.ProvideStore(sqlStore)
folderSrv := folderimpl.ProvideService(fStore, ac, bus.ProvideBus(tracer), dashboardStore, folderStore, sqlStore,
features, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
features, cfg, folderPermissions, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
service := LibraryElementService{
Cfg: cfg,
features: featuremgmt.WithFeatures(),

@ -18,6 +18,7 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol/testutil"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/database"
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/service"
@ -747,6 +748,7 @@ func createFolder(t *testing.T, sc scenarioContext, title string) *folder.Folder
features := featuremgmt.WithFeatures()
ac := actest.FakeAccessControl{ExpectedEvaluate: true}
folderPermissions := acmock.NewMockedPermissionsService()
cfg := setting.NewCfg()
quotaService := quotatest.New(false, nil)
dashboardStore, err := database.ProvideDashboardStore(sc.sqlStore, cfg, features, tagimpl.ProvideService(sc.sqlStore), quotaService)
@ -754,7 +756,7 @@ func createFolder(t *testing.T, sc scenarioContext, title string) *folder.Folder
folderStore := folderimpl.ProvideDashboardFolderStore(sc.sqlStore)
fStore := folderimpl.ProvideStore(sc.sqlStore)
s := folderimpl.ProvideService(fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, sc.sqlStore,
features, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
features, cfg, folderPermissions, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
t.Logf("Creating folder with title and UID %q", title)
ctx := identity.WithRequester(context.Background(), sc.user)
@ -838,10 +840,12 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
require.NoError(t, err)
fStore := folderimpl.ProvideStore(sqlStore)
folderPermissions, err := testutil.ProvideFolderPermissions(features, cfg, sqlStore)
require.NoError(t, err)
folderService := folderimpl.ProvideService(fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, sqlStore,
features, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
features, cfg, folderPermissions, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
elementService := libraryelements.ProvideService(cfg, sqlStore, routing.NewRouteRegister(), folderService, features, ac)
elementService := libraryelements.ProvideService(cfg, sqlStore, routing.NewRouteRegister(), folderService, fStore, features, ac)
service := LibraryPanelService{
Cfg: cfg,
SQLStore: sqlStore,

@ -27,6 +27,7 @@ import (
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/database"
@ -1817,13 +1818,14 @@ func createTestEnv(t *testing.T, testConfig string) testEnvironment {
}}, nil).Maybe()
ac := &recordingAccessControlFake{}
folderPermissions := acmock.NewMockedPermissionsService()
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotatest.New(false, nil))
require.NoError(t, err)
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
fStore := folderimpl.ProvideStore(sqlStore)
folderService := folderimpl.ProvideService(fStore, actest.FakeAccessControl{ExpectedEvaluate: true}, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, sqlStore,
featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
featuremgmt.WithFeatures(), cfg, folderPermissions, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
store := store.DBstore{
Logger: log,
SQLStore: sqlStore,

@ -1546,11 +1546,11 @@ func TestProvisiongWithFullpath(t *testing.T) {
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
_, dashboardStore := testutil.SetupDashboardService(t, sqlStore, folderStore, cfg)
ac := acmock.New()
folderPermissions := acmock.NewMockedPermissionsService()
features := featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)
fStore := folderimpl.ProvideStore(sqlStore)
folderService := folderimpl.ProvideService(fStore, ac, inProcBus, dashboardStore, folderStore, sqlStore,
features, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
features, cfg, folderPermissions, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
ruleService := createAlertRuleService(t, folderService)
var orgID int64 = 1

@ -27,9 +27,10 @@ import (
func SetupFolderService(tb testing.TB, cfg *setting.Cfg, db db.DB, dashboardStore dashboards.Store, folderStore *folderimpl.DashboardFolderStoreImpl, bus *bus.InProcBus, features featuremgmt.FeatureToggles, ac accesscontrol.AccessControl) folder.Service {
tb.Helper()
folderPermissions := acmock.NewMockedPermissionsService()
fStore := folderimpl.ProvideStore(db)
return folderimpl.ProvideService(fStore, ac, bus, dashboardStore, folderStore, db,
features, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
features, cfg, folderPermissions, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
}
func SetupDashboardService(tb testing.TB, sqlStore db.DB, fs *folderimpl.DashboardFolderStoreImpl, cfg *setting.Cfg) (*dashboardservice.DashboardServiceImpl, dashboards.Store) {

@ -22,24 +22,24 @@ type FutureAuthService interface {
var _ FutureAuthService = (*simpleAuthService)(nil)
type simpleAuthService struct {
sql db.DB
ac accesscontrol.Service
folderService folder.Service
logger log.Logger
sql db.DB
ac accesscontrol.Service
folderStore folder.Store
logger log.Logger
}
func (a *simpleAuthService) GetDashboardReadFilter(ctx context.Context, orgID int64, user *user.SignedInUser) (ResourceFilter, error) {
canReadDashboard, canReadFolder := accesscontrol.Checker(user, dashboards.ActionDashboardsRead), accesscontrol.Checker(user, dashboards.ActionFoldersRead)
return func(kind entityKind, uid, parent string) bool {
if kind == entityKindFolder {
scopes, err := dashboards.GetInheritedScopes(ctx, orgID, uid, a.folderService)
scopes, err := dashboards.GetInheritedScopes(ctx, orgID, uid, a.folderStore)
if err != nil {
a.logger.Debug("Could not retrieve inherited folder scopes:", "err", err)
}
scopes = append(scopes, dashboards.ScopeFoldersProvider.GetResourceScopeUID(uid))
return canReadFolder(scopes...)
} else if kind == entityKindDashboard {
scopes, err := dashboards.GetInheritedScopes(ctx, orgID, parent, a.folderService)
scopes, err := dashboards.GetInheritedScopes(ctx, orgID, parent, a.folderStore)
if err != nil {
a.logger.Debug("Could not retrieve inherited folder scopes:", "err", err)
}

@ -18,7 +18,7 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
"github.com/grafana/grafana/pkg/services/dashboards/database"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder/foldertest"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/org/orgtest"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
@ -754,7 +754,7 @@ func setupIntegrationEnv(t *testing.T, folderCount, dashboardsPerFolder int, sql
ExpectedOrgs: []*org.OrgDTO{{ID: 1}},
}
searchService, ok := ProvideService(cfg, sqlStore, store.NewDummyEntityEventsService(), actest.FakeService{},
tracing.InitializeTracerForTest(), features, orgSvc, nil, foldertest.NewFakeService()).(*StandardSearchService)
tracing.InitializeTracerForTest(), features, orgSvc, nil, folder.NewFakeStore()).(*StandardSearchService)
require.True(t, ok)
err = runSearchService(searchService)

@ -85,7 +85,7 @@ func (s *StandardSearchService) IsReady(ctx context.Context, orgId int64) IsSear
func ProvideService(cfg *setting.Cfg, sql db.DB, entityEventStore store.EntityEventsService,
ac accesscontrol.Service, tracer tracing.Tracer, features featuremgmt.FeatureToggles, orgService org.Service,
userService user.Service, folderService folder.Service) SearchService {
userService user.Service, folderStore folder.Store) SearchService {
extender := &NoopExtender{}
logger := log.New("searchV2")
s := &StandardSearchService{
@ -93,10 +93,10 @@ func ProvideService(cfg *setting.Cfg, sql db.DB, entityEventStore store.EntityEv
sql: sql,
ac: ac,
auth: &simpleAuthService{
sql: sql,
ac: ac,
folderService: folderService,
logger: logger,
sql: sql,
ac: ac,
folderStore: folderStore,
logger: logger,
},
dashboardIndex: newSearchIndex(
newSQLDashboardLoader(sql, tracer, cfg.Search),

@ -13,7 +13,7 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder/foldertest"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/org/orgtest"
"github.com/grafana/grafana/pkg/services/store"
@ -42,7 +42,7 @@ func setupBenchEnv(b *testing.B, folderCount, dashboardsPerFolder int) (*Standar
ExpectedOrgs: []*org.OrgDTO{{ID: 1}},
}
searchService, ok := ProvideService(cfg, sqlStore, store.NewDummyEntityEventsService(), actest.FakeService{},
tracing.InitializeTracerForTest(), features, orgSvc, nil, foldertest.NewFakeService()).(*StandardSearchService)
tracing.InitializeTracerForTest(), features, orgSvc, nil, folder.NewFakeStore()).(*StandardSearchService)
require.True(b, ok)
err = runSearchService(searchService)

@ -16,6 +16,7 @@ import (
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol/testutil"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/dashboards/database"
@ -822,10 +823,11 @@ func setupNestedTest(t *testing.T, usr *user.SignedInUser, perms []accesscontrol
dashStore, err := database.ProvideDashboardStore(db, cfg, features, tagimpl.ProvideService(db), quotatest.New(false, nil))
require.NoError(t, err)
folderPermissions, err := testutil.ProvideFolderPermissions(features, cfg, db)
require.NoError(t, err)
fStore := folderimpl.ProvideStore(db)
folderSvc := folderimpl.ProvideService(fStore, actest.FakeAccessControl{ExpectedEvaluate: true}, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore,
folderimpl.ProvideDashboardFolderStore(db), db, features, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
folderimpl.ProvideDashboardFolderStore(db), db, features, cfg, folderPermissions, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
// create parent folder
parent, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{
UID: "parent",

@ -78,12 +78,13 @@ func setupBenchMark(b *testing.B, usr user.SignedInUser, features featuremgmt.Fe
quotaService := quotatest.New(false, nil)
folderPermissions := mock.NewMockedPermissionsService()
dashboardWriteStore, err := database.ProvideDashboardStore(store, cfg, features, tagimpl.ProvideService(store), quotaService)
require.NoError(b, err)
fStore := folderimpl.ProvideStore(store)
folderSvc := folderimpl.ProvideService(fStore, mock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardWriteStore, folderimpl.ProvideDashboardFolderStore(store),
store, features, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
store, features, cfg, folderPermissions, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanViewValue: true, CanSaveValue: true})

Loading…
Cancel
Save