auth_token_test.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. package auth
  2. import (
  3. "context"
  4. "encoding/json"
  5. "testing"
  6. "time"
  7. "github.com/grafana/grafana/pkg/components/simplejson"
  8. "github.com/grafana/grafana/pkg/setting"
  9. "github.com/grafana/grafana/pkg/infra/log"
  10. "github.com/grafana/grafana/pkg/models"
  11. "github.com/grafana/grafana/pkg/services/sqlstore"
  12. . "github.com/smartystreets/goconvey/convey"
  13. )
  14. func TestUserAuthToken(t *testing.T) {
  15. Convey("Test user auth token", t, func() {
  16. ctx := createTestContext(t)
  17. userAuthTokenService := ctx.tokenService
  18. userID := int64(10)
  19. t := time.Date(2018, 12, 13, 13, 45, 0, 0, time.UTC)
  20. getTime = func() time.Time {
  21. return t
  22. }
  23. Convey("When creating token", func() {
  24. userToken, err := userAuthTokenService.CreateToken(context.Background(), userID, "192.168.10.11:1234", "some user agent")
  25. So(err, ShouldBeNil)
  26. So(userToken, ShouldNotBeNil)
  27. So(userToken.AuthTokenSeen, ShouldBeFalse)
  28. Convey("Can count active tokens", func() {
  29. count, err := userAuthTokenService.ActiveTokenCount(context.Background())
  30. So(err, ShouldBeNil)
  31. So(count, ShouldEqual, 1)
  32. })
  33. Convey("When lookup unhashed token should return user auth token", func() {
  34. userToken, err := userAuthTokenService.LookupToken(context.Background(), userToken.UnhashedToken)
  35. So(err, ShouldBeNil)
  36. So(userToken, ShouldNotBeNil)
  37. So(userToken.UserId, ShouldEqual, userID)
  38. So(userToken.AuthTokenSeen, ShouldBeTrue)
  39. storedAuthToken, err := ctx.getAuthTokenByID(userToken.Id)
  40. So(err, ShouldBeNil)
  41. So(storedAuthToken, ShouldNotBeNil)
  42. So(storedAuthToken.AuthTokenSeen, ShouldBeTrue)
  43. })
  44. Convey("When lookup hashed token should return user auth token not found error", func() {
  45. userToken, err := userAuthTokenService.LookupToken(context.Background(), userToken.AuthToken)
  46. So(err, ShouldEqual, models.ErrUserTokenNotFound)
  47. So(userToken, ShouldBeNil)
  48. })
  49. Convey("revoking existing token should delete token", func() {
  50. err = userAuthTokenService.RevokeToken(context.Background(), userToken)
  51. So(err, ShouldBeNil)
  52. model, err := ctx.getAuthTokenByID(userToken.Id)
  53. So(err, ShouldBeNil)
  54. So(model, ShouldBeNil)
  55. })
  56. Convey("revoking nil token should return error", func() {
  57. err = userAuthTokenService.RevokeToken(context.Background(), nil)
  58. So(err, ShouldEqual, models.ErrUserTokenNotFound)
  59. })
  60. Convey("revoking non-existing token should return error", func() {
  61. userToken.Id = 1000
  62. err = userAuthTokenService.RevokeToken(context.Background(), userToken)
  63. So(err, ShouldEqual, models.ErrUserTokenNotFound)
  64. })
  65. Convey("When creating an additional token", func() {
  66. userToken2, err := userAuthTokenService.CreateToken(context.Background(), userID, "192.168.10.11:1234", "some user agent")
  67. So(err, ShouldBeNil)
  68. So(userToken2, ShouldNotBeNil)
  69. Convey("Can get first user token", func() {
  70. token, err := userAuthTokenService.GetUserToken(context.Background(), userID, userToken.Id)
  71. So(err, ShouldBeNil)
  72. So(token, ShouldNotBeNil)
  73. So(token.Id, ShouldEqual, userToken.Id)
  74. })
  75. Convey("Can get second user token", func() {
  76. token, err := userAuthTokenService.GetUserToken(context.Background(), userID, userToken2.Id)
  77. So(err, ShouldBeNil)
  78. So(token, ShouldNotBeNil)
  79. So(token.Id, ShouldEqual, userToken2.Id)
  80. })
  81. Convey("Can get user tokens", func() {
  82. tokens, err := userAuthTokenService.GetUserTokens(context.Background(), userID)
  83. So(err, ShouldBeNil)
  84. So(tokens, ShouldHaveLength, 2)
  85. So(tokens[0].Id, ShouldEqual, userToken.Id)
  86. So(tokens[1].Id, ShouldEqual, userToken2.Id)
  87. })
  88. Convey("Can revoke all user tokens", func() {
  89. err := userAuthTokenService.RevokeAllUserTokens(context.Background(), userID)
  90. So(err, ShouldBeNil)
  91. model, err := ctx.getAuthTokenByID(userToken.Id)
  92. So(err, ShouldBeNil)
  93. So(model, ShouldBeNil)
  94. model2, err := ctx.getAuthTokenByID(userToken2.Id)
  95. So(err, ShouldBeNil)
  96. So(model2, ShouldBeNil)
  97. })
  98. })
  99. Convey("When revoking users tokens in a batch", func() {
  100. Convey("Can revoke all users tokens", func() {
  101. userIds := []int64{}
  102. for i := 0; i < 3; i++ {
  103. userId := userID + int64(i+1)
  104. userIds = append(userIds, userId)
  105. userAuthTokenService.CreateToken(context.Background(), userId, "192.168.10.11:1234", "some user agent")
  106. }
  107. err := userAuthTokenService.BatchRevokeAllUserTokens(context.Background(), userIds)
  108. So(err, ShouldBeNil)
  109. for _, v := range userIds {
  110. tokens, err := userAuthTokenService.GetUserTokens(context.Background(), v)
  111. So(err, ShouldBeNil)
  112. So(len(tokens), ShouldEqual, 0)
  113. }
  114. })
  115. })
  116. })
  117. Convey("expires correctly", func() {
  118. userToken, err := userAuthTokenService.CreateToken(context.Background(), userID, "192.168.10.11:1234", "some user agent")
  119. So(err, ShouldBeNil)
  120. userToken, err = userAuthTokenService.LookupToken(context.Background(), userToken.UnhashedToken)
  121. So(err, ShouldBeNil)
  122. getTime = func() time.Time {
  123. return t.Add(time.Hour)
  124. }
  125. rotated, err := userAuthTokenService.TryRotateToken(context.Background(), userToken, "192.168.10.11:1234", "some user agent")
  126. So(err, ShouldBeNil)
  127. So(rotated, ShouldBeTrue)
  128. userToken, err = userAuthTokenService.LookupToken(context.Background(), userToken.UnhashedToken)
  129. So(err, ShouldBeNil)
  130. stillGood, err := userAuthTokenService.LookupToken(context.Background(), userToken.UnhashedToken)
  131. So(err, ShouldBeNil)
  132. So(stillGood, ShouldNotBeNil)
  133. model, err := ctx.getAuthTokenByID(userToken.Id)
  134. So(err, ShouldBeNil)
  135. Convey("when rotated_at is 6:59:59 ago should find token", func() {
  136. getTime = func() time.Time {
  137. return time.Unix(model.RotatedAt, 0).Add(24 * 7 * time.Hour).Add(-time.Second)
  138. }
  139. stillGood, err = userAuthTokenService.LookupToken(context.Background(), stillGood.UnhashedToken)
  140. So(err, ShouldBeNil)
  141. So(stillGood, ShouldNotBeNil)
  142. })
  143. Convey("when rotated_at is 7:00:00 ago should not find token", func() {
  144. getTime = func() time.Time {
  145. return time.Unix(model.RotatedAt, 0).Add(24 * 7 * time.Hour)
  146. }
  147. notGood, err := userAuthTokenService.LookupToken(context.Background(), userToken.UnhashedToken)
  148. So(err, ShouldEqual, models.ErrUserTokenNotFound)
  149. So(notGood, ShouldBeNil)
  150. Convey("should not find active token when expired", func() {
  151. count, err := userAuthTokenService.ActiveTokenCount(context.Background())
  152. So(err, ShouldBeNil)
  153. So(count, ShouldEqual, 0)
  154. })
  155. })
  156. Convey("when rotated_at is 5 days ago and created_at is 29 days and 23:59:59 ago should not find token", func() {
  157. updated, err := ctx.updateRotatedAt(model.Id, time.Unix(model.CreatedAt, 0).Add(24*25*time.Hour).Unix())
  158. So(err, ShouldBeNil)
  159. So(updated, ShouldBeTrue)
  160. getTime = func() time.Time {
  161. return time.Unix(model.CreatedAt, 0).Add(24 * 30 * time.Hour).Add(-time.Second)
  162. }
  163. stillGood, err = userAuthTokenService.LookupToken(context.Background(), stillGood.UnhashedToken)
  164. So(err, ShouldBeNil)
  165. So(stillGood, ShouldNotBeNil)
  166. })
  167. Convey("when rotated_at is 5 days ago and created_at is 30 days ago should not find token", func() {
  168. updated, err := ctx.updateRotatedAt(model.Id, time.Unix(model.CreatedAt, 0).Add(24*25*time.Hour).Unix())
  169. So(err, ShouldBeNil)
  170. So(updated, ShouldBeTrue)
  171. getTime = func() time.Time {
  172. return time.Unix(model.CreatedAt, 0).Add(24 * 30 * time.Hour)
  173. }
  174. notGood, err := userAuthTokenService.LookupToken(context.Background(), userToken.UnhashedToken)
  175. So(err, ShouldEqual, models.ErrUserTokenNotFound)
  176. So(notGood, ShouldBeNil)
  177. })
  178. })
  179. Convey("can properly rotate tokens", func() {
  180. userToken, err := userAuthTokenService.CreateToken(context.Background(), userID, "192.168.10.11:1234", "some user agent")
  181. So(err, ShouldBeNil)
  182. prevToken := userToken.AuthToken
  183. unhashedPrev := userToken.UnhashedToken
  184. rotated, err := userAuthTokenService.TryRotateToken(context.Background(), userToken, "192.168.10.12:1234", "a new user agent")
  185. So(err, ShouldBeNil)
  186. So(rotated, ShouldBeFalse)
  187. updated, err := ctx.markAuthTokenAsSeen(userToken.Id)
  188. So(err, ShouldBeNil)
  189. So(updated, ShouldBeTrue)
  190. model, err := ctx.getAuthTokenByID(userToken.Id)
  191. So(err, ShouldBeNil)
  192. var tok models.UserToken
  193. err = model.toUserToken(&tok)
  194. So(err, ShouldBeNil)
  195. getTime = func() time.Time {
  196. return t.Add(time.Hour)
  197. }
  198. rotated, err = userAuthTokenService.TryRotateToken(context.Background(), &tok, "192.168.10.12:1234", "a new user agent")
  199. So(err, ShouldBeNil)
  200. So(rotated, ShouldBeTrue)
  201. unhashedToken := tok.UnhashedToken
  202. model, err = ctx.getAuthTokenByID(tok.Id)
  203. So(err, ShouldBeNil)
  204. model.UnhashedToken = unhashedToken
  205. So(model.RotatedAt, ShouldEqual, getTime().Unix())
  206. So(model.ClientIp, ShouldEqual, "192.168.10.12")
  207. So(model.UserAgent, ShouldEqual, "a new user agent")
  208. So(model.AuthTokenSeen, ShouldBeFalse)
  209. So(model.SeenAt, ShouldEqual, 0)
  210. So(model.PrevAuthToken, ShouldEqual, prevToken)
  211. // ability to auth using an old token
  212. lookedUpUserToken, err := userAuthTokenService.LookupToken(context.Background(), model.UnhashedToken)
  213. So(err, ShouldBeNil)
  214. So(lookedUpUserToken, ShouldNotBeNil)
  215. So(lookedUpUserToken.AuthTokenSeen, ShouldBeTrue)
  216. So(lookedUpUserToken.SeenAt, ShouldEqual, getTime().Unix())
  217. lookedUpUserToken, err = userAuthTokenService.LookupToken(context.Background(), unhashedPrev)
  218. So(err, ShouldBeNil)
  219. So(lookedUpUserToken, ShouldNotBeNil)
  220. So(lookedUpUserToken.Id, ShouldEqual, model.Id)
  221. So(lookedUpUserToken.AuthTokenSeen, ShouldBeTrue)
  222. getTime = func() time.Time {
  223. return t.Add(time.Hour + (2 * time.Minute))
  224. }
  225. lookedUpUserToken, err = userAuthTokenService.LookupToken(context.Background(), unhashedPrev)
  226. So(err, ShouldBeNil)
  227. So(lookedUpUserToken, ShouldNotBeNil)
  228. So(lookedUpUserToken.AuthTokenSeen, ShouldBeTrue)
  229. lookedUpModel, err := ctx.getAuthTokenByID(lookedUpUserToken.Id)
  230. So(err, ShouldBeNil)
  231. So(lookedUpModel, ShouldNotBeNil)
  232. So(lookedUpModel.AuthTokenSeen, ShouldBeFalse)
  233. rotated, err = userAuthTokenService.TryRotateToken(context.Background(), userToken, "192.168.10.12:1234", "a new user agent")
  234. So(err, ShouldBeNil)
  235. So(rotated, ShouldBeTrue)
  236. model, err = ctx.getAuthTokenByID(userToken.Id)
  237. So(err, ShouldBeNil)
  238. So(model, ShouldNotBeNil)
  239. So(model.SeenAt, ShouldEqual, 0)
  240. })
  241. Convey("keeps prev token valid for 1 minute after it is confirmed", func() {
  242. userToken, err := userAuthTokenService.CreateToken(context.Background(), userID, "192.168.10.11:1234", "some user agent")
  243. So(err, ShouldBeNil)
  244. So(userToken, ShouldNotBeNil)
  245. lookedUpUserToken, err := userAuthTokenService.LookupToken(context.Background(), userToken.UnhashedToken)
  246. So(err, ShouldBeNil)
  247. So(lookedUpUserToken, ShouldNotBeNil)
  248. getTime = func() time.Time {
  249. return t.Add(10 * time.Minute)
  250. }
  251. prevToken := userToken.UnhashedToken
  252. rotated, err := userAuthTokenService.TryRotateToken(context.Background(), userToken, "1.1.1.1", "firefox")
  253. So(err, ShouldBeNil)
  254. So(rotated, ShouldBeTrue)
  255. getTime = func() time.Time {
  256. return t.Add(20 * time.Minute)
  257. }
  258. currentUserToken, err := userAuthTokenService.LookupToken(context.Background(), userToken.UnhashedToken)
  259. So(err, ShouldBeNil)
  260. So(currentUserToken, ShouldNotBeNil)
  261. prevUserToken, err := userAuthTokenService.LookupToken(context.Background(), prevToken)
  262. So(err, ShouldBeNil)
  263. So(prevUserToken, ShouldNotBeNil)
  264. })
  265. Convey("will not mark token unseen when prev and current are the same", func() {
  266. userToken, err := userAuthTokenService.CreateToken(context.Background(), userID, "192.168.10.11:1234", "some user agent")
  267. So(err, ShouldBeNil)
  268. So(userToken, ShouldNotBeNil)
  269. lookedUpUserToken, err := userAuthTokenService.LookupToken(context.Background(), userToken.UnhashedToken)
  270. So(err, ShouldBeNil)
  271. So(lookedUpUserToken, ShouldNotBeNil)
  272. lookedUpUserToken, err = userAuthTokenService.LookupToken(context.Background(), userToken.UnhashedToken)
  273. So(err, ShouldBeNil)
  274. So(lookedUpUserToken, ShouldNotBeNil)
  275. lookedUpModel, err := ctx.getAuthTokenByID(lookedUpUserToken.Id)
  276. So(err, ShouldBeNil)
  277. So(lookedUpModel, ShouldNotBeNil)
  278. So(lookedUpModel.AuthTokenSeen, ShouldBeTrue)
  279. })
  280. Convey("Rotate token", func() {
  281. userToken, err := userAuthTokenService.CreateToken(context.Background(), userID, "192.168.10.11:1234", "some user agent")
  282. So(err, ShouldBeNil)
  283. So(userToken, ShouldNotBeNil)
  284. prevToken := userToken.AuthToken
  285. Convey("Should rotate current token and previous token when auth token seen", func() {
  286. updated, err := ctx.markAuthTokenAsSeen(userToken.Id)
  287. So(err, ShouldBeNil)
  288. So(updated, ShouldBeTrue)
  289. getTime = func() time.Time {
  290. return t.Add(10 * time.Minute)
  291. }
  292. rotated, err := userAuthTokenService.TryRotateToken(context.Background(), userToken, "1.1.1.1", "firefox")
  293. So(err, ShouldBeNil)
  294. So(rotated, ShouldBeTrue)
  295. storedToken, err := ctx.getAuthTokenByID(userToken.Id)
  296. So(err, ShouldBeNil)
  297. So(storedToken, ShouldNotBeNil)
  298. So(storedToken.AuthTokenSeen, ShouldBeFalse)
  299. So(storedToken.PrevAuthToken, ShouldEqual, prevToken)
  300. So(storedToken.AuthToken, ShouldNotEqual, prevToken)
  301. prevToken = storedToken.AuthToken
  302. updated, err = ctx.markAuthTokenAsSeen(userToken.Id)
  303. So(err, ShouldBeNil)
  304. So(updated, ShouldBeTrue)
  305. getTime = func() time.Time {
  306. return t.Add(20 * time.Minute)
  307. }
  308. rotated, err = userAuthTokenService.TryRotateToken(context.Background(), userToken, "1.1.1.1", "firefox")
  309. So(err, ShouldBeNil)
  310. So(rotated, ShouldBeTrue)
  311. storedToken, err = ctx.getAuthTokenByID(userToken.Id)
  312. So(err, ShouldBeNil)
  313. So(storedToken, ShouldNotBeNil)
  314. So(storedToken.AuthTokenSeen, ShouldBeFalse)
  315. So(storedToken.PrevAuthToken, ShouldEqual, prevToken)
  316. So(storedToken.AuthToken, ShouldNotEqual, prevToken)
  317. })
  318. Convey("Should rotate current token, but keep previous token when auth token not seen", func() {
  319. userToken.RotatedAt = getTime().Add(-2 * time.Minute).Unix()
  320. getTime = func() time.Time {
  321. return t.Add(2 * time.Minute)
  322. }
  323. rotated, err := userAuthTokenService.TryRotateToken(context.Background(), userToken, "1.1.1.1", "firefox")
  324. So(err, ShouldBeNil)
  325. So(rotated, ShouldBeTrue)
  326. storedToken, err := ctx.getAuthTokenByID(userToken.Id)
  327. So(err, ShouldBeNil)
  328. So(storedToken, ShouldNotBeNil)
  329. So(storedToken.AuthTokenSeen, ShouldBeFalse)
  330. So(storedToken.PrevAuthToken, ShouldEqual, prevToken)
  331. So(storedToken.AuthToken, ShouldNotEqual, prevToken)
  332. })
  333. })
  334. Convey("When populating userAuthToken from UserToken should copy all properties", func() {
  335. ut := models.UserToken{
  336. Id: 1,
  337. UserId: 2,
  338. AuthToken: "a",
  339. PrevAuthToken: "b",
  340. UserAgent: "c",
  341. ClientIp: "d",
  342. AuthTokenSeen: true,
  343. SeenAt: 3,
  344. RotatedAt: 4,
  345. CreatedAt: 5,
  346. UpdatedAt: 6,
  347. UnhashedToken: "e",
  348. }
  349. utBytes, err := json.Marshal(ut)
  350. So(err, ShouldBeNil)
  351. utJSON, err := simplejson.NewJson(utBytes)
  352. So(err, ShouldBeNil)
  353. utMap := utJSON.MustMap()
  354. var uat userAuthToken
  355. uat.fromUserToken(&ut)
  356. uatBytes, err := json.Marshal(uat)
  357. So(err, ShouldBeNil)
  358. uatJSON, err := simplejson.NewJson(uatBytes)
  359. So(err, ShouldBeNil)
  360. uatMap := uatJSON.MustMap()
  361. So(uatMap, ShouldResemble, utMap)
  362. })
  363. Convey("When populating userToken from userAuthToken should copy all properties", func() {
  364. uat := userAuthToken{
  365. Id: 1,
  366. UserId: 2,
  367. AuthToken: "a",
  368. PrevAuthToken: "b",
  369. UserAgent: "c",
  370. ClientIp: "d",
  371. AuthTokenSeen: true,
  372. SeenAt: 3,
  373. RotatedAt: 4,
  374. CreatedAt: 5,
  375. UpdatedAt: 6,
  376. UnhashedToken: "e",
  377. }
  378. uatBytes, err := json.Marshal(uat)
  379. So(err, ShouldBeNil)
  380. uatJSON, err := simplejson.NewJson(uatBytes)
  381. So(err, ShouldBeNil)
  382. uatMap := uatJSON.MustMap()
  383. var ut models.UserToken
  384. err = uat.toUserToken(&ut)
  385. So(err, ShouldBeNil)
  386. utBytes, err := json.Marshal(ut)
  387. So(err, ShouldBeNil)
  388. utJSON, err := simplejson.NewJson(utBytes)
  389. So(err, ShouldBeNil)
  390. utMap := utJSON.MustMap()
  391. So(utMap, ShouldResemble, uatMap)
  392. })
  393. Reset(func() {
  394. getTime = time.Now
  395. })
  396. })
  397. }
  398. func createTestContext(t *testing.T) *testContext {
  399. t.Helper()
  400. sqlstore := sqlstore.InitTestDB(t)
  401. tokenService := &UserAuthTokenService{
  402. SQLStore: sqlstore,
  403. Cfg: &setting.Cfg{
  404. LoginMaxInactiveLifetimeDays: 7,
  405. LoginMaxLifetimeDays: 30,
  406. TokenRotationIntervalMinutes: 10,
  407. },
  408. log: log.New("test-logger"),
  409. }
  410. return &testContext{
  411. sqlstore: sqlstore,
  412. tokenService: tokenService,
  413. }
  414. }
  415. type testContext struct {
  416. sqlstore *sqlstore.SqlStore
  417. tokenService *UserAuthTokenService
  418. }
  419. func (c *testContext) getAuthTokenByID(id int64) (*userAuthToken, error) {
  420. sess := c.sqlstore.NewSession()
  421. var t userAuthToken
  422. found, err := sess.ID(id).Get(&t)
  423. if err != nil || !found {
  424. return nil, err
  425. }
  426. return &t, nil
  427. }
  428. func (c *testContext) markAuthTokenAsSeen(id int64) (bool, error) {
  429. sess := c.sqlstore.NewSession()
  430. res, err := sess.Exec("UPDATE user_auth_token SET auth_token_seen = ? WHERE id = ?", c.sqlstore.Dialect.BooleanStr(true), id)
  431. if err != nil {
  432. return false, err
  433. }
  434. rowsAffected, err := res.RowsAffected()
  435. if err != nil {
  436. return false, err
  437. }
  438. return rowsAffected == 1, nil
  439. }
  440. func (c *testContext) updateRotatedAt(id, rotatedAt int64) (bool, error) {
  441. sess := c.sqlstore.NewSession()
  442. res, err := sess.Exec("UPDATE user_auth_token SET rotated_at = ? WHERE id = ?", rotatedAt, id)
  443. if err != nil {
  444. return false, err
  445. }
  446. rowsAffected, err := res.RowsAffected()
  447. if err != nil {
  448. return false, err
  449. }
  450. return rowsAffected == 1, nil
  451. }