code.gitea.io/gitea@v1.21.7/models/auth/token.go (about)

     1  // Copyright 2014 The Gogs Authors. All rights reserved.
     2  // Copyright 2019 The Gitea Authors. All rights reserved.
     3  // SPDX-License-Identifier: MIT
     4  
     5  package auth
     6  
     7  import (
     8  	"context"
     9  	"crypto/subtle"
    10  	"encoding/hex"
    11  	"fmt"
    12  	"time"
    13  
    14  	"code.gitea.io/gitea/models/db"
    15  	"code.gitea.io/gitea/modules/setting"
    16  	"code.gitea.io/gitea/modules/timeutil"
    17  	"code.gitea.io/gitea/modules/util"
    18  
    19  	lru "github.com/hashicorp/golang-lru/v2"
    20  )
    21  
    22  // ErrAccessTokenNotExist represents a "AccessTokenNotExist" kind of error.
    23  type ErrAccessTokenNotExist struct {
    24  	Token string
    25  }
    26  
    27  // IsErrAccessTokenNotExist checks if an error is a ErrAccessTokenNotExist.
    28  func IsErrAccessTokenNotExist(err error) bool {
    29  	_, ok := err.(ErrAccessTokenNotExist)
    30  	return ok
    31  }
    32  
    33  func (err ErrAccessTokenNotExist) Error() string {
    34  	return fmt.Sprintf("access token does not exist [sha: %s]", err.Token)
    35  }
    36  
    37  func (err ErrAccessTokenNotExist) Unwrap() error {
    38  	return util.ErrNotExist
    39  }
    40  
    41  // ErrAccessTokenEmpty represents a "AccessTokenEmpty" kind of error.
    42  type ErrAccessTokenEmpty struct{}
    43  
    44  // IsErrAccessTokenEmpty checks if an error is a ErrAccessTokenEmpty.
    45  func IsErrAccessTokenEmpty(err error) bool {
    46  	_, ok := err.(ErrAccessTokenEmpty)
    47  	return ok
    48  }
    49  
    50  func (err ErrAccessTokenEmpty) Error() string {
    51  	return "access token is empty"
    52  }
    53  
    54  func (err ErrAccessTokenEmpty) Unwrap() error {
    55  	return util.ErrInvalidArgument
    56  }
    57  
    58  var successfulAccessTokenCache *lru.Cache[string, any]
    59  
    60  // AccessToken represents a personal access token.
    61  type AccessToken struct {
    62  	ID             int64 `xorm:"pk autoincr"`
    63  	UID            int64 `xorm:"INDEX"`
    64  	Name           string
    65  	Token          string `xorm:"-"`
    66  	TokenHash      string `xorm:"UNIQUE"` // sha256 of token
    67  	TokenSalt      string
    68  	TokenLastEight string `xorm:"INDEX token_last_eight"`
    69  	Scope          AccessTokenScope
    70  
    71  	CreatedUnix       timeutil.TimeStamp `xorm:"INDEX created"`
    72  	UpdatedUnix       timeutil.TimeStamp `xorm:"INDEX updated"`
    73  	HasRecentActivity bool               `xorm:"-"`
    74  	HasUsed           bool               `xorm:"-"`
    75  }
    76  
    77  // AfterLoad is invoked from XORM after setting the values of all fields of this object.
    78  func (t *AccessToken) AfterLoad() {
    79  	t.HasUsed = t.UpdatedUnix > t.CreatedUnix
    80  	t.HasRecentActivity = t.UpdatedUnix.AddDuration(7*24*time.Hour) > timeutil.TimeStampNow()
    81  }
    82  
    83  func init() {
    84  	db.RegisterModel(new(AccessToken), func() error {
    85  		if setting.SuccessfulTokensCacheSize > 0 {
    86  			var err error
    87  			successfulAccessTokenCache, err = lru.New[string, any](setting.SuccessfulTokensCacheSize)
    88  			if err != nil {
    89  				return fmt.Errorf("unable to allocate AccessToken cache: %w", err)
    90  			}
    91  		} else {
    92  			successfulAccessTokenCache = nil
    93  		}
    94  		return nil
    95  	})
    96  }
    97  
    98  // NewAccessToken creates new access token.
    99  func NewAccessToken(ctx context.Context, t *AccessToken) error {
   100  	salt, err := util.CryptoRandomString(10)
   101  	if err != nil {
   102  		return err
   103  	}
   104  	token, err := util.CryptoRandomBytes(20)
   105  	if err != nil {
   106  		return err
   107  	}
   108  	t.TokenSalt = salt
   109  	t.Token = hex.EncodeToString(token)
   110  	t.TokenHash = HashToken(t.Token, t.TokenSalt)
   111  	t.TokenLastEight = t.Token[len(t.Token)-8:]
   112  	_, err = db.GetEngine(ctx).Insert(t)
   113  	return err
   114  }
   115  
   116  // DisplayPublicOnly whether to display this as a public-only token.
   117  func (t *AccessToken) DisplayPublicOnly() bool {
   118  	publicOnly, err := t.Scope.PublicOnly()
   119  	if err != nil {
   120  		return false
   121  	}
   122  	return publicOnly
   123  }
   124  
   125  func getAccessTokenIDFromCache(token string) int64 {
   126  	if successfulAccessTokenCache == nil {
   127  		return 0
   128  	}
   129  	tInterface, ok := successfulAccessTokenCache.Get(token)
   130  	if !ok {
   131  		return 0
   132  	}
   133  	t, ok := tInterface.(int64)
   134  	if !ok {
   135  		return 0
   136  	}
   137  	return t
   138  }
   139  
   140  // GetAccessTokenBySHA returns access token by given token value
   141  func GetAccessTokenBySHA(ctx context.Context, token string) (*AccessToken, error) {
   142  	if token == "" {
   143  		return nil, ErrAccessTokenEmpty{}
   144  	}
   145  	// A token is defined as being SHA1 sum these are 40 hexadecimal bytes long
   146  	if len(token) != 40 {
   147  		return nil, ErrAccessTokenNotExist{token}
   148  	}
   149  	for _, x := range []byte(token) {
   150  		if x < '0' || (x > '9' && x < 'a') || x > 'f' {
   151  			return nil, ErrAccessTokenNotExist{token}
   152  		}
   153  	}
   154  
   155  	lastEight := token[len(token)-8:]
   156  
   157  	if id := getAccessTokenIDFromCache(token); id > 0 {
   158  		accessToken := &AccessToken{
   159  			TokenLastEight: lastEight,
   160  		}
   161  		// Re-get the token from the db in case it has been deleted in the intervening period
   162  		has, err := db.GetEngine(ctx).ID(id).Get(accessToken)
   163  		if err != nil {
   164  			return nil, err
   165  		}
   166  		if has {
   167  			return accessToken, nil
   168  		}
   169  		successfulAccessTokenCache.Remove(token)
   170  	}
   171  
   172  	var tokens []AccessToken
   173  	err := db.GetEngine(ctx).Table(&AccessToken{}).Where("token_last_eight = ?", lastEight).Find(&tokens)
   174  	if err != nil {
   175  		return nil, err
   176  	} else if len(tokens) == 0 {
   177  		return nil, ErrAccessTokenNotExist{token}
   178  	}
   179  
   180  	for _, t := range tokens {
   181  		tempHash := HashToken(token, t.TokenSalt)
   182  		if subtle.ConstantTimeCompare([]byte(t.TokenHash), []byte(tempHash)) == 1 {
   183  			if successfulAccessTokenCache != nil {
   184  				successfulAccessTokenCache.Add(token, t.ID)
   185  			}
   186  			return &t, nil
   187  		}
   188  	}
   189  	return nil, ErrAccessTokenNotExist{token}
   190  }
   191  
   192  // AccessTokenByNameExists checks if a token name has been used already by a user.
   193  func AccessTokenByNameExists(ctx context.Context, token *AccessToken) (bool, error) {
   194  	return db.GetEngine(ctx).Table("access_token").Where("name = ?", token.Name).And("uid = ?", token.UID).Exist()
   195  }
   196  
   197  // ListAccessTokensOptions contain filter options
   198  type ListAccessTokensOptions struct {
   199  	db.ListOptions
   200  	Name   string
   201  	UserID int64
   202  }
   203  
   204  // ListAccessTokens returns a list of access tokens belongs to given user.
   205  func ListAccessTokens(ctx context.Context, opts ListAccessTokensOptions) ([]*AccessToken, error) {
   206  	sess := db.GetEngine(ctx).Where("uid=?", opts.UserID)
   207  
   208  	if len(opts.Name) != 0 {
   209  		sess = sess.Where("name=?", opts.Name)
   210  	}
   211  
   212  	sess = sess.Desc("created_unix")
   213  
   214  	if opts.Page != 0 {
   215  		sess = db.SetSessionPagination(sess, &opts)
   216  
   217  		tokens := make([]*AccessToken, 0, opts.PageSize)
   218  		return tokens, sess.Find(&tokens)
   219  	}
   220  
   221  	tokens := make([]*AccessToken, 0, 5)
   222  	return tokens, sess.Find(&tokens)
   223  }
   224  
   225  // UpdateAccessToken updates information of access token.
   226  func UpdateAccessToken(ctx context.Context, t *AccessToken) error {
   227  	_, err := db.GetEngine(ctx).ID(t.ID).AllCols().Update(t)
   228  	return err
   229  }
   230  
   231  // CountAccessTokens count access tokens belongs to given user by options
   232  func CountAccessTokens(ctx context.Context, opts ListAccessTokensOptions) (int64, error) {
   233  	sess := db.GetEngine(ctx).Where("uid=?", opts.UserID)
   234  	if len(opts.Name) != 0 {
   235  		sess = sess.Where("name=?", opts.Name)
   236  	}
   237  	return sess.Count(&AccessToken{})
   238  }
   239  
   240  // DeleteAccessTokenByID deletes access token by given ID.
   241  func DeleteAccessTokenByID(ctx context.Context, id, userID int64) error {
   242  	cnt, err := db.GetEngine(ctx).ID(id).Delete(&AccessToken{
   243  		UID: userID,
   244  	})
   245  	if err != nil {
   246  		return err
   247  	} else if cnt != 1 {
   248  		return ErrAccessTokenNotExist{}
   249  	}
   250  	return nil
   251  }