Files
grafana/pkg/services/dashboards/manager/folder_service.go
T
4982ca3b1d Access control: Use access control for dashboard and folder (#44702)
* Add actions and scopes

* add resource service for dashboard and folder

* Add dashboard guardian with fgac permission evaluation

* Add CanDelete function to guardian interface

* Add CanDelete property to folder and dashboard dto and set values

* change to correct function name

* Add accesscontrol to folder endpoints

* add access control to dashboard endpoints

* check access for nav links

* Add fixed roles for dashboard and folders

* use correct package

* add hack to override guardian Constructor if accesscontrol is enabled

* Add services

* Add function to handle api backward compatability

* Add permissionServices to HttpServer

* Set permission when new dashboard is created

* Add default permission when creating new dashboard

* Set default permission when creating folder and dashboard

* Add access control filter for dashboard search

* Add to accept list

* Add accesscontrol to dashboardimport

* Disable access control in tests

* Add check to see if user is allow to create a dashboard

* Use SetPermissions

* Use function to set several permissions at once

* remove permissions for folder and dashboard on delete

* update required permission

* set permission for provisioning

* Add CanCreate to dashboard guardian and set correct permisisons for
provisioning

* Dont set admin on folder / dashboard creation

* Add dashboard and folder permission migrations

* Add tests for CanCreate

* Add roles and update descriptions

* Solve uid to id for dashboard and folder permissions

* Add folder and dashboard actions to permission filter

* Handle viewer_can_edit flag

* set folder and dashboard permissions services

* Add dashboard permissions when importing a new dashboard

* Set access control permissions on provisioning

* Pass feature flags and only set permissions if access control is enabled

* only add default permissions for folders and dashboards without folders

* Batch create permissions in migrations


* Remove `dashboards:edit` action

* Remove unused function from interface

* Update pkg/services/guardian/accesscontrol_guardian_test.go

Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>
Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
2022-03-03 15:05:47 +01:00

277 lines
7.5 KiB
Go

package service
import (
"context"
"errors"
"strings"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/search"
)
type FolderServiceImpl struct {
dashboardService dashboards.DashboardService
dashboardStore dashboards.Store
searchService *search.SearchService
log log.Logger
}
func ProvideFolderService(dashboardService dashboards.DashboardService, dashboardStore dashboards.Store, searchService *search.SearchService) *FolderServiceImpl {
return &FolderServiceImpl{
dashboardService: dashboardService,
dashboardStore: dashboardStore,
searchService: searchService,
log: log.New("folder-service"),
}
}
func (f *FolderServiceImpl) GetFolders(ctx context.Context, user *models.SignedInUser, orgID int64, limit int64, page int64) ([]*models.Folder, error) {
searchQuery := search.Query{
SignedInUser: user,
DashboardIds: make([]int64, 0),
FolderIds: make([]int64, 0),
Limit: limit,
OrgId: orgID,
Type: "dash-folder",
Permission: models.PERMISSION_VIEW,
Page: page,
}
if err := f.searchService.SearchHandler(ctx, &searchQuery); err != nil {
return nil, err
}
folders := make([]*models.Folder, 0)
for _, hit := range searchQuery.Result {
folders = append(folders, &models.Folder{
Id: hit.ID,
Uid: hit.UID,
Title: hit.Title,
})
}
return folders, nil
}
func (f *FolderServiceImpl) GetFolderByID(ctx context.Context, user *models.SignedInUser, id int64, orgID int64) (*models.Folder, error) {
if id == 0 {
return &models.Folder{Id: id, Title: "General"}, nil
}
query := models.GetDashboardQuery{OrgId: orgID, Id: id}
dashFolder, err := getFolder(ctx, query)
if err != nil {
return nil, toFolderError(err)
}
g := guardian.New(ctx, dashFolder.Id, orgID, user)
if canView, err := g.CanView(); err != nil || !canView {
if err != nil {
return nil, toFolderError(err)
}
return nil, models.ErrFolderAccessDenied
}
return dashToFolder(dashFolder), nil
}
func (f *FolderServiceImpl) GetFolderByUID(ctx context.Context, user *models.SignedInUser, orgID int64, uid string) (*models.Folder, error) {
query := models.GetDashboardQuery{OrgId: orgID, Uid: uid}
dashFolder, err := getFolder(ctx, query)
if err != nil {
return nil, toFolderError(err)
}
g := guardian.New(ctx, dashFolder.Id, orgID, user)
if canView, err := g.CanView(); err != nil || !canView {
if err != nil {
return nil, toFolderError(err)
}
return nil, models.ErrFolderAccessDenied
}
return dashToFolder(dashFolder), nil
}
func (f *FolderServiceImpl) GetFolderByTitle(ctx context.Context, user *models.SignedInUser, orgID int64, title string) (*models.Folder, error) {
dashFolder, err := f.dashboardStore.GetFolderByTitle(orgID, title)
if err != nil {
return nil, toFolderError(err)
}
g := guardian.New(ctx, dashFolder.Id, orgID, user)
if canView, err := g.CanView(); err != nil || !canView {
if err != nil {
return nil, toFolderError(err)
}
return nil, models.ErrFolderAccessDenied
}
return dashToFolder(dashFolder), nil
}
func (f *FolderServiceImpl) CreateFolder(ctx context.Context, user *models.SignedInUser, orgID int64, title, uid string) (*models.Folder, error) {
dashFolder := models.NewDashboardFolder(title)
dashFolder.OrgId = orgID
dashFolder.SetUid(strings.TrimSpace(uid))
userID := user.UserId
if userID == 0 {
userID = -1
}
dashFolder.CreatedBy = userID
dashFolder.UpdatedBy = userID
dashFolder.UpdateSlug()
dto := &dashboards.SaveDashboardDTO{
Dashboard: dashFolder,
OrgId: orgID,
User: user,
}
saveDashboardCmd, err := f.dashboardService.BuildSaveDashboardCommand(ctx, dto, false, false)
if err != nil {
return nil, toFolderError(err)
}
dash, err := f.dashboardStore.SaveDashboard(*saveDashboardCmd)
if err != nil {
return nil, toFolderError(err)
}
query := models.GetDashboardQuery{OrgId: orgID, Id: dash.Id}
dashFolder, err = getFolder(ctx, query)
if err != nil {
return nil, toFolderError(err)
}
return dashToFolder(dashFolder), nil
}
func (f *FolderServiceImpl) UpdateFolder(ctx context.Context, user *models.SignedInUser, orgID int64, existingUid string, cmd *models.UpdateFolderCommand) error {
query := models.GetDashboardQuery{OrgId: orgID, Uid: existingUid}
dashFolder, err := getFolder(ctx, query)
if err != nil {
return toFolderError(err)
}
cmd.UpdateDashboardModel(dashFolder, orgID, user.UserId)
dto := &dashboards.SaveDashboardDTO{
Dashboard: dashFolder,
OrgId: orgID,
User: user,
Overwrite: cmd.Overwrite,
}
saveDashboardCmd, err := f.dashboardService.BuildSaveDashboardCommand(ctx, dto, false, false)
if err != nil {
return toFolderError(err)
}
dash, err := f.dashboardStore.SaveDashboard(*saveDashboardCmd)
if err != nil {
return toFolderError(err)
}
query = models.GetDashboardQuery{OrgId: orgID, Id: dash.Id}
dashFolder, err = getFolder(ctx, query)
if err != nil {
return toFolderError(err)
}
cmd.Result = dashToFolder(dashFolder)
return nil
}
func (f *FolderServiceImpl) DeleteFolder(ctx context.Context, user *models.SignedInUser, orgID int64, uid string, forceDeleteRules bool) (*models.Folder, error) {
query := models.GetDashboardQuery{OrgId: orgID, Uid: uid}
dashFolder, err := getFolder(ctx, query)
if err != nil {
return nil, toFolderError(err)
}
guard := guardian.New(ctx, dashFolder.Id, orgID, user)
if canSave, err := guard.CanDelete(); err != nil || !canSave {
if err != nil {
return nil, toFolderError(err)
}
return nil, models.ErrFolderAccessDenied
}
deleteCmd := models.DeleteDashboardCommand{OrgId: orgID, Id: dashFolder.Id, ForceDeleteFolderRules: forceDeleteRules}
if err := bus.Dispatch(ctx, &deleteCmd); err != nil {
return nil, toFolderError(err)
}
return dashToFolder(dashFolder), nil
}
func (f *FolderServiceImpl) MakeUserAdmin(ctx context.Context, orgID int64, userID, folderID int64, setViewAndEditPermissions bool) error {
return f.dashboardService.MakeUserAdmin(ctx, orgID, userID, folderID, setViewAndEditPermissions)
}
func getFolder(ctx context.Context, query models.GetDashboardQuery) (*models.Dashboard, error) {
if err := bus.Dispatch(ctx, &query); err != nil {
return nil, toFolderError(err)
}
if !query.Result.IsFolder {
return nil, models.ErrFolderNotFound
}
return query.Result, nil
}
func dashToFolder(dash *models.Dashboard) *models.Folder {
return &models.Folder{
Id: dash.Id,
Uid: dash.Uid,
Title: dash.Title,
HasAcl: dash.HasAcl,
Url: dash.GetUrl(),
Version: dash.Version,
Created: dash.Created,
CreatedBy: dash.CreatedBy,
Updated: dash.Updated,
UpdatedBy: dash.UpdatedBy,
}
}
func toFolderError(err error) error {
if errors.Is(err, models.ErrDashboardTitleEmpty) {
return models.ErrFolderTitleEmpty
}
if errors.Is(err, models.ErrDashboardUpdateAccessDenied) {
return models.ErrFolderAccessDenied
}
if errors.Is(err, models.ErrDashboardWithSameNameInFolderExists) {
return models.ErrFolderSameNameExists
}
if errors.Is(err, models.ErrDashboardWithSameUIDExists) {
return models.ErrFolderWithSameUIDExists
}
if errors.Is(err, models.ErrDashboardVersionMismatch) {
return models.ErrFolderVersionMismatch
}
if errors.Is(err, models.ErrDashboardNotFound) {
return models.ErrFolderNotFound
}
if errors.Is(err, models.ErrDashboardFailedGenerateUniqueUid) {
err = models.ErrFolderFailedGenerateUniqueUid
}
return err
}