2021-01-10 21:05:18 +03:00
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"crypto/sha256"
"fmt"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/crypto/scrypt"
"xorm.io/builder"
"xorm.io/xorm"
)
func recalculateUserEmptyPWD ( x * xorm . Engine ) ( err error ) {
const (
algoBcrypt = "bcrypt"
algoScrypt = "scrypt"
algoArgon2 = "argon2"
algoPbkdf2 = "pbkdf2"
)
type User struct {
ID int64 ` xorm:"pk autoincr" `
Passwd string ` xorm:"NOT NULL" `
PasswdHashAlgo string ` xorm:"NOT NULL DEFAULT 'argon2'" `
MustChangePassword bool ` xorm:"NOT NULL DEFAULT false" `
LoginType int
LoginName string
Type int
Salt string ` xorm:"VARCHAR(10)" `
}
// hashPassword hash password based on algo and salt
// state 461406070c
hashPassword := func ( passwd , salt , algo string ) string {
var tempPasswd [ ] byte
switch algo {
case algoBcrypt :
tempPasswd , _ = bcrypt . GenerateFromPassword ( [ ] byte ( passwd ) , bcrypt . DefaultCost )
return string ( tempPasswd )
case algoScrypt :
tempPasswd , _ = scrypt . Key ( [ ] byte ( passwd ) , [ ] byte ( salt ) , 65536 , 16 , 2 , 50 )
case algoArgon2 :
tempPasswd = argon2 . IDKey ( [ ] byte ( passwd ) , [ ] byte ( salt ) , 2 , 65536 , 8 , 50 )
case algoPbkdf2 :
fallthrough
default :
tempPasswd = pbkdf2 . Key ( [ ] byte ( passwd ) , [ ] byte ( salt ) , 10000 , 50 , sha256 . New )
}
return fmt . Sprintf ( "%x" , tempPasswd )
}
// ValidatePassword checks if given password matches the one belongs to the user.
// state 461406070c, changed since it's not necessary to be time constant
ValidatePassword := func ( u * User , passwd string ) bool {
tempHash := hashPassword ( passwd , u . Salt , u . PasswdHashAlgo )
if u . PasswdHashAlgo != algoBcrypt && u . Passwd == tempHash {
return true
}
if u . PasswdHashAlgo == algoBcrypt && bcrypt . CompareHashAndPassword ( [ ] byte ( u . Passwd ) , [ ] byte ( passwd ) ) == nil {
return true
}
return false
}
sess := x . NewSession ( )
defer sess . Close ( )
const batchSize = 100
for start := 0 ; ; start += batchSize {
users := make ( [ ] * User , 0 , batchSize )
if err = sess . Limit ( batchSize , start ) . Where ( builder . Neq { "passwd" : "" } , 0 ) . Find ( & users ) ; err != nil {
return
}
if len ( users ) == 0 {
break
}
if err = sess . Begin ( ) ; err != nil {
return
}
for _ , user := range users {
if ValidatePassword ( user , "" ) {
user . Passwd = ""
user . Salt = ""
user . PasswdHashAlgo = ""
if _ , err = sess . ID ( user . ID ) . Cols ( "passwd" , "salt" , "passwd_hash_algo" ) . Update ( user ) ; err != nil {
return err
}
}
}
if err = sess . Commit ( ) ; err != nil {
return
}
}
// delete salt and algo where password is empty
2021-02-20 17:02:39 +03:00
_ , err = sess . Where ( builder . Eq { "passwd" : "" } . And ( builder . Neq { "salt" : "" } . Or ( builder . Neq { "passwd_hash_algo" : "" } ) ) ) .
Cols ( "salt" , "passwd_hash_algo" ) . Update ( & User { } )
2021-01-10 21:05:18 +03:00
2021-02-20 17:02:39 +03:00
return err
2021-01-10 21:05:18 +03:00
}