code.gitea.io/gitea@v1.21.7/models/migrations/v1_14/v166.go (about)

     1  // Copyright 2021 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package v1_14 //nolint
     5  
     6  import (
     7  	"encoding/hex"
     8  
     9  	"github.com/minio/sha256-simd"
    10  	"golang.org/x/crypto/argon2"
    11  	"golang.org/x/crypto/bcrypt"
    12  	"golang.org/x/crypto/pbkdf2"
    13  	"golang.org/x/crypto/scrypt"
    14  	"xorm.io/builder"
    15  	"xorm.io/xorm"
    16  )
    17  
    18  func RecalculateUserEmptyPWD(x *xorm.Engine) (err error) {
    19  	const (
    20  		algoBcrypt = "bcrypt"
    21  		algoScrypt = "scrypt"
    22  		algoArgon2 = "argon2"
    23  		algoPbkdf2 = "pbkdf2"
    24  	)
    25  
    26  	type User struct {
    27  		ID                 int64  `xorm:"pk autoincr"`
    28  		Passwd             string `xorm:"NOT NULL"`
    29  		PasswdHashAlgo     string `xorm:"NOT NULL DEFAULT 'argon2'"`
    30  		MustChangePassword bool   `xorm:"NOT NULL DEFAULT false"`
    31  		LoginType          int
    32  		LoginName          string
    33  		Type               int
    34  		Salt               string `xorm:"VARCHAR(10)"`
    35  	}
    36  
    37  	// hashPassword hash password based on algo and salt
    38  	// state 461406070c
    39  	hashPassword := func(passwd, salt, algo string) string {
    40  		var tempPasswd []byte
    41  
    42  		switch algo {
    43  		case algoBcrypt:
    44  			tempPasswd, _ = bcrypt.GenerateFromPassword([]byte(passwd), bcrypt.DefaultCost)
    45  			return string(tempPasswd)
    46  		case algoScrypt:
    47  			tempPasswd, _ = scrypt.Key([]byte(passwd), []byte(salt), 65536, 16, 2, 50)
    48  		case algoArgon2:
    49  			tempPasswd = argon2.IDKey([]byte(passwd), []byte(salt), 2, 65536, 8, 50)
    50  		case algoPbkdf2:
    51  			fallthrough
    52  		default:
    53  			tempPasswd = pbkdf2.Key([]byte(passwd), []byte(salt), 10000, 50, sha256.New)
    54  		}
    55  
    56  		return hex.EncodeToString(tempPasswd)
    57  	}
    58  
    59  	// ValidatePassword checks if given password matches the one belongs to the user.
    60  	// state 461406070c, changed since it's not necessary to be time constant
    61  	ValidatePassword := func(u *User, passwd string) bool {
    62  		tempHash := hashPassword(passwd, u.Salt, u.PasswdHashAlgo)
    63  
    64  		if u.PasswdHashAlgo != algoBcrypt && u.Passwd == tempHash {
    65  			return true
    66  		}
    67  		if u.PasswdHashAlgo == algoBcrypt && bcrypt.CompareHashAndPassword([]byte(u.Passwd), []byte(passwd)) == nil {
    68  			return true
    69  		}
    70  		return false
    71  	}
    72  
    73  	sess := x.NewSession()
    74  	defer sess.Close()
    75  
    76  	const batchSize = 100
    77  
    78  	for start := 0; ; start += batchSize {
    79  		users := make([]*User, 0, batchSize)
    80  		if err = sess.Limit(batchSize, start).Where(builder.Neq{"passwd": ""}, 0).Find(&users); err != nil {
    81  			return err
    82  		}
    83  		if len(users) == 0 {
    84  			break
    85  		}
    86  
    87  		if err = sess.Begin(); err != nil {
    88  			return err
    89  		}
    90  
    91  		for _, user := range users {
    92  			if ValidatePassword(user, "") {
    93  				user.Passwd = ""
    94  				user.Salt = ""
    95  				user.PasswdHashAlgo = ""
    96  				if _, err = sess.ID(user.ID).Cols("passwd", "salt", "passwd_hash_algo").Update(user); err != nil {
    97  					return err
    98  				}
    99  			}
   100  		}
   101  
   102  		if err = sess.Commit(); err != nil {
   103  			return err
   104  		}
   105  	}
   106  
   107  	// delete salt and algo where password is empty
   108  	_, err = sess.Where(builder.Eq{"passwd": ""}.And(builder.Neq{"salt": ""}.Or(builder.Neq{"passwd_hash_algo": ""}))).
   109  		Cols("salt", "passwd_hash_algo").Update(&User{})
   110  
   111  	return err
   112  }