diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts index 012755695eb..c3e4cea9b05 100644 --- a/packages/grafana-data/src/types/config.ts +++ b/packages/grafana-data/src/types/config.ts @@ -225,6 +225,7 @@ export interface GrafanaConfig { rudderstackIntegrationsUrl: string | undefined; sqlConnectionLimits: SqlConnectionLimits; sharedWithMeFolderUID?: string; + rootFolderUID?: string; // The namespace to use for kubernetes apiserver requests namespace: string; diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index eb3da391bf3..f90928cde68 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -167,6 +167,7 @@ export class GrafanaBootConfig implements GrafanaConfig { tokenExpirationDayLimit: undefined; disableFrontendSandboxForPlugins: string[] = []; sharedWithMeFolderUID: string | undefined; + rootFolderUID: string | undefined; constructor(options: GrafanaBootConfig) { this.bootData = options.bootData; diff --git a/pkg/api/accesscontrol.go b/pkg/api/accesscontrol.go index 5ccdbf7e648..e8084c4fd53 100644 --- a/pkg/api/accesscontrol.go +++ b/pkg/api/accesscontrol.go @@ -457,6 +457,21 @@ func (hs *HTTPServer) declareFixedRoles() error { Grants: []string{"Admin"}, } + // Needed to be able to list permissions on the general folder for viewers, doesn't actually grant access to any resources + generalFolderReaderRole := ac.RoleRegistration{ + Role: ac.RoleDTO{ + Name: "fixed:folders.general:reader", + DisplayName: "General folder reader", + Description: "Access the general (root) folder.", + Group: "Folders", + Hidden: true, + Permissions: []ac.Permission{ + {Action: dashboards.ActionFoldersRead, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(ac.GeneralFolderUID)}, + }, + }, + Grants: []string{string(org.RoleViewer)}, + } + foldersWriterRole := ac.RoleRegistration{ Role: ac.RoleDTO{ Name: "fixed:folders:writer", @@ -593,7 +608,7 @@ func (hs *HTTPServer) declareFixedRoles() error { orgMaintainerRole, teamsCreatorRole, teamsWriterRole, teamsReaderRole, datasourcesExplorerRole, annotationsReaderRole, dashboardAnnotationsWriterRole, annotationsWriterRole, dashboardsCreatorRole, dashboardsReaderRole, dashboardsWriterRole, - foldersCreatorRole, foldersReaderRole, foldersWriterRole, apikeyReaderRole, apikeyWriterRole, + foldersCreatorRole, foldersReaderRole, generalFolderReaderRole, foldersWriterRole, apikeyReaderRole, apikeyWriterRole, publicDashboardsWriterRole, featuremgmtReaderRole, featuremgmtWriterRole, libraryPanelsCreatorRole, libraryPanelsReaderRole, libraryPanelsWriterRole, libraryPanelsGeneralReaderRole, libraryPanelsGeneralWriterRole} diff --git a/pkg/api/dtos/frontend_settings.go b/pkg/api/dtos/frontend_settings.go index 52f0a453161..95d8e94b6e8 100644 --- a/pkg/api/dtos/frontend_settings.go +++ b/pkg/api/dtos/frontend_settings.go @@ -235,6 +235,7 @@ type FrontendSettingsDTO struct { SamlName string `json:"samlName"` TokenExpirationDayLimit int `json:"tokenExpirationDayLimit"` SharedWithMeFolderUID string `json:"sharedWithMeFolderUID"` + RootFolderUID string `json:"rootFolderUID"` GeomapDefaultBaseLayerConfig *map[string]any `json:"geomapDefaultBaseLayerConfig,omitempty"` GeomapDisableCustomBaseLayer bool `json:"geomapDisableCustomBaseLayer"` diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 3016bd44c1e..0f0e6ec68d6 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -219,6 +219,7 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro PublicDashboardAccessToken: c.PublicDashboardAccessToken, PublicDashboardsEnabled: hs.Cfg.PublicDashboardsEnabled, SharedWithMeFolderUID: folder.SharedWithMeFolderUID, + RootFolderUID: accesscontrol.GeneralFolderUID, BuildInfo: dtos.FrontendSettingsBuildInfoDTO{ HideVersion: hideVersion, diff --git a/pkg/services/folder/folderimpl/folder.go b/pkg/services/folder/folderimpl/folder.go index 79ad5718aa2..64d88a9345b 100644 --- a/pkg/services/folder/folderimpl/folder.go +++ b/pkg/services/folder/folderimpl/folder.go @@ -179,6 +179,10 @@ func (s *Service) Get(ctx context.Context, q *folder.GetFolderQuery) (*folder.Fo return nil, folder.ErrBadRequest.Errorf("missing signed in user") } + if q.UID != nil && *q.UID == accesscontrol.GeneralFolderUID { + return folder.RootFolder, nil + } + if s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) && q.UID != nil && *q.UID == folder.SharedWithMeFolderUID { return folder.SharedWithMeFolder.WithURL(), nil } @@ -478,7 +482,7 @@ func (s *Service) deduplicateAvailableFolders(ctx context.Context, folders []*fo } func (s *Service) GetParents(ctx context.Context, q folder.GetParentsQuery) ([]*folder.Folder, error) { - if !s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) { + if !s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) || q.UID == accesscontrol.GeneralFolderUID { return nil, nil } if q.UID == folder.SharedWithMeFolderUID { diff --git a/pkg/services/folder/folderimpl/folder_test.go b/pkg/services/folder/folderimpl/folder_test.go index dfa61a2444e..2599259f8ba 100644 --- a/pkg/services/folder/folderimpl/folder_test.go +++ b/pkg/services/folder/folderimpl/folder_test.go @@ -299,6 +299,17 @@ func TestIntegrationFolderService(t *testing.T) { require.NoError(t, err) }) + t.Run("When get folder by uid and uid is general should return the root folder object", func(t *testing.T) { + uid := accesscontrol.GeneralFolderUID + query := &folder.GetFolderQuery{ + UID: &uid, + SignedInUser: usr, + } + actual, err := service.Get(context.Background(), query) + require.Equal(t, folder.RootFolder, actual) + require.NoError(t, err) + }) + t.Run("When get folder by title should return folder", func(t *testing.T) { expected := folder.NewFolder("TEST-"+util.GenerateShortUID(), "") diff --git a/pkg/services/folder/model.go b/pkg/services/folder/model.go index e111e90fb3b..8320f1183e1 100644 --- a/pkg/services/folder/model.go +++ b/pkg/services/folder/model.go @@ -53,7 +53,7 @@ type Folder struct { } var GeneralFolder = Folder{ID: 0, Title: "General"} - +var RootFolder = &Folder{ID: 0, Title: "Root", UID: GeneralFolderUID, ParentUID: ""} var SharedWithMeFolder = Folder{ Title: "Shared with me", Description: "Dashboards and folders shared with me",