dashboard_test.go 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843
  1. package api
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "testing"
  6. "github.com/grafana/grafana/pkg/api/dtos"
  7. "github.com/grafana/grafana/pkg/bus"
  8. "github.com/grafana/grafana/pkg/components/simplejson"
  9. "github.com/grafana/grafana/pkg/middleware"
  10. m "github.com/grafana/grafana/pkg/models"
  11. "github.com/grafana/grafana/pkg/services/dashboards"
  12. "github.com/grafana/grafana/pkg/setting"
  13. . "github.com/smartystreets/goconvey/convey"
  14. )
  15. // This tests three main scenarios.
  16. // If a user has access to execute an action on a dashboard:
  17. // 1. and the dashboard is in a folder which does not have an acl
  18. // 2. and the dashboard is in a folder which does have an acl
  19. // 3. Post dashboard response tests
  20. func TestDashboardApiEndpoint(t *testing.T) {
  21. Convey("Given a dashboard with a parent folder which does not have an acl", t, func() {
  22. fakeDash := m.NewDashboard("Child dash")
  23. fakeDash.Id = 1
  24. fakeDash.FolderId = 1
  25. fakeDash.HasAcl = false
  26. bus.AddHandler("test", func(query *m.GetDashboardsBySlugQuery) error {
  27. dashboards := []*m.Dashboard{fakeDash}
  28. query.Result = dashboards
  29. return nil
  30. })
  31. var getDashboardQueries []*m.GetDashboardQuery
  32. bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
  33. query.Result = fakeDash
  34. getDashboardQueries = append(getDashboardQueries, query)
  35. return nil
  36. })
  37. viewerRole := m.ROLE_VIEWER
  38. editorRole := m.ROLE_EDITOR
  39. aclMockResp := []*m.DashboardAclInfoDTO{
  40. {Role: &viewerRole, Permission: m.PERMISSION_VIEW},
  41. {Role: &editorRole, Permission: m.PERMISSION_EDIT},
  42. }
  43. bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
  44. query.Result = aclMockResp
  45. return nil
  46. })
  47. bus.AddHandler("test", func(query *m.GetTeamsByUserQuery) error {
  48. query.Result = []*m.Team{}
  49. return nil
  50. })
  51. // This tests two scenarios:
  52. // 1. user is an org viewer
  53. // 2. user is an org editor
  54. Convey("When user is an Org Viewer", func() {
  55. role := m.ROLE_VIEWER
  56. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/db/child-dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
  57. dash := GetDashboardShouldReturn200(sc)
  58. Convey("Should lookup dashboard by slug", func() {
  59. So(getDashboardQueries[0].Slug, ShouldEqual, "child-dash")
  60. })
  61. Convey("Should not be able to edit or save dashboard", func() {
  62. So(dash.Meta.CanEdit, ShouldBeFalse)
  63. So(dash.Meta.CanSave, ShouldBeFalse)
  64. So(dash.Meta.CanAdmin, ShouldBeFalse)
  65. })
  66. })
  67. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
  68. dash := GetDashboardShouldReturn200(sc)
  69. Convey("Should lookup dashboard by uid", func() {
  70. So(getDashboardQueries[0].Uid, ShouldEqual, "abcdefghi")
  71. })
  72. Convey("Should not be able to edit or save dashboard", func() {
  73. So(dash.Meta.CanEdit, ShouldBeFalse)
  74. So(dash.Meta.CanSave, ShouldBeFalse)
  75. So(dash.Meta.CanAdmin, ShouldBeFalse)
  76. })
  77. })
  78. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/db/child-dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
  79. CallDeleteDashboard(sc)
  80. So(sc.resp.Code, ShouldEqual, 403)
  81. Convey("Should lookup dashboard by slug", func() {
  82. So(getDashboardQueries[0].Slug, ShouldEqual, "child-dash")
  83. })
  84. })
  85. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
  86. CallDeleteDashboardByUid(sc)
  87. So(sc.resp.Code, ShouldEqual, 403)
  88. Convey("Should lookup dashboard by uid", func() {
  89. So(getDashboardQueries[0].Uid, ShouldEqual, "abcdefghi")
  90. })
  91. })
  92. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
  93. CallGetDashboardVersion(sc)
  94. So(sc.resp.Code, ShouldEqual, 403)
  95. })
  96. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) {
  97. CallGetDashboardVersions(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/db/child-dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
  104. dash := GetDashboardShouldReturn200(sc)
  105. Convey("Should lookup dashboard by slug", func() {
  106. So(getDashboardQueries[0].Slug, ShouldEqual, "child-dash")
  107. })
  108. Convey("Should be able to edit or save dashboard", func() {
  109. So(dash.Meta.CanEdit, ShouldBeTrue)
  110. So(dash.Meta.CanSave, ShouldBeTrue)
  111. So(dash.Meta.CanAdmin, ShouldBeFalse)
  112. })
  113. })
  114. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
  115. dash := GetDashboardShouldReturn200(sc)
  116. Convey("Should lookup dashboard by uid", func() {
  117. So(getDashboardQueries[0].Uid, ShouldEqual, "abcdefghi")
  118. })
  119. Convey("Should be able to edit or save dashboard", func() {
  120. So(dash.Meta.CanEdit, ShouldBeTrue)
  121. So(dash.Meta.CanSave, ShouldBeTrue)
  122. So(dash.Meta.CanAdmin, ShouldBeFalse)
  123. })
  124. })
  125. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/db/child-dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
  126. CallDeleteDashboard(sc)
  127. So(sc.resp.Code, ShouldEqual, 200)
  128. Convey("Should lookup dashboard by slug", func() {
  129. So(getDashboardQueries[0].Slug, ShouldEqual, "child-dash")
  130. })
  131. })
  132. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
  133. CallDeleteDashboardByUid(sc)
  134. So(sc.resp.Code, ShouldEqual, 200)
  135. Convey("Should lookup dashboard by uid", func() {
  136. So(getDashboardQueries[0].Uid, ShouldEqual, "abcdefghi")
  137. })
  138. })
  139. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
  140. CallGetDashboardVersion(sc)
  141. So(sc.resp.Code, ShouldEqual, 200)
  142. })
  143. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) {
  144. CallGetDashboardVersions(sc)
  145. So(sc.resp.Code, ShouldEqual, 200)
  146. })
  147. })
  148. })
  149. Convey("Given a dashboard with a parent folder which has an acl", t, func() {
  150. fakeDash := m.NewDashboard("Child dash")
  151. fakeDash.Id = 1
  152. fakeDash.FolderId = 1
  153. fakeDash.HasAcl = true
  154. setting.ViewersCanEdit = false
  155. bus.AddHandler("test", func(query *m.GetDashboardsBySlugQuery) error {
  156. dashboards := []*m.Dashboard{fakeDash}
  157. query.Result = dashboards
  158. return nil
  159. })
  160. aclMockResp := []*m.DashboardAclInfoDTO{
  161. {
  162. DashboardId: 1,
  163. Permission: m.PERMISSION_EDIT,
  164. UserId: 200,
  165. },
  166. }
  167. bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
  168. query.Result = aclMockResp
  169. return nil
  170. })
  171. var getDashboardQueries []*m.GetDashboardQuery
  172. bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
  173. query.Result = fakeDash
  174. getDashboardQueries = append(getDashboardQueries, query)
  175. return nil
  176. })
  177. bus.AddHandler("test", func(query *m.GetTeamsByUserQuery) error {
  178. query.Result = []*m.Team{}
  179. return nil
  180. })
  181. // This tests six scenarios:
  182. // 1. user is an org viewer AND has no permissions for this dashboard
  183. // 2. user is an org editor AND has no permissions for this dashboard
  184. // 3. user is an org viewer AND has been granted edit permission for the dashboard
  185. // 4. user is an org viewer AND all viewers have edit permission for this dashboard
  186. // 5. user is an org viewer AND has been granted an admin permission
  187. // 6. user is an org editor AND has been granted a view permission
  188. Convey("When user is an Org Viewer and has no permissions for this dashboard", func() {
  189. role := m.ROLE_VIEWER
  190. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/db/child-dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
  191. sc.handlerFunc = GetDashboard
  192. sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
  193. Convey("Should lookup dashboard by slug", func() {
  194. So(getDashboardQueries[0].Slug, ShouldEqual, "child-dash")
  195. })
  196. Convey("Should be denied access", func() {
  197. So(sc.resp.Code, ShouldEqual, 403)
  198. })
  199. })
  200. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
  201. sc.handlerFunc = GetDashboard
  202. sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
  203. Convey("Should lookup dashboard by uid", func() {
  204. So(getDashboardQueries[0].Uid, ShouldEqual, "abcdefghi")
  205. })
  206. Convey("Should be denied access", func() {
  207. So(sc.resp.Code, ShouldEqual, 403)
  208. })
  209. })
  210. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/db/child-dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
  211. CallDeleteDashboard(sc)
  212. So(sc.resp.Code, ShouldEqual, 403)
  213. Convey("Should lookup dashboard by slug", func() {
  214. So(getDashboardQueries[0].Slug, ShouldEqual, "child-dash")
  215. })
  216. })
  217. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
  218. CallDeleteDashboardByUid(sc)
  219. So(sc.resp.Code, ShouldEqual, 403)
  220. Convey("Should lookup dashboard by uid", func() {
  221. So(getDashboardQueries[0].Uid, ShouldEqual, "abcdefghi")
  222. })
  223. })
  224. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
  225. CallGetDashboardVersion(sc)
  226. So(sc.resp.Code, ShouldEqual, 403)
  227. })
  228. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) {
  229. CallGetDashboardVersions(sc)
  230. So(sc.resp.Code, ShouldEqual, 403)
  231. })
  232. })
  233. Convey("When user is an Org Editor and has no permissions for this dashboard", func() {
  234. role := m.ROLE_EDITOR
  235. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/db/child-dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
  236. sc.handlerFunc = GetDashboard
  237. sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
  238. Convey("Should lookup dashboard by slug", func() {
  239. So(getDashboardQueries[0].Slug, ShouldEqual, "child-dash")
  240. })
  241. Convey("Should be denied access", func() {
  242. So(sc.resp.Code, ShouldEqual, 403)
  243. })
  244. })
  245. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
  246. sc.handlerFunc = GetDashboard
  247. sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
  248. Convey("Should lookup dashboard by uid", func() {
  249. So(getDashboardQueries[0].Uid, ShouldEqual, "abcdefghi")
  250. })
  251. Convey("Should be denied access", func() {
  252. So(sc.resp.Code, ShouldEqual, 403)
  253. })
  254. })
  255. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/db/child-dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
  256. CallDeleteDashboard(sc)
  257. So(sc.resp.Code, ShouldEqual, 403)
  258. Convey("Should lookup dashboard by slug", func() {
  259. So(getDashboardQueries[0].Slug, ShouldEqual, "child-dash")
  260. })
  261. })
  262. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
  263. CallDeleteDashboardByUid(sc)
  264. So(sc.resp.Code, ShouldEqual, 403)
  265. Convey("Should lookup dashboard by uid", func() {
  266. So(getDashboardQueries[0].Uid, ShouldEqual, "abcdefghi")
  267. })
  268. })
  269. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
  270. CallGetDashboardVersion(sc)
  271. So(sc.resp.Code, ShouldEqual, 403)
  272. })
  273. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) {
  274. CallGetDashboardVersions(sc)
  275. So(sc.resp.Code, ShouldEqual, 403)
  276. })
  277. })
  278. Convey("When user is an Org Viewer but has an edit permission", func() {
  279. role := m.ROLE_VIEWER
  280. mockResult := []*m.DashboardAclInfoDTO{
  281. {OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_EDIT},
  282. }
  283. bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
  284. query.Result = mockResult
  285. return nil
  286. })
  287. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/db/child-dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
  288. dash := GetDashboardShouldReturn200(sc)
  289. Convey("Should lookup dashboard by slug", func() {
  290. So(getDashboardQueries[0].Slug, ShouldEqual, "child-dash")
  291. })
  292. Convey("Should be able to get dashboard with edit rights", func() {
  293. So(dash.Meta.CanEdit, ShouldBeTrue)
  294. So(dash.Meta.CanSave, ShouldBeTrue)
  295. So(dash.Meta.CanAdmin, ShouldBeFalse)
  296. })
  297. })
  298. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
  299. dash := GetDashboardShouldReturn200(sc)
  300. Convey("Should lookup dashboard by uid", func() {
  301. So(getDashboardQueries[0].Uid, ShouldEqual, "abcdefghi")
  302. })
  303. Convey("Should be able to get dashboard with edit rights", func() {
  304. So(dash.Meta.CanEdit, ShouldBeTrue)
  305. So(dash.Meta.CanSave, ShouldBeTrue)
  306. So(dash.Meta.CanAdmin, ShouldBeFalse)
  307. })
  308. })
  309. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/db/child-dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
  310. CallDeleteDashboard(sc)
  311. So(sc.resp.Code, ShouldEqual, 200)
  312. Convey("Should lookup dashboard by slug", func() {
  313. So(getDashboardQueries[0].Slug, ShouldEqual, "child-dash")
  314. })
  315. })
  316. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
  317. CallDeleteDashboardByUid(sc)
  318. So(sc.resp.Code, ShouldEqual, 200)
  319. Convey("Should lookup dashboard by uid", func() {
  320. So(getDashboardQueries[0].Uid, ShouldEqual, "abcdefghi")
  321. })
  322. })
  323. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
  324. CallGetDashboardVersion(sc)
  325. So(sc.resp.Code, ShouldEqual, 200)
  326. })
  327. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) {
  328. CallGetDashboardVersions(sc)
  329. So(sc.resp.Code, ShouldEqual, 200)
  330. })
  331. })
  332. Convey("When user is an Org Viewer and viewers can edit", func() {
  333. role := m.ROLE_VIEWER
  334. setting.ViewersCanEdit = true
  335. mockResult := []*m.DashboardAclInfoDTO{
  336. {OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_VIEW},
  337. }
  338. bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
  339. query.Result = mockResult
  340. return nil
  341. })
  342. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/db/child-dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
  343. dash := GetDashboardShouldReturn200(sc)
  344. Convey("Should lookup dashboard by slug", func() {
  345. So(getDashboardQueries[0].Slug, ShouldEqual, "child-dash")
  346. })
  347. Convey("Should be able to get dashboard with edit rights but can save should be false", func() {
  348. So(dash.Meta.CanEdit, ShouldBeTrue)
  349. So(dash.Meta.CanSave, ShouldBeFalse)
  350. So(dash.Meta.CanAdmin, ShouldBeFalse)
  351. })
  352. })
  353. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
  354. dash := GetDashboardShouldReturn200(sc)
  355. Convey("Should lookup dashboard by uid", func() {
  356. So(getDashboardQueries[0].Uid, ShouldEqual, "abcdefghi")
  357. })
  358. Convey("Should be able to get dashboard with edit rights but can save should be false", func() {
  359. So(dash.Meta.CanEdit, ShouldBeTrue)
  360. So(dash.Meta.CanSave, ShouldBeFalse)
  361. So(dash.Meta.CanAdmin, ShouldBeFalse)
  362. })
  363. })
  364. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/db/child-dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
  365. CallDeleteDashboard(sc)
  366. So(sc.resp.Code, ShouldEqual, 403)
  367. Convey("Should lookup dashboard by slug", func() {
  368. So(getDashboardQueries[0].Slug, ShouldEqual, "child-dash")
  369. })
  370. })
  371. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
  372. CallDeleteDashboardByUid(sc)
  373. So(sc.resp.Code, ShouldEqual, 403)
  374. Convey("Should lookup dashboard by uid", func() {
  375. So(getDashboardQueries[0].Uid, ShouldEqual, "abcdefghi")
  376. })
  377. })
  378. })
  379. Convey("When user is an Org Viewer but has an admin permission", func() {
  380. role := m.ROLE_VIEWER
  381. mockResult := []*m.DashboardAclInfoDTO{
  382. {OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_ADMIN},
  383. }
  384. bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
  385. query.Result = mockResult
  386. return nil
  387. })
  388. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/db/child-dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
  389. dash := GetDashboardShouldReturn200(sc)
  390. Convey("Should lookup dashboard by slug", func() {
  391. So(getDashboardQueries[0].Slug, ShouldEqual, "child-dash")
  392. })
  393. Convey("Should be able to get dashboard with edit rights", func() {
  394. So(dash.Meta.CanEdit, ShouldBeTrue)
  395. So(dash.Meta.CanSave, ShouldBeTrue)
  396. So(dash.Meta.CanAdmin, ShouldBeTrue)
  397. })
  398. })
  399. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
  400. dash := GetDashboardShouldReturn200(sc)
  401. Convey("Should lookup dashboard by uid", func() {
  402. So(getDashboardQueries[0].Uid, ShouldEqual, "abcdefghi")
  403. })
  404. Convey("Should be able to get dashboard with edit rights", func() {
  405. So(dash.Meta.CanEdit, ShouldBeTrue)
  406. So(dash.Meta.CanSave, ShouldBeTrue)
  407. So(dash.Meta.CanAdmin, ShouldBeTrue)
  408. })
  409. })
  410. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/db/child-dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
  411. CallDeleteDashboard(sc)
  412. So(sc.resp.Code, ShouldEqual, 200)
  413. Convey("Should lookup dashboard by slug", func() {
  414. So(getDashboardQueries[0].Slug, ShouldEqual, "child-dash")
  415. })
  416. })
  417. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
  418. CallDeleteDashboardByUid(sc)
  419. So(sc.resp.Code, ShouldEqual, 200)
  420. Convey("Should lookup dashboard by uid", func() {
  421. So(getDashboardQueries[0].Uid, ShouldEqual, "abcdefghi")
  422. })
  423. })
  424. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
  425. CallGetDashboardVersion(sc)
  426. So(sc.resp.Code, ShouldEqual, 200)
  427. })
  428. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) {
  429. CallGetDashboardVersions(sc)
  430. So(sc.resp.Code, ShouldEqual, 200)
  431. })
  432. })
  433. Convey("When user is an Org Editor but has a view permission", func() {
  434. role := m.ROLE_EDITOR
  435. mockResult := []*m.DashboardAclInfoDTO{
  436. {OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_VIEW},
  437. }
  438. bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
  439. query.Result = mockResult
  440. return nil
  441. })
  442. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/db/child-dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
  443. dash := GetDashboardShouldReturn200(sc)
  444. Convey("Should lookup dashboard by slug", func() {
  445. So(getDashboardQueries[0].Slug, ShouldEqual, "child-dash")
  446. })
  447. Convey("Should not be able to edit or save dashboard", func() {
  448. So(dash.Meta.CanEdit, ShouldBeFalse)
  449. So(dash.Meta.CanSave, ShouldBeFalse)
  450. })
  451. })
  452. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
  453. dash := GetDashboardShouldReturn200(sc)
  454. Convey("Should lookup dashboard by uid", func() {
  455. So(getDashboardQueries[0].Uid, ShouldEqual, "abcdefghi")
  456. })
  457. Convey("Should not be able to edit or save dashboard", func() {
  458. So(dash.Meta.CanEdit, ShouldBeFalse)
  459. So(dash.Meta.CanSave, ShouldBeFalse)
  460. })
  461. })
  462. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/db/child-dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
  463. CallDeleteDashboard(sc)
  464. So(sc.resp.Code, ShouldEqual, 403)
  465. Convey("Should lookup dashboard by slug", func() {
  466. So(getDashboardQueries[0].Slug, ShouldEqual, "child-dash")
  467. })
  468. })
  469. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
  470. CallDeleteDashboardByUid(sc)
  471. So(sc.resp.Code, ShouldEqual, 403)
  472. Convey("Should lookup dashboard by uid", func() {
  473. So(getDashboardQueries[0].Uid, ShouldEqual, "abcdefghi")
  474. })
  475. })
  476. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
  477. CallGetDashboardVersion(sc)
  478. So(sc.resp.Code, ShouldEqual, 403)
  479. })
  480. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) {
  481. CallGetDashboardVersions(sc)
  482. So(sc.resp.Code, ShouldEqual, 403)
  483. })
  484. })
  485. })
  486. Convey("Given two dashboards with the same title in different folders", t, func() {
  487. dashOne := m.NewDashboard("dash")
  488. dashOne.Id = 2
  489. dashOne.FolderId = 1
  490. dashOne.HasAcl = false
  491. dashTwo := m.NewDashboard("dash")
  492. dashTwo.Id = 4
  493. dashTwo.FolderId = 3
  494. dashTwo.HasAcl = false
  495. bus.AddHandler("test", func(query *m.GetDashboardsBySlugQuery) error {
  496. dashboards := []*m.Dashboard{dashOne, dashTwo}
  497. query.Result = dashboards
  498. return nil
  499. })
  500. role := m.ROLE_EDITOR
  501. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/db/dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
  502. CallDeleteDashboard(sc)
  503. Convey("Should result in 412 Precondition failed", func() {
  504. So(sc.resp.Code, ShouldEqual, 412)
  505. result := sc.ToJson()
  506. So(result.Get("status").MustString(), ShouldEqual, "multiple-slugs-exists")
  507. So(result.Get("message").MustString(), ShouldEqual, m.ErrDashboardsWithSameSlugExists.Error())
  508. })
  509. })
  510. })
  511. Convey("Post dashboard response tests", t, func() {
  512. // This tests that a valid request returns correct response
  513. Convey("Given a correct request for creating a dashboard", func() {
  514. cmd := m.SaveDashboardCommand{
  515. OrgId: 1,
  516. UserId: 5,
  517. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  518. "title": "Dash",
  519. }),
  520. Overwrite: true,
  521. FolderId: 3,
  522. IsFolder: false,
  523. Message: "msg",
  524. }
  525. mock := &dashboards.FakeDashboardService{
  526. SaveDashboardResult: &m.Dashboard{
  527. Id: 2,
  528. Uid: "uid",
  529. Title: "Dash",
  530. Slug: "dash",
  531. Version: 2,
  532. },
  533. }
  534. postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", mock, cmd, func(sc *scenarioContext) {
  535. CallPostDashboardShouldReturnSuccess(sc)
  536. Convey("It should call dashboard service with correct data", func() {
  537. dto := mock.SavedDashboards[0]
  538. So(dto.OrgId, ShouldEqual, cmd.OrgId)
  539. So(dto.User.UserId, ShouldEqual, cmd.UserId)
  540. So(dto.Dashboard.FolderId, ShouldEqual, 3)
  541. So(dto.Dashboard.Title, ShouldEqual, "Dash")
  542. So(dto.Overwrite, ShouldBeTrue)
  543. So(dto.Message, ShouldEqual, "msg")
  544. })
  545. Convey("It should return correct response data", func() {
  546. result := sc.ToJson()
  547. So(result.Get("status").MustString(), ShouldEqual, "success")
  548. So(result.Get("id").MustInt64(), ShouldEqual, 2)
  549. So(result.Get("uid").MustString(), ShouldEqual, "uid")
  550. So(result.Get("slug").MustString(), ShouldEqual, "dash")
  551. So(result.Get("url").MustString(), ShouldEqual, "/d/uid/dash")
  552. })
  553. })
  554. })
  555. // This tests that invalid requests returns expected error responses
  556. Convey("Given incorrect requests for creating a dashboard", func() {
  557. testCases := []struct {
  558. SaveError error
  559. ExpectedStatusCode int
  560. }{
  561. {SaveError: m.ErrDashboardNotFound, ExpectedStatusCode: 404},
  562. {SaveError: m.ErrFolderNotFound, ExpectedStatusCode: 400},
  563. {SaveError: m.ErrDashboardWithSameUIDExists, ExpectedStatusCode: 400},
  564. {SaveError: m.ErrDashboardWithSameNameInFolderExists, ExpectedStatusCode: 412},
  565. {SaveError: m.ErrDashboardVersionMismatch, ExpectedStatusCode: 412},
  566. {SaveError: m.ErrDashboardTitleEmpty, ExpectedStatusCode: 400},
  567. {SaveError: m.ErrDashboardFolderCannotHaveParent, ExpectedStatusCode: 400},
  568. {SaveError: m.ErrDashboardContainsInvalidAlertData, ExpectedStatusCode: 500},
  569. {SaveError: m.ErrDashboardFailedToUpdateAlertData, ExpectedStatusCode: 500},
  570. {SaveError: m.ErrDashboardFailedGenerateUniqueUid, ExpectedStatusCode: 500},
  571. {SaveError: m.ErrDashboardTypeMismatch, ExpectedStatusCode: 400},
  572. {SaveError: m.ErrDashboardFolderWithSameNameAsDashboard, ExpectedStatusCode: 400},
  573. {SaveError: m.ErrDashboardWithSameNameAsFolder, ExpectedStatusCode: 400},
  574. {SaveError: m.ErrDashboardFolderNameExists, ExpectedStatusCode: 400},
  575. {SaveError: m.ErrDashboardUpdateAccessDenied, ExpectedStatusCode: 403},
  576. {SaveError: m.ErrDashboardInvalidUid, ExpectedStatusCode: 400},
  577. {SaveError: m.ErrDashboardUidToLong, ExpectedStatusCode: 400},
  578. {SaveError: m.UpdatePluginDashboardError{PluginId: "plug"}, ExpectedStatusCode: 412},
  579. }
  580. cmd := m.SaveDashboardCommand{
  581. OrgId: 1,
  582. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  583. "title": "",
  584. }),
  585. }
  586. for _, tc := range testCases {
  587. mock := &dashboards.FakeDashboardService{
  588. SaveDashboardError: tc.SaveError,
  589. }
  590. postDashboardScenario(fmt.Sprintf("Expect '%s' error when calling POST on", tc.SaveError.Error()), "/api/dashboards", "/api/dashboards", mock, cmd, func(sc *scenarioContext) {
  591. CallPostDashboard(sc)
  592. So(sc.resp.Code, ShouldEqual, tc.ExpectedStatusCode)
  593. })
  594. }
  595. })
  596. })
  597. }
  598. func GetDashboardShouldReturn200(sc *scenarioContext) dtos.DashboardFullWithMeta {
  599. CallGetDashboard(sc)
  600. So(sc.resp.Code, ShouldEqual, 200)
  601. dash := dtos.DashboardFullWithMeta{}
  602. err := json.NewDecoder(sc.resp.Body).Decode(&dash)
  603. So(err, ShouldBeNil)
  604. return dash
  605. }
  606. func CallGetDashboard(sc *scenarioContext) {
  607. sc.handlerFunc = GetDashboard
  608. sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
  609. }
  610. func CallGetDashboardVersion(sc *scenarioContext) {
  611. bus.AddHandler("test", func(query *m.GetDashboardVersionQuery) error {
  612. query.Result = &m.DashboardVersion{}
  613. return nil
  614. })
  615. sc.handlerFunc = GetDashboardVersion
  616. sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
  617. }
  618. func CallGetDashboardVersions(sc *scenarioContext) {
  619. bus.AddHandler("test", func(query *m.GetDashboardVersionsQuery) error {
  620. query.Result = []*m.DashboardVersionDTO{}
  621. return nil
  622. })
  623. sc.handlerFunc = GetDashboardVersions
  624. sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
  625. }
  626. func CallDeleteDashboard(sc *scenarioContext) {
  627. bus.AddHandler("test", func(cmd *m.DeleteDashboardCommand) error {
  628. return nil
  629. })
  630. sc.handlerFunc = DeleteDashboard
  631. sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
  632. }
  633. func CallDeleteDashboardByUid(sc *scenarioContext) {
  634. bus.AddHandler("test", func(cmd *m.DeleteDashboardCommand) error {
  635. return nil
  636. })
  637. sc.handlerFunc = DeleteDashboardByUid
  638. sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
  639. }
  640. func CallPostDashboard(sc *scenarioContext) {
  641. sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
  642. }
  643. func CallPostDashboardShouldReturnSuccess(sc *scenarioContext) {
  644. CallPostDashboard(sc)
  645. So(sc.resp.Code, ShouldEqual, 200)
  646. }
  647. func postDashboardScenario(desc string, url string, routePattern string, mock *dashboards.FakeDashboardService, cmd m.SaveDashboardCommand, fn scenarioFunc) {
  648. Convey(desc+" "+url, func() {
  649. defer bus.ClearBusHandlers()
  650. sc := setupScenarioContext(url)
  651. sc.defaultHandler = wrap(func(c *middleware.Context) Response {
  652. sc.context = c
  653. sc.context.SignedInUser = &m.SignedInUser{OrgId: cmd.OrgId, UserId: cmd.UserId}
  654. return PostDashboard(c, cmd)
  655. })
  656. origNewDashboardService := dashboards.NewService
  657. dashboards.MockDashboardService(mock)
  658. sc.m.Post(routePattern, sc.defaultHandler)
  659. defer func() {
  660. dashboards.NewService = origNewDashboardService
  661. }()
  662. fn(sc)
  663. })
  664. }
  665. func (sc *scenarioContext) ToJson() *simplejson.Json {
  666. var result *simplejson.Json
  667. err := json.NewDecoder(sc.resp.Body).Decode(&result)
  668. So(err, ShouldBeNil)
  669. return result
  670. }