dashboard_test.go 16 KB

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