2014-11-12 14:48:50 +03:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2019-05-04 18:45:34 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2014-11-12 14:48:50 +03:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
2019-05-04 18:45:34 +03:00
"crypto/subtle"
2021-09-19 14:49:59 +03:00
"fmt"
2014-11-12 14:48:50 +03:00
"time"
2022-01-02 16:12:35 +03:00
"code.gitea.io/gitea/models/auth"
2021-09-19 14:49:59 +03:00
"code.gitea.io/gitea/models/db"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/base"
2021-09-19 14:49:59 +03:00
"code.gitea.io/gitea/modules/setting"
2019-08-15 17:46:21 +03:00
"code.gitea.io/gitea/modules/timeutil"
2021-05-10 09:45:17 +03:00
"code.gitea.io/gitea/modules/util"
2019-08-15 17:46:21 +03:00
2020-06-18 12:18:44 +03:00
gouuid "github.com/google/uuid"
2021-08-17 21:30:42 +03:00
lru "github.com/hashicorp/golang-lru"
2014-11-12 14:48:50 +03:00
)
2021-08-17 21:30:42 +03:00
var successfulAccessTokenCache * lru . Cache
2014-11-12 14:48:50 +03:00
// AccessToken represents a personal access token.
type AccessToken struct {
2019-05-04 18:45:34 +03:00
ID int64 ` xorm:"pk autoincr" `
UID int64 ` xorm:"INDEX" `
Name string
Token string ` xorm:"-" `
TokenHash string ` xorm:"UNIQUE" ` // sha256 of token
TokenSalt string
TokenLastEight string ` xorm:"token_last_eight" `
2016-03-10 03:53:30 +03:00
2019-08-15 17:46:21 +03:00
CreatedUnix timeutil . TimeStamp ` xorm:"INDEX created" `
UpdatedUnix timeutil . TimeStamp ` xorm:"INDEX updated" `
HasRecentActivity bool ` xorm:"-" `
HasUsed bool ` xorm:"-" `
2014-11-12 14:48:50 +03:00
}
2017-10-01 19:52:35 +03:00
// AfterLoad is invoked from XORM after setting the values of all fields of this object.
func ( t * AccessToken ) AfterLoad ( ) {
2017-12-11 07:37:04 +03:00
t . HasUsed = t . UpdatedUnix > t . CreatedUnix
2019-08-15 17:46:21 +03:00
t . HasRecentActivity = t . UpdatedUnix . AddDuration ( 7 * 24 * time . Hour ) > timeutil . TimeStampNow ( )
2016-03-10 03:53:30 +03:00
}
2021-09-19 14:49:59 +03:00
func init ( ) {
db . RegisterModel ( new ( AccessToken ) , func ( ) error {
if setting . SuccessfulTokensCacheSize > 0 {
var err error
successfulAccessTokenCache , err = lru . New ( setting . SuccessfulTokensCacheSize )
if err != nil {
return fmt . Errorf ( "unable to allocate AccessToken cache: %v" , err )
}
} else {
successfulAccessTokenCache = nil
}
return nil
} )
}
2014-11-12 14:48:50 +03:00
// NewAccessToken creates new access token.
func NewAccessToken ( t * AccessToken ) error {
2022-01-26 07:10:10 +03:00
salt , err := util . CryptoRandomString ( 10 )
2019-05-04 18:45:34 +03:00
if err != nil {
return err
}
t . TokenSalt = salt
2020-06-18 12:18:44 +03:00
t . Token = base . EncodeSha1 ( gouuid . New ( ) . String ( ) )
2022-01-02 16:12:35 +03:00
t . TokenHash = auth . HashToken ( t . Token , t . TokenSalt )
2019-05-04 18:45:34 +03:00
t . TokenLastEight = t . Token [ len ( t . Token ) - 8 : ]
2021-09-23 18:45:36 +03:00
_ , err = db . GetEngine ( db . DefaultContext ) . Insert ( t )
2014-11-12 14:48:50 +03:00
return err
}
2021-08-17 21:30:42 +03:00
func getAccessTokenIDFromCache ( token string ) int64 {
if successfulAccessTokenCache == nil {
return 0
}
tInterface , ok := successfulAccessTokenCache . Get ( token )
if ! ok {
return 0
}
t , ok := tInterface . ( int64 )
if ! ok {
return 0
}
return t
}
2019-05-04 18:45:34 +03:00
// GetAccessTokenBySHA returns access token by given token value
func GetAccessTokenBySHA ( token string ) ( * AccessToken , error ) {
if token == "" {
2016-06-27 12:02:39 +03:00
return nil , ErrAccessTokenEmpty { }
}
2021-06-16 01:29:25 +03:00
// A token is defined as being SHA1 sum these are 40 hexadecimal bytes long
if len ( token ) != 40 {
2019-05-04 18:45:34 +03:00
return nil , ErrAccessTokenNotExist { token }
}
2021-06-16 01:29:25 +03:00
for _ , x := range [ ] byte ( token ) {
if x < '0' || ( x > '9' && x < 'a' ) || x > 'f' {
return nil , ErrAccessTokenNotExist { token }
}
}
2021-08-17 21:30:42 +03:00
2019-05-04 18:45:34 +03:00
lastEight := token [ len ( token ) - 8 : ]
2021-08-17 21:30:42 +03:00
if id := getAccessTokenIDFromCache ( token ) ; id > 0 {
token := & AccessToken {
TokenLastEight : lastEight ,
}
// Re-get the token from the db in case it has been deleted in the intervening period
2021-09-23 18:45:36 +03:00
has , err := db . GetEngine ( db . DefaultContext ) . ID ( id ) . Get ( token )
2021-08-17 21:30:42 +03:00
if err != nil {
return nil , err
}
if has {
return token , nil
}
successfulAccessTokenCache . Remove ( token )
}
var tokens [ ] AccessToken
2021-09-23 18:45:36 +03:00
err := db . GetEngine ( db . DefaultContext ) . Table ( & AccessToken { } ) . Where ( "token_last_eight = ?" , lastEight ) . Find ( & tokens )
2014-11-12 14:48:50 +03:00
if err != nil {
return nil , err
2019-05-04 18:45:34 +03:00
} else if len ( tokens ) == 0 {
return nil , ErrAccessTokenNotExist { token }
}
2021-08-17 21:30:42 +03:00
2019-05-04 18:45:34 +03:00
for _ , t := range tokens {
2022-01-02 16:12:35 +03:00
tempHash := auth . HashToken ( token , t . TokenSalt )
2019-05-04 18:45:34 +03:00
if subtle . ConstantTimeCompare ( [ ] byte ( t . TokenHash ) , [ ] byte ( tempHash ) ) == 1 {
2021-08-17 21:30:42 +03:00
if successfulAccessTokenCache != nil {
successfulAccessTokenCache . Add ( token , t . ID )
}
2019-05-04 18:45:34 +03:00
return & t , nil
}
2014-11-12 14:48:50 +03:00
}
2019-05-04 18:45:34 +03:00
return nil , ErrAccessTokenNotExist { token }
2014-11-12 14:48:50 +03:00
}
2020-04-13 22:02:48 +03:00
// AccessTokenByNameExists checks if a token name has been used already by a user.
func AccessTokenByNameExists ( token * AccessToken ) ( bool , error ) {
2021-09-23 18:45:36 +03:00
return db . GetEngine ( db . DefaultContext ) . Table ( "access_token" ) . Where ( "name = ?" , token . Name ) . And ( "uid = ?" , token . UID ) . Exist ( )
2020-04-13 22:02:48 +03:00
}
2020-08-28 11:09:33 +03:00
// ListAccessTokensOptions contain filter options
type ListAccessTokensOptions struct {
2021-09-24 14:32:56 +03:00
db . ListOptions
2020-08-28 11:09:33 +03:00
Name string
UserID int64
}
2014-11-12 14:48:50 +03:00
// ListAccessTokens returns a list of access tokens belongs to given user.
2020-08-28 11:09:33 +03:00
func ListAccessTokens ( opts ListAccessTokensOptions ) ( [ ] * AccessToken , error ) {
2021-09-23 18:45:36 +03:00
sess := db . GetEngine ( db . DefaultContext ) . Where ( "uid=?" , opts . UserID )
2020-08-28 11:09:33 +03:00
if len ( opts . Name ) != 0 {
sess = sess . Where ( "name=?" , opts . Name )
}
2021-10-06 23:36:24 +03:00
sess = sess . Desc ( "created_unix" )
2020-01-24 22:00:29 +03:00
2020-08-28 11:09:33 +03:00
if opts . Page != 0 {
2021-09-24 14:32:56 +03:00
sess = db . SetSessionPagination ( sess , & opts )
2020-01-24 22:00:29 +03:00
2020-08-28 11:09:33 +03:00
tokens := make ( [ ] * AccessToken , 0 , opts . PageSize )
2020-01-24 22:00:29 +03:00
return tokens , sess . Find ( & tokens )
}
tokens := make ( [ ] * AccessToken , 0 , 5 )
return tokens , sess . Find ( & tokens )
2014-11-12 14:48:50 +03:00
}
2016-01-06 22:41:42 +03:00
// UpdateAccessToken updates information of access token.
func UpdateAccessToken ( t * AccessToken ) error {
2021-09-23 18:45:36 +03:00
_ , err := db . GetEngine ( db . DefaultContext ) . ID ( t . ID ) . AllCols ( ) . Update ( t )
2015-08-19 01:22:33 +03:00
return err
}
2021-08-12 15:43:08 +03:00
// CountAccessTokens count access tokens belongs to given user by options
func CountAccessTokens ( opts ListAccessTokensOptions ) ( int64 , error ) {
2021-09-23 18:45:36 +03:00
sess := db . GetEngine ( db . DefaultContext ) . Where ( "uid=?" , opts . UserID )
2021-08-12 15:43:08 +03:00
if len ( opts . Name ) != 0 {
sess = sess . Where ( "name=?" , opts . Name )
}
return sess . Count ( & AccessToken { } )
}
2015-08-18 22:36:16 +03:00
// DeleteAccessTokenByID deletes access token by given ID.
2016-12-15 11:49:06 +03:00
func DeleteAccessTokenByID ( id , userID int64 ) error {
2021-09-23 18:45:36 +03:00
cnt , err := db . GetEngine ( db . DefaultContext ) . ID ( id ) . Delete ( & AccessToken {
2016-12-15 11:49:06 +03:00
UID : userID ,
} )
if err != nil {
return err
} else if cnt != 1 {
return ErrAccessTokenNotExist { }
}
return nil
2014-11-12 14:48:50 +03:00
}