2018-01-29 06:51:01 -06:00
package api
import (
2023-01-30 08:19:42 -06:00
"context"
2020-07-23 01:15:47 -05:00
"errors"
2021-11-29 03:18:01 -06:00
"net/http"
2022-01-14 10:55:57 -06:00
"strconv"
2018-02-21 09:38:09 -06:00
2021-08-25 08:11:22 -05:00
"github.com/grafana/grafana/pkg/api/apierrors"
2018-01-29 06:51:01 -06:00
"github.com/grafana/grafana/pkg/api/dtos"
2021-01-15 07:43:20 -06:00
"github.com/grafana/grafana/pkg/api/response"
2023-01-30 08:19:42 -06:00
"github.com/grafana/grafana/pkg/services/accesscontrol"
2023-01-27 01:50:36 -06:00
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
2022-06-22 03:29:26 -05:00
"github.com/grafana/grafana/pkg/services/dashboards"
2022-11-10 08:06:52 -06:00
"github.com/grafana/grafana/pkg/services/featuremgmt"
2022-11-10 02:42:32 -06:00
"github.com/grafana/grafana/pkg/services/folder"
2018-01-29 06:51:01 -06:00
"github.com/grafana/grafana/pkg/services/guardian"
2023-02-01 10:32:05 -06:00
"github.com/grafana/grafana/pkg/services/libraryelements/model"
2023-01-30 08:19:42 -06:00
"github.com/grafana/grafana/pkg/services/org"
2023-01-23 06:09:09 -06:00
"github.com/grafana/grafana/pkg/services/search"
2023-01-30 08:19:42 -06:00
"github.com/grafana/grafana/pkg/services/user"
2021-10-11 07:30:59 -05:00
"github.com/grafana/grafana/pkg/web"
2018-01-29 06:51:01 -06:00
)
2022-07-27 08:54:37 -05:00
// swagger:route GET /folders folders getFolders
//
// Get all folders.
//
// Returns all folders that the authenticated user has permission to view.
2023-01-23 06:09:09 -06:00
// If nested folders are enabled, it expects an additional query parameter with the parent folder UID
// and returns the immediate subfolders.
2022-07-27 08:54:37 -05:00
//
// Responses:
// 200: getFoldersResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
2023-01-27 01:50:36 -06:00
func ( hs * HTTPServer ) GetFolders ( c * contextmodel . ReqContext ) response . Response {
2023-01-23 06:09:09 -06:00
var folders [ ] * folder . Folder
var err error
if hs . Features . IsEnabled ( featuremgmt . FlagNestedFolders ) {
folders , err = hs . folderService . GetChildren ( c . Req . Context ( ) , & folder . GetChildrenQuery {
OrgID : c . OrgID ,
Limit : c . QueryInt64 ( "limit" ) ,
Page : c . QueryInt64 ( "page" ) ,
2023-01-24 02:20:28 -06:00
UID : c . Query ( "parentUid" ) ,
2023-01-23 06:09:09 -06:00
SignedInUser : c . SignedInUser ,
} )
} else {
folders , err = hs . searchFolders ( c )
}
2018-01-29 06:51:01 -06:00
if err != nil {
2021-08-25 08:11:22 -05:00
return apierrors . ToFolderErrorResponse ( err )
2018-01-29 06:51:01 -06:00
}
2022-06-22 03:29:26 -05:00
uids := make ( map [ string ] bool , len ( folders ) )
2018-02-20 06:57:32 -06:00
result := make ( [ ] dtos . FolderSearchHit , 0 )
for _ , f := range folders {
2022-12-19 02:52:04 -06:00
uids [ f . UID ] = true
2018-02-20 06:57:32 -06:00
result = append ( result , dtos . FolderSearchHit {
2022-12-19 02:52:04 -06:00
Id : f . ID ,
Uid : f . UID ,
Title : f . Title ,
ParentUID : f . ParentUID ,
2018-02-20 06:57:32 -06:00
} )
2018-01-29 06:51:01 -06:00
}
2022-08-11 06:28:55 -05:00
metadata := hs . getMultiAccessControlMetadata ( c , c . OrgID , dashboards . ScopeFoldersPrefix , uids )
2022-06-22 03:29:26 -05:00
if len ( metadata ) > 0 {
for i := range result {
result [ i ] . AccessControl = metadata [ result [ i ] . Uid ]
}
}
2022-04-15 07:01:58 -05:00
return response . JSON ( http . StatusOK , result )
2018-01-29 06:51:01 -06:00
}
2022-07-27 08:54:37 -05:00
// swagger:route GET /folders/{folder_uid} folders getFolderByUID
//
// Get folder by uid.
//
// Responses:
// 200: folderResponse
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
2023-01-27 01:50:36 -06:00
func ( hs * HTTPServer ) GetFolderByUID ( c * contextmodel . ReqContext ) response . Response {
2022-11-11 07:28:24 -06:00
uid := web . Params ( c . Req ) [ ":uid" ]
2022-11-23 03:13:47 -06:00
folder , err := hs . folderService . Get ( c . Req . Context ( ) , & folder . GetFolderQuery { OrgID : c . OrgID , UID : & uid , SignedInUser : c . SignedInUser } )
2018-02-20 06:57:32 -06:00
if err != nil {
2021-08-25 08:11:22 -05:00
return apierrors . ToFolderErrorResponse ( err )
2018-01-29 06:51:01 -06:00
}
2022-12-15 08:34:17 -06:00
g , err := guardian . NewByUID ( c . Req . Context ( ) , folder . UID , c . OrgID , c . SignedInUser )
if err != nil {
return response . Err ( err )
}
2022-11-11 07:28:24 -06:00
return response . JSON ( http . StatusOK , hs . newToFolderDto ( c , g , folder ) )
2018-02-20 06:57:32 -06:00
}
2018-01-29 06:51:01 -06:00
2022-07-27 08:54:37 -05:00
// swagger:route GET /folders/id/{folder_id} folders getFolderByID
//
// Get folder by id.
//
// Returns the folder identified by id.
//
// Responses:
// 200: folderResponse
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
2023-01-27 01:50:36 -06:00
func ( hs * HTTPServer ) GetFolderByID ( c * contextmodel . ReqContext ) response . Response {
2022-01-14 10:55:57 -06:00
id , err := strconv . ParseInt ( web . Params ( c . Req ) [ ":id" ] , 10 , 64 )
if err != nil {
return response . Error ( http . StatusBadRequest , "id is invalid" , err )
}
2022-11-23 03:13:47 -06:00
folder , err := hs . folderService . Get ( c . Req . Context ( ) , & folder . GetFolderQuery { ID : & id , OrgID : c . OrgID , SignedInUser : c . SignedInUser } )
2018-01-29 06:51:01 -06:00
if err != nil {
2021-08-25 08:11:22 -05:00
return apierrors . ToFolderErrorResponse ( err )
2018-01-29 06:51:01 -06:00
}
2022-12-15 08:34:17 -06:00
g , err := guardian . NewByUID ( c . Req . Context ( ) , folder . UID , c . OrgID , c . SignedInUser )
if err != nil {
return response . Err ( err )
}
2022-11-11 07:28:24 -06:00
return response . JSON ( http . StatusOK , hs . newToFolderDto ( c , g , folder ) )
2018-02-20 06:57:32 -06:00
}
2018-01-29 06:51:01 -06:00
2022-07-27 08:54:37 -05:00
// swagger:route POST /folders folders createFolder
//
// Create folder.
//
2022-11-10 03:41:03 -06:00
// If nested folders are enabled then it additionally expects the parent folder UID.
//
2022-07-27 08:54:37 -05:00
// Responses:
// 200: folderResponse
// 400: badRequestError
// 401: unauthorisedError
// 403: forbiddenError
// 409: conflictError
// 500: internalServerError
2023-01-27 01:50:36 -06:00
func ( hs * HTTPServer ) CreateFolder ( c * contextmodel . ReqContext ) response . Response {
2022-11-10 03:41:03 -06:00
cmd := folder . CreateFolderCommand { }
2021-11-29 03:18:01 -06:00
if err := web . Bind ( c . Req , & cmd ) ; err != nil {
return response . Error ( http . StatusBadRequest , "bad request data" , err )
}
2022-11-10 03:41:03 -06:00
cmd . OrgID = c . OrgID
2022-11-23 03:13:47 -06:00
cmd . SignedInUser = c . SignedInUser
2022-11-10 03:41:03 -06:00
folder , err := hs . folderService . Create ( c . Req . Context ( ) , & cmd )
2018-01-29 06:51:01 -06:00
if err != nil {
2021-08-25 08:11:22 -05:00
return apierrors . ToFolderErrorResponse ( err )
2018-01-29 06:51:01 -06:00
}
2023-01-30 08:19:42 -06:00
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 )
}
2022-11-24 08:38:55 -06:00
// 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
if ! hs . AccessControl . IsDisabled ( ) {
hs . accesscontrolService . ClearUserPermissionCache ( c . SignedInUser )
}
2022-12-15 08:34:17 -06:00
g , err := guardian . NewByUID ( c . Req . Context ( ) , folder . UID , c . OrgID , c . SignedInUser )
if err != nil {
return response . Err ( err )
}
2022-11-10 03:41:03 -06:00
// TODO set ParentUID if nested folders are enabled
return response . JSON ( http . StatusOK , hs . newToFolderDto ( c , g , folder ) )
2018-01-29 06:51:01 -06:00
}
2023-01-30 08:19:42 -06:00
func ( hs * HTTPServer ) setDefaultFolderPermissions ( ctx context . Context , orgID int64 , user * user . SignedInUser , folder * folder . Folder ) error {
2023-01-31 06:13:26 -06:00
isNested := folder . ParentUID != ""
2023-01-30 08:19:42 -06:00
var permissionErr error
if ! accesscontrol . IsDisabled ( hs . Cfg ) {
var permissions [ ] accesscontrol . SetResourcePermissionCommand
if user . IsRealUser ( ) && ! user . IsAnonymous {
permissions = append ( permissions , accesscontrol . SetResourcePermissionCommand {
UserID : user . UserID , Permission : dashboards . PERMISSION_ADMIN . String ( ) ,
} )
}
2023-03-20 06:04:22 -05:00
if ! isNested || ! hs . Features . IsEnabled ( featuremgmt . FlagNestedFolders ) {
2023-01-31 06:13:26 -06:00
permissions = append ( permissions , [ ] accesscontrol . SetResourcePermissionCommand {
{ BuiltinRole : string ( org . RoleEditor ) , Permission : dashboards . PERMISSION_EDIT . String ( ) } ,
{ BuiltinRole : string ( org . RoleViewer ) , Permission : dashboards . PERMISSION_VIEW . String ( ) } ,
} ... )
}
2023-01-30 08:19:42 -06:00
_ , permissionErr = hs . folderPermissionsService . SetPermissions ( ctx , orgID , folder . UID , permissions ... )
return permissionErr
} else if hs . Cfg . EditorsCanAdmin && user . IsRealUser ( ) && ! user . IsAnonymous {
2023-01-31 06:13:26 -06:00
return hs . folderService . MakeUserAdmin ( ctx , orgID , user . UserID , folder . ID , ! isNested )
2023-01-30 08:19:42 -06:00
}
return nil
}
2023-03-30 03:46:11 -05:00
// swagger:route POST /folders/{folder_uid}/move folders moveFolder
//
// Move folder.
//
// Responses:
// 200: folderResponse
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
2023-01-27 01:50:36 -06:00
func ( hs * HTTPServer ) MoveFolder ( c * contextmodel . ReqContext ) response . Response {
2022-11-10 08:06:52 -06:00
if hs . Features . IsEnabled ( featuremgmt . FlagNestedFolders ) {
2023-01-25 02:14:32 -06:00
cmd := folder . MoveFolderCommand { }
2022-11-10 08:06:52 -06:00
if err := web . Bind ( c . Req , & cmd ) ; err != nil {
return response . Error ( http . StatusBadRequest , "bad request data" , err )
}
var err error
2023-03-20 06:04:22 -05:00
2023-03-30 03:46:11 -05:00
cmd . OrgID = c . OrgID
cmd . UID = web . Params ( c . Req ) [ ":uid" ]
cmd . SignedInUser = c . SignedInUser
theFolder , err := hs . folderService . Move ( c . Req . Context ( ) , & cmd )
if err != nil {
return response . Error ( http . StatusInternalServerError , "move folder failed" , err )
2022-11-10 08:06:52 -06:00
}
2023-03-30 03:46:11 -05:00
g , err := guardian . NewByUID ( c . Req . Context ( ) , cmd . UID , c . OrgID , c . SignedInUser )
if err != nil {
return response . Err ( err )
}
return response . JSON ( http . StatusOK , hs . newToFolderDto ( c , g , theFolder ) )
2022-11-10 08:06:52 -06:00
}
result := map [ string ] string { }
result [ "message" ] = "To use this service, you need to activate nested folder feature."
return response . JSON ( http . StatusNotFound , result )
}
2022-07-27 08:54:37 -05:00
// swagger:route PUT /folders/{folder_uid} folders updateFolder
//
// Update folder.
//
// Responses:
// 200: folderResponse
// 400: badRequestError
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 409: conflictError
// 500: internalServerError
2023-01-27 01:50:36 -06:00
func ( hs * HTTPServer ) UpdateFolder ( c * contextmodel . ReqContext ) response . Response {
2022-12-20 07:00:33 -06:00
cmd := folder . UpdateFolderCommand { }
2021-11-29 03:18:01 -06:00
if err := web . Bind ( c . Req , & cmd ) ; err != nil {
return response . Error ( http . StatusBadRequest , "bad request data" , err )
}
2022-12-20 07:00:33 -06:00
cmd . OrgID = c . OrgID
cmd . UID = web . Params ( c . Req ) [ ":uid" ]
cmd . SignedInUser = c . SignedInUser
result , err := hs . folderService . Update ( c . Req . Context ( ) , & cmd )
2018-01-29 06:51:01 -06:00
if err != nil {
2021-08-25 08:11:22 -05:00
return apierrors . ToFolderErrorResponse ( err )
2018-01-29 06:51:01 -06:00
}
2022-12-15 08:34:17 -06:00
g , err := guardian . NewByUID ( c . Req . Context ( ) , result . UID , c . OrgID , c . SignedInUser )
if err != nil {
return response . Err ( err )
}
2022-11-11 07:28:24 -06:00
return response . JSON ( http . StatusOK , hs . newToFolderDto ( c , g , result ) )
2018-01-29 06:51:01 -06:00
}
2022-07-27 08:54:37 -05:00
// swagger:route DELETE /folders/{folder_uid} folders deleteFolder
//
// Delete folder.
//
// Deletes an existing folder identified by UID along with all dashboards (and their alerts) stored in the folder. This operation cannot be reverted.
2022-11-10 03:41:03 -06:00
// If nested folders are enabled then it also deletes all the subfolders.
2022-07-27 08:54:37 -05:00
//
// Responses:
// 200: deleteFolderResponse
// 400: badRequestError
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
2023-01-27 01:50:36 -06:00
func ( hs * HTTPServer ) DeleteFolder ( c * contextmodel . ReqContext ) response . Response { // temporarily adding this function to HTTPServer, will be removed from HTTPServer when librarypanels featuretoggle is removed
2021-10-11 07:30:59 -05:00
err := hs . LibraryElementService . DeleteLibraryElementsInFolder ( c . Req . Context ( ) , c . SignedInUser , web . Params ( c . Req ) [ ":uid" ] )
2021-05-12 01:48:17 -05:00
if err != nil {
2023-02-01 10:32:05 -06:00
if errors . Is ( err , model . ErrFolderHasConnectedLibraryElements ) {
2021-05-12 01:48:17 -05:00
return response . Error ( 403 , "Folder could not be deleted because it contains library elements in use" , err )
2021-03-02 03:34:01 -06:00
}
2021-08-25 08:11:22 -05:00
return apierrors . ToFolderErrorResponse ( err )
2021-03-02 03:34:01 -06:00
}
2022-06-03 15:11:32 -05:00
uid := web . Params ( c . Req ) [ ":uid" ]
2022-12-20 09:38:09 -06:00
err = hs . folderService . Delete ( c . Req . Context ( ) , & folder . DeleteFolderCommand { UID : uid , OrgID : c . OrgID , ForceDeleteRules : c . QueryBool ( "forceDeleteRules" ) , SignedInUser : c . SignedInUser } )
2018-02-20 06:57:32 -06:00
if err != nil {
2021-08-25 08:11:22 -05:00
return apierrors . ToFolderErrorResponse ( err )
2018-01-29 06:51:01 -06:00
}
2022-11-10 02:42:32 -06:00
return response . JSON ( http . StatusOK , "" )
2018-01-29 06:51:01 -06:00
}
2023-01-27 01:50:36 -06:00
func ( hs * HTTPServer ) newToFolderDto ( c * contextmodel . ReqContext , g guardian . DashboardGuardian , folder * folder . Folder ) dtos . Folder {
2022-11-10 03:41:03 -06:00
canEdit , _ := g . CanEdit ( )
canSave , _ := g . CanSave ( )
canAdmin , _ := g . CanAdmin ( )
canDelete , _ := g . CanDelete ( )
// Finding creator and last updater of the folder
updater , creator := anonString , anonString
2022-11-15 04:58:12 -06:00
if folder . CreatedBy > 0 {
creator = hs . getUserLogin ( c . Req . Context ( ) , folder . CreatedBy )
}
if folder . UpdatedBy > 0 {
updater = hs . getUserLogin ( c . Req . Context ( ) , folder . UpdatedBy )
}
2022-11-10 03:41:03 -06:00
return dtos . Folder {
2022-11-15 04:58:12 -06:00
Id : folder . ID ,
Uid : folder . UID ,
Title : folder . Title ,
2023-01-25 02:14:32 -06:00
Url : folder . URL ,
2022-11-15 04:58:12 -06:00
HasACL : folder . HasACL ,
CanSave : canSave ,
CanEdit : canEdit ,
CanAdmin : canAdmin ,
CanDelete : canDelete ,
CreatedBy : creator ,
Created : folder . Created ,
UpdatedBy : updater ,
Updated : folder . Updated ,
Version : folder . Version ,
2022-11-10 03:41:03 -06:00
AccessControl : hs . getAccessControlMetadata ( c , c . OrgID , dashboards . ScopeFoldersPrefix , folder . UID ) ,
2022-11-24 07:59:47 -06:00
ParentUID : folder . ParentUID ,
2022-11-10 03:41:03 -06:00
}
}
2023-01-27 01:50:36 -06:00
func ( hs * HTTPServer ) searchFolders ( c * contextmodel . ReqContext ) ( [ ] * folder . Folder , error ) {
2023-01-23 06:09:09 -06:00
searchQuery := search . Query {
SignedInUser : c . SignedInUser ,
DashboardIds : make ( [ ] int64 , 0 ) ,
FolderIds : make ( [ ] int64 , 0 ) ,
Limit : c . QueryInt64 ( "limit" ) ,
OrgId : c . OrgID ,
Type : "dash-folder" ,
2023-01-26 07:46:30 -06:00
Permission : dashboards . PERMISSION_VIEW ,
2023-01-23 06:09:09 -06:00
Page : c . QueryInt64 ( "page" ) ,
}
2023-03-30 04:28:12 -05:00
hits , err := hs . SearchService . SearchHandler ( c . Req . Context ( ) , & searchQuery )
if err != nil {
2023-01-23 06:09:09 -06:00
return nil , err
}
folders := make ( [ ] * folder . Folder , 0 )
2023-03-30 04:28:12 -05:00
for _ , hit := range hits {
2023-01-23 06:09:09 -06:00
folders = append ( folders , & folder . Folder {
ID : hit . ID ,
UID : hit . UID ,
Title : hit . Title ,
} )
}
return folders , nil
}
2022-07-27 08:54:37 -05:00
// swagger:parameters getFolders
type GetFoldersParams struct {
// Limit the maximum number of folders to return
// in:query
// required:false
// default:1000
Limit int64 ` json:"limit" `
// Page index for starting fetching folders
// in:query
// required:false
// default:1
Page int64 ` json:"page" `
2022-12-19 02:52:04 -06:00
// The parent folder UID
// in:query
// required:false
2023-01-24 02:20:28 -06:00
ParentUID string ` json:"parentUid" `
2022-07-27 08:54:37 -05:00
}
// swagger:parameters getFolderByUID
type GetFolderByUIDParams struct {
// in:path
// required:true
FolderUID string ` json:"folder_uid" `
}
// swagger:parameters updateFolder
type UpdateFolderParams struct {
// in:path
// required:true
FolderUID string ` json:"folder_uid" `
// To change the unique identifier (uid), provide another one.
// To overwrite an existing folder with newer version, set `overwrite` to `true`.
// Provide the current version to safelly update the folder: if the provided version differs from the stored one the request will fail, unless `overwrite` is `true`.
//
// in:body
// required:true
2022-12-20 07:00:33 -06:00
Body folder . UpdateFolderCommand ` json:"body" `
2022-07-27 08:54:37 -05:00
}
// swagger:parameters getFolderByID
type GetFolderByIDParams struct {
// in:path
// required:true
FolderID int64 ` json:"folder_id" `
}
// swagger:parameters createFolder
type CreateFolderParams struct {
// in:body
// required:true
2022-11-10 03:41:03 -06:00
Body folder . CreateFolderCommand ` json:"body" `
2022-07-27 08:54:37 -05:00
}
2023-03-30 03:46:11 -05:00
// swagger:parameters moveFolder
type MoveFolderParams struct {
// in:path
// required:true
FolderUID string ` json:"folder_uid" `
// in:body
// required:true
Body folder . MoveFolderCommand ` json:"body" `
}
2022-07-27 08:54:37 -05:00
// swagger:parameters deleteFolder
type DeleteFolderParams struct {
// in:path
// required:true
FolderUID string ` json:"folder_uid" `
// If `true` any Grafana 8 Alerts under this folder will be deleted.
// Set to `false` so that the request will fail if the folder contains any Grafana 8 Alerts.
// in:query
// required:false
// default:false
ForceDeleteRules bool ` json:"forceDeleteRules" `
}
// swagger:response getFoldersResponse
type GetFoldersResponse struct {
// The response message
// in: body
Body [ ] dtos . FolderSearchHit ` json:"body" `
}
// swagger:response folderResponse
type FolderResponse struct {
// The response message
// in: body
Body dtos . Folder ` json:"body" `
}
// swagger:response deleteFolderResponse
type DeleteFolderResponse struct {
// The response message
// in: body
Body struct {
// ID Identifier of the deleted folder.
// required: true
// example: 65
ID int64 ` json:"id" `
// Title of the deleted folder.
// required: true
// example: My Folder
Title string ` json:"title" `
// Message Message of the deleted folder.
// required: true
// example: Folder My Folder deleted
Message string ` json:"message" `
} ` json:"body" `
}