2019-04-24 21:52:08 +00:00
package authentication
import (
2023-05-23 19:57:53 +00:00
"fmt"
2019-04-24 21:52:08 +00:00
"os"
2023-05-23 19:57:53 +00:00
"path/filepath"
2022-10-18 00:57:08 +00:00
"regexp"
2021-03-22 09:04:09 +00:00
"runtime"
2020-03-06 01:38:02 +00:00
"strings"
2023-05-23 19:57:53 +00:00
"sync"
2019-04-24 21:52:08 +00:00
"testing"
2023-05-23 19:57:53 +00:00
"time"
2019-04-24 21:52:08 +00:00
2023-05-23 19:57:53 +00:00
"github.com/go-crypt/crypt/algorithm/bcrypt"
"github.com/go-crypt/crypt/algorithm/pbkdf2"
"github.com/go-crypt/crypt/algorithm/scrypt"
2023-05-25 01:17:35 +00:00
"github.com/golang/mock/gomock"
2019-04-24 21:52:08 +00:00
"github.com/stretchr/testify/assert"
2020-06-17 06:25:35 +00:00
"github.com/stretchr/testify/require"
2020-04-05 12:37:21 +00:00
2021-08-11 01:04:35 +00:00
"github.com/authelia/authelia/v4/internal/configuration/schema"
2019-04-24 21:52:08 +00:00
)
2023-05-23 19:57:53 +00:00
func TestShouldErrorPermissionsOnLocalFS ( t * testing . T ) {
if runtime . GOOS == "windows" {
t . Skip ( "skipping test due to being on windows" )
2019-04-24 21:52:08 +00:00
}
2023-05-23 19:57:53 +00:00
dir := t . TempDir ( )
2019-04-24 21:52:08 +00:00
2023-05-23 19:57:53 +00:00
_ = os . Mkdir ( filepath . Join ( dir , "noperms" ) , 0000 )
f := filepath . Join ( dir , "noperms" , "users_database.yml" )
require . EqualError ( t , checkDatabase ( f ) , fmt . Sprintf ( "error checking user authentication database file: stat %s: permission denied" , f ) )
}
func TestShouldErrorAndGenerateUserDB ( t * testing . T ) {
dir := t . TempDir ( )
f := filepath . Join ( dir , "users_database.yml" )
require . EqualError ( t , checkDatabase ( f ) , fmt . Sprintf ( "user authentication database file doesn't exist at path '%s' and has been generated" , f ) )
}
func TestShouldErrorFailCreateDB ( t * testing . T ) {
dir := t . TempDir ( )
assert . NoError ( t , os . Mkdir ( filepath . Join ( dir , "x" ) , 0000 ) )
f := filepath . Join ( dir , "x" , "users.yml" )
2023-01-26 02:23:47 +00:00
provider := NewFileUserProvider ( & schema . AuthenticationBackendFile { Path : f , Password : schema . DefaultPasswordConfig } )
2023-05-23 19:57:53 +00:00
require . NotNil ( t , provider )
assert . EqualError ( t , provider . StartupCheck ( ) , "one or more errors occurred checking the authentication database" )
assert . NotNil ( t , provider . database )
2019-04-24 21:52:08 +00:00
2023-05-23 19:57:53 +00:00
reloaded , err := provider . Reload ( )
2019-04-24 21:52:08 +00:00
2023-05-23 19:57:53 +00:00
assert . False ( t , reloaded )
assert . EqualError ( t , err , fmt . Sprintf ( "failed to reload: error reading the authentication database: failed to read the '%s' file: open %s: permission denied" , f , f ) )
}
func TestShouldErrorBadPasswordConfig ( t * testing . T ) {
dir := t . TempDir ( )
f := filepath . Join ( dir , "users.yml" )
require . NoError ( t , os . WriteFile ( f , UserDatabaseContent , 0600 ) )
2023-01-26 02:23:47 +00:00
provider := NewFileUserProvider ( & schema . AuthenticationBackendFile { Path : f } )
2023-05-23 19:57:53 +00:00
require . NotNil ( t , provider )
assert . EqualError ( t , provider . StartupCheck ( ) , "failed to initialize hash settings: argon2 validation error: parameter is invalid: parameter 't' must be between 1 and 2147483647 but is set to '0'" )
}
func TestShouldNotPanicOnNilDB ( t * testing . T ) {
dir := t . TempDir ( )
f := filepath . Join ( dir , "users.yml" )
assert . NoError ( t , os . WriteFile ( f , UserDatabaseContent , 0600 ) )
provider := & FileUserProvider {
2023-01-26 02:23:47 +00:00
config : & schema . AuthenticationBackendFile { Path : f , Password : schema . DefaultPasswordConfig } ,
2023-05-23 19:57:53 +00:00
mutex : & sync . Mutex { } ,
timeoutReload : time . Now ( ) . Add ( - 1 * time . Second ) ,
2019-04-24 21:52:08 +00:00
}
2023-05-23 19:57:53 +00:00
assert . NoError ( t , provider . StartupCheck ( ) )
2019-04-24 21:52:08 +00:00
}
2023-05-23 19:57:53 +00:00
func TestShouldReloadDatabase ( t * testing . T ) {
dir := t . TempDir ( )
path := filepath . Join ( dir , "users.yml" )
testCases := [ ] struct {
name string
setup func ( t * testing . T , provider * FileUserProvider )
expected bool
err string
} {
{
"ShouldSkipReloadRecentlyReloaded" ,
func ( t * testing . T , provider * FileUserProvider ) {
provider . timeoutReload = time . Now ( ) . Add ( time . Minute )
} ,
false ,
"" ,
} ,
{
"ShouldReloadWithoutError" ,
func ( t * testing . T , provider * FileUserProvider ) {
provider . timeoutReload = time . Now ( ) . Add ( time . Minute * - 1 )
} ,
true ,
"" ,
} ,
{
"ShouldNotReloadWithNoContent" ,
func ( t * testing . T , provider * FileUserProvider ) {
p := filepath . Join ( dir , "empty.yml" )
_ , _ = os . Create ( p )
provider . timeoutReload = time . Now ( ) . Add ( time . Minute * - 1 )
provider . config . Path = p
2023-01-26 02:23:47 +00:00
provider . database = NewFileUserDatabase ( p , provider . config . Search . Email , provider . config . Search . CaseInsensitive )
2023-05-23 19:57:53 +00:00
} ,
false ,
"" ,
} ,
2021-03-22 09:04:09 +00:00
}
2023-05-23 19:57:53 +00:00
require . NoError ( t , os . WriteFile ( path , UserDatabaseContent , 0600 ) )
2020-06-17 06:25:35 +00:00
2023-05-23 19:57:53 +00:00
for _ , tc := range testCases {
t . Run ( tc . name , func ( t * testing . T ) {
2023-01-26 02:23:47 +00:00
provider := NewFileUserProvider ( & schema . AuthenticationBackendFile {
2023-05-23 19:57:53 +00:00
Path : path ,
Password : schema . DefaultPasswordConfig ,
} )
2020-06-17 06:25:35 +00:00
2023-05-23 19:57:53 +00:00
tc . setup ( t , provider )
actual , theError := provider . Reload ( )
2020-06-17 06:25:35 +00:00
2023-05-23 19:57:53 +00:00
assert . Equal ( t , tc . expected , actual )
if tc . err == "" {
assert . NoError ( t , theError )
} else {
assert . EqualError ( t , theError , tc . err )
}
} )
}
2020-06-17 06:25:35 +00:00
}
2020-03-06 01:38:02 +00:00
func TestShouldCheckUserArgon2idPasswordIsCorrect ( t * testing . T ) {
2023-05-23 19:57:53 +00:00
WithDatabase ( t , UserDatabaseContent , func ( path string ) {
2020-03-06 01:38:02 +00:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
provider := NewFileUserProvider ( & config )
2022-10-17 10:51:59 +00:00
assert . NoError ( t , provider . StartupCheck ( ) )
2019-04-24 21:52:08 +00:00
ok , err := provider . CheckUserPassword ( "john" , "password" )
assert . NoError ( t , err )
assert . True ( t , ok )
} )
}
2020-03-06 01:38:02 +00:00
func TestShouldCheckUserSHA512PasswordIsCorrect ( t * testing . T ) {
2023-05-23 19:57:53 +00:00
WithDatabase ( t , UserDatabaseContent , func ( path string ) {
2020-03-06 01:38:02 +00:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
2022-10-17 10:51:59 +00:00
2020-03-06 01:38:02 +00:00
provider := NewFileUserProvider ( & config )
2022-10-17 10:51:59 +00:00
assert . NoError ( t , provider . StartupCheck ( ) )
2020-03-06 01:38:02 +00:00
ok , err := provider . CheckUserPassword ( "harry" , "password" )
assert . NoError ( t , err )
assert . True ( t , ok )
} )
}
2019-04-24 21:52:08 +00:00
func TestShouldCheckUserPasswordIsWrong ( t * testing . T ) {
2023-05-23 19:57:53 +00:00
WithDatabase ( t , UserDatabaseContent , func ( path string ) {
2020-03-06 01:38:02 +00:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
2022-10-17 10:51:59 +00:00
2020-03-06 01:38:02 +00:00
provider := NewFileUserProvider ( & config )
2022-10-17 10:51:59 +00:00
assert . NoError ( t , provider . StartupCheck ( ) )
2019-04-24 21:52:08 +00:00
ok , err := provider . CheckUserPassword ( "john" , "wrong_password" )
assert . NoError ( t , err )
assert . False ( t , ok )
} )
}
2020-05-01 22:32:09 +00:00
func TestShouldCheckUserPasswordIsWrongForEnumerationCompare ( t * testing . T ) {
2023-05-23 19:57:53 +00:00
WithDatabase ( t , UserDatabaseContent , func ( path string ) {
2020-03-06 01:38:02 +00:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
2022-10-17 10:51:59 +00:00
2020-03-06 01:38:02 +00:00
provider := NewFileUserProvider ( & config )
2020-05-01 22:32:09 +00:00
2022-10-17 10:51:59 +00:00
assert . NoError ( t , provider . StartupCheck ( ) )
2020-05-01 22:32:09 +00:00
ok , err := provider . CheckUserPassword ( "enumeration" , "wrong_password" )
assert . NoError ( t , err )
assert . False ( t , ok )
} )
}
func TestShouldCheckUserPasswordOfUserThatDoesNotExist ( t * testing . T ) {
2023-05-23 19:57:53 +00:00
WithDatabase ( t , UserDatabaseContent , func ( path string ) {
2020-05-01 22:32:09 +00:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
2022-10-17 10:51:59 +00:00
2020-05-01 22:32:09 +00:00
provider := NewFileUserProvider ( & config )
2022-10-17 10:51:59 +00:00
assert . NoError ( t , provider . StartupCheck ( ) )
2020-05-01 22:32:09 +00:00
ok , err := provider . CheckUserPassword ( "fake" , "password" )
2020-05-08 03:38:22 +00:00
assert . Error ( t , err )
2020-05-01 22:32:09 +00:00
assert . Equal ( t , false , ok )
2020-05-08 03:38:22 +00:00
assert . EqualError ( t , err , "user not found" )
2019-04-24 21:52:08 +00:00
} )
}
func TestShouldRetrieveUserDetails ( t * testing . T ) {
2023-05-23 19:57:53 +00:00
WithDatabase ( t , UserDatabaseContent , func ( path string ) {
2020-03-06 01:38:02 +00:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
2022-10-17 10:51:59 +00:00
2020-03-06 01:38:02 +00:00
provider := NewFileUserProvider ( & config )
2022-10-17 10:51:59 +00:00
assert . NoError ( t , provider . StartupCheck ( ) )
2019-04-24 21:52:08 +00:00
details , err := provider . GetDetails ( "john" )
assert . NoError ( t , err )
2022-10-17 10:51:59 +00:00
assert . Equal ( t , "john" , details . Username )
assert . Equal ( t , [ ] string { "john.doe@authelia.com" } , details . Emails )
assert . Equal ( t , [ ] string { "admins" , "dev" } , details . Groups )
2019-04-24 21:52:08 +00:00
} )
}
2023-05-23 19:57:53 +00:00
func TestShouldErrOnUserDetailsNoUser ( t * testing . T ) {
WithDatabase ( t , UserDatabaseContent , func ( path string ) {
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
provider := NewFileUserProvider ( & config )
assert . NoError ( t , provider . StartupCheck ( ) )
details , err := provider . GetDetails ( "nouser" )
assert . Nil ( t , details )
assert . Equal ( t , err , ErrUserNotFound )
details , err = provider . GetDetails ( "dis" )
assert . Nil ( t , details )
assert . Equal ( t , err , ErrUserNotFound )
} )
}
2019-04-24 21:52:08 +00:00
func TestShouldUpdatePassword ( t * testing . T ) {
2023-05-23 19:57:53 +00:00
WithDatabase ( t , UserDatabaseContent , func ( path string ) {
2020-03-06 01:38:02 +00:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
2022-10-17 10:51:59 +00:00
2020-03-06 01:38:02 +00:00
provider := NewFileUserProvider ( & config )
2022-10-17 10:51:59 +00:00
assert . NoError ( t , provider . StartupCheck ( ) )
2020-03-06 01:38:02 +00:00
err := provider . UpdatePassword ( "harry" , "newpassword" )
assert . NoError ( t , err )
// Reset the provider to force a read from disk.
provider = NewFileUserProvider ( & config )
2022-10-17 10:51:59 +00:00
assert . NoError ( t , provider . StartupCheck ( ) )
2020-03-06 01:38:02 +00:00
ok , err := provider . CheckUserPassword ( "harry" , "newpassword" )
assert . NoError ( t , err )
assert . True ( t , ok )
} )
}
// Checks both that the hashing algo changes and that it removes {CRYPT} from the start.
func TestShouldUpdatePasswordHashingAlgorithmToArgon2id ( t * testing . T ) {
2023-05-23 19:57:53 +00:00
WithDatabase ( t , UserDatabaseContent , func ( path string ) {
2020-03-06 01:38:02 +00:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
2022-10-17 10:51:59 +00:00
2020-03-06 01:38:02 +00:00
provider := NewFileUserProvider ( & config )
2022-10-17 10:51:59 +00:00
assert . NoError ( t , provider . StartupCheck ( ) )
2023-01-26 02:23:47 +00:00
db , ok := provider . database . ( * FileUserDatabase )
2023-05-25 01:17:35 +00:00
require . True ( t , ok )
2023-01-26 02:23:47 +00:00
assert . True ( t , strings . HasPrefix ( db . Users [ "harry" ] . Password . Encode ( ) , "$6$" ) )
2020-03-06 01:38:02 +00:00
err := provider . UpdatePassword ( "harry" , "newpassword" )
assert . NoError ( t , err )
// Reset the provider to force a read from disk.
provider = NewFileUserProvider ( & config )
2022-10-17 10:51:59 +00:00
assert . NoError ( t , provider . StartupCheck ( ) )
2023-05-25 01:17:35 +00:00
ok , err = provider . CheckUserPassword ( "harry" , "newpassword" )
2020-03-06 01:38:02 +00:00
assert . NoError ( t , err )
assert . True ( t , ok )
2023-01-26 02:23:47 +00:00
assert . True ( t , strings . HasPrefix ( db . Users [ "harry" ] . Password . Encode ( ) , "$argon2id$" ) )
2020-03-06 01:38:02 +00:00
} )
}
func TestShouldUpdatePasswordHashingAlgorithmToSHA512 ( t * testing . T ) {
2023-05-23 19:57:53 +00:00
WithDatabase ( t , UserDatabaseContent , func ( path string ) {
2020-03-06 01:38:02 +00:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
2022-10-17 10:51:59 +00:00
config . Password . Algorithm = "sha2crypt"
config . Password . SHA2Crypt . Iterations = 50000
2020-03-06 01:38:02 +00:00
provider := NewFileUserProvider ( & config )
2022-10-17 10:51:59 +00:00
assert . NoError ( t , provider . StartupCheck ( ) )
2023-01-26 02:23:47 +00:00
db , ok := provider . database . ( * FileUserDatabase )
2023-05-25 01:17:35 +00:00
require . True ( t , ok )
2023-01-26 02:23:47 +00:00
assert . True ( t , strings . HasPrefix ( db . Users [ "john" ] . Password . Encode ( ) , "$argon2id$" ) )
2019-04-24 21:52:08 +00:00
err := provider . UpdatePassword ( "john" , "newpassword" )
assert . NoError ( t , err )
// Reset the provider to force a read from disk.
2020-03-06 01:38:02 +00:00
provider = NewFileUserProvider ( & config )
2022-10-17 10:51:59 +00:00
assert . NoError ( t , provider . StartupCheck ( ) )
2023-05-25 01:17:35 +00:00
ok , err = provider . CheckUserPassword ( "john" , "newpassword" )
2019-04-24 21:52:08 +00:00
assert . NoError ( t , err )
assert . True ( t , ok )
2023-01-26 02:23:47 +00:00
assert . True ( t , strings . HasPrefix ( db . Users [ "john" ] . Password . Encode ( ) , "$6$" ) )
2019-04-24 21:52:08 +00:00
} )
}
2023-05-23 19:57:53 +00:00
func TestShouldErrOnUpdatePasswordNoUser ( t * testing . T ) {
WithDatabase ( t , UserDatabaseContent , func ( path string ) {
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
provider := NewFileUserProvider ( & config )
assert . NoError ( t , provider . StartupCheck ( ) )
assert . Equal ( t , provider . UpdatePassword ( "nousers" , "newpassword" ) , ErrUserNotFound )
assert . Equal ( t , provider . UpdatePassword ( "dis" , "example" ) , ErrUserNotFound )
} )
}
2019-04-24 21:52:08 +00:00
func TestShouldRaiseWhenLoadingMalformedDatabaseForFirstTime ( t * testing . T ) {
2023-05-23 19:57:53 +00:00
WithDatabase ( t , MalformedUserDatabaseContent , func ( path string ) {
2020-03-06 01:38:02 +00:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
2022-10-17 10:51:59 +00:00
provider := NewFileUserProvider ( & config )
assert . EqualError ( t , provider . StartupCheck ( ) , "error reading the authentication database: could not parse the YAML database: yaml: line 4: mapping values are not allowed in this context" )
2019-04-24 21:52:08 +00:00
} )
}
func TestShouldRaiseWhenLoadingDatabaseWithBadSchemaForFirstTime ( t * testing . T ) {
2023-05-23 19:57:53 +00:00
WithDatabase ( t , BadSchemaUserDatabaseContent , func ( path string ) {
2020-03-06 01:38:02 +00:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
2022-10-17 10:51:59 +00:00
provider := NewFileUserProvider ( & config )
2023-01-26 02:23:47 +00:00
assert . EqualError ( t , provider . StartupCheck ( ) , "error reading the authentication database: could not validate the schema: users: non zero value required" )
2020-03-06 01:38:02 +00:00
} )
}
func TestShouldRaiseWhenLoadingDatabaseWithBadSHA512HashesForTheFirstTime ( t * testing . T ) {
2023-05-23 19:57:53 +00:00
WithDatabase ( t , BadSHA512HashContent , func ( path string ) {
2020-03-06 01:38:02 +00:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
2022-10-17 10:51:59 +00:00
provider := NewFileUserProvider ( & config )
2022-12-04 22:37:08 +00:00
assert . EqualError ( t , provider . StartupCheck ( ) , "error decoding the authentication database: failed to parse hash for user 'john': shacrypt decode error: parameter pair 'rounds00000' is not properly encoded: does not contain kv separator '='" )
2020-03-06 01:38:02 +00:00
} )
}
func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashSettingsForTheFirstTime ( t * testing . T ) {
2023-05-23 19:57:53 +00:00
WithDatabase ( t , BadArgon2idHashSettingsContent , func ( path string ) {
2020-03-06 01:38:02 +00:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
2022-10-17 10:51:59 +00:00
provider := NewFileUserProvider ( & config )
2022-12-04 22:37:08 +00:00
assert . EqualError ( t , provider . StartupCheck ( ) , "error decoding the authentication database: failed to parse hash for user 'john': argon2 decode error: parameter pair 'm65536' is not properly encoded: does not contain kv separator '='" )
2019-12-27 17:09:57 +00:00
} )
}
2020-03-06 01:38:02 +00:00
func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashKeyForTheFirstTime ( t * testing . T ) {
2023-05-23 19:57:53 +00:00
WithDatabase ( t , BadArgon2idHashKeyContent , func ( path string ) {
2020-03-06 01:38:02 +00:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
2022-10-17 10:51:59 +00:00
provider := NewFileUserProvider ( & config )
assert . EqualError ( t , provider . StartupCheck ( ) , "error decoding the authentication database: failed to parse hash for user 'john': argon2 decode error: provided encoded hash has a key value that can't be decoded: illegal base64 data at input byte 0" )
2020-03-06 01:38:02 +00:00
} )
}
func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashSaltForTheFirstTime ( t * testing . T ) {
2023-05-23 19:57:53 +00:00
WithDatabase ( t , BadArgon2idHashSaltContent , func ( path string ) {
2020-03-06 01:38:02 +00:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
2022-10-17 10:51:59 +00:00
provider := NewFileUserProvider ( & config )
assert . EqualError ( t , provider . StartupCheck ( ) , "error decoding the authentication database: failed to parse hash for user 'john': argon2 decode error: provided encoded hash has a salt value that can't be decoded: illegal base64 data at input byte 0" )
2019-04-24 21:52:08 +00:00
} )
}
2019-12-27 16:55:00 +00:00
func TestShouldSupportHashPasswordWithoutCRYPT ( t * testing . T ) {
2023-05-23 19:57:53 +00:00
WithDatabase ( t , UserDatabaseWithoutCryptContent , func ( path string ) {
2020-03-06 01:38:02 +00:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
2022-10-17 10:51:59 +00:00
2020-03-06 01:38:02 +00:00
provider := NewFileUserProvider ( & config )
2022-10-17 10:51:59 +00:00
assert . NoError ( t , provider . StartupCheck ( ) )
2019-12-27 16:55:00 +00:00
ok , err := provider . CheckUserPassword ( "john" , "password" )
assert . NoError ( t , err )
assert . True ( t , ok )
} )
}
2022-10-18 00:57:08 +00:00
func TestShouldNotAllowLoginOfDisabledUsers ( t * testing . T ) {
2023-05-23 19:57:53 +00:00
WithDatabase ( t , UserDatabaseContent , func ( path string ) {
2022-10-18 00:57:08 +00:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
provider := NewFileUserProvider ( & config )
assert . NoError ( t , provider . StartupCheck ( ) )
ok , err := provider . CheckUserPassword ( "dis" , "password" )
assert . False ( t , ok )
assert . EqualError ( t , err , "user not found" )
} )
}
func TestShouldErrorOnInvalidCaseSensitiveFile ( t * testing . T ) {
2023-05-23 19:57:53 +00:00
WithDatabase ( t , UserDatabaseContentInvalidSearchCaseInsenstive , func ( path string ) {
2022-10-18 00:57:08 +00:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
config . Search . Email = false
config . Search . CaseInsensitive = true
provider := NewFileUserProvider ( & config )
assert . EqualError ( t , provider . StartupCheck ( ) , "error loading authentication database: username 'JOHN' is not lowercase but this is required when case-insensitive search is enabled" )
} )
}
func TestShouldErrorOnDuplicateEmail ( t * testing . T ) {
2023-05-23 19:57:53 +00:00
WithDatabase ( t , UserDatabaseContentInvalidSearchEmail , func ( path string ) {
2022-10-18 00:57:08 +00:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
config . Search . Email = true
config . Search . CaseInsensitive = false
provider := NewFileUserProvider ( & config )
err := provider . StartupCheck ( )
assert . Regexp ( t , regexp . MustCompile ( ` ^error loading authentication database: email 'john.doe@authelia.com' is configured for for more than one user \(users are '(harry|john)', '(harry|john)'\) which isn't allowed when email search is enabled$ ` ) , err . Error ( ) )
} )
}
func TestShouldNotErrorOnEmailAsUsername ( t * testing . T ) {
2023-05-23 19:57:53 +00:00
WithDatabase ( t , UserDatabaseContentSearchEmailAsUsername , func ( path string ) {
2022-10-18 00:57:08 +00:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
config . Search . Email = true
config . Search . CaseInsensitive = false
provider := NewFileUserProvider ( & config )
assert . NoError ( t , provider . StartupCheck ( ) )
} )
}
func TestShouldErrorOnEmailAsUsernameWithDuplicateEmail ( t * testing . T ) {
2023-05-23 19:57:53 +00:00
WithDatabase ( t , UserDatabaseContentInvalidSearchEmailAsUsername , func ( path string ) {
2022-10-18 00:57:08 +00:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
config . Search . Email = true
config . Search . CaseInsensitive = false
provider := NewFileUserProvider ( & config )
assert . EqualError ( t , provider . StartupCheck ( ) , "error loading authentication database: email 'john.doe@authelia.com' is also a username which isn't allowed when email search is enabled" )
} )
}
func TestShouldErrorOnEmailAsUsernameWithDuplicateEmailCase ( t * testing . T ) {
2023-05-23 19:57:53 +00:00
WithDatabase ( t , UserDatabaseContentInvalidSearchEmailAsUsernameCase , func ( path string ) {
2022-10-18 00:57:08 +00:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
config . Search . Email = false
config . Search . CaseInsensitive = true
provider := NewFileUserProvider ( & config )
assert . EqualError ( t , provider . StartupCheck ( ) , "error loading authentication database: username 'john.doe@authelia.com' is configured as an email for user with username 'john' which isn't allowed when case-insensitive search is enabled" )
} )
}
func TestShouldAllowLookupByEmail ( t * testing . T ) {
2023-05-23 19:57:53 +00:00
WithDatabase ( t , UserDatabaseContent , func ( path string ) {
2022-10-18 00:57:08 +00:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
config . Search . Email = true
provider := NewFileUserProvider ( & config )
assert . NoError ( t , provider . StartupCheck ( ) )
ok , err := provider . CheckUserPassword ( "john" , "password" )
assert . NoError ( t , err )
assert . True ( t , ok )
ok , err = provider . CheckUserPassword ( "john.doe@authelia.com" , "password" )
assert . NoError ( t , err )
assert . True ( t , ok )
ok , err = provider . CheckUserPassword ( "JOHN.doe@authelia.com" , "password" )
assert . NoError ( t , err )
assert . True ( t , ok )
} )
}
func TestShouldAllowLookupCI ( t * testing . T ) {
2023-05-23 19:57:53 +00:00
WithDatabase ( t , UserDatabaseContent , func ( path string ) {
2022-10-18 00:57:08 +00:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
config . Search . CaseInsensitive = true
provider := NewFileUserProvider ( & config )
assert . NoError ( t , provider . StartupCheck ( ) )
ok , err := provider . CheckUserPassword ( "john" , "password" )
assert . NoError ( t , err )
assert . True ( t , ok )
ok , err = provider . CheckUserPassword ( "John" , "password" )
assert . NoError ( t , err )
assert . True ( t , ok )
} )
}
2023-05-23 19:57:53 +00:00
func TestNewFileCryptoHashFromConfig ( t * testing . T ) {
testCases := [ ] struct {
name string
2023-01-26 02:23:47 +00:00
have schema . AuthenticationBackendFilePassword
2023-05-23 19:57:53 +00:00
expected any
err string
} {
{
"ShouldCreatePBKDF2" ,
2023-01-26 02:23:47 +00:00
schema . AuthenticationBackendFilePassword {
2023-05-23 19:57:53 +00:00
Algorithm : "pbkdf2" ,
2023-01-26 02:23:47 +00:00
PBKDF2 : schema . AuthenticationBackendFilePasswordPBKDF2 {
2023-05-23 19:57:53 +00:00
Variant : "sha256" ,
Iterations : 100000 ,
SaltLength : 16 ,
} ,
} ,
& pbkdf2 . Hasher { } ,
"" ,
} ,
{
"ShouldCreateSCrypt" ,
2023-01-26 02:23:47 +00:00
schema . AuthenticationBackendFilePassword {
2023-05-23 19:57:53 +00:00
Algorithm : "scrypt" ,
2023-01-26 02:23:47 +00:00
SCrypt : schema . AuthenticationBackendFilePasswordSCrypt {
2023-05-23 19:57:53 +00:00
Iterations : 12 ,
SaltLength : 16 ,
Parallelism : 1 ,
BlockSize : 1 ,
KeyLength : 32 ,
} ,
} ,
& scrypt . Hasher { } ,
"" ,
} ,
{
"ShouldCreateBCrypt" ,
2023-01-26 02:23:47 +00:00
schema . AuthenticationBackendFilePassword {
2023-05-23 19:57:53 +00:00
Algorithm : "bcrypt" ,
2023-01-26 02:23:47 +00:00
BCrypt : schema . AuthenticationBackendFilePasswordBCrypt {
2023-05-23 19:57:53 +00:00
Variant : "standard" ,
Cost : 12 ,
} ,
} ,
& bcrypt . Hasher { } ,
"" ,
} ,
{
"ShouldFailToCreateSCryptInvalidParameter" ,
2023-01-26 02:23:47 +00:00
schema . AuthenticationBackendFilePassword {
2023-05-23 19:57:53 +00:00
Algorithm : "scrypt" ,
} ,
nil ,
"failed to initialize hash settings: scrypt validation error: parameter is invalid: parameter 'iterations' must be between 1 and 58 but is set to '0'" ,
} ,
{
"ShouldFailUnknown" ,
2023-01-26 02:23:47 +00:00
schema . AuthenticationBackendFilePassword {
2023-05-23 19:57:53 +00:00
Algorithm : "unknown" ,
} ,
nil ,
"algorithm 'unknown' is unknown" ,
} ,
}
for _ , tc := range testCases {
t . Run ( tc . name , func ( t * testing . T ) {
actual , theError := NewFileCryptoHashFromConfig ( tc . have )
if tc . err == "" {
assert . NoError ( t , theError )
require . NotNil ( t , actual )
assert . IsType ( t , tc . expected , actual )
} else {
assert . EqualError ( t , theError , tc . err )
assert . Nil ( t , actual )
}
} )
}
}
2023-05-25 01:17:35 +00:00
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 ) {
2023-01-26 02:23:47 +00:00
db := NewFileUserDatabase ( path , false , false )
2023-05-25 01:17:35 +00:00
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" )
} )
}
2020-03-06 01:38:02 +00:00
var (
2023-01-26 02:23:47 +00:00
DefaultFileAuthenticationBackendConfiguration = schema . AuthenticationBackendFile {
2022-10-17 10:51:59 +00:00
Path : "" ,
Password : schema . DefaultCIPasswordConfig ,
2020-03-06 01:38:02 +00:00
}
)
2019-04-24 21:52:08 +00:00
var UserDatabaseContent = [ ] byte ( `
users :
john :
2020-06-19 10:50:21 +00:00
displayname : "John Doe"
2020-03-06 01:38:02 +00:00
password : "{CRYPT}$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
2019-04-24 21:52:08 +00:00
email : john . doe @ authelia . com
groups :
- admins
- dev
harry :
2020-06-19 10:50:21 +00:00
displayname : "Harry Potter"
2019-04-24 21:52:08 +00:00
password : "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email : harry . potter @ authelia . com
groups : [ ]
bob :
2020-06-19 10:50:21 +00:00
displayname : "Bob Dylan"
2019-04-24 21:52:08 +00:00
password : "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email : bob . dylan @ authelia . com
groups :
- dev
james :
2020-06-19 10:50:21 +00:00
displayname : "James Dean"
2019-04-24 21:52:08 +00:00
password : "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email : james . dean @ authelia . com
2020-05-01 22:32:09 +00:00
enumeration :
2020-06-19 10:50:21 +00:00
displayname : "Enumeration"
2020-05-01 22:32:09 +00:00
password : "$argon2id$v=19$m=131072,p=8$BpLnfgDsc2WD8F2q$O126GHPeZ5fwj7OLSs7PndXsTbje76R+QW9/EGfhkJg"
2022-10-18 00:57:08 +00:00
email : enumeration @ authelia . com
dis :
displayname : "Enumeration"
password : "$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
disabled : true
email : disabled @ authelia . com
` )
var UserDatabaseContentInvalidSearchCaseInsenstive = [ ] byte ( `
users :
john :
displayname : "John Doe"
password : "{CRYPT}$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
email : john . doe @ authelia . com
groups :
- admins
- dev
JOHN :
displayname : "Harry Potter"
password : "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email : harry . potter @ authelia . com
groups : [ ]
` )
var UserDatabaseContentInvalidSearchEmail = [ ] byte ( `
users :
john :
displayname : "John Doe"
password : "{CRYPT}$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
email : john . doe @ authelia . com
groups :
- admins
- dev
harry :
displayname : "Harry Potter"
password : "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email : john . doe @ authelia . com
groups : [ ]
` )
var UserDatabaseContentSearchEmailAsUsername = [ ] byte ( `
users :
john . doe @ authelia . com :
displayname : "John Doe"
password : "{CRYPT}$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
email : john . doe @ authelia . com
groups :
- admins
- dev
harry :
displayname : "Harry Potter"
password : "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email : harry . potter @ authelia . com
groups : [ ]
` )
var UserDatabaseContentInvalidSearchEmailAsUsername = [ ] byte ( `
users :
john . doe @ authelia . com :
displayname : "John Doe"
password : "{CRYPT}$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
email : john @ authelia . com
groups :
- admins
- dev
harry :
displayname : "Harry Potter"
password : "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email : john . doe @ authelia . com
groups : [ ]
` )
var UserDatabaseContentInvalidSearchEmailAsUsernameCase = [ ] byte ( `
users :
john . doe @ authelia . com :
displayname : "John Doe"
password : "{CRYPT}$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
email : JOHN @ authelia . com
groups :
- admins
- dev
john :
displayname : "John Potter"
password : "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email : john . doe @ authelia . com
groups : [ ]
2019-04-24 21:52:08 +00:00
` )
var MalformedUserDatabaseContent = [ ] byte ( `
users
john
email : john . doe @ authelia . com
groups :
2019-11-30 16:49:52 +00:00
- admins
2019-04-24 21:52:08 +00:00
- dev
` )
2020-05-02 05:06:39 +00:00
// The YAML is valid but the root key is user instead of users.
2019-04-24 21:52:08 +00:00
var BadSchemaUserDatabaseContent = [ ] byte ( `
user :
john :
2020-06-19 10:50:21 +00:00
displayname : "John Doe"
2019-04-24 21:52:08 +00:00
password : "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email : john . doe @ authelia . com
groups :
- admins
- dev
` )
2019-12-27 16:55:00 +00:00
2020-03-06 01:38:02 +00:00
var UserDatabaseWithoutCryptContent = [ ] byte ( `
2019-12-27 16:55:00 +00:00
users :
john :
2020-06-19 10:50:21 +00:00
displayname : "John Doe"
2019-12-27 16:55:00 +00:00
password : "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email : john . doe @ authelia . com
groups :
- admins
- dev
james :
2020-06-19 10:50:21 +00:00
displayname : "James Dean"
2019-12-27 16:55:00 +00:00
password : "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email : james . dean @ authelia . com
` )
2019-12-27 17:09:57 +00:00
2020-03-06 01:38:02 +00:00
var BadSHA512HashContent = [ ] byte ( `
2019-12-27 17:09:57 +00:00
users :
john :
2020-06-19 10:50:21 +00:00
displayname : "John Doe"
2019-12-27 17:09:57 +00:00
password : "$6$rounds00000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email : john . doe @ authelia . com
groups :
- admins
- dev
james :
2020-06-19 10:50:21 +00:00
displayname : "James Dean"
2019-12-27 17:09:57 +00:00
password : "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email : james . dean @ authelia . com
` )
2020-03-06 01:38:02 +00:00
var BadArgon2idHashSettingsContent = [ ] byte ( `
users :
john :
2020-06-19 10:50:21 +00:00
displayname : "John Doe"
2020-03-06 01:38:02 +00:00
password : "$argon2id$v=19$m65536,t3,p2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
email : john . doe @ authelia . com
groups :
- admins
- dev
james :
2020-06-19 10:50:21 +00:00
displayname : "James Dean"
2020-03-06 01:38:02 +00:00
password : "$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
email : james . dean @ authelia . com
` )
var BadArgon2idHashKeyContent = [ ] byte ( `
users :
john :
2020-06-19 10:50:21 +00:00
displayname : "John Doe"
2020-03-06 01:38:02 +00:00
password : "$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$^^vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
email : john . doe @ authelia . com
groups :
- admins
- dev
` )
2022-10-17 10:51:59 +00:00
2020-03-06 01:38:02 +00:00
var BadArgon2idHashSaltContent = [ ] byte ( `
users :
john :
2020-06-19 10:50:21 +00:00
displayname : "John Doe"
2020-03-06 01:38:02 +00:00
password : "$argon2id$v=19$m=65536,t=3,p=2$^^LnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
email : john . doe @ authelia . com
groups :
- admins
- dev
` )
2023-05-23 19:57:53 +00:00
func WithDatabase ( t * testing . T , content [ ] byte , f func ( path string ) ) {
dir := t . TempDir ( )
db , err := os . CreateTemp ( dir , "users_database.*.yaml" )
require . NoError ( t , err )
_ , err = db . Write ( content )
require . NoError ( t , err )
f ( db . Name ( ) )
require . NoError ( t , db . Close ( ) )
}