2015-03-21 07:53:16 -05:00
package api
import (
2022-06-16 06:13:58 -05:00
"errors"
2024-02-02 16:19:45 -06:00
"fmt"
2018-12-10 15:36:32 -06:00
"net/http"
2015-03-25 03:04:38 -05:00
"time"
2015-03-21 09:56:26 -05:00
"github.com/grafana/grafana/pkg/api/dtos"
2021-01-15 07:43:20 -06:00
"github.com/grafana/grafana/pkg/api/response"
2024-02-02 00:40:11 -06:00
dashboardsnapshot "github.com/grafana/grafana/pkg/apis/dashboardsnapshot/v0alpha1"
2024-02-02 16:19:45 -06:00
"github.com/grafana/grafana/pkg/infra/appcontext"
2019-02-23 16:35:26 -06:00
"github.com/grafana/grafana/pkg/infra/metrics"
2024-02-02 16:19:45 -06:00
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
2023-01-27 01:50:36 -06:00
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
2022-06-30 08:31:54 -05:00
"github.com/grafana/grafana/pkg/services/dashboards"
2022-06-17 08:09:01 -05:00
"github.com/grafana/grafana/pkg/services/dashboardsnapshots"
2024-02-02 16:19:45 -06:00
"github.com/grafana/grafana/pkg/services/featuremgmt"
2018-02-20 16:26:08 -06:00
"github.com/grafana/grafana/pkg/services/guardian"
2015-03-21 07:53:16 -05:00
"github.com/grafana/grafana/pkg/util"
2024-02-02 16:19:45 -06:00
"github.com/grafana/grafana/pkg/util/errutil/errhttp"
2021-10-11 07:30:59 -05:00
"github.com/grafana/grafana/pkg/web"
2015-03-21 07:53:16 -05:00
)
2024-02-02 16:19:45 -06:00
// r.Post("/api/snapshots/"
func ( hs * HTTPServer ) getCreatedSnapshotHandler ( ) web . Handler {
if hs . Features . IsEnabledGlobally ( featuremgmt . FlagKubernetesSnapshots ) {
namespaceMapper := request . GetNamespaceMapper ( hs . Cfg )
return func ( w http . ResponseWriter , r * http . Request ) {
user , err := appcontext . User ( r . Context ( ) )
if err != nil || user == nil {
errhttp . Write ( r . Context ( ) , fmt . Errorf ( "no user" ) , w )
return
}
r . URL . Path = "/apis/dashboardsnapshot.grafana.app/v0alpha1/namespaces/" +
namespaceMapper ( user . OrgID ) + "/dashboardsnapshots/create"
hs . clientConfigProvider . DirectlyServeHTTP ( w , r )
}
}
return hs . CreateDashboardSnapshot
}
2022-07-27 08:54:37 -05:00
// swagger:route GET /snapshot/shared-options snapshots getSharingOptions
//
// Get snapshot sharing settings.
//
// Responses:
// 200: getSharingOptionsResponse
// 401: unauthorisedError
2023-01-27 01:50:36 -06:00
func ( hs * HTTPServer ) GetSharingOptions ( c * contextmodel . ReqContext ) {
2022-04-15 07:01:58 -05:00
c . JSON ( http . StatusOK , util . DynMap {
2023-01-26 07:28:11 -06:00
"snapshotEnabled" : hs . Cfg . SnapshotEnabled ,
"externalSnapshotURL" : hs . Cfg . ExternalSnapshotUrl ,
"externalSnapshotName" : hs . Cfg . ExternalSnapshotName ,
"externalEnabled" : hs . Cfg . ExternalEnabled ,
2015-10-14 09:39:57 -05:00
} )
}
2022-07-27 08:54:37 -05:00
// swagger:route POST /snapshots snapshots createDashboardSnapshot
//
// When creating a snapshot using the API, you have to provide the full dashboard payload including the snapshot data. This endpoint is designed for the Grafana UI.
//
// Snapshot public mode should be enabled or authentication is required.
//
// Responses:
// 200: createDashboardSnapshotResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
2024-02-02 00:40:11 -06:00
func ( hs * HTTPServer ) CreateDashboardSnapshot ( c * contextmodel . ReqContext ) {
dashboardsnapshots . CreateDashboardSnapshot ( c , dashboardsnapshot . SnapshotSharingOptions {
SnapshotsEnabled : hs . Cfg . SnapshotEnabled ,
ExternalEnabled : hs . Cfg . ExternalEnabled ,
ExternalSnapshotName : hs . Cfg . ExternalSnapshotName ,
ExternalSnapshotURL : hs . Cfg . ExternalSnapshotUrl ,
} , hs . dashboardsnapshotsService )
2015-03-25 03:04:38 -05:00
}
2018-02-20 16:26:08 -06:00
// GET /api/snapshots/:key
2022-07-27 08:54:37 -05:00
// swagger:route GET /snapshots/{key} snapshots getDashboardSnapshot
//
// Get Snapshot by Key.
//
// Responses:
// 200: getDashboardSnapshotResponse
2022-08-03 09:31:23 -05:00
// 400: badRequestError
2022-07-27 08:54:37 -05:00
// 404: notFoundError
// 500: internalServerError
2023-01-27 01:50:36 -06:00
func ( hs * HTTPServer ) GetDashboardSnapshot ( c * contextmodel . ReqContext ) response . Response {
2023-01-26 07:28:11 -06:00
if ! hs . Cfg . SnapshotEnabled {
c . JsonApiErr ( http . StatusForbidden , "Dashboard Snapshots are disabled" , nil )
return nil
}
2021-10-11 07:30:59 -05:00
key := web . Params ( c . Req ) [ ":key" ]
2021-10-05 12:38:09 -05:00
if len ( key ) == 0 {
2022-08-03 09:31:23 -05:00
return response . Error ( http . StatusBadRequest , "Empty snapshot key" , nil )
2021-10-05 12:38:09 -05:00
}
2022-06-17 08:09:01 -05:00
query := & dashboardsnapshots . GetDashboardSnapshotQuery { Key : key }
2015-03-21 07:53:16 -05:00
2023-01-25 08:09:44 -06:00
queryResult , err := hs . dashboardsnapshotsService . GetDashboardSnapshot ( c . Req . Context ( ) , query )
2015-03-21 07:53:16 -05:00
if err != nil {
2022-08-03 09:31:23 -05:00
return response . Err ( err )
2015-03-21 07:53:16 -05:00
}
2023-01-25 08:09:44 -06:00
snapshot := queryResult
2015-03-26 06:00:52 -05:00
// expired snapshots should also be removed from db
if snapshot . Expires . Before ( time . Now ( ) ) {
2021-01-15 07:43:20 -06:00
return response . Error ( 404 , "Dashboard snapshot not found" , err )
2020-10-13 03:19:42 -05:00
}
2015-05-04 01:36:44 -05:00
dto := dtos . DashboardFullWithMeta {
2021-09-01 06:05:15 -05:00
Dashboard : snapshot . Dashboard ,
2015-03-28 11:53:52 -05:00
Meta : dtos . DashboardMeta {
2023-01-18 06:52:41 -06:00
Type : dashboards . DashTypeSnapshot ,
2015-03-28 11:53:52 -05:00
IsSnapshot : true ,
Created : snapshot . Created ,
Expires : snapshot . Expires ,
} ,
2015-03-21 09:56:26 -05:00
}
2019-07-16 09:58:46 -05:00
metrics . MApiDashboardSnapshotGet . Inc ( )
2015-03-24 10:49:12 -05:00
2022-04-15 07:01:58 -05:00
return response . JSON ( http . StatusOK , dto ) . SetHeader ( "Cache-Control" , "public, max-age=3600" )
2015-03-26 14:34:58 -05:00
}
2015-03-26 06:00:52 -05:00
2022-07-27 08:54:37 -05:00
// swagger:route GET /snapshots-delete/{deleteKey} snapshots deleteDashboardSnapshotByDeleteKey
//
// Delete Snapshot by deleteKey.
//
// Snapshot public mode should be enabled or authentication is required.
//
// Responses:
// 200: okResponse
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
2023-01-27 01:50:36 -06:00
func ( hs * HTTPServer ) DeleteDashboardSnapshotByDeleteKey ( c * contextmodel . ReqContext ) response . Response {
2023-01-26 07:28:11 -06:00
if ! hs . Cfg . SnapshotEnabled {
c . JsonApiErr ( http . StatusForbidden , "Dashboard Snapshots are disabled" , nil )
return nil
}
2021-10-11 07:30:59 -05:00
key := web . Params ( c . Req ) [ ":deleteKey" ]
2021-10-05 12:38:09 -05:00
if len ( key ) == 0 {
return response . Error ( 404 , "Snapshot not found" , nil )
}
2018-05-24 01:55:16 -05:00
2024-02-02 00:40:11 -06:00
err := dashboardsnapshots . DeleteWithKey ( c . Req . Context ( ) , key , hs . dashboardsnapshotsService )
2018-05-24 01:55:16 -05:00
if err != nil {
2024-02-02 00:40:11 -06:00
if errors . Is ( err , dashboardsnapshots . ErrBaseNotFound ) {
return response . Error ( 404 , "Snapshot not found" , err )
2018-12-10 15:40:26 -06:00
}
2021-01-15 07:43:20 -06:00
return response . Error ( 500 , "Failed to delete dashboard snapshot" , err )
2018-05-24 01:55:16 -05:00
}
2022-04-15 07:01:58 -05:00
return response . JSON ( http . StatusOK , util . DynMap {
2020-12-04 09:22:58 -06:00
"message" : "Snapshot deleted. It might take an hour before it's cleared from any CDN caches." ,
} )
2018-05-24 01:55:16 -05:00
}
2022-07-27 08:54:37 -05:00
// swagger:route DELETE /snapshots/{key} snapshots deleteDashboardSnapshot
//
// Delete Snapshot by Key.
//
// Responses:
// 200: okResponse
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
2023-01-27 01:50:36 -06:00
func ( hs * HTTPServer ) DeleteDashboardSnapshot ( c * contextmodel . ReqContext ) response . Response {
2023-01-26 07:28:11 -06:00
if ! hs . Cfg . SnapshotEnabled {
c . JsonApiErr ( http . StatusForbidden , "Dashboard Snapshots are disabled" , nil )
return nil
}
2021-10-11 07:30:59 -05:00
key := web . Params ( c . Req ) [ ":key" ]
2021-10-05 12:38:09 -05:00
if len ( key ) == 0 {
2022-08-30 10:19:52 -05:00
return response . Error ( http . StatusNotFound , "Snapshot not found" , nil )
2021-10-05 12:38:09 -05:00
}
2018-02-20 16:26:08 -06:00
2022-06-17 08:09:01 -05:00
query := & dashboardsnapshots . GetDashboardSnapshotQuery { Key : key }
2018-02-20 16:26:08 -06:00
2023-01-25 08:09:44 -06:00
queryResult , err := hs . dashboardsnapshotsService . GetDashboardSnapshot ( c . Req . Context ( ) , query )
2018-02-20 16:26:08 -06:00
if err != nil {
2022-08-03 09:31:23 -05:00
return response . Err ( err )
2018-02-20 16:26:08 -06:00
}
2023-01-25 08:09:44 -06:00
if queryResult == nil {
2022-08-30 10:19:52 -05:00
return response . Error ( http . StatusNotFound , "Failed to get dashboard snapshot" , nil )
2018-02-20 16:26:08 -06:00
}
2020-11-13 02:52:38 -06:00
2024-02-21 07:04:15 -06:00
if queryResult . OrgID != c . OrgID {
return response . Error ( http . StatusUnauthorized , "OrgID mismatch" , nil )
}
2024-02-02 00:40:11 -06:00
2023-01-25 08:09:44 -06:00
if queryResult . External {
2024-02-02 00:40:11 -06:00
err := dashboardsnapshots . DeleteExternalDashboardSnapshot ( queryResult . ExternalDeleteURL )
2018-12-10 15:40:26 -06:00
if err != nil {
2022-08-30 10:19:52 -05:00
return response . Error ( http . StatusInternalServerError , "Failed to delete external dashboard" , err )
2018-12-10 15:40:26 -06:00
}
2022-08-30 10:19:52 -05:00
}
2022-07-07 08:15:39 -05:00
2022-08-30 10:19:52 -05:00
// Dashboard can be empty (creation error or external snapshot). This means that the mustInt here returns a 0,
// which before RBAC would result in a dashboard which has no ACL. A dashboard without an ACL would fallback
// to the user’ s org role, which for editors and admins would essentially always be allowed here. With RBAC,
// all permissions must be explicit, so the lack of a rule for dashboard 0 means the guardian will reject.
2023-01-25 08:09:44 -06:00
dashboardID := queryResult . Dashboard . Get ( "id" ) . MustInt64 ( )
2022-08-30 10:19:52 -05:00
if dashboardID != 0 {
2023-10-06 04:34:36 -05:00
g , err := guardian . New ( c . Req . Context ( ) , dashboardID , c . SignedInUser . GetOrgID ( ) , c . SignedInUser )
2022-12-15 08:34:17 -06:00
if err != nil {
2023-03-08 02:12:02 -06:00
if ! errors . Is ( err , dashboards . ErrDashboardNotFound ) {
return response . Err ( err )
}
} else {
canEdit , err := g . CanEdit ( )
// check for permissions only if the dashboard is found
if err != nil && ! errors . Is ( err , dashboards . ErrDashboardNotFound ) {
return response . Error ( http . StatusInternalServerError , "Error while checking permissions for snapshot" , err )
}
2022-07-07 08:15:39 -05:00
2023-03-08 02:12:02 -06:00
if ! canEdit && queryResult . UserID != c . SignedInUser . UserID && ! errors . Is ( err , dashboards . ErrDashboardNotFound ) {
return response . Error ( http . StatusForbidden , "Access denied to this snapshot" , nil )
}
2022-07-07 08:15:39 -05:00
}
2018-12-10 15:40:26 -06:00
}
2023-01-25 08:09:44 -06:00
cmd := & dashboardsnapshots . DeleteDashboardSnapshotCommand { DeleteKey : queryResult . DeleteKey }
2015-03-24 10:49:12 -05:00
2022-06-14 12:41:29 -05:00
if err := hs . dashboardsnapshotsService . DeleteDashboardSnapshot ( c . Req . Context ( ) , cmd ) ; err != nil {
2022-08-30 10:19:52 -05:00
return response . Error ( http . StatusInternalServerError , "Failed to delete dashboard snapshot" , err )
2015-03-26 14:34:58 -05:00
}
2022-04-15 07:01:58 -05:00
return response . JSON ( http . StatusOK , util . DynMap {
2020-12-04 09:22:58 -06:00
"message" : "Snapshot deleted. It might take an hour before it's cleared from any CDN caches." ,
2023-01-25 08:09:44 -06:00
"id" : queryResult . ID ,
2020-12-04 09:22:58 -06:00
} )
2015-03-21 07:53:16 -05:00
}
2016-01-19 03:37:36 -06:00
2022-07-27 08:54:37 -05:00
// swagger:route GET /dashboard/snapshots snapshots searchDashboardSnapshots
//
// List snapshots.
//
// Responses:
// 200: searchDashboardSnapshotsResponse
// 500: internalServerError
2023-01-27 01:50:36 -06:00
func ( hs * HTTPServer ) SearchDashboardSnapshots ( c * contextmodel . ReqContext ) response . Response {
2023-01-26 07:28:11 -06:00
if ! hs . Cfg . SnapshotEnabled {
c . JsonApiErr ( http . StatusForbidden , "Dashboard Snapshots are disabled" , nil )
return nil
}
2016-01-19 07:05:24 -06:00
query := c . Query ( "query" )
limit := c . QueryInt ( "limit" )
2016-01-19 03:37:36 -06:00
2016-01-19 07:05:24 -06:00
if limit == 0 {
limit = 1000
}
2016-01-19 03:37:36 -06:00
2022-06-17 08:09:01 -05:00
searchQuery := dashboardsnapshots . GetDashboardSnapshotsQuery {
2018-02-20 16:26:08 -06:00
Name : query ,
Limit : limit ,
2023-10-06 04:34:36 -05:00
OrgID : c . SignedInUser . GetOrgID ( ) ,
2018-02-20 16:26:08 -06:00
SignedInUser : c . SignedInUser ,
2016-01-19 07:05:24 -06:00
}
2016-01-19 03:37:36 -06:00
2023-01-25 08:09:44 -06:00
searchQueryResult , err := hs . dashboardsnapshotsService . SearchDashboardSnapshots ( c . Req . Context ( ) , & searchQuery )
2016-01-19 07:05:24 -06:00
if err != nil {
2021-01-15 07:43:20 -06:00
return response . Error ( 500 , "Search failed" , err )
2016-01-19 07:05:24 -06:00
}
2016-01-19 03:37:36 -06:00
2023-01-26 07:28:11 -06:00
dto := make ( [ ] * dashboardsnapshots . DashboardSnapshotDTO , len ( searchQueryResult ) )
2023-01-25 08:09:44 -06:00
for i , snapshot := range searchQueryResult {
2023-01-26 07:28:11 -06:00
dto [ i ] = & dashboardsnapshots . DashboardSnapshotDTO {
2023-01-25 08:09:44 -06:00
ID : snapshot . ID ,
2016-01-20 01:23:44 -06:00
Name : snapshot . Name ,
Key : snapshot . Key ,
2023-01-25 08:09:44 -06:00
OrgID : snapshot . OrgID ,
UserID : snapshot . UserID ,
2016-01-20 01:23:44 -06:00
External : snapshot . External ,
2023-01-25 08:09:44 -06:00
ExternalURL : snapshot . ExternalURL ,
2016-01-20 01:23:44 -06:00
Expires : snapshot . Expires ,
Created : snapshot . Created ,
Updated : snapshot . Updated ,
}
}
2023-01-26 07:28:11 -06:00
return response . JSON ( http . StatusOK , dto )
2016-01-19 03:37:36 -06:00
}
2022-07-27 08:54:37 -05:00
// swagger:parameters createDashboardSnapshot
type CreateSnapshotParams struct {
// in:body
// required:true
Body dashboardsnapshots . CreateDashboardSnapshotCommand ` json:"body" `
}
// swagger:parameters searchDashboardSnapshots
type GetSnapshotsParams struct {
// Search Query
// in:query
Query string ` json:"query" `
// Limit the number of returned results
// in:query
// default:1000
Limit int64 ` json:"limit" `
}
// swagger:parameters getDashboardSnapshot
type GetDashboardSnapshotParams struct {
// in:path
Key string ` json:"key" `
}
// swagger:parameters deleteDashboardSnapshot
type DeleteDashboardSnapshotParams struct {
// in:path
Key string ` json:"key" `
}
// swagger:parameters deleteDashboardSnapshotByDeleteKey
type DeleteSnapshotByDeleteKeyParams struct {
// in:path
DeleteKey string ` json:"deleteKey" `
}
// swagger:response createDashboardSnapshotResponse
type CreateSnapshotResponse struct {
// in:body
Body struct {
// Unique key
Key string ` json:"key" `
// Unique key used to delete the snapshot. It is different from the key so that only the creator can delete the snapshot.
DeleteKey string ` json:"deleteKey" `
URL string ` json:"url" `
DeleteUrl string ` json:"deleteUrl" `
// Snapshot id
ID int64 ` json:"id" `
} ` json:"body" `
}
// swagger:response searchDashboardSnapshotsResponse
type SearchDashboardSnapshotsResponse struct {
// in:body
Body [ ] * dashboardsnapshots . DashboardSnapshotDTO ` json:"body" `
}
// swagger:response getDashboardSnapshotResponse
type GetDashboardSnapshotResponse DashboardResponse
// swagger:response getSharingOptionsResponse
type GetSharingOptionsResponse struct {
// in:body
Body struct {
ExternalSnapshotURL string ` json:"externalSnapshotURL" `
ExternalSnapshotName string ` json:"externalSnapshotName" `
ExternalEnabled bool ` json:"externalEnabled" `
} ` json:"body" `
}