dashboard_test.go 17 KB

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