2018-01-29 20:51:01 +08:00
package api
import (
2024-10-07 18:08:16 +08:00
"context"
2020-07-23 14:15:47 +08:00
"errors"
2024-11-22 21:38:00 +08:00
"fmt"
2021-11-29 17:18:01 +08:00
"net/http"
2022-01-15 00:55:57 +08:00
"strconv"
2024-10-07 18:32:25 +08:00
"strings"
2018-02-21 23:38:09 +08:00
2024-09-10 17:22:08 +08:00
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2024-10-07 18:08:16 +08:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2024-09-10 17:22:08 +08:00
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
2024-12-31 00:48:35 +08:00
"github.com/grafana/authlib/claims"
2021-08-25 21:11:22 +08:00
"github.com/grafana/grafana/pkg/api/apierrors"
2018-01-29 20:51:01 +08:00
"github.com/grafana/grafana/pkg/api/dtos"
2021-01-15 21:43:20 +08:00
"github.com/grafana/grafana/pkg/api/response"
2024-09-25 14:56:15 +08:00
"github.com/grafana/grafana/pkg/api/routing"
2024-12-31 00:48:35 +08:00
"github.com/grafana/grafana/pkg/apimachinery/identity"
2024-09-10 17:22:08 +08:00
folderalpha1 "github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
2024-01-24 19:39:11 +08:00
"github.com/grafana/grafana/pkg/infra/metrics"
2024-10-10 19:22:57 +08:00
"github.com/grafana/grafana/pkg/infra/slugify"
2024-09-10 17:22:08 +08:00
internalfolders "github.com/grafana/grafana/pkg/registry/apis/folders"
2023-01-30 22:19:42 +08:00
"github.com/grafana/grafana/pkg/services/accesscontrol"
2024-09-10 17:22:08 +08:00
grafanaapiserver "github.com/grafana/grafana/pkg/services/apiserver"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
2023-01-27 15:50:36 +08:00
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
2022-06-22 16:29:26 +08:00
"github.com/grafana/grafana/pkg/services/dashboards"
2023-11-22 21:20:22 +08:00
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
2022-11-10 22:06:52 +08:00
"github.com/grafana/grafana/pkg/services/featuremgmt"
2022-11-10 16:42:32 +08:00
"github.com/grafana/grafana/pkg/services/folder"
2018-01-29 20:51:01 +08:00
"github.com/grafana/grafana/pkg/services/guardian"
2023-02-02 00:32:05 +08:00
"github.com/grafana/grafana/pkg/services/libraryelements/model"
2024-12-31 00:48:35 +08:00
"github.com/grafana/grafana/pkg/services/org"
2023-01-23 20:09:09 +08:00
"github.com/grafana/grafana/pkg/services/search"
2024-10-07 18:08:16 +08:00
"github.com/grafana/grafana/pkg/services/user"
2023-08-25 00:10:58 +08:00
"github.com/grafana/grafana/pkg/util"
2024-09-10 17:22:08 +08:00
"github.com/grafana/grafana/pkg/util/errhttp"
2021-10-11 20:30:59 +08:00
"github.com/grafana/grafana/pkg/web"
2018-01-29 20:51:01 +08:00
)
2023-09-08 15:43:41 +08:00
const REDACTED = "redacted"
2024-09-25 14:56:15 +08:00
func ( hs * HTTPServer ) registerFolderAPI ( apiRoute routing . RouteRegister , authorize func ( accesscontrol . Evaluator ) web . Handler ) {
// #TODO add back auth part
apiRoute . Group ( "/folders" , func ( folderRoute routing . RouteRegister ) {
2024-10-04 17:26:36 +08:00
idScope := dashboards . ScopeFoldersProvider . GetResourceScope ( accesscontrol . Parameter ( ":id" ) )
uidScope := dashboards . ScopeFoldersProvider . GetResourceScopeUID ( accesscontrol . Parameter ( ":uid" ) )
folderRoute . Get ( "/id/:id" , authorize ( accesscontrol . EvalPermission ( dashboards . ActionFoldersRead , idScope ) ) , routing . Wrap ( hs . GetFolderByID ) )
folderRoute . Group ( "/:uid" , func ( folderUidRoute routing . RouteRegister ) {
folderUidRoute . Group ( "/permissions" , func ( folderPermissionRoute routing . RouteRegister ) {
folderPermissionRoute . Get ( "/" , authorize ( accesscontrol . EvalPermission ( dashboards . ActionFoldersPermissionsRead , uidScope ) ) , routing . Wrap ( hs . GetFolderPermissionList ) )
folderPermissionRoute . Post ( "/" , authorize ( accesscontrol . EvalPermission ( dashboards . ActionFoldersPermissionsWrite , uidScope ) ) , routing . Wrap ( hs . UpdateFolderPermissions ) )
} )
} )
2025-01-14 05:15:35 +08:00
if hs . Features . IsEnabledGlobally ( featuremgmt . FlagKubernetesFolders ) && ! hs . Features . IsEnabledGlobally ( featuremgmt . FlagKubernetesFoldersServiceV2 ) {
2024-09-25 14:56:15 +08:00
// Use k8s client to implement legacy API
handler := newFolderK8sHandler ( hs )
folderRoute . Post ( "/" , handler . createFolder )
2024-11-22 21:38:00 +08:00
folderRoute . Get ( "/" , handler . getFolders )
2024-11-22 23:51:53 +08:00
folderRoute . Group ( "/:uid" , func ( folderUidRoute routing . RouteRegister ) {
2024-12-04 02:33:01 +08:00
folderUidRoute . Put ( "/" , handler . updateFolder )
2024-11-22 23:51:53 +08:00
folderUidRoute . Delete ( "/" , handler . deleteFolder )
2024-11-26 22:20:00 +08:00
folderUidRoute . Get ( "/" , handler . getFolder )
2024-12-28 01:10:44 +08:00
if hs . Features . IsEnabledGlobally ( featuremgmt . FlagK8SFolderCounts ) {
folderUidRoute . Get ( "/counts" , handler . countFolderContent )
} else {
folderUidRoute . Get ( "/counts" , authorize ( accesscontrol . EvalPermission ( dashboards . ActionFoldersRead , uidScope ) ) , routing . Wrap ( hs . GetFolderDescendantCounts ) )
}
if hs . Features . IsEnabledGlobally ( featuremgmt . FlagK8SFolderMove ) {
folderUidRoute . Post ( "/move" , handler . moveFolder )
} else {
folderUidRoute . Post ( "/move" , authorize ( accesscontrol . EvalPermission ( dashboards . ActionFoldersWrite , uidScope ) ) , routing . Wrap ( hs . MoveFolder ) )
}
2025-01-03 17:43:56 +08:00
folderUidRoute . Get ( "parents" , handler . getFolderParents )
2024-11-22 23:51:53 +08:00
} )
2024-09-25 14:56:15 +08:00
} else {
folderRoute . Post ( "/" , authorize ( accesscontrol . EvalPermission ( dashboards . ActionFoldersCreate ) ) , routing . Wrap ( hs . CreateFolder ) )
2024-11-22 21:38:00 +08:00
folderRoute . Get ( "/" , authorize ( accesscontrol . EvalPermission ( dashboards . ActionFoldersRead ) ) , routing . Wrap ( hs . GetFolders ) )
2024-11-22 23:51:53 +08:00
folderRoute . Group ( "/:uid" , func ( folderUidRoute routing . RouteRegister ) {
2024-12-04 02:33:01 +08:00
folderUidRoute . Put ( "/" , authorize ( accesscontrol . EvalPermission ( dashboards . ActionFoldersWrite , uidScope ) ) , routing . Wrap ( hs . UpdateFolder ) )
2024-11-22 23:51:53 +08:00
folderUidRoute . Delete ( "/" , authorize ( accesscontrol . EvalPermission ( dashboards . ActionFoldersDelete , uidScope ) ) , routing . Wrap ( hs . DeleteFolder ) )
2024-11-26 22:20:00 +08:00
folderUidRoute . Get ( "/" , authorize ( accesscontrol . EvalPermission ( dashboards . ActionFoldersRead , uidScope ) ) , routing . Wrap ( hs . GetFolderByUID ) )
2024-12-16 21:08:29 +08:00
folderUidRoute . Get ( "/counts" , authorize ( accesscontrol . EvalPermission ( dashboards . ActionFoldersRead , uidScope ) ) , routing . Wrap ( hs . GetFolderDescendantCounts ) )
2024-12-19 16:59:14 +08:00
folderUidRoute . Post ( "/move" , authorize ( accesscontrol . EvalPermission ( dashboards . ActionFoldersWrite , uidScope ) ) , routing . Wrap ( hs . MoveFolder ) )
2024-11-22 23:51:53 +08:00
} )
2024-09-25 14:56:15 +08:00
}
} )
}
2022-07-27 21:54:37 +08:00
// swagger:route GET /folders folders getFolders
//
// Get all folders.
//
2024-03-07 18:07:35 +08:00
// It returns all folders that the authenticated user has permission to view.
2023-01-23 20:09:09 +08:00
// If nested folders are enabled, it expects an additional query parameter with the parent folder UID
2023-04-27 22:24:47 +08:00
// and returns the immediate subfolders that the authenticated user has permission to view.
// If the parameter is not supplied then it returns immediate subfolders under the root
// that the authenticated user has permission to view.
2022-07-27 21:54:37 +08:00
//
// Responses:
// 200: getFoldersResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
2023-01-27 15:50:36 +08:00
func ( hs * HTTPServer ) GetFolders ( c * contextmodel . ReqContext ) response . Response {
2024-03-15 20:05:27 +08:00
permission := dashboardaccess . PERMISSION_VIEW
if c . Query ( "permission" ) == "Edit" {
permission = dashboardaccess . PERMISSION_EDIT
}
2023-11-15 04:50:27 +08:00
if hs . Features . IsEnabled ( c . Req . Context ( ) , featuremgmt . FlagNestedFolders ) {
2024-01-16 19:35:10 +08:00
q := & folder . GetChildrenQuery {
2023-10-06 17:34:36 +08:00
OrgID : c . SignedInUser . GetOrgID ( ) ,
2023-01-23 20:09:09 +08:00
Limit : c . QueryInt64 ( "limit" ) ,
Page : c . QueryInt64 ( "page" ) ,
2023-01-24 16:20:28 +08:00
UID : c . Query ( "parentUid" ) ,
2024-03-15 20:05:27 +08:00
Permission : permission ,
2023-01-23 20:09:09 +08:00
SignedInUser : c . SignedInUser ,
2024-01-16 19:35:10 +08:00
}
folders , err := hs . folderService . GetChildren ( c . Req . Context ( ) , q )
if err != nil {
return apierrors . ToFolderErrorResponse ( err )
}
hits := make ( [ ] dtos . FolderSearchHit , 0 )
for _ , f := range folders {
hits = append ( hits , dtos . FolderSearchHit {
ID : f . ID , // nolint:staticcheck
UID : f . UID ,
Title : f . Title ,
ParentUID : f . ParentUID ,
} )
2024-01-25 19:14:18 +08:00
metrics . MFolderIDsAPICount . WithLabelValues ( metrics . GetFolders ) . Inc ( )
2024-01-16 19:35:10 +08:00
}
return response . JSON ( http . StatusOK , hits )
2023-01-23 20:09:09 +08:00
}
2018-01-29 20:51:01 +08:00
2024-03-15 20:05:27 +08:00
hits , err := hs . searchFolders ( c , permission )
2018-01-29 20:51:01 +08:00
if err != nil {
2021-08-25 21:11:22 +08:00
return apierrors . ToFolderErrorResponse ( err )
2018-01-29 20:51:01 +08:00
}
2024-01-16 19:35:10 +08:00
return response . JSON ( http . StatusOK , hits )
2018-01-29 20:51:01 +08:00
}
2022-07-27 21:54:37 +08: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 15:50:36 +08:00
func ( hs * HTTPServer ) GetFolderByUID ( c * contextmodel . ReqContext ) response . Response {
2022-11-11 21:28:24 +08:00
uid := web . Params ( c . Req ) [ ":uid" ]
2023-10-06 17:34:36 +08:00
folder , err := hs . folderService . Get ( c . Req . Context ( ) , & folder . GetFolderQuery { OrgID : c . SignedInUser . GetOrgID ( ) , UID : & uid , SignedInUser : c . SignedInUser } )
2018-02-20 20:57:32 +08:00
if err != nil {
2021-08-25 21:11:22 +08:00
return apierrors . ToFolderErrorResponse ( err )
2018-01-29 20:51:01 +08:00
}
2023-09-08 15:43:41 +08:00
folderDTO , err := hs . newToFolderDto ( c , folder )
2022-12-15 22:34:17 +08:00
if err != nil {
return response . Err ( err )
}
2023-09-08 15:43:41 +08:00
return response . JSON ( http . StatusOK , folderDTO )
2018-02-20 20:57:32 +08:00
}
2018-01-29 20:51:01 +08:00
2022-07-27 21:54:37 +08:00
// swagger:route GET /folders/id/{folder_id} folders getFolderByID
//
// Get folder by id.
//
2023-04-25 15:33:47 +08:00
// Returns the folder identified by id. This is deprecated.
// Please refer to [updated API](#/folders/getFolderByUID) instead
//
// Deprecated: true
2022-07-27 21:54:37 +08:00
//
// Responses:
// 200: folderResponse
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
2023-01-27 15:50:36 +08:00
func ( hs * HTTPServer ) GetFolderByID ( c * contextmodel . ReqContext ) response . Response {
2022-01-15 00:55:57 +08:00
id , err := strconv . ParseInt ( web . Params ( c . Req ) [ ":id" ] , 10 , 64 )
if err != nil {
return response . Error ( http . StatusBadRequest , "id is invalid" , err )
}
2024-01-25 19:14:18 +08:00
metrics . MFolderIDsAPICount . WithLabelValues ( metrics . GetFolderByID ) . Inc ( )
2023-11-15 23:30:00 +08:00
// nolint:staticcheck
2023-10-06 17:34:36 +08:00
folder , err := hs . folderService . Get ( c . Req . Context ( ) , & folder . GetFolderQuery { ID : & id , OrgID : c . SignedInUser . GetOrgID ( ) , SignedInUser : c . SignedInUser } )
2018-01-29 20:51:01 +08:00
if err != nil {
2021-08-25 21:11:22 +08:00
return apierrors . ToFolderErrorResponse ( err )
2018-01-29 20:51:01 +08:00
}
2023-09-08 15:43:41 +08:00
folderDTO , err := hs . newToFolderDto ( c , folder )
2022-12-15 22:34:17 +08:00
if err != nil {
return response . Err ( err )
}
2023-09-08 15:43:41 +08:00
return response . JSON ( http . StatusOK , folderDTO )
2018-02-20 20:57:32 +08:00
}
2018-01-29 20:51:01 +08:00
2022-07-27 21:54:37 +08:00
// swagger:route POST /folders folders createFolder
//
// Create folder.
//
2022-11-10 17:41:03 +08:00
// If nested folders are enabled then it additionally expects the parent folder UID.
//
2022-07-27 21:54:37 +08:00
// Responses:
// 200: folderResponse
// 400: badRequestError
// 401: unauthorisedError
// 403: forbiddenError
// 409: conflictError
// 500: internalServerError
2023-01-27 15:50:36 +08:00
func ( hs * HTTPServer ) CreateFolder ( c * contextmodel . ReqContext ) response . Response {
2022-11-10 17:41:03 +08:00
cmd := folder . CreateFolderCommand { }
2021-11-29 17:18:01 +08:00
if err := web . Bind ( c . Req , & cmd ) ; err != nil {
return response . Error ( http . StatusBadRequest , "bad request data" , err )
}
2023-10-06 17:34:36 +08:00
cmd . OrgID = c . SignedInUser . GetOrgID ( )
2022-11-23 17:13:47 +08:00
cmd . SignedInUser = c . SignedInUser
2022-11-10 17:41:03 +08:00
folder , err := hs . folderService . Create ( c . Req . Context ( ) , & cmd )
2018-01-29 20:51:01 +08:00
if err != nil {
2021-08-25 21:11:22 +08:00
return apierrors . ToFolderErrorResponse ( err )
2018-01-29 20:51:01 +08:00
}
2024-12-31 00:48:35 +08: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 22:38:55 +08: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
2023-07-10 20:14:21 +08:00
hs . accesscontrolService . ClearUserPermissionCache ( c . SignedInUser )
2022-11-24 22:38:55 +08:00
2023-09-08 15:43:41 +08:00
folderDTO , err := hs . newToFolderDto ( c , folder )
2022-12-15 22:34:17 +08:00
if err != nil {
return response . Err ( err )
}
2022-11-10 17:41:03 +08:00
// TODO set ParentUID if nested folders are enabled
2023-09-08 15:43:41 +08:00
return response . JSON ( http . StatusOK , folderDTO )
2018-01-29 20:51:01 +08:00
}
2024-12-31 00:48:35 +08:00
func ( hs * HTTPServer ) setDefaultFolderPermissions ( ctx context . Context , orgID int64 , user identity . Requester , folder * folder . Folder ) error {
if ! hs . Cfg . RBAC . PermissionsOnCreation ( "folder" ) {
return nil
}
var permissions [ ] accesscontrol . SetResourcePermissionCommand
if user . IsIdentityType ( claims . TypeUser ) {
userID , err := user . GetInternalID ( )
if err != nil {
return err
}
permissions = append ( permissions , accesscontrol . SetResourcePermissionCommand {
UserID : userID , Permission : dashboardaccess . PERMISSION_ADMIN . String ( ) ,
} )
}
isNested := folder . ParentUID != ""
if ! isNested || ! hs . Features . IsEnabled ( ctx , featuremgmt . FlagNestedFolders ) {
permissions = append ( permissions , [ ] accesscontrol . SetResourcePermissionCommand {
{ BuiltinRole : string ( org . RoleEditor ) , Permission : dashboardaccess . PERMISSION_EDIT . String ( ) } ,
{ BuiltinRole : string ( org . RoleViewer ) , Permission : dashboardaccess . PERMISSION_VIEW . String ( ) } ,
} ... )
}
_ , err := hs . folderPermissionsService . SetPermissions ( ctx , orgID , folder . UID , permissions ... )
return err
}
2023-03-30 16:46:11 +08: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 15:50:36 +08:00
func ( hs * HTTPServer ) MoveFolder ( c * contextmodel . ReqContext ) response . Response {
2023-11-15 04:50:27 +08:00
if hs . Features . IsEnabled ( c . Req . Context ( ) , featuremgmt . FlagNestedFolders ) {
2023-01-25 16:14:32 +08:00
cmd := folder . MoveFolderCommand { }
2022-11-10 22:06:52 +08: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 19:04:22 +08:00
2023-10-06 17:34:36 +08:00
cmd . OrgID = c . SignedInUser . GetOrgID ( )
2023-03-30 16:46:11 +08:00
cmd . UID = web . Params ( c . Req ) [ ":uid" ]
cmd . SignedInUser = c . SignedInUser
theFolder , err := hs . folderService . Move ( c . Req . Context ( ) , & cmd )
if err != nil {
2023-10-24 15:04:45 +08:00
return response . ErrOrFallback ( http . StatusInternalServerError , "move folder failed" , err )
2022-11-10 22:06:52 +08:00
}
2023-03-30 16:46:11 +08:00
2023-09-08 15:43:41 +08:00
folderDTO , err := hs . newToFolderDto ( c , theFolder )
2023-03-30 16:46:11 +08:00
if err != nil {
return response . Err ( err )
}
2023-09-08 15:43:41 +08:00
return response . JSON ( http . StatusOK , folderDTO )
2022-11-10 22:06:52 +08: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 21:54:37 +08: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 15:50:36 +08:00
func ( hs * HTTPServer ) UpdateFolder ( c * contextmodel . ReqContext ) response . Response {
2022-12-20 21:00:33 +08:00
cmd := folder . UpdateFolderCommand { }
2021-11-29 17:18:01 +08:00
if err := web . Bind ( c . Req , & cmd ) ; err != nil {
return response . Error ( http . StatusBadRequest , "bad request data" , err )
}
2022-12-20 21:00:33 +08:00
2023-10-06 17:34:36 +08:00
cmd . OrgID = c . SignedInUser . GetOrgID ( )
2022-12-20 21:00:33 +08:00
cmd . UID = web . Params ( c . Req ) [ ":uid" ]
cmd . SignedInUser = c . SignedInUser
result , err := hs . folderService . Update ( c . Req . Context ( ) , & cmd )
2018-01-29 20:51:01 +08:00
if err != nil {
2021-08-25 21:11:22 +08:00
return apierrors . ToFolderErrorResponse ( err )
2018-01-29 20:51:01 +08:00
}
2023-09-08 15:43:41 +08:00
folderDTO , err := hs . newToFolderDto ( c , result )
2022-12-15 22:34:17 +08:00
if err != nil {
return response . Err ( err )
}
2023-09-08 15:43:41 +08:00
return response . JSON ( http . StatusOK , folderDTO )
2018-01-29 20:51:01 +08:00
}
2022-07-27 21:54:37 +08: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 17:41:03 +08:00
// If nested folders are enabled then it also deletes all the subfolders.
2022-07-27 21:54:37 +08:00
//
// Responses:
// 200: deleteFolderResponse
// 400: badRequestError
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
2023-01-27 15:50:36 +08: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 20:30:59 +08:00
err := hs . LibraryElementService . DeleteLibraryElementsInFolder ( c . Req . Context ( ) , c . SignedInUser , web . Params ( c . Req ) [ ":uid" ] )
2021-05-12 14:48:17 +08:00
if err != nil {
2023-02-02 00:32:05 +08:00
if errors . Is ( err , model . ErrFolderHasConnectedLibraryElements ) {
2024-02-28 00:39:51 +08:00
return response . Error ( http . StatusForbidden , "Folder could not be deleted because it contains library elements in use" , err )
2021-03-02 17:34:01 +08:00
}
2021-08-25 21:11:22 +08:00
return apierrors . ToFolderErrorResponse ( err )
2021-03-02 17:34:01 +08:00
}
2023-07-25 19:05:53 +08:00
/ * TODO : after a decision regarding folder deletion permissions has been made
( https : //github.com/grafana/grafana-enterprise/issues/5144),
remove the previous call to hs . LibraryElementService . DeleteLibraryElementsInFolder
and remove "user" from the signature of DeleteInFolder in the folder RegistryService .
Context : https : //github.com/grafana/grafana/pull/69149#discussion_r1235057903
* /
2021-03-02 17:34:01 +08:00
2022-06-04 04:11:32 +08:00
uid := web . Params ( c . Req ) [ ":uid" ]
2023-10-06 17:34:36 +08:00
err = hs . folderService . Delete ( c . Req . Context ( ) , & folder . DeleteFolderCommand { UID : uid , OrgID : c . SignedInUser . GetOrgID ( ) , ForceDeleteRules : c . QueryBool ( "forceDeleteRules" ) , SignedInUser : c . SignedInUser } )
2018-02-20 20:57:32 +08:00
if err != nil {
2021-08-25 21:11:22 +08:00
return apierrors . ToFolderErrorResponse ( err )
2018-01-29 20:51:01 +08:00
}
2023-08-25 00:10:58 +08:00
return response . JSON ( http . StatusOK , util . DynMap {
"message" : "Folder deleted" ,
} )
2018-01-29 20:51:01 +08:00
}
2023-04-27 23:00:09 +08:00
// swagger:route GET /folders/{folder_uid}/counts folders getFolderDescendantCounts
2023-04-24 21:57:28 +08:00
//
// Gets the count of each descendant of a folder by kind. The folder is identified by UID.
//
// Responses:
2023-04-27 23:00:09 +08:00
// 200: getFolderDescendantCountsResponse
2023-04-24 21:57:28 +08:00
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
2023-04-27 23:00:09 +08:00
func ( hs * HTTPServer ) GetFolderDescendantCounts ( c * contextmodel . ReqContext ) response . Response {
2023-04-24 21:57:28 +08:00
uid := web . Params ( c . Req ) [ ":uid" ]
2023-10-06 17:34:36 +08:00
counts , err := hs . folderService . GetDescendantCounts ( c . Req . Context ( ) , & folder . GetDescendantCountsQuery { OrgID : c . SignedInUser . GetOrgID ( ) , UID : & uid , SignedInUser : c . SignedInUser } )
2023-04-24 21:57:28 +08:00
if err != nil {
return apierrors . ToFolderErrorResponse ( err )
}
return response . JSON ( http . StatusOK , counts )
}
2023-09-08 15:43:41 +08:00
func ( hs * HTTPServer ) newToFolderDto ( c * contextmodel . ReqContext , f * folder . Folder ) ( dtos . Folder , error ) {
2023-04-25 16:22:20 +08:00
ctx := c . Req . Context ( )
2023-09-08 15:43:41 +08:00
toDTO := func ( f * folder . Folder , checkCanView bool ) ( dtos . Folder , error ) {
2023-10-06 17:34:36 +08:00
g , err := guardian . NewByFolder ( c . Req . Context ( ) , f , c . SignedInUser . GetOrgID ( ) , c . SignedInUser )
2023-09-08 15:43:41 +08:00
if err != nil {
return dtos . Folder { } , err
}
2023-04-25 16:22:20 +08: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
if f . CreatedBy > 0 {
2025-01-10 17:06:59 +08:00
creator = hs . getIdentityName ( ctx , f . OrgID , f . CreatedBy )
2023-04-25 16:22:20 +08:00
}
if f . UpdatedBy > 0 {
2025-01-10 17:06:59 +08:00
updater = hs . getIdentityName ( ctx , f . OrgID , f . UpdatedBy )
2023-04-25 16:22:20 +08:00
}
acMetadata , _ := hs . getFolderACMetadata ( c , f )
2023-09-08 15:43:41 +08:00
if checkCanView {
canView , _ := g . CanView ( )
if ! canView {
return dtos . Folder {
2023-12-07 20:15:58 +08:00
UID : REDACTED ,
2023-09-08 15:43:41 +08:00
Title : REDACTED ,
} , nil
}
}
2024-01-25 19:14:18 +08:00
metrics . MFolderIDsAPICount . WithLabelValues ( metrics . NewToFolderDTO ) . Inc ( )
2023-04-25 16:22:20 +08:00
return dtos . Folder {
2023-12-07 20:15:58 +08:00
ID : f . ID , // nolint:staticcheck
UID : f . UID ,
2023-04-25 16:22:20 +08:00
Title : f . Title ,
2023-12-07 20:15:58 +08:00
URL : f . URL ,
2023-04-25 16:22:20 +08:00
HasACL : f . HasACL ,
CanSave : canSave ,
CanEdit : canEdit ,
CanAdmin : canAdmin ,
CanDelete : canDelete ,
CreatedBy : creator ,
Created : f . Created ,
UpdatedBy : updater ,
Updated : f . Updated ,
Version : f . Version ,
AccessControl : acMetadata ,
ParentUID : f . ParentUID ,
2023-09-08 15:43:41 +08:00
} , nil
2023-04-25 16:22:20 +08:00
}
2023-04-24 21:57:28 +08:00
2023-09-08 15:43:41 +08:00
// no need to check view permission for the starting folder since it's already checked by the callers
folderDTO , err := toDTO ( f , false )
if err != nil {
return dtos . Folder { } , err
}
2022-11-10 17:41:03 +08:00
2023-11-15 04:50:27 +08:00
if ! hs . Features . IsEnabled ( c . Req . Context ( ) , featuremgmt . FlagNestedFolders ) {
2023-09-08 15:43:41 +08:00
return folderDTO , nil
2022-11-15 18:58:12 +08:00
}
2023-04-25 16:22:20 +08:00
parents , err := hs . folderService . GetParents ( ctx , folder . GetParentsQuery { UID : f . UID , OrgID : f . OrgID } )
if err != nil {
// log the error instead of failing
hs . log . Error ( "failed to fetch folder parents" , "folder" , f . UID , "org" , f . OrgID , "error" , err )
2022-11-15 18:58:12 +08:00
}
2022-11-10 17:41:03 +08:00
2023-04-25 16:22:20 +08:00
folderDTO . Parents = make ( [ ] dtos . Folder , 0 , len ( parents ) )
for _ , f := range parents {
2023-09-08 15:43:41 +08:00
DTO , err := toDTO ( f , true )
if err != nil {
hs . log . Error ( "failed to convert folder to DTO" , "folder" , f . UID , "org" , f . OrgID , "error" , err )
continue
}
folderDTO . Parents = append ( folderDTO . Parents , DTO )
2022-11-10 17:41:03 +08:00
}
2023-04-25 16:22:20 +08:00
2023-09-08 15:43:41 +08:00
return folderDTO , nil
2022-11-10 17:41:03 +08:00
}
2023-04-21 22:05:11 +08:00
func ( hs * HTTPServer ) getFolderACMetadata ( c * contextmodel . ReqContext , f * folder . Folder ) ( accesscontrol . Metadata , error ) {
2023-07-10 20:14:21 +08:00
if ! c . QueryBool ( "accesscontrol" ) {
2023-04-21 22:05:11 +08:00
return nil , nil
}
2023-10-06 17:34:36 +08:00
parents , err := hs . folderService . GetParents ( c . Req . Context ( ) , folder . GetParentsQuery { UID : f . UID , OrgID : c . SignedInUser . GetOrgID ( ) } )
2023-04-21 22:05:11 +08:00
if err != nil {
return nil , err
}
folderIDs := map [ string ] bool { f . UID : true }
for _ , p := range parents {
folderIDs [ p . UID ] = true
}
2024-10-07 18:08:16 +08:00
allMetadata := getMultiAccessControlMetadata ( c , dashboards . ScopeFoldersPrefix , folderIDs )
2023-11-28 17:28:47 +08:00
metadata := map [ string ] bool { }
2023-04-21 22:05:11 +08:00
// Flatten metadata - if any parent has a permission, the child folder inherits it
for _ , md := range allMetadata {
for action := range md {
metadata [ action ] = true
}
}
return metadata , nil
}
2024-03-15 20:05:27 +08:00
func ( hs * HTTPServer ) searchFolders ( c * contextmodel . ReqContext , permission dashboardaccess . PermissionType ) ( [ ] dtos . FolderSearchHit , error ) {
2023-01-23 20:09:09 +08:00
searchQuery := search . Query {
SignedInUser : c . SignedInUser ,
DashboardIds : make ( [ ] int64 , 0 ) ,
2023-11-07 22:51:44 +08:00
FolderIds : make ( [ ] int64 , 0 ) , // nolint:staticcheck
2023-01-23 20:09:09 +08:00
Limit : c . QueryInt64 ( "limit" ) ,
2023-10-06 17:34:36 +08:00
OrgId : c . SignedInUser . GetOrgID ( ) ,
2023-01-23 20:09:09 +08:00
Type : "dash-folder" ,
2024-03-15 20:05:27 +08:00
Permission : permission ,
2023-01-23 20:09:09 +08:00
Page : c . QueryInt64 ( "page" ) ,
}
2023-03-30 17:28:12 +08:00
hits , err := hs . SearchService . SearchHandler ( c . Req . Context ( ) , & searchQuery )
if err != nil {
2023-01-23 20:09:09 +08:00
return nil , err
}
2024-01-16 19:35:10 +08:00
folderHits := make ( [ ] dtos . FolderSearchHit , 0 )
2023-03-30 17:28:12 +08:00
for _ , hit := range hits {
2024-01-16 19:35:10 +08:00
folderHits = append ( folderHits , dtos . FolderSearchHit {
2023-11-21 04:44:51 +08:00
ID : hit . ID , // nolint:staticcheck
2023-01-23 20:09:09 +08:00
UID : hit . UID ,
Title : hit . Title ,
} )
2024-01-25 19:14:18 +08:00
metrics . MFolderIDsAPICount . WithLabelValues ( metrics . SearchFolders ) . Inc ( )
2023-01-23 20:09:09 +08:00
}
2024-01-16 19:35:10 +08:00
return folderHits , nil
2023-01-23 20:09:09 +08:00
}
2022-07-27 21:54:37 +08: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 16:52:04 +08:00
// The parent folder UID
// in:query
// required:false
2023-01-24 16:20:28 +08:00
ParentUID string ` json:"parentUid" `
2024-03-15 20:05:27 +08:00
// Set to `Edit` to return folders that the user can edit
// in:query
// required: false
// default:View
// Enum: Edit,View
Permission string ` json:"permission" `
2022-07-27 21:54:37 +08: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 21:00:33 +08:00
Body folder . UpdateFolderCommand ` json:"body" `
2022-07-27 21:54:37 +08:00
}
// swagger:parameters getFolderByID
type GetFolderByIDParams struct {
// in:path
// required:true
2023-11-16 22:57:04 +08:00
//
// Deprecated: use FolderUID instead
2022-07-27 21:54:37 +08:00
FolderID int64 ` json:"folder_id" `
}
// swagger:parameters createFolder
type CreateFolderParams struct {
// in:body
// required:true
2022-11-10 17:41:03 +08:00
Body folder . CreateFolderCommand ` json:"body" `
2022-07-27 21:54:37 +08:00
}
2023-03-30 16:46:11 +08: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 21:54:37 +08: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" `
}
2023-04-24 21:57:28 +08:00
2023-04-27 23:00:09 +08:00
// swagger:parameters getFolderDescendantCounts
type GetFolderDescendantCountsParams struct {
2023-04-24 21:57:28 +08:00
// in:path
// required:true
FolderUID string ` json:"folder_uid" `
}
2023-04-27 23:00:09 +08:00
// swagger:response getFolderDescendantCountsResponse
type GetFolderDescendantCountsResponse struct {
2023-04-24 21:57:28 +08:00
// The response message
// in: body
2023-04-27 23:00:09 +08:00
Body folder . DescendantCounts ` json:"body" `
2023-04-24 21:57:28 +08:00
}
2024-09-10 17:22:08 +08:00
type folderK8sHandler struct {
namespacer request . NamespaceMapper
gvr schema . GroupVersionResource
clientConfigProvider grafanaapiserver . DirectRestConfigProvider
2024-10-04 17:26:36 +08:00
// #TODO check if it makes more sense to move this to FolderAPIBuilder
accesscontrolService accesscontrol . Service
2024-10-07 18:08:16 +08:00
userService user . Service
2024-09-10 17:22:08 +08:00
}
//-----------------------------------------------------------------------------------------
// Folder k8s wrapper functions
//-----------------------------------------------------------------------------------------
func newFolderK8sHandler ( hs * HTTPServer ) * folderK8sHandler {
return & folderK8sHandler {
gvr : folderalpha1 . FolderResourceInfo . GroupVersionResource ( ) ,
namespacer : request . GetNamespaceMapper ( hs . Cfg ) ,
clientConfigProvider : hs . clientConfigProvider ,
2024-10-04 17:26:36 +08:00
accesscontrolService : hs . accesscontrolService ,
2024-10-07 18:08:16 +08:00
userService : hs . userService ,
2024-10-04 17:26:36 +08:00
}
}
2024-09-10 17:22:08 +08:00
func ( fk8s * folderK8sHandler ) createFolder ( c * contextmodel . ReqContext ) {
client , ok := fk8s . getClient ( c )
if ! ok {
return // error is already sent
}
2024-12-27 20:51:10 +08:00
cmd := & folder . CreateFolderCommand { }
if err := web . Bind ( c . Req , cmd ) ; err != nil {
2024-09-10 17:22:08 +08:00
c . JsonApiErr ( http . StatusBadRequest , "bad request data" , err )
return
}
2024-09-25 14:56:15 +08:00
obj , err := internalfolders . LegacyCreateCommandToUnstructured ( cmd )
if err != nil {
fk8s . writeError ( c , err )
return
}
2024-12-27 20:51:10 +08:00
out , err := client . Create ( c . Req . Context ( ) , obj , v1 . CreateOptions { } )
2024-09-10 17:22:08 +08:00
if err != nil {
fk8s . writeError ( c , err )
return
}
2024-09-25 14:56:15 +08:00
2024-10-07 18:32:25 +08:00
fk8s . accesscontrolService . ClearUserPermissionCache ( c . SignedInUser )
2024-10-07 22:48:56 +08:00
folderDTO , err := fk8s . newToFolderDto ( c , * out , c . SignedInUser . GetOrgID ( ) )
2024-09-25 14:56:15 +08:00
if err != nil {
fk8s . writeError ( c , err )
return
}
2024-10-07 18:08:16 +08:00
c . JSON ( http . StatusOK , folderDTO )
2024-09-10 17:22:08 +08:00
}
2024-11-22 21:38:00 +08:00
func ( fk8s * folderK8sHandler ) getFolders ( c * contextmodel . ReqContext ) {
// NOTE: the current implementation is temporary and it will be
// replaced by a proper indexing service/search API
// Also, the current implementation does not support pagination
parentUid := strings . ToUpper ( c . Query ( "parentUid" ) )
client , ok := fk8s . getClient ( c )
if ! ok {
return // error is already sent
}
// check that parent exists
if parentUid != "" {
_ , err := client . Get ( c . Req . Context ( ) , c . Query ( "parentUid" ) , v1 . GetOptions { } )
if err != nil {
fk8s . writeError ( c , err )
return
}
}
out , err := client . List ( c . Req . Context ( ) , v1 . ListOptions { } )
if err != nil {
fk8s . writeError ( c , err )
return
}
hits := make ( [ ] dtos . FolderSearchHit , 0 )
for _ , item := range out . Items {
// convert item to legacy folder format
f , _ := internalfolders . UnstructuredToLegacyFolder ( item , c . SignedInUser . GetOrgID ( ) )
if f == nil {
fk8s . writeError ( c , fmt . Errorf ( "unable covert unstructured item to legacy folder" ) )
return
}
// it we are at root level, skip subfolder
if parentUid == "" && f . ParentUID != "" {
continue // query filter
}
// if we are at a nested folder, then skip folders that don't belong to parentUid
if parentUid != "" && strings . ToUpper ( f . ParentUID ) != parentUid {
continue
}
hits = append ( hits , dtos . FolderSearchHit {
ID : f . ID , // nolint:staticcheck
UID : f . UID ,
Title : f . Title ,
ParentUID : f . ParentUID ,
} )
}
c . JSON ( http . StatusOK , hits )
}
2024-12-16 21:08:29 +08:00
func ( fk8s * folderK8sHandler ) countFolderContent ( c * contextmodel . ReqContext ) {
client , ok := fk8s . getClient ( c )
if ! ok {
return
}
uid := web . Params ( c . Req ) [ ":uid" ]
counts , err := client . Get ( c . Req . Context ( ) , uid , v1 . GetOptions { } , "counts" )
if err != nil {
fk8s . writeError ( c , err )
return
}
out , err := toFolderLegacyCounts ( counts )
if err != nil {
fk8s . writeError ( c , err )
return
}
c . JSON ( http . StatusOK , out )
}
2025-01-03 17:43:56 +08:00
func ( fk8s * folderK8sHandler ) getFolderParents ( c * contextmodel . ReqContext ) {
client , ok := fk8s . getClient ( c )
if ! ok {
return
}
uid := web . Params ( c . Req ) [ ":uid" ]
out , err := client . Get ( c . Req . Context ( ) , uid , v1 . GetOptions { } , "parents" )
if err != nil {
fk8s . writeError ( c , err )
return
}
c . JSON ( http . StatusOK , out )
}
2024-10-22 01:08:03 +08:00
func ( fk8s * folderK8sHandler ) getFolder ( c * contextmodel . ReqContext ) {
client , ok := fk8s . getClient ( c )
if ! ok {
return // error is already sent
}
uid := web . Params ( c . Req ) [ ":uid" ]
2024-12-19 09:31:55 +08:00
var out * unstructured . Unstructured
var err error
if uid == accesscontrol . GeneralFolderUID {
out = & unstructured . Unstructured {
Object : map [ string ] interface { } {
"spec" : map [ string ] interface { } {
"title" : folder . RootFolder . Title ,
"description" : folder . RootFolder . Description ,
} ,
} ,
}
out . SetName ( folder . RootFolder . UID )
} else {
out , err = client . Get ( c . Req . Context ( ) , uid , v1 . GetOptions { } )
}
2024-10-22 01:08:03 +08:00
if err != nil {
fk8s . writeError ( c , err )
return
}
folderDTO , err := fk8s . newToFolderDto ( c , * out , c . SignedInUser . GetOrgID ( ) )
if err != nil {
fk8s . writeError ( c , err )
return
}
c . JSON ( http . StatusOK , folderDTO )
}
func ( fk8s * folderK8sHandler ) deleteFolder ( c * contextmodel . ReqContext ) {
client , ok := fk8s . getClient ( c )
if ! ok {
return // error is already sent
}
uid := web . Params ( c . Req ) [ ":uid" ]
err := client . Delete ( c . Req . Context ( ) , uid , v1 . DeleteOptions { } )
if err != nil {
fk8s . writeError ( c , err )
return
}
c . JSON ( http . StatusOK , "" )
}
func ( fk8s * folderK8sHandler ) updateFolder ( c * contextmodel . ReqContext ) {
client , ok := fk8s . getClient ( c )
if ! ok {
return // error is already sent
}
2024-12-27 20:51:10 +08:00
var ctx = c . Req . Context ( )
cmd := & folder . UpdateFolderCommand { }
if err := web . Bind ( c . Req , cmd ) ; err != nil {
2024-10-22 01:08:03 +08:00
c . JsonApiErr ( http . StatusBadRequest , "bad request data" , err )
return
}
cmd . UID = web . Params ( c . Req ) [ ":uid" ]
2024-12-27 20:51:10 +08:00
obj , err := client . Get ( ctx , cmd . UID , v1 . GetOptions { } )
2024-10-22 01:08:03 +08:00
if err != nil {
fk8s . writeError ( c , err )
return
}
2024-12-27 20:51:10 +08:00
updated , err := internalfolders . LegacyUpdateCommandToUnstructured ( obj , cmd )
if err != nil {
return
}
out , err := client . Update ( ctx , updated , v1 . UpdateOptions { } )
2024-12-19 16:59:14 +08:00
if err != nil {
fk8s . writeError ( c , err )
return
}
folderDTO , err := fk8s . newToFolderDto ( c , * out , c . SignedInUser . GetOrgID ( ) )
if err != nil {
fk8s . writeError ( c , err )
return
}
c . JSON ( http . StatusOK , folderDTO )
}
func ( fk8s * folderK8sHandler ) moveFolder ( c * contextmodel . ReqContext ) {
client , ok := fk8s . getClient ( c )
if ! ok {
return
}
ctx := c . Req . Context ( )
cmd := folder . MoveFolderCommand { }
if err := web . Bind ( c . Req , & cmd ) ; err != nil {
c . JsonApiErr ( http . StatusBadRequest , "bad request data" , err )
return
}
cmd . UID = web . Params ( c . Req ) [ ":uid" ]
obj , err := client . Get ( ctx , cmd . UID , v1 . GetOptions { } )
if err != nil {
fk8s . writeError ( c , err )
return
}
obj , err = internalfolders . LegacyMoveCommandToUnstructured ( obj , cmd )
if err != nil {
fk8s . writeError ( c , err )
return
}
out , err := client . Update ( c . Req . Context ( ) , obj , v1 . UpdateOptions { } )
2024-10-22 01:08:03 +08:00
if err != nil {
fk8s . writeError ( c , err )
return
}
folderDTO , err := fk8s . newToFolderDto ( c , * out , c . SignedInUser . GetOrgID ( ) )
if err != nil {
fk8s . writeError ( c , err )
return
}
c . JSON ( http . StatusOK , folderDTO )
}
2024-09-13 01:38:27 +08:00
2024-09-10 17:22:08 +08:00
//-----------------------------------------------------------------------------------------
// Utility functions
//-----------------------------------------------------------------------------------------
func ( fk8s * folderK8sHandler ) getClient ( c * contextmodel . ReqContext ) ( dynamic . ResourceInterface , bool ) {
dyn , err := dynamic . NewForConfig ( fk8s . clientConfigProvider . GetDirectRestConfig ( c ) )
if err != nil {
c . JsonApiErr ( 500 , "client" , err )
return nil , false
}
return dyn . Resource ( fk8s . gvr ) . Namespace ( fk8s . namespacer ( c . OrgID ) ) , true
}
func ( fk8s * folderK8sHandler ) writeError ( c * contextmodel . ReqContext , err error ) {
//nolint:errorlint
statusError , ok := err . ( * k8sErrors . StatusError )
if ok {
2024-10-28 19:33:56 +08:00
message := statusError . Status ( ) . Message
// #TODO: Is there a better way to set the correct meesage? Instead of "access denied to folder", currently we are
// returning something like `folders.folder.grafana.app is forbidden: User "" cannot create resource "folders" in
// API group "folder.grafana.app" in the namespace "default": folder``
if statusError . Status ( ) . Code == http . StatusForbidden {
message = dashboards . ErrFolderAccessDenied . Error ( )
}
c . JsonApiErr ( int ( statusError . Status ( ) . Code ) , message , err )
2024-09-10 17:22:08 +08:00
return
}
errhttp . Write ( c . Req . Context ( ) , err , c . Resp )
}
2024-10-07 18:08:16 +08:00
2024-10-07 22:48:56 +08:00
func ( fk8s * folderK8sHandler ) newToFolderDto ( c * contextmodel . ReqContext , item unstructured . Unstructured , orgID int64 ) ( dtos . Folder , error ) {
2024-11-15 22:21:57 +08:00
f , createdBy := internalfolders . UnstructuredToLegacyFolder ( item , orgID )
2024-10-07 18:08:16 +08:00
2024-11-15 22:21:57 +08:00
dontCheckCanView := false
checkCanView := true
2024-10-07 18:08:16 +08:00
// no need to check view permission for the starting folder since it's already checked by the callers
2024-11-15 22:21:57 +08:00
folderDTO , err := fk8s . toDTO ( c , f , createdBy , dontCheckCanView )
2024-10-07 18:08:16 +08:00
if err != nil {
return dtos . Folder { } , err
}
2024-10-10 19:22:57 +08:00
if len ( f . Fullpath ) == 0 || len ( f . FullpathUIDs ) == 0 {
return folderDTO , nil
2024-10-07 22:48:56 +08:00
}
2024-10-07 18:08:16 +08:00
2024-10-10 19:22:57 +08:00
parentsFullPath , err := internalfolders . GetParentTitles ( f . Fullpath )
if err != nil {
return dtos . Folder { } , err
}
parentsFullPathUIDs := strings . Split ( f . FullpathUIDs , "/" )
2024-10-07 22:48:56 +08:00
2024-10-10 19:22:57 +08:00
// The first part of the path is the newly created folder which we don't need to include
// in the parents field
if len ( parentsFullPath ) < 2 || len ( parentsFullPathUIDs ) < 2 {
return folderDTO , nil
2024-10-07 22:48:56 +08:00
}
2024-10-10 19:22:57 +08:00
parents := [ ] dtos . Folder { }
for i , v := range parentsFullPath [ 1 : ] {
slug := slugify . Slugify ( v )
uid := parentsFullPathUIDs [ 1 : ] [ i ]
url := dashboards . GetFolderURL ( uid , slug )
2024-11-15 22:21:57 +08:00
ff := folder . Folder {
2024-10-10 19:22:57 +08:00
UID : uid ,
Title : v ,
URL : url ,
2024-11-15 22:21:57 +08:00
}
parentDTO , err := fk8s . toDTO ( c , & ff , "" , checkCanView )
if err != nil {
// #TODO should we log this error?
return dtos . Folder { } , err
}
parents = append ( parents , parentDTO )
2024-10-07 22:48:56 +08:00
}
2024-10-07 18:08:16 +08:00
2024-10-10 19:22:57 +08:00
folderDTO . Parents = parents
2024-10-07 18:08:16 +08:00
return folderDTO , nil
}
2024-11-15 22:21:57 +08:00
func toUID ( rawIdentifier string ) string {
// #TODO Is there a preexisting function we can use instead, something along the lines of UserIdentifier?
parts := strings . Split ( rawIdentifier , ":" )
if len ( parts ) < 2 {
return ""
}
return parts [ 1 ]
}
func ( fk8s * folderK8sHandler ) toDTO ( c * contextmodel . ReqContext , fold * folder . Folder , createdBy string , checkCanView bool ) ( dtos . Folder , error ) {
// #TODO revisit how/where we get orgID
ctx := c . Req . Context ( )
g , err := guardian . NewByFolder ( c . Req . Context ( ) , fold , c . SignedInUser . GetOrgID ( ) , c . SignedInUser )
if err != nil {
return dtos . Folder { } , err
}
canEdit , _ := g . CanEdit ( )
canSave , _ := g . CanSave ( )
canAdmin , _ := g . CanAdmin ( )
canDelete , _ := g . CanDelete ( )
// Finding creator and last updater of the folder
updater , creator := anonString , anonString
// #TODO refactor the various conversions of the folder so that we either set created by in folder.Folder or
// we convert from unstructured to folder DTO without an intermediate conversion to folder.Folder
if len ( createdBy ) > 0 {
2025-01-10 17:06:59 +08:00
creator = fk8s . getIdentityName ( ctx , toUID ( createdBy ) )
2024-11-15 22:21:57 +08:00
}
if len ( createdBy ) > 0 {
2025-01-10 17:06:59 +08:00
updater = fk8s . getIdentityName ( ctx , toUID ( createdBy ) )
2024-11-15 22:21:57 +08:00
}
acMetadata , _ := fk8s . getFolderACMetadata ( c , fold )
if checkCanView {
canView , _ := g . CanView ( )
if ! canView {
return dtos . Folder {
UID : REDACTED ,
Title : REDACTED ,
} , nil
}
}
metrics . MFolderIDsAPICount . WithLabelValues ( metrics . NewToFolderDTO ) . Inc ( )
return dtos . Folder {
ID : fold . ID , // nolint:staticcheck
UID : fold . UID ,
Title : fold . Title ,
URL : fold . URL ,
HasACL : fold . HasACL ,
CanSave : canSave ,
CanEdit : canEdit ,
CanAdmin : canAdmin ,
CanDelete : canDelete ,
CreatedBy : creator ,
Created : fold . Created ,
UpdatedBy : updater ,
Updated : fold . Updated ,
// #TODO version doesn't seem to be used--confirm or set it properly
2024-12-04 02:33:01 +08:00
Version : fold . Version ,
2024-11-15 22:21:57 +08:00
AccessControl : acMetadata ,
ParentUID : fold . ParentUID ,
} , nil
}
2025-01-10 17:06:59 +08:00
func ( fk8s * folderK8sHandler ) getIdentityName ( ctx context . Context , uid string ) string {
2024-10-07 18:08:16 +08:00
ctx , span := tracer . Start ( ctx , "api.getUserLogin" )
defer span . End ( )
2025-01-10 17:06:59 +08:00
ident , err := fk8s . userService . GetByUID ( ctx , & user . GetUserByUIDQuery {
UID : uid ,
} )
2024-10-07 18:08:16 +08:00
if err != nil {
return anonString
}
2025-01-10 17:06:59 +08:00
if ident . IsServiceAccount {
return ident . Name
}
return ident . Login
2024-10-07 18:08:16 +08:00
}
func ( fk8s * folderK8sHandler ) getFolderACMetadata ( c * contextmodel . ReqContext , f * folder . Folder ) ( accesscontrol . Metadata , error ) {
if ! c . QueryBool ( "accesscontrol" ) {
return nil , nil
}
2024-12-16 21:08:29 +08:00
folderIDs , err := getParents ( f )
2024-11-26 22:20:00 +08:00
if err != nil {
return nil , err
2024-10-07 22:48:56 +08:00
}
2024-10-07 18:08:16 +08:00
allMetadata := getMultiAccessControlMetadata ( c , dashboards . ScopeFoldersPrefix , folderIDs )
metadata := map [ string ] bool { }
// Flatten metadata - if any parent has a permission, the child folder inherits it
for _ , md := range allMetadata {
for action := range md {
metadata [ action ] = true
}
}
return metadata , nil
}
2024-11-26 22:20:00 +08:00
2024-12-16 21:08:29 +08:00
func getParents ( f * folder . Folder ) ( map [ string ] bool , error ) {
2024-11-26 22:20:00 +08:00
folderIDs := map [ string ] bool { f . UID : true }
if ( f . UID == accesscontrol . GeneralFolderUID ) || ( f . UID == folder . SharedWithMeFolderUID ) {
return folderIDs , nil
}
parentsFullPathUIDs := strings . Split ( f . FullpathUIDs , "/" )
// The first part of the path is the newly created folder which we don't need to check here
if len ( parentsFullPathUIDs ) < 2 {
return folderIDs , nil
}
for _ , uid := range parentsFullPathUIDs [ 1 : ] {
folderIDs [ uid ] = true
}
return folderIDs , nil
}
2024-12-16 21:08:29 +08:00
func toFolderLegacyCounts ( u * unstructured . Unstructured ) ( * folder . DescendantCounts , error ) {
ds , err := folderalpha1 . UnstructuredToDescendantCounts ( u )
if err != nil {
return nil , err
}
var out = make ( folder . DescendantCounts )
for _ , v := range ds . Counts {
// if stats come from unified storage, we will use them
if v . Group != "sql-fallback" {
out [ v . Resource ] = v . Count
continue
}
// if stats are from single tenant DB and they are not in unified storage, we will use them
if _ , ok := out [ v . Resource ] ; ! ok {
out [ v . Resource ] = v . Count
}
}
return & out , nil
}