dashboard_test.go 29 KB

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