Move twofactor to models/login (#17143)

This commit is contained in:
Lunny Xiao 2021-09-25 21:00:12 +08:00 committed by GitHub
parent 6fb7fb6cfc
commit 91e21d4fca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 131 additions and 121 deletions

View file

@ -1876,25 +1876,6 @@ func (err ErrTeamNotExist) Error() string {
return fmt.Sprintf("team does not exist [org_id %d, team_id %d, name: %s]", err.OrgID, err.TeamID, err.Name)
}
//
// Two-factor authentication
//
// ErrTwoFactorNotEnrolled indicates that a user is not enrolled in two-factor authentication.
type ErrTwoFactorNotEnrolled struct {
UID int64
}
// IsErrTwoFactorNotEnrolled checks if an error is a ErrTwoFactorNotEnrolled.
func IsErrTwoFactorNotEnrolled(err error) bool {
_, ok := err.(ErrTwoFactorNotEnrolled)
return ok
}
func (err ErrTwoFactorNotEnrolled) Error() string {
return fmt.Sprintf("user not enrolled in 2FA [uid: %d]", err.UID)
}
// ____ ___ .__ .___
// | | \______ | | _________ __| _/
// | | /\____ \| | / _ \__ \ / __ |
@ -1959,28 +1940,6 @@ func (err ErrExternalLoginUserNotExist) Error() string {
return fmt.Sprintf("external login user link does not exists [userID: %d, loginSourceID: %d]", err.UserID, err.LoginSourceID)
}
// ____ ________________________________ .__ __ __ .__
// | | \_____ \_ _____/\______ \ ____ ____ |__| _______/ |_____________ _/ |_|__| ____ ____
// | | // ____/| __) | _// __ \ / ___\| |/ ___/\ __\_ __ \__ \\ __\ |/ _ \ / \
// | | // \| \ | | \ ___// /_/ > |\___ \ | | | | \// __ \| | | ( <_> ) | \
// |______/ \_______ \___ / |____|_ /\___ >___ /|__/____ > |__| |__| (____ /__| |__|\____/|___| /
// \/ \/ \/ \/_____/ \/ \/ \/
// ErrU2FRegistrationNotExist represents a "ErrU2FRegistrationNotExist" kind of error.
type ErrU2FRegistrationNotExist struct {
ID int64
}
func (err ErrU2FRegistrationNotExist) Error() string {
return fmt.Sprintf("U2F registration does not exist [id: %d]", err.ID)
}
// IsErrU2FRegistrationNotExist checks if an error is a ErrU2FRegistrationNotExist.
func IsErrU2FRegistrationNotExist(err error) bool {
_, ok := err.(ErrU2FRegistrationNotExist)
return ok
}
// .___ ________ .___ .__
// | | ______ ________ __ ____ \______ \ ____ ______ ____ ____ __| _/____ ____ ____ |__| ____ ______
// | |/ ___// ___/ | \_/ __ \ | | \_/ __ \\____ \_/ __ \ / \ / __ |/ __ \ / \_/ ___\| |/ __ \ / ___/

View file

@ -17,5 +17,6 @@ func TestMain(m *testing.M) {
"oauth2_application.yml",
"oauth2_authorization_code.yml",
"oauth2_grant.yml",
"u2f_registration.yml",
)
}

View file

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
package login
import (
"crypto/md5"
@ -21,6 +21,25 @@ import (
"golang.org/x/crypto/pbkdf2"
)
//
// Two-factor authentication
//
// ErrTwoFactorNotEnrolled indicates that a user is not enrolled in two-factor authentication.
type ErrTwoFactorNotEnrolled struct {
UID int64
}
// IsErrTwoFactorNotEnrolled checks if an error is a ErrTwoFactorNotEnrolled.
func IsErrTwoFactorNotEnrolled(err error) bool {
_, ok := err.(ErrTwoFactorNotEnrolled)
return ok
}
func (err ErrTwoFactorNotEnrolled) Error() string {
return fmt.Sprintf("user not enrolled in 2FA [uid: %d]", err.UID)
}
// TwoFactor represents a two-factor authentication token.
type TwoFactor struct {
ID int64 `xorm:"pk autoincr"`
@ -44,11 +63,12 @@ func (t *TwoFactor) GenerateScratchToken() (string, error) {
return "", err
}
t.ScratchSalt, _ = util.RandomString(10)
t.ScratchHash = hashToken(token, t.ScratchSalt)
t.ScratchHash = HashToken(token, t.ScratchSalt)
return token, nil
}
func hashToken(token, salt string) string {
// HashToken return the hashable salt
func HashToken(token, salt string) string {
tempHash := pbkdf2.Key([]byte(token), []byte(salt), 10000, 50, sha256.New)
return fmt.Sprintf("%x", tempHash)
}
@ -58,7 +78,7 @@ func (t *TwoFactor) VerifyScratchToken(token string) bool {
if len(token) == 0 {
return false
}
tempHash := hashToken(token, t.ScratchSalt)
tempHash := HashToken(token, t.ScratchSalt)
return subtle.ConstantTimeCompare([]byte(t.ScratchHash), []byte(tempHash)) == 1
}

View file

@ -2,9 +2,11 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
package login
import (
"fmt"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
@ -12,6 +14,28 @@ import (
"github.com/tstranex/u2f"
)
// ____ ________________________________ .__ __ __ .__
// | | \_____ \_ _____/\______ \ ____ ____ |__| _______/ |_____________ _/ |_|__| ____ ____
// | | // ____/| __) | _// __ \ / ___\| |/ ___/\ __\_ __ \__ \\ __\ |/ _ \ / \
// | | // \| \ | | \ ___// /_/ > |\___ \ | | | | \// __ \| | | ( <_> ) | \
// |______/ \_______ \___ / |____|_ /\___ >___ /|__/____ > |__| |__| (____ /__| |__|\____/|___| /
// \/ \/ \/ \/_____/ \/ \/ \/
// ErrU2FRegistrationNotExist represents a "ErrU2FRegistrationNotExist" kind of error.
type ErrU2FRegistrationNotExist struct {
ID int64
}
func (err ErrU2FRegistrationNotExist) Error() string {
return fmt.Sprintf("U2F registration does not exist [id: %d]", err.ID)
}
// IsErrU2FRegistrationNotExist checks if an error is a ErrU2FRegistrationNotExist.
func IsErrU2FRegistrationNotExist(err error) bool {
_, ok := err.(ErrU2FRegistrationNotExist)
return ok
}
// U2FRegistration represents the registration data and counter of a security key
type U2FRegistration struct {
ID int64 `xorm:"pk autoincr"`
@ -91,13 +115,13 @@ func GetU2FRegistrationsByUID(uid int64) (U2FRegistrationList, error) {
return getU2FRegistrationsByUID(db.GetEngine(db.DefaultContext), uid)
}
func createRegistration(e db.Engine, user *User, name string, reg *u2f.Registration) (*U2FRegistration, error) {
func createRegistration(e db.Engine, userID int64, name string, reg *u2f.Registration) (*U2FRegistration, error) {
raw, err := reg.MarshalBinary()
if err != nil {
return nil, err
}
r := &U2FRegistration{
UserID: user.ID,
UserID: userID,
Name: name,
Counter: 0,
Raw: raw,
@ -110,8 +134,8 @@ func createRegistration(e db.Engine, user *User, name string, reg *u2f.Registrat
}
// CreateRegistration will create a new U2FRegistration from the given Registration
func CreateRegistration(user *User, name string, reg *u2f.Registration) (*U2FRegistration, error) {
return createRegistration(db.GetEngine(db.DefaultContext), user, name, reg)
func CreateRegistration(userID int64, name string, reg *u2f.Registration) (*U2FRegistration, error) {
return createRegistration(db.GetEngine(db.DefaultContext), userID, name, reg)
}
// DeleteRegistration will delete U2FRegistration

View file

@ -2,12 +2,13 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
package login
import (
"testing"
"code.gitea.io/gitea/models/db"
"github.com/stretchr/testify/assert"
"github.com/tstranex/u2f"
)
@ -55,14 +56,13 @@ func TestU2FRegistration_UpdateLargeCounter(t *testing.T) {
func TestCreateRegistration(t *testing.T) {
assert.NoError(t, db.PrepareTestDatabase())
user := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
res, err := CreateRegistration(user, "U2F Created Key", &u2f.Registration{Raw: []byte("Test")})
res, err := CreateRegistration(1, "U2F Created Key", &u2f.Registration{Raw: []byte("Test")})
assert.NoError(t, err)
assert.Equal(t, "U2F Created Key", res.Name)
assert.Equal(t, []byte("Test"), res.Raw)
db.AssertExistsIf(t, true, &U2FRegistration{Name: "U2F Created Key", UserID: user.ID})
db.AssertExistsIf(t, true, &U2FRegistration{Name: "U2F Created Key", UserID: 1})
}
func TestDeleteRegistration(t *testing.T) {

View file

@ -6,6 +6,7 @@ package models
import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/login"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@ -44,8 +45,8 @@ Loop:
return false, "", nil, &ErrWontSign{pubkey}
}
case twofa:
twofaModel, err := GetTwoFactorByUID(u.ID)
if err != nil && !IsErrTwoFactorNotEnrolled(err) {
twofaModel, err := login.GetTwoFactorByUID(u.ID)
if err != nil && !login.IsErrTwoFactorNotEnrolled(err) {
return false, "", nil, err
}
if twofaModel == nil {

View file

@ -8,6 +8,7 @@ import (
"strings"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/login"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
@ -129,8 +130,8 @@ Loop:
return false, "", nil, &ErrWontSign{pubkey}
}
case twofa:
twofaModel, err := GetTwoFactorByUID(u.ID)
if err != nil && !IsErrTwoFactorNotEnrolled(err) {
twofaModel, err := login.GetTwoFactorByUID(u.ID)
if err != nil && !login.IsErrTwoFactorNotEnrolled(err) {
return false, "", nil, err
}
if twofaModel == nil {
@ -165,8 +166,8 @@ Loop:
return false, "", nil, &ErrWontSign{pubkey}
}
case twofa:
twofaModel, err := GetTwoFactorByUID(u.ID)
if err != nil && !IsErrTwoFactorNotEnrolled(err) {
twofaModel, err := login.GetTwoFactorByUID(u.ID)
if err != nil && !login.IsErrTwoFactorNotEnrolled(err) {
return false, "", nil, err
}
if twofaModel == nil {
@ -218,8 +219,8 @@ Loop:
return false, "", nil, &ErrWontSign{pubkey}
}
case twofa:
twofaModel, err := GetTwoFactorByUID(u.ID)
if err != nil && !IsErrTwoFactorNotEnrolled(err) {
twofaModel, err := login.GetTwoFactorByUID(u.ID)
if err != nil && !login.IsErrTwoFactorNotEnrolled(err) {
return false, "", nil, err
}
if twofaModel == nil {

View file

@ -11,6 +11,7 @@ import (
"time"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/login"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
@ -67,7 +68,7 @@ func NewAccessToken(t *AccessToken) error {
}
t.TokenSalt = salt
t.Token = base.EncodeSha1(gouuid.New().String())
t.TokenHash = hashToken(t.Token, t.TokenSalt)
t.TokenHash = login.HashToken(t.Token, t.TokenSalt)
t.TokenLastEight = t.Token[len(t.Token)-8:]
_, err = db.GetEngine(db.DefaultContext).Insert(t)
return err
@ -129,7 +130,7 @@ func GetAccessTokenBySHA(token string) (*AccessToken, error) {
}
for _, t := range tokens {
tempHash := hashToken(token, t.TokenSalt)
tempHash := login.HashToken(token, t.TokenSalt)
if subtle.ConstantTimeCompare([]byte(t.TokenHash), []byte(tempHash)) == 1 {
if successfulAccessTokenCache != nil {
successfulAccessTokenCache.Add(token, t.ID)

View file

@ -8,6 +8,7 @@ import (
"fmt"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/login"
"code.gitea.io/gitea/modules/log"
)
@ -79,13 +80,13 @@ func (users UserList) GetTwoFaStatus() map[int64]bool {
return results
}
func (users UserList) loadTwoFactorStatus(e db.Engine) (map[int64]*TwoFactor, error) {
func (users UserList) loadTwoFactorStatus(e db.Engine) (map[int64]*login.TwoFactor, error) {
if len(users) == 0 {
return nil, nil
}
userIDs := users.getUserIDs()
tokenMaps := make(map[int64]*TwoFactor, len(userIDs))
tokenMaps := make(map[int64]*login.TwoFactor, len(userIDs))
err := e.
In("uid", userIDs).
Find(&tokenMaps)