2019-02-06 10:02:57 -06:00
package auth
2019-02-02 05:11:30 -06:00
import (
2019-04-30 07:42:01 -05:00
"context"
2019-02-02 05:11:30 -06:00
"crypto/sha256"
"encoding/hex"
2020-11-25 00:55:22 -06:00
"net"
2019-05-31 05:22:22 -05:00
"strings"
2019-02-02 05:11:30 -06:00
"time"
"github.com/grafana/grafana/pkg/infra/serverlock"
2019-05-13 01:45:54 -05:00
"github.com/grafana/grafana/pkg/infra/log"
2019-02-06 09:45:48 -06:00
"github.com/grafana/grafana/pkg/models"
2019-02-02 05:11:30 -06:00
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
2020-12-11 04:44:44 -06:00
const ServiceName = "UserAuthTokenService"
2019-02-02 05:11:30 -06:00
var getTime = time . Now
const urgentRotateTime = 1 * time . Minute
2021-08-25 08:11:22 -05:00
func ProvideUserAuthTokenService ( sqlStore * sqlstore . SQLStore , serverLockService * serverlock . ServerLockService ,
cfg * setting . Cfg ) * UserAuthTokenService {
s := & UserAuthTokenService {
SQLStore : sqlStore ,
ServerLockService : serverLockService ,
Cfg : cfg ,
log : log . New ( "auth" ) ,
}
return s
2019-02-02 05:11:30 -06:00
}
2021-08-25 08:11:22 -05:00
type UserAuthTokenService struct {
SQLStore * sqlstore . SQLStore
ServerLockService * serverlock . ServerLockService
Cfg * setting . Cfg
log log . Logger
2019-02-02 05:11:30 -06:00
}
2019-04-30 07:42:01 -05:00
func ( s * UserAuthTokenService ) ActiveTokenCount ( ctx context . Context ) ( int64 , error ) {
var count int64
var err error
err = s . SQLStore . WithDbSession ( ctx , func ( dbSession * sqlstore . DBSession ) error {
var model userAuthToken
2021-03-16 11:44:02 -05:00
count , err = dbSession . Where ( ` created_at > ? AND rotated_at > ? AND revoked_at = 0 ` ,
2019-04-30 07:42:01 -05:00
s . createdAfterParam ( ) ,
s . rotatedAfterParam ( ) ) .
Count ( & model )
return err
} )
2019-02-11 14:12:01 -06:00
return count , err
}
2021-01-19 10:55:53 -06:00
func ( s * UserAuthTokenService ) CreateToken ( ctx context . Context , user * models . User , clientIP net . IP , userAgent string ) ( * models . UserToken , error ) {
2019-02-02 05:11:30 -06:00
token , err := util . RandomHex ( 16 )
if err != nil {
return nil , err
}
hashedToken := hashToken ( token )
now := getTime ( ) . Unix ( )
2020-11-25 00:55:22 -06:00
clientIPStr := clientIP . String ( )
if len ( clientIP ) == 0 {
clientIPStr = ""
}
2019-02-02 05:11:30 -06:00
userAuthToken := userAuthToken {
2021-01-19 10:55:53 -06:00
UserId : user . Id ,
2019-02-02 05:11:30 -06:00
AuthToken : hashedToken ,
PrevAuthToken : hashedToken ,
2020-11-25 00:55:22 -06:00
ClientIp : clientIPStr ,
2019-02-02 05:11:30 -06:00
UserAgent : userAgent ,
RotatedAt : now ,
CreatedAt : now ,
UpdatedAt : now ,
SeenAt : 0 ,
2021-03-16 11:44:02 -05:00
RevokedAt : 0 ,
2019-02-02 05:11:30 -06:00
AuthTokenSeen : false ,
}
2019-04-30 07:42:01 -05:00
err = s . SQLStore . WithDbSession ( ctx , func ( dbSession * sqlstore . DBSession ) error {
_ , err = dbSession . Insert ( & userAuthToken )
return err
} )
2019-02-02 05:11:30 -06:00
if err != nil {
return nil , err
}
userAuthToken . UnhashedToken = token
s . log . Debug ( "user auth token created" , "tokenId" , userAuthToken . Id , "userId" , userAuthToken . UserId , "clientIP" , userAuthToken . ClientIp , "userAgent" , userAuthToken . UserAgent , "authToken" , userAuthToken . AuthToken )
2019-02-06 09:45:48 -06:00
var userToken models . UserToken
2019-02-06 09:21:16 -06:00
err = userAuthToken . toUserToken ( & userToken )
return & userToken , err
2019-02-02 05:11:30 -06:00
}
2019-04-30 07:42:01 -05:00
func ( s * UserAuthTokenService ) LookupToken ( ctx context . Context , unhashedToken string ) ( * models . UserToken , error ) {
2019-02-02 05:11:30 -06:00
hashedToken := hashToken ( unhashedToken )
var model userAuthToken
2019-04-30 07:42:01 -05:00
var exists bool
var err error
err = s . SQLStore . WithDbSession ( ctx , func ( dbSession * sqlstore . DBSession ) error {
2021-01-19 10:55:53 -06:00
exists , err = dbSession . Where ( "(auth_token = ? OR prev_auth_token = ?)" ,
2019-04-30 07:42:01 -05:00
hashedToken ,
2021-01-19 10:55:53 -06:00
hashedToken ) .
2019-04-30 07:42:01 -05:00
Get ( & model )
return err
} )
2019-02-02 05:11:30 -06:00
if err != nil {
return nil , err
}
if ! exists {
2019-02-06 10:02:57 -06:00
return nil , models . ErrUserTokenNotFound
2019-02-02 05:11:30 -06:00
}
2021-03-16 11:44:02 -05:00
if model . RevokedAt > 0 {
return nil , & models . TokenRevokedError {
UserID : model . UserId ,
TokenID : model . Id ,
}
}
2021-01-19 10:55:53 -06:00
if model . CreatedAt <= s . createdAfterParam ( ) || model . RotatedAt <= s . rotatedAfterParam ( ) {
return nil , & models . TokenExpiredError {
UserID : model . UserId ,
TokenID : model . Id ,
}
}
2019-02-02 05:11:30 -06:00
if model . AuthToken != hashedToken && model . PrevAuthToken == hashedToken && model . AuthTokenSeen {
modelCopy := model
modelCopy . AuthTokenSeen = false
expireBefore := getTime ( ) . Add ( - urgentRotateTime ) . Unix ( )
2019-04-30 07:42:01 -05:00
var affectedRows int64
err = s . SQLStore . WithTransactionalDbSession ( ctx , func ( dbSession * sqlstore . DBSession ) error {
affectedRows , err = dbSession . Where ( "id = ? AND prev_auth_token = ? AND rotated_at < ?" ,
modelCopy . Id ,
modelCopy . PrevAuthToken ,
expireBefore ) .
AllCols ( ) . Update ( & modelCopy )
return err
} )
2019-02-02 05:11:30 -06:00
if err != nil {
return nil , err
}
if affectedRows == 0 {
s . log . Debug ( "prev seen token unchanged" , "tokenId" , model . Id , "userId" , model . UserId , "clientIP" , model . ClientIp , "userAgent" , model . UserAgent , "authToken" , model . AuthToken )
} else {
s . log . Debug ( "prev seen token" , "tokenId" , model . Id , "userId" , model . UserId , "clientIP" , model . ClientIp , "userAgent" , model . UserAgent , "authToken" , model . AuthToken )
}
}
if ! model . AuthTokenSeen && model . AuthToken == hashedToken {
modelCopy := model
modelCopy . AuthTokenSeen = true
modelCopy . SeenAt = getTime ( ) . Unix ( )
2019-04-30 07:42:01 -05:00
var affectedRows int64
err = s . SQLStore . WithTransactionalDbSession ( ctx , func ( dbSession * sqlstore . DBSession ) error {
affectedRows , err = dbSession . Where ( "id = ? AND auth_token = ?" ,
modelCopy . Id ,
modelCopy . AuthToken ) .
AllCols ( ) . Update ( & modelCopy )
return err
} )
2019-02-02 05:11:30 -06:00
if err != nil {
return nil , err
}
if affectedRows == 1 {
model = modelCopy
}
if affectedRows == 0 {
s . log . Debug ( "seen wrong token" , "tokenId" , model . Id , "userId" , model . UserId , "clientIP" , model . ClientIp , "userAgent" , model . UserAgent , "authToken" , model . AuthToken )
} else {
s . log . Debug ( "seen token" , "tokenId" , model . Id , "userId" , model . UserId , "clientIP" , model . ClientIp , "userAgent" , model . UserAgent , "authToken" , model . AuthToken )
}
}
model . UnhashedToken = unhashedToken
2019-02-06 09:21:16 -06:00
2019-02-06 09:45:48 -06:00
var userToken models . UserToken
2019-02-06 09:21:16 -06:00
err = model . toUserToken ( & userToken )
return & userToken , err
2019-02-02 05:11:30 -06:00
}
2019-10-09 01:58:45 -05:00
func ( s * UserAuthTokenService ) TryRotateToken ( ctx context . Context , token * models . UserToken ,
2020-11-25 00:55:22 -06:00
clientIP net . IP , userAgent string ) ( bool , error ) {
2019-02-02 05:11:30 -06:00
if token == nil {
return false , nil
}
2019-10-22 07:08:18 -05:00
model , err := userAuthTokenFromUserToken ( token )
if err != nil {
return false , err
}
2019-02-02 05:11:30 -06:00
now := getTime ( )
2019-04-16 03:27:07 -05:00
var needsRotation bool
2019-02-02 05:11:30 -06:00
rotatedAt := time . Unix ( model . RotatedAt , 0 )
if model . AuthTokenSeen {
2019-02-05 14:12:30 -06:00
needsRotation = rotatedAt . Before ( now . Add ( - time . Duration ( s . Cfg . TokenRotationIntervalMinutes ) * time . Minute ) )
2019-02-02 05:11:30 -06:00
} else {
needsRotation = rotatedAt . Before ( now . Add ( - urgentRotateTime ) )
}
if ! needsRotation {
return false , nil
}
s . log . Debug ( "token needs rotation" , "tokenId" , model . Id , "authTokenSeen" , model . AuthTokenSeen , "rotatedAt" , rotatedAt )
2020-11-25 00:55:22 -06:00
clientIPStr := clientIP . String ( )
if len ( clientIP ) == 0 {
clientIPStr = ""
2019-10-09 01:58:45 -05:00
}
2019-02-02 05:11:30 -06:00
newToken , err := util . RandomHex ( 16 )
if err != nil {
return false , err
}
hashedToken := hashToken ( newToken )
// very important that auth_token_seen is set after the prev_auth_token = case when ... for mysql to function correctly
sql := `
UPDATE user_auth_token
SET
seen_at = 0 ,
user_agent = ? ,
client_ip = ? ,
prev_auth_token = case when auth_token_seen = ? then auth_token else prev_auth_token end ,
auth_token = ? ,
auth_token_seen = ? ,
rotated_at = ?
WHERE id = ? AND ( auth_token_seen = ? OR rotated_at < ? ) `
2019-04-30 07:42:01 -05:00
var affected int64
err = s . SQLStore . WithTransactionalDbSession ( ctx , func ( dbSession * sqlstore . DBSession ) error {
2020-11-25 00:55:22 -06:00
res , err := dbSession . Exec ( sql , userAgent , clientIPStr , s . SQLStore . Dialect . BooleanStr ( true ) , hashedToken ,
s . SQLStore . Dialect . BooleanStr ( false ) , now . Unix ( ) , model . Id , s . SQLStore . Dialect . BooleanStr ( true ) ,
now . Add ( - 30 * time . Second ) . Unix ( ) )
2019-04-30 07:42:01 -05:00
if err != nil {
return err
}
affected , err = res . RowsAffected ( )
return err
} )
2019-02-02 05:11:30 -06:00
if err != nil {
return false , err
}
s . log . Debug ( "auth token rotated" , "affected" , affected , "auth_token_id" , model . Id , "userId" , model . UserId )
if affected > 0 {
model . UnhashedToken = newToken
2019-10-22 07:08:18 -05:00
if err := model . toUserToken ( token ) ; err != nil {
return false , err
}
2019-02-02 05:11:30 -06:00
return true , nil
}
return false , nil
}
2021-03-16 11:44:02 -05:00
func ( s * UserAuthTokenService ) RevokeToken ( ctx context . Context , token * models . UserToken , soft bool ) error {
2019-02-02 05:11:30 -06:00
if token == nil {
2019-02-06 10:02:57 -06:00
return models . ErrUserTokenNotFound
2019-02-02 05:11:30 -06:00
}
2019-10-22 07:08:18 -05:00
model , err := userAuthTokenFromUserToken ( token )
if err != nil {
return err
}
2019-02-02 05:11:30 -06:00
2019-04-30 07:42:01 -05:00
var rowsAffected int64
2021-03-16 11:44:02 -05:00
if soft {
model . RevokedAt = getTime ( ) . Unix ( )
err = s . SQLStore . WithDbSession ( ctx , func ( dbSession * sqlstore . DBSession ) error {
rowsAffected , err = dbSession . ID ( model . Id ) . Update ( model )
return err
} )
} else {
err = s . SQLStore . WithDbSession ( ctx , func ( dbSession * sqlstore . DBSession ) error {
rowsAffected , err = dbSession . Delete ( model )
return err
} )
}
2019-04-30 07:42:01 -05:00
2019-02-02 05:11:30 -06:00
if err != nil {
return err
}
if rowsAffected == 0 {
s . log . Debug ( "user auth token not found/revoked" , "tokenId" , model . Id , "userId" , model . UserId , "clientIP" , model . ClientIp , "userAgent" , model . UserAgent )
2019-02-06 10:02:57 -06:00
return models . ErrUserTokenNotFound
2019-02-02 05:11:30 -06:00
}
2021-03-16 11:44:02 -05:00
s . log . Debug ( "user auth token revoked" , "tokenId" , model . Id , "userId" , model . UserId , "clientIP" , model . ClientIp , "userAgent" , model . UserAgent , "soft" , soft )
2019-02-02 05:11:30 -06:00
return nil
}
2019-04-30 07:42:01 -05:00
func ( s * UserAuthTokenService ) RevokeAllUserTokens ( ctx context . Context , userId int64 ) error {
return s . SQLStore . WithDbSession ( ctx , func ( dbSession * sqlstore . DBSession ) error {
sql := ` DELETE from user_auth_token WHERE user_id = ? `
res , err := dbSession . Exec ( sql , userId )
if err != nil {
return err
}
2019-03-08 08:15:17 -06:00
2019-04-30 07:42:01 -05:00
affected , err := res . RowsAffected ( )
if err != nil {
return err
}
2019-03-08 08:15:17 -06:00
2019-04-30 07:42:01 -05:00
s . log . Debug ( "all user tokens for user revoked" , "userId" , userId , "count" , affected )
2019-03-08 08:15:17 -06:00
2019-04-30 07:42:01 -05:00
return err
} )
2019-03-08 08:15:17 -06:00
}
2019-05-31 05:22:22 -05:00
func ( s * UserAuthTokenService ) BatchRevokeAllUserTokens ( ctx context . Context , userIds [ ] int64 ) error {
return s . SQLStore . WithTransactionalDbSession ( ctx , func ( dbSession * sqlstore . DBSession ) error {
if len ( userIds ) == 0 {
return nil
}
user_id_params := strings . Repeat ( ",?" , len ( userIds ) - 1 )
sql := "DELETE from user_auth_token WHERE user_id IN (?" + user_id_params + ")"
params := [ ] interface { } { sql }
for _ , v := range userIds {
params = append ( params , v )
}
res , err := dbSession . Exec ( params ... )
if err != nil {
return err
}
affected , err := res . RowsAffected ( )
if err != nil {
return err
}
s . log . Debug ( "all user tokens for given users revoked" , "usersCount" , len ( userIds ) , "count" , affected )
return err
} )
}
2019-04-30 07:42:01 -05:00
func ( s * UserAuthTokenService ) GetUserToken ( ctx context . Context , userId , userTokenId int64 ) ( * models . UserToken , error ) {
2019-03-08 08:15:17 -06:00
var result models . UserToken
2019-04-30 07:42:01 -05:00
err := s . SQLStore . WithDbSession ( ctx , func ( dbSession * sqlstore . DBSession ) error {
var token userAuthToken
exists , err := dbSession . Where ( "id = ? AND user_id = ?" , userTokenId , userId ) . Get ( & token )
if err != nil {
return err
}
if ! exists {
return models . ErrUserTokenNotFound
}
2019-10-22 07:08:18 -05:00
return token . toUserToken ( & result )
2019-04-30 07:42:01 -05:00
} )
2019-03-08 08:15:17 -06:00
2019-04-30 07:42:01 -05:00
return & result , err
2019-03-08 08:15:17 -06:00
}
2019-04-30 07:42:01 -05:00
func ( s * UserAuthTokenService ) GetUserTokens ( ctx context . Context , userId int64 ) ( [ ] * models . UserToken , error ) {
2019-03-08 08:15:17 -06:00
result := [ ] * models . UserToken { }
2019-04-30 07:42:01 -05:00
err := s . SQLStore . WithDbSession ( ctx , func ( dbSession * sqlstore . DBSession ) error {
var tokens [ ] * userAuthToken
2021-03-16 11:44:02 -05:00
err := dbSession . Where ( "user_id = ? AND created_at > ? AND rotated_at > ? AND revoked_at = 0" ,
2019-04-30 07:42:01 -05:00
userId ,
s . createdAfterParam ( ) ,
s . rotatedAfterParam ( ) ) .
Find ( & tokens )
if err != nil {
return err
}
for _ , token := range tokens {
var userToken models . UserToken
2019-10-22 07:08:18 -05:00
if err := token . toUserToken ( & userToken ) ; err != nil {
return err
}
2019-04-30 07:42:01 -05:00
result = append ( result , & userToken )
}
return nil
} )
2019-03-08 08:15:17 -06:00
2019-04-30 07:42:01 -05:00
return result , err
2019-03-08 08:15:17 -06:00
}
2021-03-16 11:44:02 -05:00
func ( s * UserAuthTokenService ) GetUserRevokedTokens ( ctx context . Context , userId int64 ) ( [ ] * models . UserToken , error ) {
result := [ ] * models . UserToken { }
err := s . SQLStore . WithDbSession ( ctx , func ( dbSession * sqlstore . DBSession ) error {
var tokens [ ] * userAuthToken
err := dbSession . Where ( "user_id = ? AND revoked_at > 0" , userId ) . Find ( & tokens )
if err != nil {
return err
}
for _ , token := range tokens {
var userToken models . UserToken
if err := token . toUserToken ( & userToken ) ; err != nil {
return err
}
result = append ( result , & userToken )
}
return nil
} )
return result , err
}
2019-02-11 14:12:01 -06:00
func ( s * UserAuthTokenService ) createdAfterParam ( ) int64 {
2020-09-14 08:57:38 -05:00
return getTime ( ) . Add ( - s . Cfg . LoginMaxLifetime ) . Unix ( )
2019-02-11 14:12:01 -06:00
}
func ( s * UserAuthTokenService ) rotatedAfterParam ( ) int64 {
2020-09-14 08:57:38 -05:00
return getTime ( ) . Add ( - s . Cfg . LoginMaxInactiveLifetime ) . Unix ( )
2019-02-11 14:12:01 -06:00
}
2019-02-02 05:11:30 -06:00
func hashToken ( token string ) string {
hashBytes := sha256 . Sum256 ( [ ] byte ( token + setting . SecretKey ) )
return hex . EncodeToString ( hashBytes [ : ] )
}