auth_token_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. package auth
  2. import (
  3. "fmt"
  4. "net/http"
  5. "net/http/httptest"
  6. "testing"
  7. "time"
  8. "github.com/grafana/grafana/pkg/models"
  9. "github.com/grafana/grafana/pkg/setting"
  10. macaron "gopkg.in/macaron.v1"
  11. "github.com/grafana/grafana/pkg/log"
  12. "github.com/grafana/grafana/pkg/services/sqlstore"
  13. . "github.com/smartystreets/goconvey/convey"
  14. )
  15. func TestUserAuthToken(t *testing.T) {
  16. Convey("Test user auth token", t, func() {
  17. ctx := createTestContext(t)
  18. userAuthTokenService := ctx.tokenService
  19. userID := int64(10)
  20. t := time.Date(2018, 12, 13, 13, 45, 0, 0, time.UTC)
  21. getTime = func() time.Time {
  22. return t
  23. }
  24. Convey("When creating token", func() {
  25. token, err := userAuthTokenService.CreateToken(userID, "192.168.10.11:1234", "some user agent")
  26. So(err, ShouldBeNil)
  27. So(token, ShouldNotBeNil)
  28. So(token.AuthTokenSeen, ShouldBeFalse)
  29. Convey("When lookup unhashed token should return user auth token", func() {
  30. LookupToken, err := userAuthTokenService.LookupToken(token.UnhashedToken)
  31. So(err, ShouldBeNil)
  32. So(LookupToken, ShouldNotBeNil)
  33. So(LookupToken.UserId, ShouldEqual, userID)
  34. So(LookupToken.AuthTokenSeen, ShouldBeTrue)
  35. storedAuthToken, err := ctx.getAuthTokenByID(LookupToken.Id)
  36. So(err, ShouldBeNil)
  37. So(storedAuthToken, ShouldNotBeNil)
  38. So(storedAuthToken.AuthTokenSeen, ShouldBeTrue)
  39. })
  40. Convey("When lookup hashed token should return user auth token not found error", func() {
  41. LookupToken, err := userAuthTokenService.LookupToken(token.AuthToken)
  42. So(err, ShouldEqual, ErrAuthTokenNotFound)
  43. So(LookupToken, ShouldBeNil)
  44. })
  45. Convey("signing out should delete token and cookie if present", func() {
  46. httpreq := &http.Request{Header: make(http.Header)}
  47. httpreq.AddCookie(&http.Cookie{Name: userAuthTokenService.Cfg.LoginCookieName, Value: token.UnhashedToken})
  48. ctx := &models.ReqContext{Context: &macaron.Context{
  49. Req: macaron.Request{Request: httpreq},
  50. Resp: macaron.NewResponseWriter("POST", httptest.NewRecorder()),
  51. },
  52. Logger: log.New("fakelogger"),
  53. }
  54. err = userAuthTokenService.SignOutUser(ctx)
  55. So(err, ShouldBeNil)
  56. // makes sure we tell the browser to overwrite the cookie
  57. cookieHeader := fmt.Sprintf("%s=; Path=/; Max-Age=0; HttpOnly", userAuthTokenService.Cfg.LoginCookieName)
  58. So(ctx.Resp.Header().Get("Set-Cookie"), ShouldEqual, cookieHeader)
  59. })
  60. Convey("signing out an none existing session should return an error", func() {
  61. httpreq := &http.Request{Header: make(http.Header)}
  62. httpreq.AddCookie(&http.Cookie{Name: userAuthTokenService.Cfg.LoginCookieName, Value: ""})
  63. ctx := &models.ReqContext{Context: &macaron.Context{
  64. Req: macaron.Request{Request: httpreq},
  65. Resp: macaron.NewResponseWriter("POST", httptest.NewRecorder()),
  66. },
  67. Logger: log.New("fakelogger"),
  68. }
  69. err = userAuthTokenService.SignOutUser(ctx)
  70. So(err, ShouldNotBeNil)
  71. })
  72. })
  73. Convey("expires correctly", func() {
  74. token, err := userAuthTokenService.CreateToken(userID, "192.168.10.11:1234", "some user agent")
  75. So(err, ShouldBeNil)
  76. So(token, ShouldNotBeNil)
  77. _, err = userAuthTokenService.LookupToken(token.UnhashedToken)
  78. So(err, ShouldBeNil)
  79. token, err = ctx.getAuthTokenByID(token.Id)
  80. So(err, ShouldBeNil)
  81. getTime = func() time.Time {
  82. return t.Add(time.Hour)
  83. }
  84. refreshed, err := userAuthTokenService.RefreshToken(token, "192.168.10.11:1234", "some user agent")
  85. So(err, ShouldBeNil)
  86. So(refreshed, ShouldBeTrue)
  87. _, err = userAuthTokenService.LookupToken(token.UnhashedToken)
  88. So(err, ShouldBeNil)
  89. stillGood, err := userAuthTokenService.LookupToken(token.UnhashedToken)
  90. So(err, ShouldBeNil)
  91. So(stillGood, ShouldNotBeNil)
  92. getTime = func() time.Time {
  93. return t.Add(24 * 7 * time.Hour)
  94. }
  95. notGood, err := userAuthTokenService.LookupToken(token.UnhashedToken)
  96. So(err, ShouldEqual, ErrAuthTokenNotFound)
  97. So(notGood, ShouldBeNil)
  98. })
  99. Convey("can properly rotate tokens", func() {
  100. token, err := userAuthTokenService.CreateToken(userID, "192.168.10.11:1234", "some user agent")
  101. So(err, ShouldBeNil)
  102. So(token, ShouldNotBeNil)
  103. prevToken := token.AuthToken
  104. unhashedPrev := token.UnhashedToken
  105. refreshed, err := userAuthTokenService.RefreshToken(token, "192.168.10.12:1234", "a new user agent")
  106. So(err, ShouldBeNil)
  107. So(refreshed, ShouldBeFalse)
  108. updated, err := ctx.markAuthTokenAsSeen(token.Id)
  109. So(err, ShouldBeNil)
  110. So(updated, ShouldBeTrue)
  111. token, err = ctx.getAuthTokenByID(token.Id)
  112. So(err, ShouldBeNil)
  113. getTime = func() time.Time {
  114. return t.Add(time.Hour)
  115. }
  116. refreshed, err = userAuthTokenService.RefreshToken(token, "192.168.10.12:1234", "a new user agent")
  117. So(err, ShouldBeNil)
  118. So(refreshed, ShouldBeTrue)
  119. unhashedToken := token.UnhashedToken
  120. token, err = ctx.getAuthTokenByID(token.Id)
  121. So(err, ShouldBeNil)
  122. token.UnhashedToken = unhashedToken
  123. So(token.RotatedAt, ShouldEqual, getTime().Unix())
  124. So(token.ClientIp, ShouldEqual, "192.168.10.12")
  125. So(token.UserAgent, ShouldEqual, "a new user agent")
  126. So(token.AuthTokenSeen, ShouldBeFalse)
  127. So(token.SeenAt, ShouldEqual, 0)
  128. So(token.PrevAuthToken, ShouldEqual, prevToken)
  129. // ability to auth using an old token
  130. lookedUp, err := userAuthTokenService.LookupToken(token.UnhashedToken)
  131. So(err, ShouldBeNil)
  132. So(lookedUp, ShouldNotBeNil)
  133. So(lookedUp.AuthTokenSeen, ShouldBeTrue)
  134. So(lookedUp.SeenAt, ShouldEqual, getTime().Unix())
  135. lookedUp, err = userAuthTokenService.LookupToken(unhashedPrev)
  136. So(err, ShouldBeNil)
  137. So(lookedUp, ShouldNotBeNil)
  138. So(lookedUp.Id, ShouldEqual, token.Id)
  139. So(lookedUp.AuthTokenSeen, ShouldBeTrue)
  140. getTime = func() time.Time {
  141. return t.Add(time.Hour + (2 * time.Minute))
  142. }
  143. lookedUp, err = userAuthTokenService.LookupToken(unhashedPrev)
  144. So(err, ShouldBeNil)
  145. So(lookedUp, ShouldNotBeNil)
  146. So(lookedUp.AuthTokenSeen, ShouldBeTrue)
  147. lookedUp, err = ctx.getAuthTokenByID(lookedUp.Id)
  148. So(err, ShouldBeNil)
  149. So(lookedUp, ShouldNotBeNil)
  150. So(lookedUp.AuthTokenSeen, ShouldBeFalse)
  151. refreshed, err = userAuthTokenService.RefreshToken(token, "192.168.10.12:1234", "a new user agent")
  152. So(err, ShouldBeNil)
  153. So(refreshed, ShouldBeTrue)
  154. token, err = ctx.getAuthTokenByID(token.Id)
  155. So(err, ShouldBeNil)
  156. So(token, ShouldNotBeNil)
  157. So(token.SeenAt, ShouldEqual, 0)
  158. })
  159. Convey("keeps prev token valid for 1 minute after it is confirmed", func() {
  160. token, err := userAuthTokenService.CreateToken(userID, "192.168.10.11:1234", "some user agent")
  161. So(err, ShouldBeNil)
  162. So(token, ShouldNotBeNil)
  163. lookedUp, err := userAuthTokenService.LookupToken(token.UnhashedToken)
  164. So(err, ShouldBeNil)
  165. So(lookedUp, ShouldNotBeNil)
  166. getTime = func() time.Time {
  167. return t.Add(10 * time.Minute)
  168. }
  169. prevToken := token.UnhashedToken
  170. refreshed, err := userAuthTokenService.RefreshToken(token, "1.1.1.1", "firefox")
  171. So(err, ShouldBeNil)
  172. So(refreshed, ShouldBeTrue)
  173. getTime = func() time.Time {
  174. return t.Add(20 * time.Minute)
  175. }
  176. current, err := userAuthTokenService.LookupToken(token.UnhashedToken)
  177. So(err, ShouldBeNil)
  178. So(current, ShouldNotBeNil)
  179. prev, err := userAuthTokenService.LookupToken(prevToken)
  180. So(err, ShouldBeNil)
  181. So(prev, ShouldNotBeNil)
  182. })
  183. Convey("will not mark token unseen when prev and current are the same", func() {
  184. token, err := userAuthTokenService.CreateToken(userID, "192.168.10.11:1234", "some user agent")
  185. So(err, ShouldBeNil)
  186. So(token, ShouldNotBeNil)
  187. lookedUp, err := userAuthTokenService.LookupToken(token.UnhashedToken)
  188. So(err, ShouldBeNil)
  189. So(lookedUp, ShouldNotBeNil)
  190. lookedUp, err = userAuthTokenService.LookupToken(token.UnhashedToken)
  191. So(err, ShouldBeNil)
  192. So(lookedUp, ShouldNotBeNil)
  193. lookedUp, err = ctx.getAuthTokenByID(lookedUp.Id)
  194. So(err, ShouldBeNil)
  195. So(lookedUp, ShouldNotBeNil)
  196. So(lookedUp.AuthTokenSeen, ShouldBeTrue)
  197. })
  198. Convey("Rotate token", func() {
  199. token, err := userAuthTokenService.CreateToken(userID, "192.168.10.11:1234", "some user agent")
  200. So(err, ShouldBeNil)
  201. So(token, ShouldNotBeNil)
  202. prevToken := token.AuthToken
  203. Convey("Should rotate current token and previous token when auth token seen", func() {
  204. updated, err := ctx.markAuthTokenAsSeen(token.Id)
  205. So(err, ShouldBeNil)
  206. So(updated, ShouldBeTrue)
  207. getTime = func() time.Time {
  208. return t.Add(10 * time.Minute)
  209. }
  210. refreshed, err := userAuthTokenService.RefreshToken(token, "1.1.1.1", "firefox")
  211. So(err, ShouldBeNil)
  212. So(refreshed, ShouldBeTrue)
  213. storedToken, err := ctx.getAuthTokenByID(token.Id)
  214. So(err, ShouldBeNil)
  215. So(storedToken, ShouldNotBeNil)
  216. So(storedToken.AuthTokenSeen, ShouldBeFalse)
  217. So(storedToken.PrevAuthToken, ShouldEqual, prevToken)
  218. So(storedToken.AuthToken, ShouldNotEqual, prevToken)
  219. prevToken = storedToken.AuthToken
  220. updated, err = ctx.markAuthTokenAsSeen(token.Id)
  221. So(err, ShouldBeNil)
  222. So(updated, ShouldBeTrue)
  223. getTime = func() time.Time {
  224. return t.Add(20 * time.Minute)
  225. }
  226. refreshed, err = userAuthTokenService.RefreshToken(token, "1.1.1.1", "firefox")
  227. So(err, ShouldBeNil)
  228. So(refreshed, ShouldBeTrue)
  229. storedToken, err = ctx.getAuthTokenByID(token.Id)
  230. So(err, ShouldBeNil)
  231. So(storedToken, ShouldNotBeNil)
  232. So(storedToken.AuthTokenSeen, ShouldBeFalse)
  233. So(storedToken.PrevAuthToken, ShouldEqual, prevToken)
  234. So(storedToken.AuthToken, ShouldNotEqual, prevToken)
  235. })
  236. Convey("Should rotate current token, but keep previous token when auth token not seen", func() {
  237. token.RotatedAt = getTime().Add(-2 * time.Minute).Unix()
  238. getTime = func() time.Time {
  239. return t.Add(2 * time.Minute)
  240. }
  241. refreshed, err := userAuthTokenService.RefreshToken(token, "1.1.1.1", "firefox")
  242. So(err, ShouldBeNil)
  243. So(refreshed, ShouldBeTrue)
  244. storedToken, err := ctx.getAuthTokenByID(token.Id)
  245. So(err, ShouldBeNil)
  246. So(storedToken, ShouldNotBeNil)
  247. So(storedToken.AuthTokenSeen, ShouldBeFalse)
  248. So(storedToken.PrevAuthToken, ShouldEqual, prevToken)
  249. So(storedToken.AuthToken, ShouldNotEqual, prevToken)
  250. })
  251. })
  252. Reset(func() {
  253. getTime = time.Now
  254. })
  255. })
  256. }
  257. func createTestContext(t *testing.T) *testContext {
  258. t.Helper()
  259. sqlstore := sqlstore.InitTestDB(t)
  260. tokenService := &UserAuthTokenServiceImpl{
  261. SQLStore: sqlstore,
  262. Cfg: &setting.Cfg{
  263. LoginCookieName: "grafana_session",
  264. LoginCookieMaxDays: 7,
  265. LoginDeleteExpiredTokensAfterDays: 30,
  266. LoginCookieRotation: 10,
  267. },
  268. log: log.New("test-logger"),
  269. }
  270. UrgentRotateTime = time.Minute
  271. return &testContext{
  272. sqlstore: sqlstore,
  273. tokenService: tokenService,
  274. }
  275. }
  276. type testContext struct {
  277. sqlstore *sqlstore.SqlStore
  278. tokenService *UserAuthTokenServiceImpl
  279. }
  280. func (c *testContext) getAuthTokenByID(id int64) (*userAuthToken, error) {
  281. sess := c.sqlstore.NewSession()
  282. var t userAuthToken
  283. found, err := sess.ID(id).Get(&t)
  284. if err != nil || !found {
  285. return nil, err
  286. }
  287. return &t, nil
  288. }
  289. func (c *testContext) markAuthTokenAsSeen(id int64) (bool, error) {
  290. sess := c.sqlstore.NewSession()
  291. res, err := sess.Exec("UPDATE user_auth_token SET auth_token_seen = ? WHERE id = ?", c.sqlstore.Dialect.BooleanStr(true), id)
  292. if err != nil {
  293. return false, err
  294. }
  295. rowsAffected, err := res.RowsAffected()
  296. if err != nil {
  297. return false, err
  298. }
  299. return rowsAffected == 1, nil
  300. }