middleware_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. package middleware
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "net/http"
  7. "net/http/httptest"
  8. "path/filepath"
  9. "testing"
  10. "time"
  11. "github.com/grafana/grafana/pkg/api/dtos"
  12. "github.com/grafana/grafana/pkg/bus"
  13. "github.com/grafana/grafana/pkg/infra/remotecache"
  14. m "github.com/grafana/grafana/pkg/models"
  15. "github.com/grafana/grafana/pkg/services/auth"
  16. "github.com/grafana/grafana/pkg/setting"
  17. "github.com/grafana/grafana/pkg/util"
  18. . "github.com/smartystreets/goconvey/convey"
  19. "gopkg.in/macaron.v1"
  20. )
  21. func TestMiddlewareContext(t *testing.T) {
  22. setting.ERR_TEMPLATE_NAME = "error-template"
  23. Convey("Given the grafana middleware", t, func() {
  24. middlewareScenario(t, "middleware should add context to injector", func(sc *scenarioContext) {
  25. sc.fakeReq("GET", "/").exec()
  26. So(sc.context, ShouldNotBeNil)
  27. })
  28. middlewareScenario(t, "Default middleware should allow get request", func(sc *scenarioContext) {
  29. sc.fakeReq("GET", "/").exec()
  30. So(sc.resp.Code, ShouldEqual, 200)
  31. })
  32. middlewareScenario(t, "middleware should add Cache-Control header for requests to API", func(sc *scenarioContext) {
  33. sc.fakeReq("GET", "/api/search").exec()
  34. So(sc.resp.Header().Get("Cache-Control"), ShouldEqual, "no-cache")
  35. So(sc.resp.Header().Get("Pragma"), ShouldEqual, "no-cache")
  36. So(sc.resp.Header().Get("Expires"), ShouldEqual, "-1")
  37. })
  38. middlewareScenario(t, "middleware should not add Cache-Control header for requests to datasource proxy API", func(sc *scenarioContext) {
  39. sc.fakeReq("GET", "/api/datasources/proxy/1/test").exec()
  40. So(sc.resp.Header().Get("Cache-Control"), ShouldBeEmpty)
  41. So(sc.resp.Header().Get("Pragma"), ShouldBeEmpty)
  42. So(sc.resp.Header().Get("Expires"), ShouldBeEmpty)
  43. })
  44. middlewareScenario(t, "middleware should add Cache-Control header for requests with html response", func(sc *scenarioContext) {
  45. sc.handler(func(c *m.ReqContext) {
  46. data := &dtos.IndexViewData{
  47. User: &dtos.CurrentUser{},
  48. Settings: map[string]interface{}{},
  49. NavTree: []*dtos.NavLink{},
  50. }
  51. c.HTML(200, "index-template", data)
  52. })
  53. sc.fakeReq("GET", "/").exec()
  54. So(sc.resp.Code, ShouldEqual, 200)
  55. So(sc.resp.Header().Get("Cache-Control"), ShouldEqual, "no-cache")
  56. So(sc.resp.Header().Get("Pragma"), ShouldEqual, "no-cache")
  57. So(sc.resp.Header().Get("Expires"), ShouldEqual, "-1")
  58. })
  59. middlewareScenario(t, "middleware should add X-Frame-Options header with deny for request when not allowing embedding", func(sc *scenarioContext) {
  60. sc.fakeReq("GET", "/api/search").exec()
  61. So(sc.resp.Header().Get("X-Frame-Options"), ShouldEqual, "deny")
  62. })
  63. middlewareScenario(t, "middleware should not add X-Frame-Options header for request when allowing embedding", func(sc *scenarioContext) {
  64. setting.AllowEmbedding = true
  65. sc.fakeReq("GET", "/api/search").exec()
  66. So(sc.resp.Header().Get("X-Frame-Options"), ShouldBeEmpty)
  67. })
  68. middlewareScenario(t, "Invalid api key", func(sc *scenarioContext) {
  69. sc.apiKey = "invalid_key_test"
  70. sc.fakeReq("GET", "/").exec()
  71. Convey("Should not init session", func() {
  72. So(sc.resp.Header().Get("Set-Cookie"), ShouldBeEmpty)
  73. })
  74. Convey("Should return 401", func() {
  75. So(sc.resp.Code, ShouldEqual, 401)
  76. So(sc.respJson["message"], ShouldEqual, "Invalid API key")
  77. })
  78. })
  79. middlewareScenario(t, "Using basic auth", func(sc *scenarioContext) {
  80. bus.AddHandler("test", func(query *m.GetUserByLoginQuery) error {
  81. query.Result = &m.User{
  82. Password: util.EncodePassword("myPass", "salt"),
  83. Salt: "salt",
  84. }
  85. return nil
  86. })
  87. bus.AddHandler("test", func(loginUserQuery *m.LoginUserQuery) error {
  88. return nil
  89. })
  90. bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
  91. query.Result = &m.SignedInUser{OrgId: 2, UserId: 12}
  92. return nil
  93. })
  94. setting.BasicAuthEnabled = true
  95. authHeader := util.GetBasicAuthHeader("myUser", "myPass")
  96. sc.fakeReq("GET", "/").withAuthorizationHeader(authHeader).exec()
  97. Convey("Should init middleware context with user", func() {
  98. So(sc.context.IsSignedIn, ShouldEqual, true)
  99. So(sc.context.OrgId, ShouldEqual, 2)
  100. So(sc.context.UserId, ShouldEqual, 12)
  101. })
  102. })
  103. middlewareScenario(t, "Valid api key", func(sc *scenarioContext) {
  104. keyhash := util.EncodePassword("v5nAwpMafFP6znaS4urhdWDLS5511M42", "asd")
  105. bus.AddHandler("test", func(query *m.GetApiKeyByNameQuery) error {
  106. query.Result = &m.ApiKey{OrgId: 12, Role: m.ROLE_EDITOR, Key: keyhash}
  107. return nil
  108. })
  109. sc.fakeReq("GET", "/").withValidApiKey().exec()
  110. Convey("Should return 200", func() {
  111. So(sc.resp.Code, ShouldEqual, 200)
  112. })
  113. Convey("Should init middleware context", func() {
  114. So(sc.context.IsSignedIn, ShouldEqual, true)
  115. So(sc.context.OrgId, ShouldEqual, 12)
  116. So(sc.context.OrgRole, ShouldEqual, m.ROLE_EDITOR)
  117. })
  118. })
  119. middlewareScenario(t, "Valid api key, but does not match db hash", func(sc *scenarioContext) {
  120. keyhash := "something_not_matching"
  121. bus.AddHandler("test", func(query *m.GetApiKeyByNameQuery) error {
  122. query.Result = &m.ApiKey{OrgId: 12, Role: m.ROLE_EDITOR, Key: keyhash}
  123. return nil
  124. })
  125. sc.fakeReq("GET", "/").withValidApiKey().exec()
  126. Convey("Should return api key invalid", func() {
  127. So(sc.resp.Code, ShouldEqual, 401)
  128. So(sc.respJson["message"], ShouldEqual, "Invalid API key")
  129. })
  130. })
  131. middlewareScenario(t, "Valid api key via Basic auth", func(sc *scenarioContext) {
  132. keyhash := util.EncodePassword("v5nAwpMafFP6znaS4urhdWDLS5511M42", "asd")
  133. bus.AddHandler("test", func(query *m.GetApiKeyByNameQuery) error {
  134. query.Result = &m.ApiKey{OrgId: 12, Role: m.ROLE_EDITOR, Key: keyhash}
  135. return nil
  136. })
  137. authHeader := util.GetBasicAuthHeader("api_key", "eyJrIjoidjVuQXdwTWFmRlA2em5hUzR1cmhkV0RMUzU1MTFNNDIiLCJuIjoiYXNkIiwiaWQiOjF9")
  138. sc.fakeReq("GET", "/").withAuthorizationHeader(authHeader).exec()
  139. Convey("Should return 200", func() {
  140. So(sc.resp.Code, ShouldEqual, 200)
  141. })
  142. Convey("Should init middleware context", func() {
  143. So(sc.context.IsSignedIn, ShouldEqual, true)
  144. So(sc.context.OrgId, ShouldEqual, 12)
  145. So(sc.context.OrgRole, ShouldEqual, m.ROLE_EDITOR)
  146. })
  147. })
  148. middlewareScenario(t, "Non-expired auth token in cookie which not are being rotated", func(sc *scenarioContext) {
  149. sc.withTokenSessionCookie("token")
  150. bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
  151. query.Result = &m.SignedInUser{OrgId: 2, UserId: 12}
  152. return nil
  153. })
  154. sc.userAuthTokenService.LookupTokenProvider = func(ctx context.Context, unhashedToken string) (*m.UserToken, error) {
  155. return &m.UserToken{
  156. UserId: 12,
  157. UnhashedToken: unhashedToken,
  158. }, nil
  159. }
  160. sc.fakeReq("GET", "/").exec()
  161. Convey("should init context with user info", func() {
  162. So(sc.context.IsSignedIn, ShouldBeTrue)
  163. So(sc.context.UserId, ShouldEqual, 12)
  164. So(sc.context.UserToken.UserId, ShouldEqual, 12)
  165. So(sc.context.UserToken.UnhashedToken, ShouldEqual, "token")
  166. })
  167. Convey("should not set cookie", func() {
  168. So(sc.resp.Header().Get("Set-Cookie"), ShouldEqual, "")
  169. })
  170. })
  171. middlewareScenario(t, "Non-expired auth token in cookie which are being rotated", func(sc *scenarioContext) {
  172. sc.withTokenSessionCookie("token")
  173. bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
  174. query.Result = &m.SignedInUser{OrgId: 2, UserId: 12}
  175. return nil
  176. })
  177. sc.userAuthTokenService.LookupTokenProvider = func(ctx context.Context, unhashedToken string) (*m.UserToken, error) {
  178. return &m.UserToken{
  179. UserId: 12,
  180. UnhashedToken: "",
  181. }, nil
  182. }
  183. sc.userAuthTokenService.TryRotateTokenProvider = func(ctx context.Context, userToken *m.UserToken, clientIP, userAgent string) (bool, error) {
  184. userToken.UnhashedToken = "rotated"
  185. return true, nil
  186. }
  187. maxAgeHours := (time.Duration(setting.LoginMaxLifetimeDays) * 24 * time.Hour)
  188. maxAge := (maxAgeHours + time.Hour).Seconds()
  189. expectedCookie := &http.Cookie{
  190. Name: setting.LoginCookieName,
  191. Value: "rotated",
  192. Path: setting.AppSubUrl + "/",
  193. HttpOnly: true,
  194. MaxAge: int(maxAge),
  195. Secure: setting.CookieSecure,
  196. SameSite: setting.CookieSameSite,
  197. }
  198. sc.fakeReq("GET", "/").exec()
  199. Convey("should init context with user info", func() {
  200. So(sc.context.IsSignedIn, ShouldBeTrue)
  201. So(sc.context.UserId, ShouldEqual, 12)
  202. So(sc.context.UserToken.UserId, ShouldEqual, 12)
  203. So(sc.context.UserToken.UnhashedToken, ShouldEqual, "rotated")
  204. })
  205. Convey("should set cookie", func() {
  206. So(sc.resp.Header().Get("Set-Cookie"), ShouldEqual, expectedCookie.String())
  207. })
  208. })
  209. middlewareScenario(t, "Invalid/expired auth token in cookie", func(sc *scenarioContext) {
  210. sc.withTokenSessionCookie("token")
  211. sc.userAuthTokenService.LookupTokenProvider = func(ctx context.Context, unhashedToken string) (*m.UserToken, error) {
  212. return nil, m.ErrUserTokenNotFound
  213. }
  214. sc.fakeReq("GET", "/").exec()
  215. Convey("should not init context with user info", func() {
  216. So(sc.context.IsSignedIn, ShouldBeFalse)
  217. So(sc.context.UserId, ShouldEqual, 0)
  218. So(sc.context.UserToken, ShouldBeNil)
  219. })
  220. })
  221. middlewareScenario(t, "When anonymous access is enabled", func(sc *scenarioContext) {
  222. setting.AnonymousEnabled = true
  223. setting.AnonymousOrgName = "test"
  224. setting.AnonymousOrgRole = string(m.ROLE_EDITOR)
  225. bus.AddHandler("test", func(query *m.GetOrgByNameQuery) error {
  226. So(query.Name, ShouldEqual, "test")
  227. query.Result = &m.Org{Id: 2, Name: "test"}
  228. return nil
  229. })
  230. sc.fakeReq("GET", "/").exec()
  231. Convey("should init context with org info", func() {
  232. So(sc.context.UserId, ShouldEqual, 0)
  233. So(sc.context.OrgId, ShouldEqual, 2)
  234. So(sc.context.OrgRole, ShouldEqual, m.ROLE_EDITOR)
  235. })
  236. Convey("context signed in should be false", func() {
  237. So(sc.context.IsSignedIn, ShouldBeFalse)
  238. })
  239. })
  240. Convey("auth_proxy", func() {
  241. setting.AuthProxyEnabled = true
  242. setting.AuthProxyWhitelist = ""
  243. setting.AuthProxyAutoSignUp = true
  244. setting.LdapEnabled = true
  245. setting.AuthProxyHeaderName = "X-WEBAUTH-USER"
  246. setting.AuthProxyHeaderProperty = "username"
  247. name := "markelog"
  248. middlewareScenario(t, "should not sync the user if it's in the cache", func(sc *scenarioContext) {
  249. bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
  250. query.Result = &m.SignedInUser{OrgId: 4, UserId: query.UserId}
  251. return nil
  252. })
  253. key := fmt.Sprintf(cachePrefix, name)
  254. sc.remoteCacheService.Set(key, int64(33), 0)
  255. sc.fakeReq("GET", "/")
  256. sc.req.Header.Add(setting.AuthProxyHeaderName, name)
  257. sc.exec()
  258. Convey("Should init user via cache", func() {
  259. So(sc.context.IsSignedIn, ShouldBeTrue)
  260. So(sc.context.UserId, ShouldEqual, 33)
  261. So(sc.context.OrgId, ShouldEqual, 4)
  262. })
  263. })
  264. middlewareScenario(t, "should create an user from a header", func(sc *scenarioContext) {
  265. setting.LdapEnabled = false
  266. setting.AuthProxyAutoSignUp = true
  267. bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
  268. if query.UserId > 0 {
  269. query.Result = &m.SignedInUser{OrgId: 4, UserId: 33}
  270. return nil
  271. }
  272. return m.ErrUserNotFound
  273. })
  274. bus.AddHandler("test", func(cmd *m.UpsertUserCommand) error {
  275. cmd.Result = &m.User{Id: 33}
  276. return nil
  277. })
  278. sc.fakeReq("GET", "/")
  279. sc.req.Header.Add(setting.AuthProxyHeaderName, name)
  280. sc.exec()
  281. Convey("Should create user from header info", func() {
  282. So(sc.context.IsSignedIn, ShouldBeTrue)
  283. So(sc.context.UserId, ShouldEqual, 33)
  284. So(sc.context.OrgId, ShouldEqual, 4)
  285. })
  286. })
  287. middlewareScenario(t, "should get an existing user from header", func(sc *scenarioContext) {
  288. setting.LdapEnabled = false
  289. bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
  290. query.Result = &m.SignedInUser{OrgId: 2, UserId: 12}
  291. return nil
  292. })
  293. bus.AddHandler("test", func(cmd *m.UpsertUserCommand) error {
  294. cmd.Result = &m.User{Id: 12}
  295. return nil
  296. })
  297. sc.fakeReq("GET", "/")
  298. sc.req.Header.Add(setting.AuthProxyHeaderName, name)
  299. sc.exec()
  300. Convey("should init context with user info", func() {
  301. So(sc.context.IsSignedIn, ShouldBeTrue)
  302. So(sc.context.UserId, ShouldEqual, 12)
  303. So(sc.context.OrgId, ShouldEqual, 2)
  304. })
  305. })
  306. middlewareScenario(t, "should allow the request from whitelist IP", func(sc *scenarioContext) {
  307. setting.AuthProxyWhitelist = "192.168.1.0/24, 2001::0/120"
  308. setting.LdapEnabled = false
  309. bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
  310. query.Result = &m.SignedInUser{OrgId: 4, UserId: 33}
  311. return nil
  312. })
  313. bus.AddHandler("test", func(cmd *m.UpsertUserCommand) error {
  314. cmd.Result = &m.User{Id: 33}
  315. return nil
  316. })
  317. sc.fakeReq("GET", "/")
  318. sc.req.Header.Add(setting.AuthProxyHeaderName, name)
  319. sc.req.RemoteAddr = "[2001::23]:12345"
  320. sc.exec()
  321. Convey("Should init context with user info", func() {
  322. So(sc.context.IsSignedIn, ShouldBeTrue)
  323. So(sc.context.UserId, ShouldEqual, 33)
  324. So(sc.context.OrgId, ShouldEqual, 4)
  325. })
  326. })
  327. middlewareScenario(t, "should not allow the request from whitelist IP", func(sc *scenarioContext) {
  328. setting.AuthProxyWhitelist = "8.8.8.8"
  329. setting.LdapEnabled = false
  330. bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
  331. query.Result = &m.SignedInUser{OrgId: 4, UserId: 33}
  332. return nil
  333. })
  334. bus.AddHandler("test", func(cmd *m.UpsertUserCommand) error {
  335. cmd.Result = &m.User{Id: 33}
  336. return nil
  337. })
  338. sc.fakeReq("GET", "/")
  339. sc.req.Header.Add(setting.AuthProxyHeaderName, name)
  340. sc.req.RemoteAddr = "[2001::23]:12345"
  341. sc.exec()
  342. Convey("should return 407 status code", func() {
  343. So(sc.resp.Code, ShouldEqual, 407)
  344. So(sc.context, ShouldBeNil)
  345. })
  346. })
  347. })
  348. })
  349. }
  350. func middlewareScenario(t *testing.T, desc string, fn scenarioFunc) {
  351. Convey(desc, func() {
  352. defer bus.ClearBusHandlers()
  353. setting.LoginCookieName = "grafana_session"
  354. setting.LoginMaxLifetimeDays = 30
  355. sc := &scenarioContext{}
  356. viewsPath, _ := filepath.Abs("../../public/views")
  357. sc.m = macaron.New()
  358. sc.m.Use(AddDefaultResponseHeaders())
  359. sc.m.Use(macaron.Renderer(macaron.RenderOptions{
  360. Directory: viewsPath,
  361. Delims: macaron.Delims{Left: "[[", Right: "]]"},
  362. }))
  363. sc.userAuthTokenService = auth.NewFakeUserAuthTokenService()
  364. sc.remoteCacheService = remotecache.NewFakeStore(t)
  365. sc.m.Use(GetContextHandler(sc.userAuthTokenService, sc.remoteCacheService))
  366. sc.m.Use(OrgRedirect())
  367. sc.defaultHandler = func(c *m.ReqContext) {
  368. sc.context = c
  369. if sc.handlerFunc != nil {
  370. sc.handlerFunc(sc.context)
  371. }
  372. }
  373. sc.m.Get("/", sc.defaultHandler)
  374. fn(sc)
  375. })
  376. }
  377. type scenarioContext struct {
  378. m *macaron.Macaron
  379. context *m.ReqContext
  380. resp *httptest.ResponseRecorder
  381. apiKey string
  382. authHeader string
  383. tokenSessionCookie string
  384. respJson map[string]interface{}
  385. handlerFunc handlerFunc
  386. defaultHandler macaron.Handler
  387. url string
  388. userAuthTokenService *auth.FakeUserAuthTokenService
  389. remoteCacheService *remotecache.RemoteCache
  390. req *http.Request
  391. }
  392. func (sc *scenarioContext) withValidApiKey() *scenarioContext {
  393. sc.apiKey = "eyJrIjoidjVuQXdwTWFmRlA2em5hUzR1cmhkV0RMUzU1MTFNNDIiLCJuIjoiYXNkIiwiaWQiOjF9"
  394. return sc
  395. }
  396. func (sc *scenarioContext) withTokenSessionCookie(unhashedToken string) *scenarioContext {
  397. sc.tokenSessionCookie = unhashedToken
  398. return sc
  399. }
  400. func (sc *scenarioContext) withAuthorizationHeader(authHeader string) *scenarioContext {
  401. sc.authHeader = authHeader
  402. return sc
  403. }
  404. func (sc *scenarioContext) fakeReq(method, url string) *scenarioContext {
  405. sc.resp = httptest.NewRecorder()
  406. req, err := http.NewRequest(method, url, nil)
  407. So(err, ShouldBeNil)
  408. sc.req = req
  409. return sc
  410. }
  411. func (sc *scenarioContext) fakeReqWithParams(method, url string, queryParams map[string]string) *scenarioContext {
  412. sc.resp = httptest.NewRecorder()
  413. req, err := http.NewRequest(method, url, nil)
  414. q := req.URL.Query()
  415. for k, v := range queryParams {
  416. q.Add(k, v)
  417. }
  418. req.URL.RawQuery = q.Encode()
  419. So(err, ShouldBeNil)
  420. sc.req = req
  421. return sc
  422. }
  423. func (sc *scenarioContext) handler(fn handlerFunc) *scenarioContext {
  424. sc.handlerFunc = fn
  425. return sc
  426. }
  427. func (sc *scenarioContext) exec() {
  428. if sc.apiKey != "" {
  429. sc.req.Header.Add("Authorization", "Bearer "+sc.apiKey)
  430. }
  431. if sc.authHeader != "" {
  432. sc.req.Header.Add("Authorization", sc.authHeader)
  433. }
  434. if sc.tokenSessionCookie != "" {
  435. sc.req.AddCookie(&http.Cookie{
  436. Name: setting.LoginCookieName,
  437. Value: sc.tokenSessionCookie,
  438. })
  439. }
  440. sc.m.ServeHTTP(sc.resp, sc.req)
  441. if sc.resp.Header().Get("Content-Type") == "application/json; charset=UTF-8" {
  442. err := json.NewDecoder(sc.resp.Body).Decode(&sc.respJson)
  443. So(err, ShouldBeNil)
  444. }
  445. }
  446. type scenarioFunc func(c *scenarioContext)
  447. type handlerFunc func(c *m.ReqContext)