dashboard_test.go 17 KB

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