middleware_test.go 16 KB

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