dashboard_test.go 15 KB

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