diff options
author | Jonas Franz <info@jonasfranz.software> | 2018-05-19 16:12:37 +0200 |
---|---|---|
committer | Lauris BH <lauris@nix.lv> | 2018-05-19 17:12:37 +0300 |
commit | 951309f76aab22e3742e8872bf0642fcea2570ae (patch) | |
tree | 041e43fcc393d0ca07e4e274b28c1938e6604780 /models | |
parent | f933bcdfeef359d8d9592dc0cf0aea244963e23c (diff) | |
download | gitea-951309f76aab22e3742e8872bf0642fcea2570ae.tar.gz gitea-951309f76aab22e3742e8872bf0642fcea2570ae.zip |
Add support for FIDO U2F (#3971)
* Add support for U2F
Signed-off-by: Jonas Franz <info@jonasfranz.software>
* Add vendor library
Add missing translations
Signed-off-by: Jonas Franz <info@jonasfranz.software>
* Minor improvements
Signed-off-by: Jonas Franz <info@jonasfranz.software>
* Add U2F support for Firefox, Chrome (Android) by introducing a custom JS library
Add U2F error handling
Signed-off-by: Jonas Franz <info@jonasfranz.software>
* Add U2F login page to OAuth
Signed-off-by: Jonas Franz <info@jonasfranz.software>
* Move U2F user settings to a separate file
Signed-off-by: Jonas Franz <info@jonasfranz.software>
* Add unit tests for u2f model
Renamed u2f table name
Signed-off-by: Jonas Franz <info@jonasfranz.software>
* Fix problems caused by refactoring
Signed-off-by: Jonas Franz <info@jonasfranz.software>
* Add U2F documentation
Signed-off-by: Jonas Franz <info@jonasfranz.software>
* Remove not needed console.log-s
Signed-off-by: Jonas Franz <info@jonasfranz.software>
* Add default values to app.ini.sample
Add FIDO U2F to comparison
Signed-off-by: Jonas Franz <info@jonasfranz.software>
Diffstat (limited to 'models')
-rw-r--r-- | models/error.go | 22 | ||||
-rw-r--r-- | models/fixtures/u2f_registration.yml | 7 | ||||
-rw-r--r-- | models/migrations/migrations.go | 2 | ||||
-rw-r--r-- | models/migrations/v65.go | 19 | ||||
-rw-r--r-- | models/models.go | 1 | ||||
-rw-r--r-- | models/u2f.go | 120 | ||||
-rw-r--r-- | models/u2f_test.go | 61 |
7 files changed, 232 insertions, 0 deletions
diff --git a/models/error.go b/models/error.go index cdb18d23ce..316f8c34bd 100644 --- a/models/error.go +++ b/models/error.go @@ -1237,3 +1237,25 @@ func IsErrExternalLoginUserNotExist(err error) bool { 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 +} diff --git a/models/fixtures/u2f_registration.yml b/models/fixtures/u2f_registration.yml new file mode 100644 index 0000000000..4a9d1d9624 --- /dev/null +++ b/models/fixtures/u2f_registration.yml @@ -0,0 +1,7 @@ +- + id: 1 + name: "U2F Key" + user_id: 1 + counter: 0 + created_unix: 946684800 + updated_unix: 946684800 diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index e85da8de79..7c90f1eb1f 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -182,6 +182,8 @@ var migrations = []Migration{ NewMigration("add language column for user setting", addLanguageSetting), // v64 -> v65 NewMigration("add multiple assignees", addMultipleAssignees), + // v65 -> v66 + NewMigration("add u2f", addU2FReg), } // Migrate database to current version diff --git a/models/migrations/v65.go b/models/migrations/v65.go new file mode 100644 index 0000000000..f73e632877 --- /dev/null +++ b/models/migrations/v65.go @@ -0,0 +1,19 @@ +package migrations + +import ( + "code.gitea.io/gitea/modules/util" + "github.com/go-xorm/xorm" +) + +func addU2FReg(x *xorm.Engine) error { + type U2FRegistration struct { + ID int64 `xorm:"pk autoincr"` + Name string + UserID int64 `xorm:"INDEX"` + Raw []byte + Counter uint32 + CreatedUnix util.TimeStamp `xorm:"INDEX created"` + UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` + } + return x.Sync2(&U2FRegistration{}) +} diff --git a/models/models.go b/models/models.go index 9213cd3b79..ddf784deee 100644 --- a/models/models.go +++ b/models/models.go @@ -120,6 +120,7 @@ func init() { new(LFSLock), new(Reaction), new(IssueAssignees), + new(U2FRegistration), ) gonicNames := []string{"SSL", "UID"} diff --git a/models/u2f.go b/models/u2f.go new file mode 100644 index 0000000000..a7b031d9e2 --- /dev/null +++ b/models/u2f.go @@ -0,0 +1,120 @@ +// 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/modules/log" + "code.gitea.io/gitea/modules/util" + + "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 + CreatedUnix util.TimeStamp `xorm:"INDEX created"` + UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` +} + +// 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 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(x) +} + +// 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, len(list)) + for _, reg := range list { + r, err := reg.Parse() + if err != nil { + log.Fatal(4, "parsing u2f registration: %v", err) + continue + } + regs = append(regs, *r) + } + + return regs +} + +func getU2FRegistrationsByUID(e 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(x, id) +} + +func getU2FRegistrationByID(e 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(x, uid) +} + +func createRegistration(e 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(x, user, name, reg) +} + +// DeleteRegistration will delete U2FRegistration +func DeleteRegistration(reg *U2FRegistration) error { + return deleteRegistration(x, reg) +} + +func deleteRegistration(e Engine, reg *U2FRegistration) error { + _, err := e.Delete(reg) + return err +} diff --git a/models/u2f_test.go b/models/u2f_test.go new file mode 100644 index 0000000000..6d6cd495ca --- /dev/null +++ b/models/u2f_test.go @@ -0,0 +1,61 @@ +package models + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tstranex/u2f" +) + +func TestGetU2FRegistrationByID(t *testing.T) { + assert.NoError(t, 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, 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, PrepareTestDatabase()) + reg := AssertExistsAndLoadBean(t, &U2FRegistration{ID: 1}).(*U2FRegistration) + reg.Counter = 1 + assert.NoError(t, reg.UpdateCounter()) + AssertExistsIf(t, true, &U2FRegistration{ID: 1, Counter: 1}) +} + +func TestCreateRegistration(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + user := 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) + + AssertExistsIf(t, true, &U2FRegistration{Name: "U2F Created Key", UserID: user.ID}) +} + +func TestDeleteRegistration(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + reg := AssertExistsAndLoadBean(t, &U2FRegistration{ID: 1}).(*U2FRegistration) + + assert.NoError(t, DeleteRegistration(reg)) + AssertNotExistsBean(t, &U2FRegistration{ID: 1}) +} |