dashboard_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. package api
  2. import (
  3. "encoding/json"
  4. "path/filepath"
  5. "testing"
  6. macaron "gopkg.in/macaron.v1"
  7. "github.com/go-macaron/session"
  8. "github.com/grafana/grafana/pkg/api/dtos"
  9. "github.com/grafana/grafana/pkg/bus"
  10. "github.com/grafana/grafana/pkg/components/simplejson"
  11. "github.com/grafana/grafana/pkg/middleware"
  12. m "github.com/grafana/grafana/pkg/models"
  13. "github.com/grafana/grafana/pkg/services/alerting"
  14. . "github.com/smartystreets/goconvey/convey"
  15. )
  16. func TestDashboardApiEndpoint(t *testing.T) {
  17. Convey("Given a dashboard with a parent folder which does not have an acl", t, func() {
  18. fakeDash := m.NewDashboard("Child dash")
  19. fakeDash.Id = 1
  20. fakeDash.FolderId = 1
  21. fakeDash.HasAcl = false
  22. bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
  23. query.Result = fakeDash
  24. return nil
  25. })
  26. viewerRole := m.ROLE_VIEWER
  27. editorRole := m.ROLE_EDITOR
  28. aclMockResp := []*m.DashboardAclInfoDTO{
  29. {Role: &viewerRole, Permission: m.PERMISSION_VIEW},
  30. {Role: &editorRole, Permission: m.PERMISSION_EDIT},
  31. }
  32. bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
  33. query.Result = aclMockResp
  34. return nil
  35. })
  36. bus.AddHandler("test", func(query *m.GetUserGroupsByUserQuery) error {
  37. query.Result = []*m.UserGroup{}
  38. return nil
  39. })
  40. cmd := m.SaveDashboardCommand{
  41. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  42. "folderId": fakeDash.FolderId,
  43. "title": fakeDash.Title,
  44. "id": fakeDash.Id,
  45. }),
  46. }
  47. Convey("When user is an Org Viewer", func() {
  48. role := m.ROLE_VIEWER
  49. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  50. dash := GetDashboardShouldReturn200(sc)
  51. Convey("Should not be able to edit or save dashboard", func() {
  52. So(dash.Meta.CanEdit, ShouldBeFalse)
  53. So(dash.Meta.CanSave, ShouldBeFalse)
  54. So(dash.Meta.CanAdmin, ShouldBeFalse)
  55. })
  56. })
  57. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  58. CallDeleteDashboard(sc)
  59. So(sc.resp.Code, ShouldEqual, 403)
  60. })
  61. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
  62. CallGetDashboardVersion(sc)
  63. So(sc.resp.Code, ShouldEqual, 403)
  64. })
  65. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) {
  66. CallGetDashboardVersions(sc)
  67. So(sc.resp.Code, ShouldEqual, 403)
  68. })
  69. postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, cmd, func(sc *scenarioContext) {
  70. CallPostDashboard(sc)
  71. So(sc.resp.Code, ShouldEqual, 403)
  72. })
  73. })
  74. Convey("When user is an Org Read Only Editor", func() {
  75. role := m.ROLE_READ_ONLY_EDITOR
  76. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  77. dash := GetDashboardShouldReturn200(sc)
  78. Convey("Should be able to view but not save the dashboard", func() {
  79. So(dash.Meta.CanEdit, ShouldBeFalse)
  80. So(dash.Meta.CanSave, ShouldBeFalse)
  81. So(dash.Meta.CanAdmin, ShouldBeFalse)
  82. })
  83. })
  84. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  85. CallDeleteDashboard(sc)
  86. So(sc.resp.Code, ShouldEqual, 403)
  87. })
  88. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
  89. CallGetDashboardVersion(sc)
  90. So(sc.resp.Code, ShouldEqual, 403)
  91. })
  92. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) {
  93. CallGetDashboardVersions(sc)
  94. So(sc.resp.Code, ShouldEqual, 403)
  95. })
  96. postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, cmd, func(sc *scenarioContext) {
  97. CallPostDashboard(sc)
  98. So(sc.resp.Code, ShouldEqual, 403)
  99. })
  100. })
  101. Convey("When user is an Org Editor", func() {
  102. role := m.ROLE_EDITOR
  103. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  104. dash := GetDashboardShouldReturn200(sc)
  105. Convey("Should be able to edit or save dashboard", func() {
  106. So(dash.Meta.CanEdit, ShouldBeTrue)
  107. So(dash.Meta.CanSave, ShouldBeTrue)
  108. So(dash.Meta.CanAdmin, ShouldBeFalse)
  109. })
  110. })
  111. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  112. CallDeleteDashboard(sc)
  113. So(sc.resp.Code, ShouldEqual, 200)
  114. })
  115. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
  116. CallGetDashboardVersion(sc)
  117. So(sc.resp.Code, ShouldEqual, 200)
  118. })
  119. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) {
  120. CallGetDashboardVersions(sc)
  121. So(sc.resp.Code, ShouldEqual, 200)
  122. })
  123. postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, cmd, func(sc *scenarioContext) {
  124. CallPostDashboard(sc)
  125. So(sc.resp.Code, ShouldEqual, 200)
  126. })
  127. Convey("When saving a dashboard folder in another folder", func() {
  128. bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
  129. query.Result = fakeDash
  130. query.Result.IsFolder = true
  131. return nil
  132. })
  133. invalidCmd := m.SaveDashboardCommand{
  134. FolderId: fakeDash.FolderId,
  135. IsFolder: true,
  136. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  137. "folderId": fakeDash.FolderId,
  138. "title": fakeDash.Title,
  139. }),
  140. }
  141. Convey("Should return an error", func() {
  142. postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, invalidCmd, func(sc *scenarioContext) {
  143. CallPostDashboard(sc)
  144. So(sc.resp.Code, ShouldEqual, 400)
  145. })
  146. })
  147. })
  148. })
  149. })
  150. Convey("Given a dashboard with a parent folder which has an acl", t, func() {
  151. fakeDash := m.NewDashboard("Child dash")
  152. fakeDash.Id = 1
  153. fakeDash.FolderId = 1
  154. fakeDash.HasAcl = true
  155. aclMockResp := []*m.DashboardAclInfoDTO{
  156. {
  157. DashboardId: 1,
  158. Permission: m.PERMISSION_EDIT,
  159. UserId: 200,
  160. },
  161. }
  162. bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
  163. query.Result = aclMockResp
  164. return nil
  165. })
  166. bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
  167. query.Result = fakeDash
  168. return nil
  169. })
  170. bus.AddHandler("test", func(query *m.GetUserGroupsByUserQuery) error {
  171. query.Result = []*m.UserGroup{}
  172. return nil
  173. })
  174. cmd := m.SaveDashboardCommand{
  175. FolderId: fakeDash.FolderId,
  176. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  177. "id": fakeDash.Id,
  178. "folderId": fakeDash.FolderId,
  179. "title": fakeDash.Title,
  180. }),
  181. }
  182. Convey("When user is an Org Viewer and has no permissions for this dashboard", func() {
  183. role := m.ROLE_VIEWER
  184. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  185. sc.handlerFunc = GetDashboard
  186. sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
  187. Convey("Should be denied access", func() {
  188. So(sc.resp.Code, ShouldEqual, 403)
  189. })
  190. })
  191. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  192. CallDeleteDashboard(sc)
  193. So(sc.resp.Code, ShouldEqual, 403)
  194. })
  195. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
  196. CallGetDashboardVersion(sc)
  197. So(sc.resp.Code, ShouldEqual, 403)
  198. })
  199. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) {
  200. CallGetDashboardVersions(sc)
  201. So(sc.resp.Code, ShouldEqual, 403)
  202. })
  203. postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, cmd, func(sc *scenarioContext) {
  204. CallPostDashboard(sc)
  205. So(sc.resp.Code, ShouldEqual, 403)
  206. })
  207. })
  208. Convey("When user is an Org Editor and has no permissions for this dashboard", func() {
  209. role := m.ROLE_EDITOR
  210. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  211. sc.handlerFunc = GetDashboard
  212. sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
  213. Convey("Should be denied access", func() {
  214. So(sc.resp.Code, ShouldEqual, 403)
  215. })
  216. })
  217. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  218. CallDeleteDashboard(sc)
  219. So(sc.resp.Code, ShouldEqual, 403)
  220. })
  221. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
  222. CallGetDashboardVersion(sc)
  223. So(sc.resp.Code, ShouldEqual, 403)
  224. })
  225. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) {
  226. CallGetDashboardVersions(sc)
  227. So(sc.resp.Code, ShouldEqual, 403)
  228. })
  229. postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, cmd, func(sc *scenarioContext) {
  230. CallPostDashboard(sc)
  231. So(sc.resp.Code, ShouldEqual, 403)
  232. })
  233. })
  234. Convey("When user is an Org Viewer but has an edit permission", func() {
  235. role := m.ROLE_VIEWER
  236. mockResult := []*m.DashboardAclInfoDTO{
  237. {Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_EDIT},
  238. }
  239. bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
  240. query.Result = mockResult
  241. return nil
  242. })
  243. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  244. dash := GetDashboardShouldReturn200(sc)
  245. Convey("Should be able to get dashboard with edit rights", func() {
  246. So(dash.Meta.CanEdit, ShouldBeTrue)
  247. So(dash.Meta.CanSave, ShouldBeTrue)
  248. So(dash.Meta.CanAdmin, ShouldBeFalse)
  249. })
  250. })
  251. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  252. CallDeleteDashboard(sc)
  253. So(sc.resp.Code, ShouldEqual, 200)
  254. })
  255. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
  256. CallGetDashboardVersion(sc)
  257. So(sc.resp.Code, ShouldEqual, 200)
  258. })
  259. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) {
  260. CallGetDashboardVersions(sc)
  261. So(sc.resp.Code, ShouldEqual, 200)
  262. })
  263. postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, cmd, func(sc *scenarioContext) {
  264. CallPostDashboard(sc)
  265. So(sc.resp.Code, ShouldEqual, 200)
  266. })
  267. })
  268. Convey("When user is an Org Viewer but has an admin permission", func() {
  269. role := m.ROLE_VIEWER
  270. mockResult := []*m.DashboardAclInfoDTO{
  271. {Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_ADMIN},
  272. }
  273. bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
  274. query.Result = mockResult
  275. return nil
  276. })
  277. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  278. dash := GetDashboardShouldReturn200(sc)
  279. Convey("Should be able to get dashboard with edit rights", func() {
  280. So(dash.Meta.CanEdit, ShouldBeTrue)
  281. So(dash.Meta.CanSave, ShouldBeTrue)
  282. So(dash.Meta.CanAdmin, ShouldBeTrue)
  283. })
  284. })
  285. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  286. CallDeleteDashboard(sc)
  287. So(sc.resp.Code, ShouldEqual, 200)
  288. })
  289. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
  290. CallGetDashboardVersion(sc)
  291. So(sc.resp.Code, ShouldEqual, 200)
  292. })
  293. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) {
  294. CallGetDashboardVersions(sc)
  295. So(sc.resp.Code, ShouldEqual, 200)
  296. })
  297. postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, cmd, func(sc *scenarioContext) {
  298. CallPostDashboard(sc)
  299. So(sc.resp.Code, ShouldEqual, 200)
  300. })
  301. })
  302. Convey("When user is an Org Editor but has a view permission", func() {
  303. role := m.ROLE_EDITOR
  304. mockResult := []*m.DashboardAclInfoDTO{
  305. {Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_VIEW},
  306. }
  307. bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
  308. query.Result = mockResult
  309. return nil
  310. })
  311. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  312. dash := GetDashboardShouldReturn200(sc)
  313. Convey("Should not be able to edit or save dashboard", func() {
  314. So(dash.Meta.CanEdit, ShouldBeFalse)
  315. So(dash.Meta.CanSave, ShouldBeFalse)
  316. })
  317. })
  318. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  319. CallDeleteDashboard(sc)
  320. So(sc.resp.Code, ShouldEqual, 403)
  321. })
  322. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
  323. CallGetDashboardVersion(sc)
  324. So(sc.resp.Code, ShouldEqual, 403)
  325. })
  326. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) {
  327. CallGetDashboardVersions(sc)
  328. So(sc.resp.Code, ShouldEqual, 403)
  329. })
  330. postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, cmd, func(sc *scenarioContext) {
  331. CallPostDashboard(sc)
  332. So(sc.resp.Code, ShouldEqual, 403)
  333. })
  334. })
  335. })
  336. }
  337. func GetDashboardShouldReturn200(sc *scenarioContext) dtos.DashboardFullWithMeta {
  338. sc.handlerFunc = GetDashboard
  339. sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
  340. So(sc.resp.Code, ShouldEqual, 200)
  341. dash := dtos.DashboardFullWithMeta{}
  342. err := json.NewDecoder(sc.resp.Body).Decode(&dash)
  343. So(err, ShouldBeNil)
  344. return dash
  345. }
  346. func CallGetDashboardVersion(sc *scenarioContext) {
  347. bus.AddHandler("test", func(query *m.GetDashboardVersionQuery) error {
  348. query.Result = &m.DashboardVersion{}
  349. return nil
  350. })
  351. sc.handlerFunc = GetDashboardVersion
  352. sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
  353. }
  354. func CallGetDashboardVersions(sc *scenarioContext) {
  355. bus.AddHandler("test", func(query *m.GetDashboardVersionsQuery) error {
  356. query.Result = []*m.DashboardVersionDTO{}
  357. return nil
  358. })
  359. sc.handlerFunc = GetDashboardVersions
  360. sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
  361. }
  362. func CallDeleteDashboard(sc *scenarioContext) {
  363. bus.AddHandler("test", func(cmd *m.DeleteDashboardCommand) error {
  364. return nil
  365. })
  366. sc.handlerFunc = DeleteDashboard
  367. sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
  368. }
  369. func CallPostDashboard(sc *scenarioContext) {
  370. bus.AddHandler("test", func(cmd *alerting.ValidateDashboardAlertsCommand) error {
  371. return nil
  372. })
  373. bus.AddHandler("test", func(cmd *m.SaveDashboardCommand) error {
  374. cmd.Result = &m.Dashboard{Id: 2, Slug: "Dash", Version: 2}
  375. return nil
  376. })
  377. bus.AddHandler("test", func(cmd *alerting.UpdateDashboardAlertsCommand) error {
  378. return nil
  379. })
  380. sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
  381. }
  382. func postDashboardScenario(desc string, url string, routePattern string, role m.RoleType, cmd m.SaveDashboardCommand, fn scenarioFunc) {
  383. Convey(desc+" "+url, func() {
  384. defer bus.ClearBusHandlers()
  385. sc := &scenarioContext{
  386. url: url,
  387. }
  388. viewsPath, _ := filepath.Abs("../../public/views")
  389. sc.m = macaron.New()
  390. sc.m.Use(macaron.Renderer(macaron.RenderOptions{
  391. Directory: viewsPath,
  392. Delims: macaron.Delims{Left: "[[", Right: "]]"},
  393. }))
  394. sc.m.Use(middleware.GetContextHandler())
  395. sc.m.Use(middleware.Sessioner(&session.Options{}))
  396. sc.defaultHandler = wrap(func(c *middleware.Context) Response {
  397. sc.context = c
  398. sc.context.UserId = TestUserID
  399. sc.context.OrgId = TestOrgID
  400. sc.context.OrgRole = role
  401. return PostDashboard(c, cmd)
  402. })
  403. sc.m.Post(routePattern, sc.defaultHandler)
  404. fn(sc)
  405. })
  406. }