test(authentication): add missing tests (#5482)

Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
pull/5483/head
James Elliott 2023-05-25 11:17:35 +10:00 committed by GitHub
parent f1b3fc7b31
commit fbbeef3ae8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 286 additions and 32 deletions

4
go.mod
View File

@ -11,7 +11,7 @@ require (
github.com/fasthttp/session/v2 v2.5.0
github.com/fsnotify/fsnotify v1.6.0
github.com/go-asn1-ber/asn1-ber v1.5.4
github.com/go-crypt/crypt v0.2.7
github.com/go-crypt/crypt v0.2.9
github.com/go-ldap/ldap/v3 v3.4.5-0.20230521105649-cdb0754f6668
github.com/go-rod/rod v0.113.0
github.com/go-sql-driver/mysql v1.7.1
@ -70,7 +70,7 @@ require (
github.com/ecordell/optgen v0.0.6 // indirect
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
github.com/go-crypt/x v0.2.0 // indirect
github.com/go-crypt/x v0.2.1 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/go-webauthn/revoke v0.1.9 // indirect
github.com/golang/glog v1.0.0 // indirect

6
go.sum
View File

@ -132,8 +132,14 @@ github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF
github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-crypt/crypt v0.2.7 h1:Ir6E59c1wrskJhpJXMqaynHA2xAxpGN7nQXlLkbpzR0=
github.com/go-crypt/crypt v0.2.7/go.mod h1:ulieouNs4qwFCq4wF61oyTQYXAXSoOv995EU4hcHwMU=
github.com/go-crypt/crypt v0.2.8 h1:nI4HYMSHpXi68N5/LrhZCV8gDqLcIwtnHs8Xkuipooo=
github.com/go-crypt/crypt v0.2.8/go.mod h1:JjzdTYE2mArb6nBoIvvpF7o46/rK/1pfmlArCRMTFUk=
github.com/go-crypt/crypt v0.2.9 h1:5gWWTId2Qyqs9ROIsegt5pnqo9wUSRLbhpkR6JSftjg=
github.com/go-crypt/crypt v0.2.9/go.mod h1:JjzdTYE2mArb6nBoIvvpF7o46/rK/1pfmlArCRMTFUk=
github.com/go-crypt/x v0.2.0 h1:rHMiKRAu6kFc+xAnQywDb3iHGpvrFbIGXnP3IfCZ+2U=
github.com/go-crypt/x v0.2.0/go.mod h1:uLo5o+Cc8nvahDASQpntR1g3ZMUoq2LM/859PkhykC4=
github.com/go-crypt/x v0.2.1 h1:OGw78Bswme9lffCOX6tyuC280ouU5391glsvThMtM5U=
github.com/go-crypt/x v0.2.1/go.mod h1:Q/y9rms7yw4/1CavBlNGn0Itg4HqwNpe1N9FX0TxXrc=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=

View File

@ -23,7 +23,7 @@ import (
type FileUserProvider struct {
config *schema.FileAuthenticationBackend
hash algorithm.Hash
database *FileUserDatabase
database FileUserDatabase
mutex *sync.Mutex
timeoutReload time.Time
}
@ -34,7 +34,7 @@ func NewFileUserProvider(config *schema.FileAuthenticationBackend) (provider *Fi
config: config,
mutex: &sync.Mutex{},
timeoutReload: time.Now().Add(-1 * time.Second),
database: NewFileUserDatabase(config.Path, config.Search.Email, config.Search.CaseInsensitive),
database: NewYAMLUserDatabase(config.Path, config.Search.Email, config.Search.CaseInsensitive),
}
}
@ -138,7 +138,7 @@ func (p *FileUserProvider) StartupCheck() (err error) {
}
if p.database == nil {
p.database = NewFileUserDatabase(p.config.Path, p.config.Search.Email, p.config.Search.CaseInsensitive)
p.database = NewYAMLUserDatabase(p.config.Path, p.config.Search.Email, p.config.Search.CaseInsensitive)
}
if err = p.database.Load(); err != nil {
@ -197,10 +197,6 @@ func NewFileCryptoHashFromConfig(config schema.Password) (hash algorithm.Hash, e
return nil, fmt.Errorf("failed to initialize hash settings: %w", err)
}
if err = hash.Validate(); err != nil {
return nil, fmt.Errorf("failed to validate hash settings: %w", err)
}
return hash, nil
}

View File

@ -12,9 +12,16 @@ import (
"gopkg.in/yaml.v3"
)
// NewFileUserDatabase creates a new FileUserDatabase.
func NewFileUserDatabase(filePath string, searchEmail, searchCI bool) (database *FileUserDatabase) {
return &FileUserDatabase{
type FileUserDatabase interface {
Save() (err error)
Load() (err error)
GetUserDetails(username string) (user DatabaseUserDetails, err error)
SetUserDetails(username string, details *DatabaseUserDetails)
}
// NewYAMLUserDatabase creates a new YAMLUserDatabase.
func NewYAMLUserDatabase(filePath string, searchEmail, searchCI bool) (database *YAMLUserDatabase) {
return &YAMLUserDatabase{
RWMutex: &sync.RWMutex{},
Path: filePath,
Users: map[string]DatabaseUserDetails{},
@ -25,8 +32,8 @@ func NewFileUserDatabase(filePath string, searchEmail, searchCI bool) (database
}
}
// FileUserDatabase is a user details database that is concurrency safe database and can be reloaded.
type FileUserDatabase struct {
// YAMLUserDatabase is a user details database that is concurrency safe database and can be reloaded.
type YAMLUserDatabase struct {
*sync.RWMutex
Path string
@ -39,7 +46,7 @@ type FileUserDatabase struct {
}
// Save the database to disk.
func (m *FileUserDatabase) Save() (err error) {
func (m *YAMLUserDatabase) Save() (err error) {
m.RLock()
defer m.RUnlock()
@ -52,7 +59,7 @@ func (m *FileUserDatabase) Save() (err error) {
}
// Load the database from disk.
func (m *FileUserDatabase) Load() (err error) {
func (m *YAMLUserDatabase) Load() (err error) {
yml := &DatabaseModel{Users: map[string]UserDetailsModel{}}
if err = yml.Read(m.Path); err != nil {
@ -71,7 +78,7 @@ func (m *FileUserDatabase) Load() (err error) {
}
// LoadAliases performs the loading of alias information from the database.
func (m *FileUserDatabase) LoadAliases() (err error) {
func (m *YAMLUserDatabase) LoadAliases() (err error) {
if m.SearchEmail || m.SearchCI {
for k, user := range m.Users {
if m.SearchEmail && user.Email != "" {
@ -91,7 +98,7 @@ func (m *FileUserDatabase) LoadAliases() (err error) {
return nil
}
func (m *FileUserDatabase) loadAlias(k string) (err error) {
func (m *YAMLUserDatabase) loadAlias(k string) (err error) {
u := strings.ToLower(k)
if u != k {
@ -113,7 +120,7 @@ func (m *FileUserDatabase) loadAlias(k string) (err error) {
return nil
}
func (m *FileUserDatabase) loadAliasEmail(k string, user DatabaseUserDetails) (err error) {
func (m *YAMLUserDatabase) loadAliasEmail(k string, user DatabaseUserDetails) (err error) {
e := strings.ToLower(user.Email)
var duplicates []string
@ -145,7 +152,7 @@ func (m *FileUserDatabase) loadAliasEmail(k string, user DatabaseUserDetails) (e
// GetUserDetails get a DatabaseUserDetails given a username as a value type where the username must be the users actual
// username.
func (m *FileUserDatabase) GetUserDetails(username string) (user DatabaseUserDetails, err error) {
func (m *YAMLUserDatabase) GetUserDetails(username string) (user DatabaseUserDetails, err error) {
m.RLock()
defer m.RUnlock()
@ -172,7 +179,7 @@ func (m *FileUserDatabase) GetUserDetails(username string) (user DatabaseUserDet
}
// SetUserDetails sets the DatabaseUserDetails for a given user.
func (m *FileUserDatabase) SetUserDetails(username string, details *DatabaseUserDetails) {
func (m *YAMLUserDatabase) SetUserDetails(username string, details *DatabaseUserDetails) {
if details == nil {
return
}
@ -184,8 +191,8 @@ func (m *FileUserDatabase) SetUserDetails(username string, details *DatabaseUser
m.Unlock()
}
// ToDatabaseModel converts the FileUserDatabase into the DatabaseModel for saving.
func (m *FileUserDatabase) ToDatabaseModel() (model *DatabaseModel) {
// ToDatabaseModel converts the YAMLUserDatabase into the DatabaseModel for saving.
func (m *YAMLUserDatabase) ToDatabaseModel() (model *DatabaseModel) {
model = &DatabaseModel{
Users: map[string]UserDetailsModel{},
}
@ -236,8 +243,8 @@ type DatabaseModel struct {
Users map[string]UserDetailsModel `yaml:"users" valid:"required"`
}
// ReadToFileUserDatabase reads the DatabaseModel into a FileUserDatabase.
func (m *DatabaseModel) ReadToFileUserDatabase(db *FileUserDatabase) (err error) {
// ReadToFileUserDatabase reads the DatabaseModel into a YAMLUserDatabase.
func (m *DatabaseModel) ReadToFileUserDatabase(db *YAMLUserDatabase) (err error) {
users := map[string]DatabaseUserDetails{}
var udm *DatabaseUserDetails

View File

@ -0,0 +1,89 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/authelia/authelia/v4/internal/authentication (interfaces: FileUserDatabase)
// Package authentication is a generated GoMock package.
package authentication
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
)
// MockFileUserDatabase is a mock of FileUserDatabase interface.
type MockFileUserDatabase struct {
ctrl *gomock.Controller
recorder *MockFileUserDatabaseMockRecorder
}
// MockFileUserDatabaseMockRecorder is the mock recorder for MockFileUserDatabase.
type MockFileUserDatabaseMockRecorder struct {
mock *MockFileUserDatabase
}
// NewMockFileUserDatabase creates a new mock instance.
func NewMockFileUserDatabase(ctrl *gomock.Controller) *MockFileUserDatabase {
mock := &MockFileUserDatabase{ctrl: ctrl}
mock.recorder = &MockFileUserDatabaseMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockFileUserDatabase) EXPECT() *MockFileUserDatabaseMockRecorder {
return m.recorder
}
// GetUserDetails mocks base method.
func (m *MockFileUserDatabase) GetUserDetails(arg0 string) (DatabaseUserDetails, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetUserDetails", arg0)
ret0, _ := ret[0].(DatabaseUserDetails)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetUserDetails indicates an expected call of GetUserDetails.
func (mr *MockFileUserDatabaseMockRecorder) GetUserDetails(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserDetails", reflect.TypeOf((*MockFileUserDatabase)(nil).GetUserDetails), arg0)
}
// Load mocks base method.
func (m *MockFileUserDatabase) Load() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Load")
ret0, _ := ret[0].(error)
return ret0
}
// Load indicates an expected call of Load.
func (mr *MockFileUserDatabaseMockRecorder) Load() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Load", reflect.TypeOf((*MockFileUserDatabase)(nil).Load))
}
// Save mocks base method.
func (m *MockFileUserDatabase) Save() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Save")
ret0, _ := ret[0].(error)
return ret0
}
// Save indicates an expected call of Save.
func (mr *MockFileUserDatabaseMockRecorder) Save() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Save", reflect.TypeOf((*MockFileUserDatabase)(nil).Save))
}
// SetUserDetails mocks base method.
func (m *MockFileUserDatabase) SetUserDetails(arg0 string, arg1 *DatabaseUserDetails) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetUserDetails", arg0, arg1)
}
// SetUserDetails indicates an expected call of SetUserDetails.
func (mr *MockFileUserDatabaseMockRecorder) SetUserDetails(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetUserDetails", reflect.TypeOf((*MockFileUserDatabase)(nil).SetUserDetails), arg0, arg1)
}

View File

@ -0,0 +1,93 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/go-crypt/crypt/algorithm (interfaces: Hash)
// Package authentication is a generated GoMock package.
package authentication
import (
reflect "reflect"
algorithm "github.com/go-crypt/crypt/algorithm"
gomock "github.com/golang/mock/gomock"
)
// MockHash is a mock of Hash interface.
type MockHash struct {
ctrl *gomock.Controller
recorder *MockHashMockRecorder
}
// MockHashMockRecorder is the mock recorder for MockHash.
type MockHashMockRecorder struct {
mock *MockHash
}
// NewMockHash creates a new mock instance.
func NewMockHash(ctrl *gomock.Controller) *MockHash {
mock := &MockHash{ctrl: ctrl}
mock.recorder = &MockHashMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockHash) EXPECT() *MockHashMockRecorder {
return m.recorder
}
// Hash mocks base method.
func (m *MockHash) Hash(arg0 string) (algorithm.Digest, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Hash", arg0)
ret0, _ := ret[0].(algorithm.Digest)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Hash indicates an expected call of Hash.
func (mr *MockHashMockRecorder) Hash(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Hash", reflect.TypeOf((*MockHash)(nil).Hash), arg0)
}
// HashWithSalt mocks base method.
func (m *MockHash) HashWithSalt(arg0 string, arg1 []byte) (algorithm.Digest, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HashWithSalt", arg0, arg1)
ret0, _ := ret[0].(algorithm.Digest)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// HashWithSalt indicates an expected call of HashWithSalt.
func (mr *MockHashMockRecorder) HashWithSalt(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HashWithSalt", reflect.TypeOf((*MockHash)(nil).HashWithSalt), arg0, arg1)
}
// MustHash mocks base method.
func (m *MockHash) MustHash(arg0 string) algorithm.Digest {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "MustHash", arg0)
ret0, _ := ret[0].(algorithm.Digest)
return ret0
}
// MustHash indicates an expected call of MustHash.
func (mr *MockHashMockRecorder) MustHash(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MustHash", reflect.TypeOf((*MockHash)(nil).MustHash), arg0)
}
// Validate mocks base method.
func (m *MockHash) Validate() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Validate")
ret0, _ := ret[0].(error)
return ret0
}
// Validate indicates an expected call of Validate.
func (mr *MockHashMockRecorder) Validate() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockHash)(nil).Validate))
}

View File

@ -14,6 +14,7 @@ import (
"github.com/go-crypt/crypt/algorithm/bcrypt"
"github.com/go-crypt/crypt/algorithm/pbkdf2"
"github.com/go-crypt/crypt/algorithm/scrypt"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -129,7 +130,7 @@ func TestShouldReloadDatabase(t *testing.T) {
provider.config.Path = p
provider.database = NewFileUserDatabase(p, provider.config.Search.Email, provider.config.Search.CaseInsensitive)
provider.database = NewYAMLUserDatabase(p, provider.config.Search.Email, provider.config.Search.CaseInsensitive)
},
false,
"",
@ -306,7 +307,10 @@ func TestShouldUpdatePasswordHashingAlgorithmToArgon2id(t *testing.T) {
assert.NoError(t, provider.StartupCheck())
assert.True(t, strings.HasPrefix(provider.database.Users["harry"].Digest.Encode(), "$6$"))
db, ok := provider.database.(*YAMLUserDatabase)
require.True(t, ok)
assert.True(t, strings.HasPrefix(db.Users["harry"].Digest.Encode(), "$6$"))
err := provider.UpdatePassword("harry", "newpassword")
assert.NoError(t, err)
@ -315,10 +319,10 @@ func TestShouldUpdatePasswordHashingAlgorithmToArgon2id(t *testing.T) {
assert.NoError(t, provider.StartupCheck())
ok, err := provider.CheckUserPassword("harry", "newpassword")
ok, err = provider.CheckUserPassword("harry", "newpassword")
assert.NoError(t, err)
assert.True(t, ok)
assert.True(t, strings.HasPrefix(provider.database.Users["harry"].Digest.Encode(), "$argon2id$"))
assert.True(t, strings.HasPrefix(db.Users["harry"].Digest.Encode(), "$argon2id$"))
})
}
@ -333,7 +337,10 @@ func TestShouldUpdatePasswordHashingAlgorithmToSHA512(t *testing.T) {
assert.NoError(t, provider.StartupCheck())
assert.True(t, strings.HasPrefix(provider.database.Users["john"].Digest.Encode(), "$argon2id$"))
db, ok := provider.database.(*YAMLUserDatabase)
require.True(t, ok)
assert.True(t, strings.HasPrefix(db.Users["john"].Digest.Encode(), "$argon2id$"))
err := provider.UpdatePassword("john", "newpassword")
assert.NoError(t, err)
@ -342,10 +349,10 @@ func TestShouldUpdatePasswordHashingAlgorithmToSHA512(t *testing.T) {
assert.NoError(t, provider.StartupCheck())
ok, err := provider.CheckUserPassword("john", "newpassword")
ok, err = provider.CheckUserPassword("john", "newpassword")
assert.NoError(t, err)
assert.True(t, ok)
assert.True(t, strings.HasPrefix(provider.database.Users["john"].Digest.Encode(), "$6$"))
assert.True(t, strings.HasPrefix(db.Users["john"].Digest.Encode(), "$6$"))
})
}
@ -657,6 +664,58 @@ func TestNewFileCryptoHashFromConfig(t *testing.T) {
}
}
func TestHashError(t *testing.T) {
WithDatabase(t, UserDatabaseContent, func(path string) {
config := DefaultFileAuthenticationBackendConfiguration
config.Search.CaseInsensitive = true
config.Path = path
provider := NewFileUserProvider(&config)
assert.NoError(t, provider.StartupCheck())
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mock := NewMockHash(ctrl)
provider.hash = mock
mock.EXPECT().Hash("apple123").Return(nil, fmt.Errorf("failed to mock hash"))
assert.EqualError(t, provider.UpdatePassword("john", "apple123"), "failed to mock hash")
})
}
func TestDatabaseError(t *testing.T) {
WithDatabase(t, UserDatabaseContent, func(path string) {
db := NewYAMLUserDatabase(path, false, false)
assert.NoError(t, db.Load())
config := DefaultFileAuthenticationBackendConfiguration
config.Search.CaseInsensitive = true
config.Path = path
provider := NewFileUserProvider(&config)
assert.NoError(t, provider.StartupCheck())
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mock := NewMockFileUserDatabase(ctrl)
provider.database = mock
gomock.InOrder(
mock.EXPECT().GetUserDetails("john").Return(db.GetUserDetails("john")),
mock.EXPECT().SetUserDetails("john", gomock.Any()),
mock.EXPECT().Save().Return(fmt.Errorf("failed to mock save")),
)
assert.EqualError(t, provider.UpdatePassword("john", "apple123"), "failed to mock save")
})
}
var (
DefaultFileAuthenticationBackendConfiguration = schema.FileAuthenticationBackend{
Path: "",

View File

@ -5,3 +5,5 @@ package authentication
//go:generate mockgen -package authentication -destination ldap_client_mock.go -mock_names LDAPClient=MockLDAPClient github.com/authelia/authelia/v4/internal/authentication LDAPClient
//go:generate mockgen -package authentication -destination ldap_client_factory_mock.go -mock_names LDAPClientFactory=MockLDAPClientFactory github.com/authelia/authelia/v4/internal/authentication LDAPClientFactory
//go:generate mockgen -package authentication -destination file_user_provider_database_mock_test.go -mock_names FileUserDatabase=MockFileUserDatabase github.com/authelia/authelia/v4/internal/authentication FileUserDatabase
//go:generate mockgen -package authentication -destination file_user_provider_hash_mock_test.go -mock_names Hash=MockHash github.com/go-crypt/crypt/algorithm Hash

View File

@ -154,6 +154,8 @@ func TestIssuerURL(t *testing.T) {
func TestShouldCallNextWithAutheliaCtx(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := &fasthttp.RequestCtx{}
configuration := schema.Configuration{}
userProvider := mocks.NewMockUserProvider(ctrl)