auth_token.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. package auth
  2. import (
  3. "crypto/sha256"
  4. "encoding/hex"
  5. "errors"
  6. "net/http"
  7. "net/url"
  8. "time"
  9. "github.com/grafana/grafana/pkg/bus"
  10. "github.com/grafana/grafana/pkg/infra/serverlock"
  11. "github.com/grafana/grafana/pkg/log"
  12. "github.com/grafana/grafana/pkg/models"
  13. "github.com/grafana/grafana/pkg/registry"
  14. "github.com/grafana/grafana/pkg/services/sqlstore"
  15. "github.com/grafana/grafana/pkg/setting"
  16. "github.com/grafana/grafana/pkg/util"
  17. )
  18. func init() {
  19. registry.RegisterService(&UserAuthTokenServiceImpl{})
  20. }
  21. var (
  22. getTime = time.Now
  23. UrgentRotateTime = 1 * time.Minute
  24. oneYearInSeconds = 31557600 //used as default maxage for session cookies. We validate/rotate them more often.
  25. )
  26. // UserAuthTokenService are used for generating and validating user auth tokens
  27. type UserAuthTokenService interface {
  28. InitContextWithToken(ctx *models.ReqContext, orgID int64) bool
  29. UserAuthenticatedHook(user *models.User, c *models.ReqContext) error
  30. SignOutUser(c *models.ReqContext) error
  31. }
  32. type UserAuthTokenServiceImpl struct {
  33. SQLStore *sqlstore.SqlStore `inject:""`
  34. ServerLockService *serverlock.ServerLockService `inject:""`
  35. Cfg *setting.Cfg `inject:""`
  36. log log.Logger
  37. }
  38. // Init this service
  39. func (s *UserAuthTokenServiceImpl) Init() error {
  40. s.log = log.New("auth")
  41. return nil
  42. }
  43. func (s *UserAuthTokenServiceImpl) InitContextWithToken(ctx *models.ReqContext, orgID int64) bool {
  44. //auth User
  45. unhashedToken := ctx.GetCookie(s.Cfg.LoginCookieName)
  46. if unhashedToken == "" {
  47. return false
  48. }
  49. userToken, err := s.LookupToken(unhashedToken)
  50. if err != nil {
  51. ctx.Logger.Info("failed to look up user based on cookie", "error", err)
  52. return false
  53. }
  54. query := models.GetSignedInUserQuery{UserId: userToken.UserId, OrgId: orgID}
  55. if err := bus.Dispatch(&query); err != nil {
  56. ctx.Logger.Error("Failed to get user with id", "userId", userToken.UserId, "error", err)
  57. return false
  58. }
  59. ctx.SignedInUser = query.Result
  60. ctx.IsSignedIn = true
  61. //rotate session token if needed.
  62. rotated, err := s.RefreshToken(userToken, ctx.RemoteAddr(), ctx.Req.UserAgent())
  63. if err != nil {
  64. ctx.Logger.Error("failed to rotate token", "error", err, "userId", userToken.UserId, "tokenId", userToken.Id)
  65. return true
  66. }
  67. if rotated {
  68. s.writeSessionCookie(ctx, userToken.UnhashedToken, oneYearInSeconds)
  69. }
  70. return true
  71. }
  72. func (s *UserAuthTokenServiceImpl) writeSessionCookie(ctx *models.ReqContext, value string, maxAge int) {
  73. if setting.Env == setting.DEV {
  74. ctx.Logger.Debug("new token", "unhashed token", value)
  75. }
  76. ctx.Resp.Header().Del("Set-Cookie")
  77. cookie := http.Cookie{
  78. Name: s.Cfg.LoginCookieName,
  79. Value: url.QueryEscape(value),
  80. HttpOnly: true,
  81. Path: setting.AppSubUrl + "/",
  82. Secure: s.Cfg.SecurityHTTPSCookies,
  83. MaxAge: maxAge,
  84. SameSite: s.Cfg.LoginCookieSameSite,
  85. }
  86. http.SetCookie(ctx.Resp, &cookie)
  87. }
  88. func (s *UserAuthTokenServiceImpl) UserAuthenticatedHook(user *models.User, c *models.ReqContext) error {
  89. userToken, err := s.CreateToken(user.Id, c.RemoteAddr(), c.Req.UserAgent())
  90. if err != nil {
  91. return err
  92. }
  93. s.writeSessionCookie(c, userToken.UnhashedToken, oneYearInSeconds)
  94. return nil
  95. }
  96. func (s *UserAuthTokenServiceImpl) SignOutUser(c *models.ReqContext) error {
  97. unhashedToken := c.GetCookie(s.Cfg.LoginCookieName)
  98. if unhashedToken == "" {
  99. return errors.New("cannot logout without session token")
  100. }
  101. hashedToken := hashToken(unhashedToken)
  102. sql := `DELETE FROM user_auth_token WHERE auth_token = ?`
  103. _, err := s.SQLStore.NewSession().Exec(sql, hashedToken)
  104. s.writeSessionCookie(c, "", -1)
  105. return err
  106. }
  107. func (s *UserAuthTokenServiceImpl) CreateToken(userId int64, clientIP, userAgent string) (*userAuthToken, error) {
  108. clientIP = util.ParseIPAddress(clientIP)
  109. token, err := util.RandomHex(16)
  110. if err != nil {
  111. return nil, err
  112. }
  113. hashedToken := hashToken(token)
  114. now := getTime().Unix()
  115. userToken := userAuthToken{
  116. UserId: userId,
  117. AuthToken: hashedToken,
  118. PrevAuthToken: hashedToken,
  119. ClientIp: clientIP,
  120. UserAgent: userAgent,
  121. RotatedAt: now,
  122. CreatedAt: now,
  123. UpdatedAt: now,
  124. SeenAt: 0,
  125. AuthTokenSeen: false,
  126. }
  127. _, err = s.SQLStore.NewSession().Insert(&userToken)
  128. if err != nil {
  129. return nil, err
  130. }
  131. userToken.UnhashedToken = token
  132. return &userToken, nil
  133. }
  134. func (s *UserAuthTokenServiceImpl) LookupToken(unhashedToken string) (*userAuthToken, error) {
  135. hashedToken := hashToken(unhashedToken)
  136. if setting.Env == setting.DEV {
  137. s.log.Debug("looking up token", "unhashed", unhashedToken, "hashed", hashedToken)
  138. }
  139. expireBefore := getTime().Add(time.Duration(-86400*s.Cfg.LoginCookieMaxDays) * time.Second).Unix()
  140. var userToken userAuthToken
  141. exists, err := s.SQLStore.NewSession().Where("(auth_token = ? OR prev_auth_token = ?) AND created_at > ?", hashedToken, hashedToken, expireBefore).Get(&userToken)
  142. if err != nil {
  143. return nil, err
  144. }
  145. if !exists {
  146. return nil, ErrAuthTokenNotFound
  147. }
  148. if userToken.AuthToken != hashedToken && userToken.PrevAuthToken == hashedToken && userToken.AuthTokenSeen {
  149. userTokenCopy := userToken
  150. userTokenCopy.AuthTokenSeen = false
  151. expireBefore := getTime().Add(-UrgentRotateTime).Unix()
  152. affectedRows, err := s.SQLStore.NewSession().Where("id = ? AND prev_auth_token = ? AND rotated_at < ?", userTokenCopy.Id, userTokenCopy.PrevAuthToken, expireBefore).AllCols().Update(&userTokenCopy)
  153. if err != nil {
  154. return nil, err
  155. }
  156. if affectedRows == 0 {
  157. s.log.Debug("prev seen token unchanged", "userTokenId", userToken.Id, "userId", userToken.UserId, "authToken", userToken.AuthToken, "clientIP", userToken.ClientIp, "userAgent", userToken.UserAgent)
  158. } else {
  159. s.log.Debug("prev seen token", "userTokenId", userToken.Id, "userId", userToken.UserId, "authToken", userToken.AuthToken, "clientIP", userToken.ClientIp, "userAgent", userToken.UserAgent)
  160. }
  161. }
  162. if !userToken.AuthTokenSeen && userToken.AuthToken == hashedToken {
  163. userTokenCopy := userToken
  164. userTokenCopy.AuthTokenSeen = true
  165. userTokenCopy.SeenAt = getTime().Unix()
  166. affectedRows, err := s.SQLStore.NewSession().Where("id = ? AND auth_token = ?", userTokenCopy.Id, userTokenCopy.AuthToken).AllCols().Update(&userTokenCopy)
  167. if err != nil {
  168. return nil, err
  169. }
  170. if affectedRows == 1 {
  171. userToken = userTokenCopy
  172. }
  173. if affectedRows == 0 {
  174. s.log.Debug("seen wrong token", "userTokenId", userToken.Id, "userId", userToken.UserId, "authToken", userToken.AuthToken, "clientIP", userToken.ClientIp, "userAgent", userToken.UserAgent)
  175. } else {
  176. s.log.Debug("seen token", "userTokenId", userToken.Id, "userId", userToken.UserId, "authToken", userToken.AuthToken, "clientIP", userToken.ClientIp, "userAgent", userToken.UserAgent)
  177. }
  178. }
  179. userToken.UnhashedToken = unhashedToken
  180. return &userToken, nil
  181. }
  182. func (s *UserAuthTokenServiceImpl) RefreshToken(token *userAuthToken, clientIP, userAgent string) (bool, error) {
  183. if token == nil {
  184. return false, nil
  185. }
  186. now := getTime()
  187. needsRotation := false
  188. rotatedAt := time.Unix(token.RotatedAt, 0)
  189. if token.AuthTokenSeen {
  190. needsRotation = rotatedAt.Before(now.Add(-time.Duration(s.Cfg.LoginCookieRotation) * time.Minute))
  191. } else {
  192. needsRotation = rotatedAt.Before(now.Add(-UrgentRotateTime))
  193. }
  194. if !needsRotation {
  195. return false, nil
  196. }
  197. s.log.Debug("refresh token needs rotation?", "auth_token_seen", token.AuthTokenSeen, "rotated_at", rotatedAt, "token.Id", token.Id)
  198. clientIP = util.ParseIPAddress(clientIP)
  199. newToken, _ := util.RandomHex(16)
  200. hashedToken := hashToken(newToken)
  201. // very important that auth_token_seen is set after the prev_auth_token = case when ... for mysql to function correctly
  202. sql := `
  203. UPDATE user_auth_token
  204. SET
  205. seen_at = 0,
  206. user_agent = ?,
  207. client_ip = ?,
  208. prev_auth_token = case when auth_token_seen = ? then auth_token else prev_auth_token end,
  209. auth_token = ?,
  210. auth_token_seen = ?,
  211. rotated_at = ?
  212. WHERE id = ? AND (auth_token_seen = ? OR rotated_at < ?)`
  213. res, err := s.SQLStore.NewSession().Exec(sql, userAgent, clientIP, s.SQLStore.Dialect.BooleanStr(true), hashedToken, s.SQLStore.Dialect.BooleanStr(false), now.Unix(), token.Id, s.SQLStore.Dialect.BooleanStr(true), now.Add(-30*time.Second).Unix())
  214. if err != nil {
  215. return false, err
  216. }
  217. affected, _ := res.RowsAffected()
  218. s.log.Debug("rotated", "affected", affected, "auth_token_id", token.Id, "userId", token.UserId)
  219. if affected > 0 {
  220. token.UnhashedToken = newToken
  221. return true, nil
  222. }
  223. return false, nil
  224. }
  225. func hashToken(token string) string {
  226. hashBytes := sha256.Sum256([]byte(token + setting.SecretKey))
  227. return hex.EncodeToString(hashBytes[:])
  228. }