dashboard_test.go 30 KB

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