diff --git a/conf/defaults.ini b/conf/defaults.ini index 54373992cf4..f6de3fe15b1 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -214,6 +214,10 @@ external_enabled = true external_snapshot_url = https://snapshots-origin.raintank.io external_snapshot_name = Publish to snapshot.raintank.io +# Set to true to enable this Grafana instance act as an external snapshot server and allow unauthenticated requests for +# creating and deleting snapshots. +public_mode = false + # remove expired snapshot snapshot_remove_expired = true diff --git a/conf/sample.ini b/conf/sample.ini index ffa0d93091d..31468bfa15b 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -209,6 +209,10 @@ ;external_snapshot_url = https://snapshots-origin.raintank.io ;external_snapshot_name = Publish to snapshot.raintank.io +# Set to true to enable this Grafana instance act as an external snapshot server and allow unauthenticated requests for +# creating and deleting snapshots. +;public_mode = false + # remove expired snapshot ;snapshot_remove_expired = true diff --git a/pkg/api/api.go b/pkg/api/api.go index 3d83a467e10..0d36aadf4bd 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -15,6 +15,7 @@ func (hs *HTTPServer) registerRoutes() { reqEditorRole := middleware.ReqEditorRole reqOrgAdmin := middleware.ReqOrgAdmin reqCanAccessTeams := middleware.AdminOrFeatureEnabled(hs.Cfg.EditorsCanAdmin) + reqSnapshotPublicModeOrSignedIn := middleware.SnapshotPublicModeOrSignedIn() redirectFromLegacyDashboardURL := middleware.RedirectFromLegacyDashboardURL() redirectFromLegacyDashboardSoloURL := middleware.RedirectFromLegacyDashboardSoloURL() quota := middleware.Quota(hs.QuotaService) @@ -104,13 +105,6 @@ func (hs *HTTPServer) registerRoutes() { r.Get("/dashboard/snapshot/*", hs.Index) r.Get("/dashboard/snapshots/", reqSignedIn, hs.Index) - // api for dashboard snapshots - r.Post("/api/snapshots/", bind(models.CreateDashboardSnapshotCommand{}), CreateDashboardSnapshot) - r.Get("/api/snapshot/shared-options/", GetSharingOptions) - r.Get("/api/snapshots/:key", GetDashboardSnapshot) - r.Get("/api/snapshots-delete/:deleteKey", Wrap(DeleteDashboardSnapshotByDeleteKey)) - r.Delete("/api/snapshots/:key", reqEditorRole, Wrap(DeleteDashboardSnapshot)) - // api renew session based on cookie r.Get("/api/login/ping", quota("session"), Wrap(hs.LoginAPIPing)) @@ -418,4 +412,11 @@ func (hs *HTTPServer) registerRoutes() { // streams //r.Post("/api/streams/push", reqSignedIn, bind(dtos.StreamMessage{}), liveConn.PushToStream) + + // Snapshots + r.Post("/api/snapshots/", reqSnapshotPublicModeOrSignedIn, bind(models.CreateDashboardSnapshotCommand{}), CreateDashboardSnapshot) + r.Get("/api/snapshot/shared-options/", reqSignedIn, GetSharingOptions) + r.Get("/api/snapshots/:key", GetDashboardSnapshot) + r.Get("/api/snapshots-delete/:deleteKey", reqSnapshotPublicModeOrSignedIn, Wrap(DeleteDashboardSnapshotByDeleteKey)) + r.Delete("/api/snapshots/:key", reqEditorRole, Wrap(DeleteDashboardSnapshot)) } diff --git a/pkg/middleware/auth.go b/pkg/middleware/auth.go index c00241ea34c..f5007be9a3c 100644 --- a/pkg/middleware/auth.go +++ b/pkg/middleware/auth.go @@ -103,3 +103,16 @@ func AdminOrFeatureEnabled(enabled bool) macaron.Handler { } } } + +func SnapshotPublicModeOrSignedIn() macaron.Handler { + return func(c *m.ReqContext) { + if setting.SnapshotPublicMode { + return + } + + _, err := c.Invoke(ReqSignedIn) + if err != nil { + c.JsonApiErr(500, "Failed to invoke required signed in middleware", err) + } + } +} diff --git a/pkg/middleware/auth_test.go b/pkg/middleware/auth_test.go index 50d959270ad..0688b458ded 100644 --- a/pkg/middleware/auth_test.go +++ b/pkg/middleware/auth_test.go @@ -3,6 +3,8 @@ package middleware import ( "testing" + "github.com/grafana/grafana/pkg/setting" + . "github.com/smartystreets/goconvey/convey" ) @@ -31,5 +33,19 @@ func TestMiddlewareAuth(t *testing.T) { }) }) + Convey("snapshot public mode or signed in", func() { + middlewareScenario(t, "Snapshot public mode disabled and unauthenticated request should return 401", func(sc *scenarioContext) { + sc.m.Get("/api/snapshot", SnapshotPublicModeOrSignedIn(), sc.defaultHandler) + sc.fakeReq("GET", "/api/snapshot").exec() + So(sc.resp.Code, ShouldEqual, 401) + }) + + middlewareScenario(t, "Snapshot public mode enabled and unauthenticated request should return 200", func(sc *scenarioContext) { + setting.SnapshotPublicMode = true + sc.m.Get("/api/snapshot", SnapshotPublicModeOrSignedIn(), sc.defaultHandler) + sc.fakeReq("GET", "/api/snapshot").exec() + So(sc.resp.Code, ShouldEqual, 200) + }) + }) }) } diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index d5f62ef8893..05915c55705 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -108,6 +108,7 @@ var ( ExternalSnapshotName string ExternalEnabled bool SnapShotRemoveExpired bool + SnapshotPublicMode bool // Dashboard history DashboardVersionsToKeep int @@ -734,6 +735,7 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error { } ExternalEnabled = snapshots.Key("external_enabled").MustBool(true) SnapShotRemoveExpired = snapshots.Key("snapshot_remove_expired").MustBool(true) + SnapshotPublicMode = snapshots.Key("public_mode").MustBool(false) // read dashboard settings dashboards := iniFile.Section("dashboards")