dashboard_test.go 32 KB

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