dashboard_test.go 15 KB

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