2019-02-06 17:02:57 +01:00
package auth
2019-02-02 12:11:30 +01:00
import (
2019-04-30 14:42:01 +02:00
"context"
2019-02-02 12:11:30 +01:00
"crypto/sha256"
"encoding/hex"
2020-11-25 03:55:22 -03:00
"net"
2019-05-31 13:22:22 +03:00
"strings"
2019-02-02 12:11:30 +01:00
"time"
"github.com/grafana/grafana/pkg/infra/serverlock"
2019-05-13 14:45:54 +08:00
"github.com/grafana/grafana/pkg/infra/log"
2019-02-06 16:45:48 +01:00
"github.com/grafana/grafana/pkg/models"
2019-02-02 12:11:30 +01:00
"github.com/grafana/grafana/pkg/services/sqlstore"
2022-06-28 14:32:25 +02:00
"github.com/grafana/grafana/pkg/services/user"
2019-02-02 12:11:30 +01:00
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
2020-12-11 11:44:44 +01:00
const ServiceName = "UserAuthTokenService"
2019-02-02 12:11:30 +01:00
var getTime = time . Now
const urgentRotateTime = 1 * time . Minute
2021-08-25 15:11:22 +02: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 12:11:30 +01:00
}
2021-08-25 15:11:22 +02:00
type UserAuthTokenService struct {
SQLStore * sqlstore . SQLStore
ServerLockService * serverlock . ServerLockService
Cfg * setting . Cfg
log log . Logger
2019-02-02 12:11:30 +01:00
}
2022-07-29 16:30:46 +02:00
type ActiveAuthTokenService struct {
cfg * setting . Cfg
sqlStore sqlstore . Store
}
func ProvideActiveAuthTokenService ( cfg * setting . Cfg , sqlStore sqlstore . Store ) * ActiveAuthTokenService {
return & ActiveAuthTokenService {
cfg : cfg ,
sqlStore : sqlStore ,
}
}
func ( a * ActiveAuthTokenService ) ActiveTokenCount ( ctx context . Context ) ( int64 , error ) {
2019-04-30 14:42:01 +02:00
var count int64
var err error
2022-07-29 16:30:46 +02:00
err = a . sqlStore . WithDbSession ( ctx , func ( dbSession * sqlstore . DBSession ) error {
2019-04-30 14:42:01 +02:00
var model userAuthToken
2021-03-16 17:44:02 +01:00
count , err = dbSession . Where ( `created_at > ? AND rotated_at > ? AND revoked_at = 0` ,
2022-07-29 16:30:46 +02:00
getTime (). Add ( - a . cfg . LoginMaxLifetime ). Unix (),
getTime (). Add ( - a . cfg . LoginMaxInactiveLifetime ). Unix ()).
2019-04-30 14:42:01 +02:00
Count ( & model )
return err
})
2019-02-11 21:12:01 +01:00
return count , err
}
2022-06-28 14:32:25 +02:00
func ( s * UserAuthTokenService ) CreateToken ( ctx context . Context , user * user . User , clientIP net . IP , userAgent string ) ( * models . UserToken , error ) {
2019-02-02 12:11:30 +01:00
token , err := util . RandomHex ( 16 )
if err != nil {
return nil , err
}
hashedToken := hashToken ( token )
now := getTime (). Unix ()
2020-11-25 03:55:22 -03:00
clientIPStr := clientIP . String ()
if len ( clientIP ) == 0 {
clientIPStr = ""
}
2019-02-02 12:11:30 +01:00
userAuthToken := userAuthToken {
2022-06-28 14:32:25 +02:00
UserId : user . ID ,
2019-02-02 12:11:30 +01:00
AuthToken : hashedToken ,
PrevAuthToken : hashedToken ,
2020-11-25 03:55:22 -03:00
ClientIp : clientIPStr ,
2019-02-02 12:11:30 +01:00
UserAgent : userAgent ,
RotatedAt : now ,
CreatedAt : now ,
UpdatedAt : now ,
SeenAt : 0 ,
2021-03-16 17:44:02 +01:00
RevokedAt : 0 ,
2019-02-02 12:11:30 +01:00
AuthTokenSeen : false ,
}
2019-04-30 14:42:01 +02:00
err = s . SQLStore . WithDbSession ( ctx , func ( dbSession * sqlstore . DBSession ) error {
_ , err = dbSession . Insert ( & userAuthToken )
return err
})
2019-02-02 12:11:30 +01: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 16:45:48 +01:00
var userToken models . UserToken
2019-02-06 16:21:16 +01:00
err = userAuthToken . toUserToken ( & userToken )
return & userToken , err
2019-02-02 12:11:30 +01:00
}
2019-04-30 14:42:01 +02:00
func ( s * UserAuthTokenService ) LookupToken ( ctx context . Context , unhashedToken string ) ( * models . UserToken , error ) {
2019-02-02 12:11:30 +01:00
hashedToken := hashToken ( unhashedToken )
var model userAuthToken
2019-04-30 14:42:01 +02:00
var exists bool
var err error
err = s . SQLStore . WithDbSession ( ctx , func ( dbSession * sqlstore . DBSession ) error {
2021-01-19 17:55:53 +01:00
exists , err = dbSession . Where ( "(auth_token = ? OR prev_auth_token = ?)" ,
2019-04-30 14:42:01 +02:00
hashedToken ,
2021-01-19 17:55:53 +01:00
hashedToken ).
2019-04-30 14:42:01 +02:00
Get ( & model )
return err
})
2019-02-02 12:11:30 +01:00
if err != nil {
return nil , err
}
if ! exists {
2019-02-06 17:02:57 +01:00
return nil , models . ErrUserTokenNotFound
2019-02-02 12:11:30 +01:00
}
2021-03-16 17:44:02 +01:00
if model . RevokedAt > 0 {
return nil , & models . TokenRevokedError {
UserID : model . UserId ,
TokenID : model . Id ,
}
}
2021-01-19 17:55:53 +01:00
if model . CreatedAt <= s . createdAfterParam () || model . RotatedAt <= s . rotatedAfterParam () {
return nil , & models . TokenExpiredError {
UserID : model . UserId ,
TokenID : model . Id ,
}
}
2019-02-02 12:11:30 +01:00
if model . AuthToken != hashedToken && model . PrevAuthToken == hashedToken && model . AuthTokenSeen {
modelCopy := model
modelCopy . AuthTokenSeen = false
expireBefore := getTime (). Add ( - urgentRotateTime ). Unix ()
2019-04-30 14:42:01 +02: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 12:11:30 +01: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 14:42:01 +02: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 12:11:30 +01: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 16:21:16 +01:00
2019-02-06 16:45:48 +01:00
var userToken models . UserToken
2019-02-06 16:21:16 +01:00
err = model . toUserToken ( & userToken )
return & userToken , err
2019-02-02 12:11:30 +01:00
}
2019-10-09 08:58:45 +02:00
func ( s * UserAuthTokenService ) TryRotateToken ( ctx context . Context , token * models . UserToken ,
2020-11-25 03:55:22 -03:00
clientIP net . IP , userAgent string ) ( bool , error ) {
2019-02-02 12:11:30 +01:00
if token == nil {
return false , nil
}
2019-10-22 14:08:18 +02:00
model , err := userAuthTokenFromUserToken ( token )
if err != nil {
return false , err
}
2019-02-02 12:11:30 +01:00
now := getTime ()
2019-04-16 10:27:07 +02:00
var needsRotation bool
2019-02-02 12:11:30 +01:00
rotatedAt := time . Unix ( model . RotatedAt , 0 )
if model . AuthTokenSeen {
2019-02-05 21:12:30 +01:00
needsRotation = rotatedAt . Before ( now . Add ( - time . Duration ( s . Cfg . TokenRotationIntervalMinutes ) * time . Minute ))
2019-02-02 12:11:30 +01: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 03:55:22 -03:00
clientIPStr := clientIP . String ()
if len ( clientIP ) == 0 {
clientIPStr = ""
2019-10-09 08:58:45 +02:00
}
2019-02-02 12:11:30 +01: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 14:42:01 +02:00
var affected int64
err = s . SQLStore . WithTransactionalDbSession ( ctx , func ( dbSession * sqlstore . DBSession ) error {
2020-11-25 03:55:22 -03: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 14:42:01 +02:00
if err != nil {
return err
}
affected , err = res . RowsAffected ()
return err
})
2019-02-02 12:11:30 +01: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 14:08:18 +02:00
if err := model . toUserToken ( token ); err != nil {
return false , err
}
2019-02-02 12:11:30 +01:00
return true , nil
}
return false , nil
}
2021-03-16 17:44:02 +01:00
func ( s * UserAuthTokenService ) RevokeToken ( ctx context . Context , token * models . UserToken , soft bool ) error {
2019-02-02 12:11:30 +01:00
if token == nil {
2019-02-06 17:02:57 +01:00
return models . ErrUserTokenNotFound
2019-02-02 12:11:30 +01:00
}
2019-10-22 14:08:18 +02:00
model , err := userAuthTokenFromUserToken ( token )
if err != nil {
return err
}
2019-02-02 12:11:30 +01:00
2019-04-30 14:42:01 +02:00
var rowsAffected int64
2021-03-16 17:44:02 +01: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 14:42:01 +02:00
2019-02-02 12:11:30 +01: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 17:02:57 +01:00
return models . ErrUserTokenNotFound
2019-02-02 12:11:30 +01:00
}
2021-03-16 17:44:02 +01: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 12:11:30 +01:00
return nil
}
2019-04-30 14:42:01 +02: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 15:15:17 +01:00
2019-04-30 14:42:01 +02:00
affected , err := res . RowsAffected ()
if err != nil {
return err
}
2019-03-08 15:15:17 +01:00
2019-04-30 14:42:01 +02:00
s . log . Debug ( "all user tokens for user revoked" , "userId" , userId , "count" , affected )
2019-03-08 15:15:17 +01:00
2019-04-30 14:42:01 +02:00
return err
})
2019-03-08 15:15:17 +01:00
}
2019-05-31 13:22:22 +03: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 14:42:01 +02:00
func ( s * UserAuthTokenService ) GetUserToken ( ctx context . Context , userId , userTokenId int64 ) ( * models . UserToken , error ) {
2019-03-08 15:15:17 +01:00
var result models . UserToken
2019-04-30 14:42:01 +02: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 14:08:18 +02:00
return token . toUserToken ( & result )
2019-04-30 14:42:01 +02:00
})
2019-03-08 15:15:17 +01:00
2019-04-30 14:42:01 +02:00
return & result , err
2019-03-08 15:15:17 +01:00
}
2019-04-30 14:42:01 +02:00
func ( s * UserAuthTokenService ) GetUserTokens ( ctx context . Context , userId int64 ) ([] * models . UserToken , error ) {
2019-03-08 15:15:17 +01:00
result := [] * models . UserToken {}
2019-04-30 14:42:01 +02:00
err := s . SQLStore . WithDbSession ( ctx , func ( dbSession * sqlstore . DBSession ) error {
var tokens [] * userAuthToken
2021-03-16 17:44:02 +01:00
err := dbSession . Where ( "user_id = ? AND created_at > ? AND rotated_at > ? AND revoked_at = 0" ,
2019-04-30 14:42:01 +02:00
userId ,
s . createdAfterParam (),
s . rotatedAfterParam ()).
Find ( & tokens )
if err != nil {
return err
}
for _ , token := range tokens {
var userToken models . UserToken
2019-10-22 14:08:18 +02:00
if err := token . toUserToken ( & userToken ); err != nil {
return err
}
2019-04-30 14:42:01 +02:00
result = append ( result , & userToken )
}
return nil
})
2019-03-08 15:15:17 +01:00
2019-04-30 14:42:01 +02:00
return result , err
2019-03-08 15:15:17 +01:00
}
2021-03-16 17:44:02 +01: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 21:12:01 +01:00
func ( s * UserAuthTokenService ) createdAfterParam () int64 {
2020-09-14 21:57:38 +08:00
return getTime (). Add ( - s . Cfg . LoginMaxLifetime ). Unix ()
2019-02-11 21:12:01 +01:00
}
func ( s * UserAuthTokenService ) rotatedAfterParam () int64 {
2020-09-14 21:57:38 +08:00
return getTime (). Add ( - s . Cfg . LoginMaxInactiveLifetime ). Unix ()
2019-02-11 21:12:01 +01:00
}
2019-02-02 12:11:30 +01:00
func hashToken ( token string ) string {
hashBytes := sha256 . Sum256 ([] byte ( token + setting . SecretKey ))
return hex . EncodeToString ( hashBytes [:])
}