2022-10-11 03:47:53 +08:00
|
|
|
package folderimpl
|
2018-02-20 20:55:43 +08:00
|
|
|
|
|
|
|
import (
|
2021-09-14 22:08:04 +08:00
|
|
|
"context"
|
2020-11-19 21:47:17 +08:00
|
|
|
"errors"
|
2021-03-17 23:06:10 +08:00
|
|
|
"strings"
|
2020-11-19 21:47:17 +08:00
|
|
|
|
2022-06-18 01:10:49 +08:00
|
|
|
"github.com/grafana/grafana/pkg/bus"
|
|
|
|
"github.com/grafana/grafana/pkg/events"
|
2022-02-16 21:15:44 +08:00
|
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
2018-02-20 20:55:43 +08:00
|
|
|
"github.com/grafana/grafana/pkg/models"
|
2022-03-10 19:58:18 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
2022-02-16 21:15:44 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
2022-03-24 02:40:22 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
2022-10-11 03:47:53 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/folder"
|
2018-02-20 20:55:43 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/guardian"
|
2022-08-10 17:56:48 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/org"
|
2018-02-20 20:55:43 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/search"
|
2022-08-10 17:56:48 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/user"
|
2022-03-10 19:58:18 +08:00
|
|
|
"github.com/grafana/grafana/pkg/setting"
|
2018-02-20 20:55:43 +08:00
|
|
|
)
|
|
|
|
|
2022-10-11 03:47:53 +08:00
|
|
|
type Service struct {
|
2022-03-10 19:58:18 +08:00
|
|
|
log log.Logger
|
|
|
|
cfg *setting.Cfg
|
2022-02-16 21:15:44 +08:00
|
|
|
dashboardService dashboards.DashboardService
|
|
|
|
dashboardStore dashboards.Store
|
|
|
|
searchService *search.SearchService
|
2022-03-10 19:58:18 +08:00
|
|
|
features featuremgmt.FeatureToggles
|
2022-05-10 21:48:47 +08:00
|
|
|
permissions accesscontrol.FolderPermissionsService
|
2022-06-18 01:10:49 +08:00
|
|
|
|
|
|
|
// bus is currently used to publish events that cause scheduler to update rules.
|
|
|
|
bus bus.Bus
|
2018-02-20 20:55:43 +08:00
|
|
|
}
|
|
|
|
|
2022-10-11 03:47:53 +08:00
|
|
|
func ProvideService(
|
|
|
|
ac accesscontrol.AccessControl,
|
|
|
|
bus bus.Bus,
|
|
|
|
cfg *setting.Cfg,
|
|
|
|
dashboardService dashboards.DashboardService,
|
|
|
|
dashboardStore dashboards.Store,
|
|
|
|
features featuremgmt.FeatureToggles,
|
|
|
|
folderPermissionsService accesscontrol.FolderPermissionsService,
|
|
|
|
searchService *search.SearchService,
|
|
|
|
) folder.Service {
|
2022-05-02 15:29:30 +08:00
|
|
|
ac.RegisterScopeAttributeResolver(dashboards.NewFolderNameScopeResolver(dashboardStore))
|
|
|
|
ac.RegisterScopeAttributeResolver(dashboards.NewFolderIDScopeResolver(dashboardStore))
|
2022-10-11 03:47:53 +08:00
|
|
|
return &Service{
|
2022-03-10 19:58:18 +08:00
|
|
|
cfg: cfg,
|
|
|
|
log: log.New("folder-service"),
|
2022-02-16 21:15:44 +08:00
|
|
|
dashboardService: dashboardService,
|
|
|
|
dashboardStore: dashboardStore,
|
|
|
|
searchService: searchService,
|
2022-03-10 19:58:18 +08:00
|
|
|
features: features,
|
2022-05-10 21:48:47 +08:00
|
|
|
permissions: folderPermissionsService,
|
2022-06-18 01:10:49 +08:00
|
|
|
bus: bus,
|
2018-02-20 20:55:43 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-11 03:47:53 +08:00
|
|
|
func (s *Service) GetFolders(ctx context.Context, user *user.SignedInUser, orgID int64, limit int64, page int64) ([]*models.Folder, error) {
|
2018-02-20 20:55:43 +08:00
|
|
|
searchQuery := search.Query{
|
2022-02-16 21:15:44 +08:00
|
|
|
SignedInUser: user,
|
2018-02-20 20:55:43 +08:00
|
|
|
DashboardIds: make([]int64, 0),
|
|
|
|
FolderIds: make([]int64, 0),
|
|
|
|
Limit: limit,
|
2022-02-16 21:15:44 +08:00
|
|
|
OrgId: orgID,
|
2018-02-20 20:55:43 +08:00
|
|
|
Type: "dash-folder",
|
|
|
|
Permission: models.PERMISSION_VIEW,
|
2021-07-22 14:53:14 +08:00
|
|
|
Page: page,
|
2018-02-20 20:55:43 +08:00
|
|
|
}
|
|
|
|
|
2022-10-11 03:47:53 +08:00
|
|
|
if err := s.searchService.SearchHandler(ctx, &searchQuery); err != nil {
|
2018-02-20 20:55:43 +08:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
folders := make([]*models.Folder, 0)
|
|
|
|
|
|
|
|
for _, hit := range searchQuery.Result {
|
|
|
|
folders = append(folders, &models.Folder{
|
2021-02-11 15:49:16 +08:00
|
|
|
Id: hit.ID,
|
|
|
|
Uid: hit.UID,
|
2018-02-20 20:55:43 +08:00
|
|
|
Title: hit.Title,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return folders, nil
|
|
|
|
}
|
|
|
|
|
2022-10-11 03:47:53 +08:00
|
|
|
func (s *Service) GetFolderByID(ctx context.Context, user *user.SignedInUser, id int64, orgID int64) (*models.Folder, error) {
|
2021-07-01 16:40:38 +08:00
|
|
|
if id == 0 {
|
|
|
|
return &models.Folder{Id: id, Title: "General"}, nil
|
|
|
|
}
|
2022-10-26 22:15:14 +08:00
|
|
|
|
2022-10-11 03:47:53 +08:00
|
|
|
dashFolder, err := s.dashboardStore.GetFolderByID(ctx, orgID, id)
|
2018-02-20 20:55:43 +08:00
|
|
|
if err != nil {
|
2022-03-14 23:21:42 +08:00
|
|
|
return nil, err
|
2018-02-20 20:55:43 +08:00
|
|
|
}
|
|
|
|
|
2022-02-16 21:15:44 +08:00
|
|
|
g := guardian.New(ctx, dashFolder.Id, orgID, user)
|
2018-02-20 20:55:43 +08:00
|
|
|
if canView, err := g.CanView(); err != nil || !canView {
|
|
|
|
if err != nil {
|
|
|
|
return nil, toFolderError(err)
|
|
|
|
}
|
2022-06-30 21:31:54 +08:00
|
|
|
return nil, dashboards.ErrFolderAccessDenied
|
2018-02-20 20:55:43 +08:00
|
|
|
}
|
|
|
|
|
2022-03-14 23:21:42 +08:00
|
|
|
return dashFolder, nil
|
2018-02-20 20:55:43 +08:00
|
|
|
}
|
|
|
|
|
2022-10-11 03:47:53 +08:00
|
|
|
func (s *Service) GetFolderByUID(ctx context.Context, user *user.SignedInUser, orgID int64, uid string) (*models.Folder, error) {
|
|
|
|
dashFolder, err := s.dashboardStore.GetFolderByUID(ctx, orgID, uid)
|
2018-02-20 20:55:43 +08:00
|
|
|
if err != nil {
|
2022-03-14 23:21:42 +08:00
|
|
|
return nil, err
|
2018-02-20 20:55:43 +08:00
|
|
|
}
|
|
|
|
|
2022-02-16 21:15:44 +08:00
|
|
|
g := guardian.New(ctx, dashFolder.Id, orgID, user)
|
2021-04-01 16:11:45 +08:00
|
|
|
if canView, err := g.CanView(); err != nil || !canView {
|
|
|
|
if err != nil {
|
|
|
|
return nil, toFolderError(err)
|
|
|
|
}
|
2022-06-30 21:31:54 +08:00
|
|
|
return nil, dashboards.ErrFolderAccessDenied
|
2021-04-01 16:11:45 +08:00
|
|
|
}
|
|
|
|
|
2022-03-14 23:21:42 +08:00
|
|
|
return dashFolder, nil
|
2021-04-01 16:11:45 +08:00
|
|
|
}
|
|
|
|
|
2022-10-11 03:47:53 +08:00
|
|
|
func (s *Service) GetFolderByTitle(ctx context.Context, user *user.SignedInUser, orgID int64, title string) (*models.Folder, error) {
|
|
|
|
dashFolder, err := s.dashboardStore.GetFolderByTitle(ctx, orgID, title)
|
2021-04-01 16:11:45 +08:00
|
|
|
if err != nil {
|
2022-03-14 23:21:42 +08:00
|
|
|
return nil, err
|
2021-04-01 16:11:45 +08:00
|
|
|
}
|
|
|
|
|
2022-02-16 21:15:44 +08:00
|
|
|
g := guardian.New(ctx, dashFolder.Id, orgID, user)
|
2018-02-20 20:55:43 +08:00
|
|
|
if canView, err := g.CanView(); err != nil || !canView {
|
|
|
|
if err != nil {
|
|
|
|
return nil, toFolderError(err)
|
|
|
|
}
|
2022-06-30 21:31:54 +08:00
|
|
|
return nil, dashboards.ErrFolderAccessDenied
|
2018-02-20 20:55:43 +08:00
|
|
|
}
|
|
|
|
|
2022-03-14 23:21:42 +08:00
|
|
|
return dashFolder, nil
|
2018-02-20 20:55:43 +08:00
|
|
|
}
|
|
|
|
|
2022-10-11 03:47:53 +08:00
|
|
|
func (s *Service) CreateFolder(ctx context.Context, user *user.SignedInUser, orgID int64, title, uid string) (*models.Folder, error) {
|
2021-03-17 23:06:10 +08:00
|
|
|
dashFolder := models.NewDashboardFolder(title)
|
2022-02-16 21:15:44 +08:00
|
|
|
dashFolder.OrgId = orgID
|
2022-03-30 21:14:26 +08:00
|
|
|
|
|
|
|
trimmedUID := strings.TrimSpace(uid)
|
|
|
|
if trimmedUID == accesscontrol.GeneralFolderUID {
|
2022-06-30 21:31:54 +08:00
|
|
|
return nil, dashboards.ErrFolderInvalidUID
|
2022-03-30 21:14:26 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
dashFolder.SetUid(trimmedUID)
|
2022-08-11 19:28:55 +08:00
|
|
|
userID := user.UserID
|
2021-03-17 23:06:10 +08:00
|
|
|
if userID == 0 {
|
|
|
|
userID = -1
|
|
|
|
}
|
|
|
|
dashFolder.CreatedBy = userID
|
|
|
|
dashFolder.UpdatedBy = userID
|
|
|
|
dashFolder.UpdateSlug()
|
2018-02-20 20:55:43 +08:00
|
|
|
|
2022-02-16 21:15:44 +08:00
|
|
|
dto := &dashboards.SaveDashboardDTO{
|
2018-02-20 20:55:43 +08:00
|
|
|
Dashboard: dashFolder,
|
2022-02-16 21:15:44 +08:00
|
|
|
OrgId: orgID,
|
|
|
|
User: user,
|
2018-02-20 20:55:43 +08:00
|
|
|
}
|
|
|
|
|
2022-10-11 03:47:53 +08:00
|
|
|
saveDashboardCmd, err := s.dashboardService.BuildSaveDashboardCommand(ctx, dto, false, false)
|
2018-02-20 20:55:43 +08:00
|
|
|
if err != nil {
|
2021-03-17 23:06:10 +08:00
|
|
|
return nil, toFolderError(err)
|
2018-02-20 20:55:43 +08:00
|
|
|
}
|
|
|
|
|
2022-10-11 03:47:53 +08:00
|
|
|
dash, err := s.dashboardStore.SaveDashboard(ctx, *saveDashboardCmd)
|
2018-02-20 20:55:43 +08:00
|
|
|
if err != nil {
|
2021-03-17 23:06:10 +08:00
|
|
|
return nil, toFolderError(err)
|
2018-02-20 20:55:43 +08:00
|
|
|
}
|
|
|
|
|
2022-03-14 23:21:42 +08:00
|
|
|
var folder *models.Folder
|
2022-10-11 03:47:53 +08:00
|
|
|
folder, err = s.dashboardStore.GetFolderByID(ctx, orgID, dash.Id)
|
2018-02-20 20:55:43 +08:00
|
|
|
if err != nil {
|
2022-03-14 23:21:42 +08:00
|
|
|
return nil, err
|
2018-02-20 20:55:43 +08:00
|
|
|
}
|
|
|
|
|
2022-03-10 19:58:18 +08:00
|
|
|
var permissionErr error
|
2022-10-11 03:47:53 +08:00
|
|
|
if !accesscontrol.IsDisabled(s.cfg) {
|
2022-07-22 16:35:26 +08:00
|
|
|
var permissions []accesscontrol.SetResourcePermissionCommand
|
|
|
|
if user.IsRealUser() && !user.IsAnonymous {
|
|
|
|
permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{
|
|
|
|
UserID: userID, Permission: models.PERMISSION_ADMIN.String(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
permissions = append(permissions, []accesscontrol.SetResourcePermissionCommand{
|
2022-08-10 17:56:48 +08:00
|
|
|
{BuiltinRole: string(org.RoleEditor), Permission: models.PERMISSION_EDIT.String()},
|
|
|
|
{BuiltinRole: string(org.RoleViewer), Permission: models.PERMISSION_VIEW.String()},
|
2022-03-10 19:58:18 +08:00
|
|
|
}...)
|
2022-07-22 16:35:26 +08:00
|
|
|
|
2022-10-11 03:47:53 +08:00
|
|
|
_, permissionErr = s.permissions.SetPermissions(ctx, orgID, folder.Uid, permissions...)
|
|
|
|
} else if s.cfg.EditorsCanAdmin && user.IsRealUser() && !user.IsAnonymous {
|
|
|
|
permissionErr = s.MakeUserAdmin(ctx, orgID, userID, folder.Id, true)
|
2022-03-10 19:58:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if permissionErr != nil {
|
2022-10-11 03:47:53 +08:00
|
|
|
s.log.Error("Could not make user admin", "folder", folder.Title, "user", userID, "error", permissionErr)
|
2022-03-10 19:58:18 +08:00
|
|
|
}
|
|
|
|
|
2022-03-14 23:21:42 +08:00
|
|
|
return folder, nil
|
2018-02-20 20:55:43 +08:00
|
|
|
}
|
|
|
|
|
2022-10-11 03:47:53 +08:00
|
|
|
func (s *Service) UpdateFolder(ctx context.Context, user *user.SignedInUser, orgID int64, existingUid string, cmd *models.UpdateFolderCommand) error {
|
2022-02-16 21:15:44 +08:00
|
|
|
query := models.GetDashboardQuery{OrgId: orgID, Uid: existingUid}
|
2022-10-11 03:47:53 +08:00
|
|
|
if _, err := s.dashboardStore.GetDashboard(ctx, &query); err != nil {
|
2018-02-20 20:55:43 +08:00
|
|
|
return toFolderError(err)
|
|
|
|
}
|
|
|
|
|
2022-03-24 02:40:22 +08:00
|
|
|
dashFolder := query.Result
|
2022-08-02 07:28:38 +08:00
|
|
|
currentTitle := dashFolder.Title
|
2022-03-24 02:40:22 +08:00
|
|
|
|
|
|
|
if !dashFolder.IsFolder {
|
2022-06-30 21:31:54 +08:00
|
|
|
return dashboards.ErrFolderNotFound
|
2022-03-24 02:40:22 +08:00
|
|
|
}
|
|
|
|
|
2022-08-11 19:28:55 +08:00
|
|
|
cmd.UpdateDashboardModel(dashFolder, orgID, user.UserID)
|
2018-02-20 20:55:43 +08:00
|
|
|
|
2022-02-16 21:15:44 +08:00
|
|
|
dto := &dashboards.SaveDashboardDTO{
|
2018-02-20 20:55:43 +08:00
|
|
|
Dashboard: dashFolder,
|
2022-02-16 21:15:44 +08:00
|
|
|
OrgId: orgID,
|
|
|
|
User: user,
|
2018-02-20 20:55:43 +08:00
|
|
|
Overwrite: cmd.Overwrite,
|
|
|
|
}
|
|
|
|
|
2022-10-11 03:47:53 +08:00
|
|
|
saveDashboardCmd, err := s.dashboardService.BuildSaveDashboardCommand(ctx, dto, false, false)
|
2018-02-20 20:55:43 +08:00
|
|
|
if err != nil {
|
|
|
|
return toFolderError(err)
|
|
|
|
}
|
|
|
|
|
2022-10-11 03:47:53 +08:00
|
|
|
dash, err := s.dashboardStore.SaveDashboard(ctx, *saveDashboardCmd)
|
2018-02-20 20:55:43 +08:00
|
|
|
if err != nil {
|
|
|
|
return toFolderError(err)
|
|
|
|
}
|
|
|
|
|
2022-03-14 23:21:42 +08:00
|
|
|
var folder *models.Folder
|
2022-10-11 03:47:53 +08:00
|
|
|
folder, err = s.dashboardStore.GetFolderByID(ctx, orgID, dash.Id)
|
2018-02-20 20:55:43 +08:00
|
|
|
if err != nil {
|
2022-03-14 23:21:42 +08:00
|
|
|
return err
|
2018-02-20 20:55:43 +08:00
|
|
|
}
|
2022-03-14 23:21:42 +08:00
|
|
|
cmd.Result = folder
|
2022-06-18 01:10:49 +08:00
|
|
|
|
2022-08-02 07:28:38 +08:00
|
|
|
if currentTitle != folder.Title {
|
2022-10-11 03:47:53 +08:00
|
|
|
if err := s.bus.Publish(ctx, &events.FolderTitleUpdated{
|
2022-08-02 07:28:38 +08:00
|
|
|
Timestamp: folder.Updated,
|
|
|
|
Title: folder.Title,
|
|
|
|
ID: dash.Id,
|
|
|
|
UID: dash.Uid,
|
|
|
|
OrgID: orgID,
|
|
|
|
}); err != nil {
|
2022-10-11 03:47:53 +08:00
|
|
|
s.log.Error("failed to publish FolderTitleUpdated event", "folder", folder.Title, "user", user.UserID, "error", err)
|
2022-08-02 07:28:38 +08:00
|
|
|
}
|
2022-06-18 01:10:49 +08:00
|
|
|
}
|
|
|
|
|
2018-02-20 20:55:43 +08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-10-11 03:47:53 +08:00
|
|
|
func (s *Service) DeleteFolder(ctx context.Context, user *user.SignedInUser, orgID int64, uid string, forceDeleteRules bool) (*models.Folder, error) {
|
|
|
|
dashFolder, err := s.dashboardStore.GetFolderByUID(ctx, orgID, uid)
|
2018-02-20 20:55:43 +08:00
|
|
|
if err != nil {
|
2022-03-14 23:21:42 +08:00
|
|
|
return nil, err
|
2018-02-20 20:55:43 +08:00
|
|
|
}
|
|
|
|
|
2022-03-03 22:05:47 +08:00
|
|
|
guard := guardian.New(ctx, dashFolder.Id, orgID, user)
|
|
|
|
if canSave, err := guard.CanDelete(); err != nil || !canSave {
|
2018-02-20 20:55:43 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, toFolderError(err)
|
|
|
|
}
|
2022-06-30 21:31:54 +08:00
|
|
|
return nil, dashboards.ErrFolderAccessDenied
|
2018-02-20 20:55:43 +08:00
|
|
|
}
|
|
|
|
|
2022-02-16 21:15:44 +08:00
|
|
|
deleteCmd := models.DeleteDashboardCommand{OrgId: orgID, Id: dashFolder.Id, ForceDeleteFolderRules: forceDeleteRules}
|
2022-03-22 21:36:50 +08:00
|
|
|
|
2022-10-11 03:47:53 +08:00
|
|
|
if err := s.dashboardStore.DeleteDashboard(ctx, &deleteCmd); err != nil {
|
2018-02-20 20:55:43 +08:00
|
|
|
return nil, toFolderError(err)
|
|
|
|
}
|
|
|
|
|
2022-03-14 23:21:42 +08:00
|
|
|
return dashFolder, nil
|
2018-02-20 20:55:43 +08:00
|
|
|
}
|
|
|
|
|
2022-10-11 03:47:53 +08:00
|
|
|
func (s *Service) MakeUserAdmin(ctx context.Context, orgID int64, userID, folderID int64, setViewAndEditPermissions bool) error {
|
|
|
|
return s.dashboardService.MakeUserAdmin(ctx, orgID, userID, folderID, setViewAndEditPermissions)
|
2022-02-16 21:15:44 +08:00
|
|
|
}
|
|
|
|
|
2018-02-20 20:55:43 +08:00
|
|
|
func toFolderError(err error) error {
|
2022-06-30 21:31:54 +08:00
|
|
|
if errors.Is(err, dashboards.ErrDashboardTitleEmpty) {
|
|
|
|
return dashboards.ErrFolderTitleEmpty
|
2018-02-20 20:55:43 +08:00
|
|
|
}
|
|
|
|
|
2022-06-30 21:31:54 +08:00
|
|
|
if errors.Is(err, dashboards.ErrDashboardUpdateAccessDenied) {
|
|
|
|
return dashboards.ErrFolderAccessDenied
|
2018-02-20 20:55:43 +08:00
|
|
|
}
|
|
|
|
|
2022-06-30 21:31:54 +08:00
|
|
|
if errors.Is(err, dashboards.ErrDashboardWithSameNameInFolderExists) {
|
|
|
|
return dashboards.ErrFolderSameNameExists
|
2018-02-20 20:55:43 +08:00
|
|
|
}
|
|
|
|
|
2022-06-30 21:31:54 +08:00
|
|
|
if errors.Is(err, dashboards.ErrDashboardWithSameUIDExists) {
|
|
|
|
return dashboards.ErrFolderWithSameUIDExists
|
2018-02-20 20:55:43 +08:00
|
|
|
}
|
|
|
|
|
2022-06-30 21:31:54 +08:00
|
|
|
if errors.Is(err, dashboards.ErrDashboardVersionMismatch) {
|
|
|
|
return dashboards.ErrFolderVersionMismatch
|
2018-02-20 20:55:43 +08:00
|
|
|
}
|
|
|
|
|
2022-06-30 21:31:54 +08:00
|
|
|
if errors.Is(err, dashboards.ErrDashboardNotFound) {
|
|
|
|
return dashboards.ErrFolderNotFound
|
2018-02-20 20:55:43 +08:00
|
|
|
}
|
|
|
|
|
2022-06-30 21:31:54 +08:00
|
|
|
if errors.Is(err, dashboards.ErrDashboardFailedGenerateUniqueUid) {
|
|
|
|
err = dashboards.ErrFolderFailedGenerateUniqueUid
|
2018-02-20 20:55:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|