Fix dashboard snapshot deletion (#12025)

* fix issue deleting dashboard snapshots by deleteKey, add dedicated endopoint for authenticated requests, update frontend, tests & docs.
This commit is contained in:
Dan Cech
2018-05-24 02:55:16 -04:00
committed by Carl Bergquist
parent ed110bc312
commit 417db13efb
7 changed files with 141 additions and 26 deletions

View File

@@ -107,7 +107,8 @@ func (hs *HTTPServer) registerRoutes() {
r.Post("/api/snapshots/", bind(m.CreateDashboardSnapshotCommand{}), CreateDashboardSnapshot)
r.Get("/api/snapshot/shared-options/", GetSharingOptions)
r.Get("/api/snapshots/:key", GetDashboardSnapshot)
r.Get("/api/snapshots-delete/:key", reqEditorRole, wrap(DeleteDashboardSnapshot))
r.Get("/api/snapshots-delete/:deleteKey", wrap(DeleteDashboardSnapshotByDeleteKey))
r.Delete("/api/snapshots/:key", reqEditorRole, wrap(DeleteDashboardSnapshot))
// api renew session based on remember cookie
r.Get("/api/login/ping", quota("session"), LoginAPIPing)

View File

@@ -46,6 +46,31 @@ func loggedInUserScenarioWithRole(desc string, method string, url string, routeP
})
}
func anonymousUserScenario(desc string, method string, url string, routePattern string, fn scenarioFunc) {
Convey(desc+" "+url, func() {
defer bus.ClearBusHandlers()
sc := setupScenarioContext(url)
sc.defaultHandler = wrap(func(c *m.ReqContext) Response {
sc.context = c
if sc.handlerFunc != nil {
return sc.handlerFunc(sc.context)
}
return nil
})
switch method {
case "GET":
sc.m.Get(routePattern, sc.defaultHandler)
case "DELETE":
sc.m.Delete(routePattern, sc.defaultHandler)
}
fn(sc)
})
}
func (sc *scenarioContext) fakeReq(method, url string) *scenarioContext {
sc.resp = httptest.NewRecorder()
req, err := http.NewRequest(method, url, nil)

View File

@@ -91,11 +91,31 @@ func GetDashboardSnapshot(c *m.ReqContext) {
c.JSON(200, dto)
}
// GET /api/snapshots-delete/:key
// GET /api/snapshots-delete/:deleteKey
func DeleteDashboardSnapshotByDeleteKey(c *m.ReqContext) Response {
key := c.Params(":deleteKey")
query := &m.GetDashboardSnapshotQuery{DeleteKey: key}
err := bus.Dispatch(query)
if err != nil {
return Error(500, "Failed to get dashboard snapshot", err)
}
cmd := &m.DeleteDashboardSnapshotCommand{DeleteKey: query.Result.DeleteKey}
if err := bus.Dispatch(cmd); err != nil {
return Error(500, "Failed to delete dashboard snapshot", err)
}
return JSON(200, util.DynMap{"message": "Snapshot deleted. It might take an hour before it's cleared from any CDN caches."})
}
// DELETE /api/snapshots/:key
func DeleteDashboardSnapshot(c *m.ReqContext) Response {
key := c.Params(":key")
query := &m.GetDashboardSnapshotQuery{DeleteKey: key}
query := &m.GetDashboardSnapshotQuery{Key: key}
err := bus.Dispatch(query)
if err != nil {
@@ -118,13 +138,13 @@ func DeleteDashboardSnapshot(c *m.ReqContext) Response {
return Error(403, "Access denied to this snapshot", nil)
}
cmd := &m.DeleteDashboardSnapshotCommand{DeleteKey: key}
cmd := &m.DeleteDashboardSnapshotCommand{DeleteKey: query.Result.DeleteKey}
if err := bus.Dispatch(cmd); err != nil {
return Error(500, "Failed to delete dashboard snapshot", err)
}
return JSON(200, util.DynMap{"message": "Snapshot deleted. It might take an hour before it's cleared from a CDN cache."})
return JSON(200, util.DynMap{"message": "Snapshot deleted. It might take an hour before it's cleared from any CDN caches."})
}
// GET /api/dashboard/snapshots
@@ -154,7 +174,6 @@ func SearchDashboardSnapshots(c *m.ReqContext) Response {
Id: snapshot.Id,
Name: snapshot.Name,
Key: snapshot.Key,
DeleteKey: snapshot.DeleteKey,
OrgId: snapshot.OrgId,
UserId: snapshot.UserId,
External: snapshot.External,

View File

@@ -47,15 +47,30 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) {
Convey("When user has editor role and is not in the ACL", func() {
Convey("Should not be able to delete snapshot", func() {
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/snapshots-delete/12345", "/api/snapshots-delete/:key", m.ROLE_EDITOR, func(sc *scenarioContext) {
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", m.ROLE_EDITOR, func(sc *scenarioContext) {
sc.handlerFunc = DeleteDashboardSnapshot
sc.fakeReqWithParams("GET", sc.url, map[string]string{"key": "12345"}).exec()
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec()
So(sc.resp.Code, ShouldEqual, 403)
})
})
})
Convey("When user is anonymous", func() {
Convey("Should be able to delete snapshot by deleteKey", func() {
anonymousUserScenario("When calling GET on", "GET", "/api/snapshots-delete/12345", "/api/snapshots-delete/:deleteKey", func(sc *scenarioContext) {
sc.handlerFunc = DeleteDashboardSnapshotByDeleteKey
sc.fakeReqWithParams("GET", sc.url, map[string]string{"deleteKey": "12345"}).exec()
So(sc.resp.Code, ShouldEqual, 200)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
So(err, ShouldBeNil)
So(respJSON.Get("message").MustString(), ShouldStartWith, "Snapshot deleted")
})
})
})
Convey("When user is editor and dashboard has default ACL", func() {
aclMockResp = []*m.DashboardAclInfoDTO{
{Role: &viewerRole, Permission: m.PERMISSION_VIEW},
@@ -63,9 +78,9 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) {
}
Convey("Should be able to delete a snapshot", func() {
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/snapshots-delete/12345", "/api/snapshots-delete/:key", m.ROLE_EDITOR, func(sc *scenarioContext) {
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", m.ROLE_EDITOR, func(sc *scenarioContext) {
sc.handlerFunc = DeleteDashboardSnapshot
sc.fakeReqWithParams("GET", sc.url, map[string]string{"key": "12345"}).exec()
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec()
So(sc.resp.Code, ShouldEqual, 200)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
@@ -81,9 +96,9 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) {
mockSnapshotResult.UserId = TestUserID
Convey("Should be able to delete a snapshot", func() {
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/snapshots-delete/12345", "/api/snapshots-delete/:key", m.ROLE_EDITOR, func(sc *scenarioContext) {
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", m.ROLE_EDITOR, func(sc *scenarioContext) {
sc.handlerFunc = DeleteDashboardSnapshot
sc.fakeReqWithParams("GET", sc.url, map[string]string{"key": "12345"}).exec()
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec()
So(sc.resp.Code, ShouldEqual, 200)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())

View File

@@ -29,7 +29,6 @@ type DashboardSnapshotDTO struct {
Id int64 `json:"id"`
Name string `json:"name"`
Key string `json:"key"`
DeleteKey string `json:"deleteKey"`
OrgId int64 `json:"orgId"`
UserId int64 `json:"userId"`
External bool `json:"external"`