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)
-}
-
// ____ ___ .__ .___
// | | \______ | | _________ __| _/
// | | /\____ \| | / _ \__ \ / __ |
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
-}
-
// .___ ________ .___ .__
// | | ______ ________ __ ____ \______ \ ____ ______ ____ ____ __| _/____ ____ ____ |__| ____ ______
// | |/ ___// ___/ | \_/ __ \ | | \_/ __ \\____ \_/ __ \ / \ / __ |/ __ \ / \_/ ___\| |/ __ \ / ___/
"oauth2_application.yml",
"oauth2_authorization_code.yml",
"oauth2_grant.yml",
+ "u2f_registration.yml",
)
}
--- /dev/null
+// Copyright 2017 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 login
+
+import (
+ "crypto/md5"
+ "crypto/sha256"
+ "crypto/subtle"
+ "encoding/base64"
+ "fmt"
+
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/modules/secret"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/timeutil"
+ "code.gitea.io/gitea/modules/util"
+
+ "github.com/pquerna/otp/totp"
+ "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"`
+ UID int64 `xorm:"UNIQUE"`
+ Secret string
+ ScratchSalt string
+ ScratchHash string
+ LastUsedPasscode string `xorm:"VARCHAR(10)"`
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+}
+
+func init() {
+ db.RegisterModel(new(TwoFactor))
+}
+
+// GenerateScratchToken recreates the scratch token the user is using.
+func (t *TwoFactor) GenerateScratchToken() (string, error) {
+ token, err := util.RandomString(8)
+ if err != nil {
+ return "", err
+ }
+ t.ScratchSalt, _ = util.RandomString(10)
+ t.ScratchHash = HashToken(token, t.ScratchSalt)
+ return token, nil
+}
+
+// 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)
+}
+
+// VerifyScratchToken verifies if the specified scratch token is valid.
+func (t *TwoFactor) VerifyScratchToken(token string) bool {
+ if len(token) == 0 {
+ return false
+ }
+ tempHash := HashToken(token, t.ScratchSalt)
+ return subtle.ConstantTimeCompare([]byte(t.ScratchHash), []byte(tempHash)) == 1
+}
+
+func (t *TwoFactor) getEncryptionKey() []byte {
+ k := md5.Sum([]byte(setting.SecretKey))
+ return k[:]
+}
+
+// SetSecret sets the 2FA secret.
+func (t *TwoFactor) SetSecret(secretString string) error {
+ secretBytes, err := secret.AesEncrypt(t.getEncryptionKey(), []byte(secretString))
+ if err != nil {
+ return err
+ }
+ t.Secret = base64.StdEncoding.EncodeToString(secretBytes)
+ return nil
+}
+
+// ValidateTOTP validates the provided passcode.
+func (t *TwoFactor) ValidateTOTP(passcode string) (bool, error) {
+ decodedStoredSecret, err := base64.StdEncoding.DecodeString(t.Secret)
+ if err != nil {
+ return false, err
+ }
+ secretBytes, err := secret.AesDecrypt(t.getEncryptionKey(), decodedStoredSecret)
+ if err != nil {
+ return false, err
+ }
+ secretStr := string(secretBytes)
+ return totp.Validate(passcode, secretStr), nil
+}
+
+// NewTwoFactor creates a new two-factor authentication token.
+func NewTwoFactor(t *TwoFactor) error {
+ _, err := db.GetEngine(db.DefaultContext).Insert(t)
+ return err
+}
+
+// UpdateTwoFactor updates a two-factor authentication token.
+func UpdateTwoFactor(t *TwoFactor) error {
+ _, err := db.GetEngine(db.DefaultContext).ID(t.ID).AllCols().Update(t)
+ return err
+}
+
+// GetTwoFactorByUID returns the two-factor authentication token associated with
+// the user, if any.
+func GetTwoFactorByUID(uid int64) (*TwoFactor, error) {
+ twofa := &TwoFactor{}
+ has, err := db.GetEngine(db.DefaultContext).Where("uid=?", uid).Get(twofa)
+ if err != nil {
+ return nil, err
+ } else if !has {
+ return nil, ErrTwoFactorNotEnrolled{uid}
+ }
+ return twofa, nil
+}
+
+// DeleteTwoFactorByID deletes two-factor authentication token by given ID.
+func DeleteTwoFactorByID(id, userID int64) error {
+ cnt, err := db.GetEngine(db.DefaultContext).ID(id).Delete(&TwoFactor{
+ UID: userID,
+ })
+ if err != nil {
+ return err
+ } else if cnt != 1 {
+ return ErrTwoFactorNotEnrolled{userID}
+ }
+ return nil
+}
--- /dev/null
+// Copyright 2018 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 login
+
+import (
+ "fmt"
+
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "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"`
+ Name string
+ UserID int64 `xorm:"INDEX"`
+ Raw []byte
+ Counter uint32 `xorm:"BIGINT"`
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+}
+
+func init() {
+ db.RegisterModel(new(U2FRegistration))
+}
+
+// TableName returns a better table name for U2FRegistration
+func (reg U2FRegistration) TableName() string {
+ return "u2f_registration"
+}
+
+// Parse will convert the db entry U2FRegistration to an u2f.Registration struct
+func (reg *U2FRegistration) Parse() (*u2f.Registration, error) {
+ r := new(u2f.Registration)
+ return r, r.UnmarshalBinary(reg.Raw)
+}
+
+func (reg *U2FRegistration) updateCounter(e db.Engine) error {
+ _, err := e.ID(reg.ID).Cols("counter").Update(reg)
+ return err
+}
+
+// UpdateCounter will update the database value of counter
+func (reg *U2FRegistration) UpdateCounter() error {
+ return reg.updateCounter(db.GetEngine(db.DefaultContext))
+}
+
+// U2FRegistrationList is a list of *U2FRegistration
+type U2FRegistrationList []*U2FRegistration
+
+// ToRegistrations will convert all U2FRegistrations to u2f.Registrations
+func (list U2FRegistrationList) ToRegistrations() []u2f.Registration {
+ regs := make([]u2f.Registration, 0, len(list))
+ for _, reg := range list {
+ r, err := reg.Parse()
+ if err != nil {
+ log.Fatal("parsing u2f registration: %v", err)
+ continue
+ }
+ regs = append(regs, *r)
+ }
+
+ return regs
+}
+
+func getU2FRegistrationsByUID(e db.Engine, uid int64) (U2FRegistrationList, error) {
+ regs := make(U2FRegistrationList, 0)
+ return regs, e.Where("user_id = ?", uid).Find(®s)
+}
+
+// GetU2FRegistrationByID returns U2F registration by id
+func GetU2FRegistrationByID(id int64) (*U2FRegistration, error) {
+ return getU2FRegistrationByID(db.GetEngine(db.DefaultContext), id)
+}
+
+func getU2FRegistrationByID(e db.Engine, id int64) (*U2FRegistration, error) {
+ reg := new(U2FRegistration)
+ if found, err := e.ID(id).Get(reg); err != nil {
+ return nil, err
+ } else if !found {
+ return nil, ErrU2FRegistrationNotExist{ID: id}
+ }
+ return reg, nil
+}
+
+// GetU2FRegistrationsByUID returns all U2F registrations of the given user
+func GetU2FRegistrationsByUID(uid int64) (U2FRegistrationList, error) {
+ return getU2FRegistrationsByUID(db.GetEngine(db.DefaultContext), uid)
+}
+
+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: userID,
+ Name: name,
+ Counter: 0,
+ Raw: raw,
+ }
+ _, err = e.InsertOne(r)
+ if err != nil {
+ return nil, err
+ }
+ return r, nil
+}
+
+// CreateRegistration will create a new U2FRegistration from the given Registration
+func CreateRegistration(userID int64, name string, reg *u2f.Registration) (*U2FRegistration, error) {
+ return createRegistration(db.GetEngine(db.DefaultContext), userID, name, reg)
+}
+
+// DeleteRegistration will delete U2FRegistration
+func DeleteRegistration(reg *U2FRegistration) error {
+ return deleteRegistration(db.GetEngine(db.DefaultContext), reg)
+}
+
+func deleteRegistration(e db.Engine, reg *U2FRegistration) error {
+ _, err := e.Delete(reg)
+ return err
+}
--- /dev/null
+// Copyright 2020 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 login
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/db"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/tstranex/u2f"
+)
+
+func TestGetU2FRegistrationByID(t *testing.T) {
+ assert.NoError(t, db.PrepareTestDatabase())
+
+ res, err := GetU2FRegistrationByID(1)
+ assert.NoError(t, err)
+ assert.Equal(t, "U2F Key", res.Name)
+
+ _, err = GetU2FRegistrationByID(342432)
+ assert.Error(t, err)
+ assert.True(t, IsErrU2FRegistrationNotExist(err))
+}
+
+func TestGetU2FRegistrationsByUID(t *testing.T) {
+ assert.NoError(t, db.PrepareTestDatabase())
+
+ res, err := GetU2FRegistrationsByUID(1)
+ assert.NoError(t, err)
+ assert.Len(t, res, 1)
+ assert.Equal(t, "U2F Key", res[0].Name)
+}
+
+func TestU2FRegistration_TableName(t *testing.T) {
+ assert.Equal(t, "u2f_registration", U2FRegistration{}.TableName())
+}
+
+func TestU2FRegistration_UpdateCounter(t *testing.T) {
+ assert.NoError(t, db.PrepareTestDatabase())
+ reg := db.AssertExistsAndLoadBean(t, &U2FRegistration{ID: 1}).(*U2FRegistration)
+ reg.Counter = 1
+ assert.NoError(t, reg.UpdateCounter())
+ db.AssertExistsIf(t, true, &U2FRegistration{ID: 1, Counter: 1})
+}
+
+func TestU2FRegistration_UpdateLargeCounter(t *testing.T) {
+ assert.NoError(t, db.PrepareTestDatabase())
+ reg := db.AssertExistsAndLoadBean(t, &U2FRegistration{ID: 1}).(*U2FRegistration)
+ reg.Counter = 0xffffffff
+ assert.NoError(t, reg.UpdateCounter())
+ db.AssertExistsIf(t, true, &U2FRegistration{ID: 1, Counter: 0xffffffff})
+}
+
+func TestCreateRegistration(t *testing.T) {
+ assert.NoError(t, db.PrepareTestDatabase())
+
+ 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: 1})
+}
+
+func TestDeleteRegistration(t *testing.T) {
+ assert.NoError(t, db.PrepareTestDatabase())
+ reg := db.AssertExistsAndLoadBean(t, &U2FRegistration{ID: 1}).(*U2FRegistration)
+
+ assert.NoError(t, DeleteRegistration(reg))
+ db.AssertNotExistsBean(t, &U2FRegistration{ID: 1})
+}
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"
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 {
"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"
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 {
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 {
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 {
"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"
}
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
}
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)
+++ /dev/null
-// Copyright 2017 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 models
-
-import (
- "crypto/md5"
- "crypto/sha256"
- "crypto/subtle"
- "encoding/base64"
- "fmt"
-
- "code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/secret"
- "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/timeutil"
- "code.gitea.io/gitea/modules/util"
-
- "github.com/pquerna/otp/totp"
- "golang.org/x/crypto/pbkdf2"
-)
-
-// TwoFactor represents a two-factor authentication token.
-type TwoFactor struct {
- ID int64 `xorm:"pk autoincr"`
- UID int64 `xorm:"UNIQUE"`
- Secret string
- ScratchSalt string
- ScratchHash string
- LastUsedPasscode string `xorm:"VARCHAR(10)"`
- CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
- UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
-}
-
-func init() {
- db.RegisterModel(new(TwoFactor))
-}
-
-// GenerateScratchToken recreates the scratch token the user is using.
-func (t *TwoFactor) GenerateScratchToken() (string, error) {
- token, err := util.RandomString(8)
- if err != nil {
- return "", err
- }
- t.ScratchSalt, _ = util.RandomString(10)
- t.ScratchHash = hashToken(token, t.ScratchSalt)
- return token, nil
-}
-
-func hashToken(token, salt string) string {
- tempHash := pbkdf2.Key([]byte(token), []byte(salt), 10000, 50, sha256.New)
- return fmt.Sprintf("%x", tempHash)
-}
-
-// VerifyScratchToken verifies if the specified scratch token is valid.
-func (t *TwoFactor) VerifyScratchToken(token string) bool {
- if len(token) == 0 {
- return false
- }
- tempHash := hashToken(token, t.ScratchSalt)
- return subtle.ConstantTimeCompare([]byte(t.ScratchHash), []byte(tempHash)) == 1
-}
-
-func (t *TwoFactor) getEncryptionKey() []byte {
- k := md5.Sum([]byte(setting.SecretKey))
- return k[:]
-}
-
-// SetSecret sets the 2FA secret.
-func (t *TwoFactor) SetSecret(secretString string) error {
- secretBytes, err := secret.AesEncrypt(t.getEncryptionKey(), []byte(secretString))
- if err != nil {
- return err
- }
- t.Secret = base64.StdEncoding.EncodeToString(secretBytes)
- return nil
-}
-
-// ValidateTOTP validates the provided passcode.
-func (t *TwoFactor) ValidateTOTP(passcode string) (bool, error) {
- decodedStoredSecret, err := base64.StdEncoding.DecodeString(t.Secret)
- if err != nil {
- return false, err
- }
- secretBytes, err := secret.AesDecrypt(t.getEncryptionKey(), decodedStoredSecret)
- if err != nil {
- return false, err
- }
- secretStr := string(secretBytes)
- return totp.Validate(passcode, secretStr), nil
-}
-
-// NewTwoFactor creates a new two-factor authentication token.
-func NewTwoFactor(t *TwoFactor) error {
- _, err := db.GetEngine(db.DefaultContext).Insert(t)
- return err
-}
-
-// UpdateTwoFactor updates a two-factor authentication token.
-func UpdateTwoFactor(t *TwoFactor) error {
- _, err := db.GetEngine(db.DefaultContext).ID(t.ID).AllCols().Update(t)
- return err
-}
-
-// GetTwoFactorByUID returns the two-factor authentication token associated with
-// the user, if any.
-func GetTwoFactorByUID(uid int64) (*TwoFactor, error) {
- twofa := &TwoFactor{}
- has, err := db.GetEngine(db.DefaultContext).Where("uid=?", uid).Get(twofa)
- if err != nil {
- return nil, err
- } else if !has {
- return nil, ErrTwoFactorNotEnrolled{uid}
- }
- return twofa, nil
-}
-
-// DeleteTwoFactorByID deletes two-factor authentication token by given ID.
-func DeleteTwoFactorByID(id, userID int64) error {
- cnt, err := db.GetEngine(db.DefaultContext).ID(id).Delete(&TwoFactor{
- UID: userID,
- })
- if err != nil {
- return err
- } else if cnt != 1 {
- return ErrTwoFactorNotEnrolled{userID}
- }
- return nil
-}
+++ /dev/null
-// Copyright 2018 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 models
-
-import (
- "code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/timeutil"
-
- "github.com/tstranex/u2f"
-)
-
-// U2FRegistration represents the registration data and counter of a security key
-type U2FRegistration struct {
- ID int64 `xorm:"pk autoincr"`
- Name string
- UserID int64 `xorm:"INDEX"`
- Raw []byte
- Counter uint32 `xorm:"BIGINT"`
- CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
- UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
-}
-
-func init() {
- db.RegisterModel(new(U2FRegistration))
-}
-
-// TableName returns a better table name for U2FRegistration
-func (reg U2FRegistration) TableName() string {
- return "u2f_registration"
-}
-
-// Parse will convert the db entry U2FRegistration to an u2f.Registration struct
-func (reg *U2FRegistration) Parse() (*u2f.Registration, error) {
- r := new(u2f.Registration)
- return r, r.UnmarshalBinary(reg.Raw)
-}
-
-func (reg *U2FRegistration) updateCounter(e db.Engine) error {
- _, err := e.ID(reg.ID).Cols("counter").Update(reg)
- return err
-}
-
-// UpdateCounter will update the database value of counter
-func (reg *U2FRegistration) UpdateCounter() error {
- return reg.updateCounter(db.GetEngine(db.DefaultContext))
-}
-
-// U2FRegistrationList is a list of *U2FRegistration
-type U2FRegistrationList []*U2FRegistration
-
-// ToRegistrations will convert all U2FRegistrations to u2f.Registrations
-func (list U2FRegistrationList) ToRegistrations() []u2f.Registration {
- regs := make([]u2f.Registration, 0, len(list))
- for _, reg := range list {
- r, err := reg.Parse()
- if err != nil {
- log.Fatal("parsing u2f registration: %v", err)
- continue
- }
- regs = append(regs, *r)
- }
-
- return regs
-}
-
-func getU2FRegistrationsByUID(e db.Engine, uid int64) (U2FRegistrationList, error) {
- regs := make(U2FRegistrationList, 0)
- return regs, e.Where("user_id = ?", uid).Find(®s)
-}
-
-// GetU2FRegistrationByID returns U2F registration by id
-func GetU2FRegistrationByID(id int64) (*U2FRegistration, error) {
- return getU2FRegistrationByID(db.GetEngine(db.DefaultContext), id)
-}
-
-func getU2FRegistrationByID(e db.Engine, id int64) (*U2FRegistration, error) {
- reg := new(U2FRegistration)
- if found, err := e.ID(id).Get(reg); err != nil {
- return nil, err
- } else if !found {
- return nil, ErrU2FRegistrationNotExist{ID: id}
- }
- return reg, nil
-}
-
-// GetU2FRegistrationsByUID returns all U2F registrations of the given user
-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) {
- raw, err := reg.MarshalBinary()
- if err != nil {
- return nil, err
- }
- r := &U2FRegistration{
- UserID: user.ID,
- Name: name,
- Counter: 0,
- Raw: raw,
- }
- _, err = e.InsertOne(r)
- if err != nil {
- return nil, err
- }
- return r, nil
-}
-
-// 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)
-}
-
-// DeleteRegistration will delete U2FRegistration
-func DeleteRegistration(reg *U2FRegistration) error {
- return deleteRegistration(db.GetEngine(db.DefaultContext), reg)
-}
-
-func deleteRegistration(e db.Engine, reg *U2FRegistration) error {
- _, err := e.Delete(reg)
- return err
-}
+++ /dev/null
-// Copyright 2020 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 models
-
-import (
- "testing"
-
- "code.gitea.io/gitea/models/db"
- "github.com/stretchr/testify/assert"
- "github.com/tstranex/u2f"
-)
-
-func TestGetU2FRegistrationByID(t *testing.T) {
- assert.NoError(t, db.PrepareTestDatabase())
-
- res, err := GetU2FRegistrationByID(1)
- assert.NoError(t, err)
- assert.Equal(t, "U2F Key", res.Name)
-
- _, err = GetU2FRegistrationByID(342432)
- assert.Error(t, err)
- assert.True(t, IsErrU2FRegistrationNotExist(err))
-}
-
-func TestGetU2FRegistrationsByUID(t *testing.T) {
- assert.NoError(t, db.PrepareTestDatabase())
-
- res, err := GetU2FRegistrationsByUID(1)
- assert.NoError(t, err)
- assert.Len(t, res, 1)
- assert.Equal(t, "U2F Key", res[0].Name)
-}
-
-func TestU2FRegistration_TableName(t *testing.T) {
- assert.Equal(t, "u2f_registration", U2FRegistration{}.TableName())
-}
-
-func TestU2FRegistration_UpdateCounter(t *testing.T) {
- assert.NoError(t, db.PrepareTestDatabase())
- reg := db.AssertExistsAndLoadBean(t, &U2FRegistration{ID: 1}).(*U2FRegistration)
- reg.Counter = 1
- assert.NoError(t, reg.UpdateCounter())
- db.AssertExistsIf(t, true, &U2FRegistration{ID: 1, Counter: 1})
-}
-
-func TestU2FRegistration_UpdateLargeCounter(t *testing.T) {
- assert.NoError(t, db.PrepareTestDatabase())
- reg := db.AssertExistsAndLoadBean(t, &U2FRegistration{ID: 1}).(*U2FRegistration)
- reg.Counter = 0xffffffff
- assert.NoError(t, reg.UpdateCounter())
- db.AssertExistsIf(t, true, &U2FRegistration{ID: 1, Counter: 0xffffffff})
-}
-
-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")})
- 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})
-}
-
-func TestDeleteRegistration(t *testing.T) {
- assert.NoError(t, db.PrepareTestDatabase())
- reg := db.AssertExistsAndLoadBean(t, &U2FRegistration{ID: 1}).(*U2FRegistration)
-
- assert.NoError(t, DeleteRegistration(reg))
- db.AssertNotExistsBean(t, &U2FRegistration{ID: 1})
-}
"fmt"
"code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/models/login"
"code.gitea.io/gitea/modules/log"
)
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)
"strings"
"code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/models/login"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
}
otpHeader := ctx.Req.Header.Get("X-Gitea-OTP")
- twofa, err := models.GetTwoFactorByUID(ctx.Context.User.ID)
+ twofa, err := login.GetTwoFactorByUID(ctx.Context.User.ID)
if err != nil {
- if models.IsErrTwoFactorNotEnrolled(err) {
+ if login.IsErrTwoFactorNotEnrolled(err) {
return // No 2FA enrollment for this user
}
ctx.Context.Error(http.StatusInternalServerError)
import (
"net/http"
- "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/models/login"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web/middleware"
if skip, ok := ctx.Data["SkipLocalTwoFA"]; ok && skip.(bool) {
return // Skip 2FA
}
- twofa, err := models.GetTwoFactorByUID(ctx.User.ID)
+ twofa, err := login.GetTwoFactorByUID(ctx.User.ID)
if err != nil {
- if models.IsErrTwoFactorNotEnrolled(err) {
+ if login.IsErrTwoFactorNotEnrolled(err) {
return // No 2FA enrollment for this user
}
ctx.InternalServerError(err)
ctx.Data["Sources"] = sources
ctx.Data["TwoFactorEnabled"] = true
- _, err = models.GetTwoFactorByUID(u.ID)
+ _, err = login.GetTwoFactorByUID(u.ID)
if err != nil {
- if !models.IsErrTwoFactorNotEnrolled(err) {
+ if !login.IsErrTwoFactorNotEnrolled(err) {
ctx.ServerError("IsErrTwoFactorNotEnrolled", err)
return nil
}
}
if form.Reset2FA {
- tf, err := models.GetTwoFactorByUID(u.ID)
- if err != nil && !models.IsErrTwoFactorNotEnrolled(err) {
+ tf, err := login.GetTwoFactorByUID(u.ID)
+ if err != nil && !login.IsErrTwoFactorNotEnrolled(err) {
ctx.ServerError("GetTwoFactorByUID", err)
return
}
- if err = models.DeleteTwoFactorByID(tf.ID, u.ID); err != nil {
+ if err = login.DeleteTwoFactorByID(tf.ID, u.ID); err != nil {
ctx.ServerError("DeleteTwoFactorByID", err)
return
}
"time"
"code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/models/login"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
}
if ctx.IsBasicAuth && ctx.Data["IsApiToken"] != true {
- _, err = models.GetTwoFactorByUID(ctx.User.ID)
+ _, err = login.GetTwoFactorByUID(ctx.User.ID)
if err == nil {
// TODO: This response should be changed to "invalid credentials" for security reasons once the expectation behind it (creating an app token to authenticate) is properly documented
ctx.HandleText(http.StatusUnauthorized, "Users with two-factor authentication enabled cannot perform HTTP/HTTPS operations via plain username and password. Please create and use a personal access token on the user settings page")
return
- } else if !models.IsErrTwoFactorNotEnrolled(err) {
+ } else if !login.IsErrTwoFactorNotEnrolled(err) {
ctx.ServerError("IsErrTwoFactorNotEnrolled", err)
return
}
// If this user is enrolled in 2FA, we can't sign the user in just yet.
// Instead, redirect them to the 2FA authentication page.
- _, err = models.GetTwoFactorByUID(u.ID)
+ _, err = login.GetTwoFactorByUID(u.ID)
if err != nil {
- if models.IsErrTwoFactorNotEnrolled(err) {
+ if login.IsErrTwoFactorNotEnrolled(err) {
handleSignIn(ctx, u, form.Remember)
} else {
ctx.ServerError("UserSignIn", err)
return
}
- regs, err := models.GetU2FRegistrationsByUID(u.ID)
+ regs, err := login.GetU2FRegistrationsByUID(u.ID)
if err == nil && len(regs) > 0 {
ctx.Redirect(setting.AppSubURL + "/user/u2f")
return
}
id := idSess.(int64)
- twofa, err := models.GetTwoFactorByUID(id)
+ twofa, err := login.GetTwoFactorByUID(id)
if err != nil {
ctx.ServerError("UserSignIn", err)
return
}
twofa.LastUsedPasscode = form.Passcode
- if err = models.UpdateTwoFactor(twofa); err != nil {
+ if err = login.UpdateTwoFactor(twofa); err != nil {
ctx.ServerError("UserSignIn", err)
return
}
}
id := idSess.(int64)
- twofa, err := models.GetTwoFactorByUID(id)
+ twofa, err := login.GetTwoFactorByUID(id)
if err != nil {
ctx.ServerError("UserSignIn", err)
return
ctx.ServerError("UserSignIn", err)
return
}
- if err = models.UpdateTwoFactor(twofa); err != nil {
+ if err = login.UpdateTwoFactor(twofa); err != nil {
ctx.ServerError("UserSignIn", err)
return
}
return
}
id := idSess.(int64)
- regs, err := models.GetU2FRegistrationsByUID(id)
+ regs, err := login.GetU2FRegistrationsByUID(id)
if err != nil {
ctx.ServerError("UserSignIn", err)
return
}
challenge := challSess.(*u2f.Challenge)
id := idSess.(int64)
- regs, err := models.GetU2FRegistrationsByUID(id)
+ regs, err := login.GetU2FRegistrationsByUID(id)
if err != nil {
ctx.ServerError("UserSignIn", err)
return
needs2FA := false
if !source.Cfg.(*oauth2.Source).SkipLocalTwoFA {
- _, err := models.GetTwoFactorByUID(u.ID)
- if err != nil && !models.IsErrTwoFactorNotEnrolled(err) {
+ _, err := login.GetTwoFactorByUID(u.ID)
+ if err != nil && !login.IsErrTwoFactorNotEnrolled(err) {
ctx.ServerError("UserSignIn", err)
return
}
}
// If U2F is enrolled -> Redirect to U2F instead
- regs, err := models.GetU2FRegistrationsByUID(u.ID)
+ regs, err := login.GetU2FRegistrationsByUID(u.ID)
if err == nil && len(regs) > 0 {
ctx.Redirect(setting.AppSubURL + "/user/u2f")
return
// If this user is enrolled in 2FA, we can't sign the user in just yet.
// Instead, redirect them to the 2FA authentication page.
// We deliberately ignore the skip local 2fa setting here because we are linking to a previous user here
- _, err := models.GetTwoFactorByUID(u.ID)
+ _, err := login.GetTwoFactorByUID(u.ID)
if err != nil {
- if !models.IsErrTwoFactorNotEnrolled(err) {
+ if !login.IsErrTwoFactorNotEnrolled(err) {
ctx.ServerError("UserLinkAccount", err)
return
}
}
// If U2F is enrolled -> Redirect to U2F instead
- regs, err := models.GetU2FRegistrationsByUID(u.ID)
+ regs, err := login.GetU2FRegistrationsByUID(u.ID)
if err == nil && len(regs) > 0 {
ctx.Redirect(setting.AppSubURL + "/user/u2f")
return
ctx.HTML(http.StatusOK, tplForgotPassword)
}
-func commonResetPassword(ctx *context.Context) (*models.User, *models.TwoFactor) {
+func commonResetPassword(ctx *context.Context) (*models.User, *login.TwoFactor) {
code := ctx.FormString("code")
ctx.Data["Title"] = ctx.Tr("auth.reset_password")
return nil, nil
}
- twofa, err := models.GetTwoFactorByUID(u.ID)
+ twofa, err := login.GetTwoFactorByUID(u.ID)
if err != nil {
- if !models.IsErrTwoFactorNotEnrolled(err) {
+ if !login.IsErrTwoFactorNotEnrolled(err) {
ctx.Error(http.StatusInternalServerError, "CommonResetPassword", err.Error())
return nil, nil
}
}
twofa.LastUsedPasscode = passcode
- if err = models.UpdateTwoFactor(twofa); err != nil {
+ if err = login.UpdateTwoFactor(twofa); err != nil {
ctx.ServerError("ResetPasswdPost: UpdateTwoFactor", err)
return
}
ctx.ServerError("UserSignIn", err)
return
}
- if err = models.UpdateTwoFactor(twofa); err != nil {
+ if err = login.UpdateTwoFactor(twofa); err != nil {
ctx.ServerError("UserSignIn", err)
return
}
func loadSecurityData(ctx *context.Context) {
enrolled := true
- _, err := models.GetTwoFactorByUID(ctx.User.ID)
+ _, err := login.GetTwoFactorByUID(ctx.User.ID)
if err != nil {
- if models.IsErrTwoFactorNotEnrolled(err) {
+ if login.IsErrTwoFactorNotEnrolled(err) {
enrolled = false
} else {
ctx.ServerError("SettingsTwoFactor", err)
}
ctx.Data["TwofaEnrolled"] = enrolled
if enrolled {
- ctx.Data["U2FRegistrations"], err = models.GetU2FRegistrationsByUID(ctx.User.ID)
+ ctx.Data["U2FRegistrations"], err = login.GetU2FRegistrationsByUID(ctx.User.ID)
if err != nil {
ctx.ServerError("GetU2FRegistrationsByUID", err)
return
"net/http"
"strings"
- "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/models/login"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsSecurity"] = true
- t, err := models.GetTwoFactorByUID(ctx.User.ID)
+ t, err := login.GetTwoFactorByUID(ctx.User.ID)
if err != nil {
- if models.IsErrTwoFactorNotEnrolled(err) {
+ if login.IsErrTwoFactorNotEnrolled(err) {
ctx.Flash.Error(ctx.Tr("setting.twofa_not_enrolled"))
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
}
return
}
- if err = models.UpdateTwoFactor(t); err != nil {
+ if err = login.UpdateTwoFactor(t); err != nil {
ctx.ServerError("SettingsTwoFactor: Failed to UpdateTwoFactor", err)
return
}
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsSecurity"] = true
- t, err := models.GetTwoFactorByUID(ctx.User.ID)
+ t, err := login.GetTwoFactorByUID(ctx.User.ID)
if err != nil {
- if models.IsErrTwoFactorNotEnrolled(err) {
+ if login.IsErrTwoFactorNotEnrolled(err) {
ctx.Flash.Error(ctx.Tr("setting.twofa_not_enrolled"))
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
}
return
}
- if err = models.DeleteTwoFactorByID(t.ID, ctx.User.ID); err != nil {
- if models.IsErrTwoFactorNotEnrolled(err) {
+ if err = login.DeleteTwoFactorByID(t.ID, ctx.User.ID); err != nil {
+ if login.IsErrTwoFactorNotEnrolled(err) {
// There is a potential DB race here - we must have been disabled by another request in the intervening period
ctx.Flash.Success(ctx.Tr("settings.twofa_disabled"))
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsSecurity"] = true
- t, err := models.GetTwoFactorByUID(ctx.User.ID)
+ t, err := login.GetTwoFactorByUID(ctx.User.ID)
if t != nil {
// already enrolled - we should redirect back!
log.Warn("Trying to re-enroll %-v in twofa when already enrolled", ctx.User)
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
return
}
- if err != nil && !models.IsErrTwoFactorNotEnrolled(err) {
+ if err != nil && !login.IsErrTwoFactorNotEnrolled(err) {
ctx.ServerError("SettingsTwoFactor: GetTwoFactorByUID", err)
return
}
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsSecurity"] = true
- t, err := models.GetTwoFactorByUID(ctx.User.ID)
+ t, err := login.GetTwoFactorByUID(ctx.User.ID)
if t != nil {
// already enrolled
ctx.Flash.Error(ctx.Tr("setting.twofa_is_enrolled"))
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
return
}
- if err != nil && !models.IsErrTwoFactorNotEnrolled(err) {
+ if err != nil && !login.IsErrTwoFactorNotEnrolled(err) {
ctx.ServerError("SettingsTwoFactor: Failed to check if already enrolled with GetTwoFactorByUID", err)
return
}
return
}
- t = &models.TwoFactor{
+ t = &login.TwoFactor{
UID: ctx.User.ID,
}
err = t.SetSecret(secret)
log.Error("Unable to save changes to the session: %v", err)
}
- if err = models.NewTwoFactor(t); err != nil {
+ if err = login.NewTwoFactor(t); err != nil {
// FIXME: We need to handle a unique constraint fail here it's entirely possible that another request has beaten us.
// If there is a unique constraint fail we should just tolerate the error
ctx.ServerError("SettingsTwoFactor: Failed to save two factor", err)
"errors"
"net/http"
- "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/models/login"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
ctx.ServerError("Unable to set session key for u2fChallenge", err)
return
}
- regs, err := models.GetU2FRegistrationsByUID(ctx.User.ID)
+ regs, err := login.GetU2FRegistrationsByUID(ctx.User.ID)
if err != nil {
ctx.ServerError("GetU2FRegistrationsByUID", err)
return
ctx.ServerError("u2f.Register", err)
return
}
- if _, err = models.CreateRegistration(ctx.User, name, reg); err != nil {
+ if _, err = login.CreateRegistration(ctx.User.ID, name, reg); err != nil {
ctx.ServerError("u2f.Register", err)
return
}
// U2FDelete deletes an security key by id
func U2FDelete(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.U2FDeleteForm)
- reg, err := models.GetU2FRegistrationByID(form.ID)
+ reg, err := login.GetU2FRegistrationByID(form.ID)
if err != nil {
- if models.IsErrU2FRegistrationNotExist(err) {
+ if login.IsErrU2FRegistrationNotExist(err) {
ctx.Status(200)
return
}
ctx.Status(401)
return
}
- if err := models.DeleteRegistration(reg); err != nil {
+ if err := login.DeleteRegistration(reg); err != nil {
ctx.ServerError("DeleteRegistration", err)
return
}