Folders: Improve folder move permission checks (#90588)
* check that a user doesn't have higher plugin access on the destination folder than they have on the source folder when moving folders * Update pkg/services/folder/folderimpl/folder_test.go --------- Co-authored-by: Jo <joao.guerreiro@grafana.com>
This commit is contained in:
@@ -882,14 +882,7 @@ func (s *Service) Move(ctx context.Context, cmd *folder.MoveFolderCommand) (*fol
|
||||
}
|
||||
|
||||
// Check that the user is allowed to move the folder to the destination folder
|
||||
var evaluator accesscontrol.Evaluator
|
||||
if cmd.NewParentUID != "" {
|
||||
evaluator = accesscontrol.EvalPermission(dashboards.ActionFoldersWrite, dashboards.ScopeFoldersProvider.GetResourceScopeUID(cmd.NewParentUID))
|
||||
} else {
|
||||
// Evaluate folder creation permission when moving folder to the root level
|
||||
evaluator = accesscontrol.EvalPermission(dashboards.ActionFoldersCreate)
|
||||
}
|
||||
hasAccess, evalErr := s.accessControl.Evaluate(ctx, cmd.SignedInUser, evaluator)
|
||||
hasAccess, evalErr := s.canMove(ctx, cmd)
|
||||
if evalErr != nil {
|
||||
return nil, evalErr
|
||||
}
|
||||
@@ -955,6 +948,68 @@ func (s *Service) Move(ctx context.Context, cmd *folder.MoveFolderCommand) (*fol
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func (s *Service) canMove(ctx context.Context, cmd *folder.MoveFolderCommand) (bool, error) {
|
||||
// Check that the user is allowed to move the folder to the destination folder
|
||||
var evaluator accesscontrol.Evaluator
|
||||
parentUID := cmd.NewParentUID
|
||||
if parentUID != "" {
|
||||
evaluator = accesscontrol.EvalPermission(dashboards.ActionFoldersWrite, dashboards.ScopeFoldersProvider.GetResourceScopeUID(parentUID))
|
||||
} else {
|
||||
// Evaluate folder creation permission when moving folder to the root level
|
||||
evaluator = accesscontrol.EvalPermission(dashboards.ActionFoldersCreate)
|
||||
parentUID = folder.GeneralFolderUID
|
||||
}
|
||||
if hasAccess, err := s.accessControl.Evaluate(ctx, cmd.SignedInUser, evaluator); err != nil {
|
||||
return false, err
|
||||
} else if !hasAccess {
|
||||
return false, dashboards.ErrMoveAccessDenied.Errorf("user does not have permissions to move a folder to folder with UID %s", parentUID)
|
||||
}
|
||||
|
||||
// Check that the user would not be elevating their permissions by moving a folder to the destination folder
|
||||
// This is needed for plugins, as different folders can have different plugin configs
|
||||
// We do this by checking that there are no permissions that user has on the destination parent folder but not on the source folder
|
||||
// We also need to look at the folder tree for the destination folder, as folder permissions are inherited
|
||||
newFolderAndParentUIDs, err := s.getFolderAndParentUIDScopes(ctx, parentUID, cmd.OrgID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
permissions := cmd.SignedInUser.GetPermissions()
|
||||
var evaluators []accesscontrol.Evaluator
|
||||
currentFolderScope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(cmd.UID)
|
||||
for action, scopes := range permissions {
|
||||
for _, scope := range newFolderAndParentUIDs {
|
||||
if slices.Contains(scopes, scope) {
|
||||
evaluators = append(evaluators, accesscontrol.EvalPermission(action, currentFolderScope))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if hasAccess, err := s.accessControl.Evaluate(ctx, cmd.SignedInUser, accesscontrol.EvalAll(evaluators...)); err != nil {
|
||||
return false, err
|
||||
} else if !hasAccess {
|
||||
return false, dashboards.ErrFolderAccessEscalation.Errorf("user cannot move a folder to another folder where they have higher permissions")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (s *Service) getFolderAndParentUIDScopes(ctx context.Context, folderUID string, orgID int64) ([]string, error) {
|
||||
folderAndParentUIDScopes := []string{dashboards.ScopeFoldersProvider.GetResourceScopeUID(folderUID)}
|
||||
if folderUID == folder.GeneralFolderUID {
|
||||
return folderAndParentUIDScopes, nil
|
||||
}
|
||||
folderParents, err := s.store.GetParents(ctx, folder.GetParentsQuery{UID: folderUID, OrgID: orgID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, newParent := range folderParents {
|
||||
scope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(newParent.UID)
|
||||
folderAndParentUIDScopes = append(folderAndParentUIDScopes, scope)
|
||||
}
|
||||
return folderAndParentUIDScopes, nil
|
||||
}
|
||||
|
||||
// nestedFolderDelete inspects the folder referenced by the cmd argument, deletes all the entries for
|
||||
// its descendant folders (folders which are nested within it either directly or indirectly) from
|
||||
// the folder store and returns the UIDs for all its descendants.
|
||||
|
||||
Reference in New Issue
Block a user