Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move twofactor to models/login #17143

Merged
merged 2 commits into from
Sep 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 0 additions & 41 deletions models/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

// ____ ___ .__ .___
// | | \______ | | _________ __| _/
// | | /\____ \| | / _ \__ \ / __ |
Expand Down Expand Up @@ -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
}

// .___ ________ .___ .__
// | | ______ ________ __ ____ \______ \ ____ ______ ____ ____ __| _/____ ____ ____ |__| ____ ______
// | |/ ___// ___/ | \_/ __ \ | | \_/ __ \\____ \_/ __ \ / \ / __ |/ __ \ / \_/ ___\| |/ __ \ / ___/
Expand Down
1 change: 1 addition & 0 deletions models/login/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ func TestMain(m *testing.M) {
"oauth2_application.yml",
"oauth2_authorization_code.yml",
"oauth2_grant.yml",
"u2f_registration.yml",
)
}
28 changes: 24 additions & 4 deletions models/twofactor.go → models/login/twofactor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"`
Expand All @@ -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)
}
Expand All @@ -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
}

Expand Down
34 changes: 29 additions & 5 deletions models/u2f.go → models/login/u2f.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,40 @@
// 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"

"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"`
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down
8 changes: 4 additions & 4 deletions models/u2f_test.go → models/login/u2f_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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) {
Expand Down
5 changes: 3 additions & 2 deletions models/pull_sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand Down
13 changes: 7 additions & 6 deletions models/repo_sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
5 changes: 3 additions & 2 deletions models/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
5 changes: 3 additions & 2 deletions models/userlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"

"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/login"
"code.gitea.io/gitea/modules/log"
)

Expand Down Expand Up @@ -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)
Expand Down
5 changes: 3 additions & 2 deletions modules/context/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"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"
Expand Down Expand Up @@ -219,9 +220,9 @@ func (ctx *APIContext) CheckForOTP() {
}

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)
Expand Down
6 changes: 3 additions & 3 deletions modules/context/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ package context
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"
Expand Down Expand Up @@ -154,9 +154,9 @@ func ToggleAPI(options *ToggleOptions) func(ctx *APIContext) {
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)
Expand Down
Loading