dashboard_test.go 15 KB

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