package authentication import ( "fmt" "os" "path/filepath" "regexp" "runtime" "strings" "sync" "testing" "time" "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" "github.com/authelia/authelia/v4/internal/configuration/schema" ) func TestShouldErrorPermissionsOnLocalFS(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("skipping test due to being on windows") } dir := t.TempDir() _ = 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") provider := NewFileUserProvider(&schema.FileAuthenticationBackend{Path: f, Password: schema.DefaultPasswordConfig}) require.NotNil(t, provider) assert.EqualError(t, provider.StartupCheck(), "one or more errors occurred checking the authentication database") assert.NotNil(t, provider.database) reloaded, err := provider.Reload() 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)) provider := NewFileUserProvider(&schema.FileAuthenticationBackend{Path: f}) 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{ config: &schema.FileAuthenticationBackend{Path: f, Password: schema.DefaultPasswordConfig}, mutex: &sync.Mutex{}, timeoutReload: time.Now().Add(-1 * time.Second), } assert.NoError(t, provider.StartupCheck()) } 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 provider.database = NewYAMLUserDatabase(p, provider.config.Search.Email, provider.config.Search.CaseInsensitive) }, false, "", }, } require.NoError(t, os.WriteFile(path, UserDatabaseContent, 0600)) for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { provider := NewFileUserProvider(&schema.FileAuthenticationBackend{ Path: path, Password: schema.DefaultPasswordConfig, }) tc.setup(t, provider) actual, theError := provider.Reload() assert.Equal(t, tc.expected, actual) if tc.err == "" { assert.NoError(t, theError) } else { assert.EqualError(t, theError, tc.err) } }) } } func TestShouldCheckUserArgon2idPasswordIsCorrect(t *testing.T) { WithDatabase(t, UserDatabaseContent, func(path string) { config := DefaultFileAuthenticationBackendConfiguration config.Path = path provider := NewFileUserProvider(&config) assert.NoError(t, provider.StartupCheck()) ok, err := provider.CheckUserPassword("john", "password") assert.NoError(t, err) assert.True(t, ok) }) } func TestShouldCheckUserSHA512PasswordIsCorrect(t *testing.T) { WithDatabase(t, UserDatabaseContent, func(path string) { config := DefaultFileAuthenticationBackendConfiguration config.Path = path provider := NewFileUserProvider(&config) assert.NoError(t, provider.StartupCheck()) ok, err := provider.CheckUserPassword("harry", "password") assert.NoError(t, err) assert.True(t, ok) }) } func TestShouldCheckUserPasswordIsWrong(t *testing.T) { WithDatabase(t, UserDatabaseContent, func(path string) { config := DefaultFileAuthenticationBackendConfiguration config.Path = path provider := NewFileUserProvider(&config) assert.NoError(t, provider.StartupCheck()) ok, err := provider.CheckUserPassword("john", "wrong_password") assert.NoError(t, err) assert.False(t, ok) }) } func TestShouldCheckUserPasswordIsWrongForEnumerationCompare(t *testing.T) { WithDatabase(t, UserDatabaseContent, func(path string) { config := DefaultFileAuthenticationBackendConfiguration config.Path = path provider := NewFileUserProvider(&config) assert.NoError(t, provider.StartupCheck()) ok, err := provider.CheckUserPassword("enumeration", "wrong_password") assert.NoError(t, err) assert.False(t, ok) }) } func TestShouldCheckUserPasswordOfUserThatDoesNotExist(t *testing.T) { WithDatabase(t, UserDatabaseContent, func(path string) { config := DefaultFileAuthenticationBackendConfiguration config.Path = path provider := NewFileUserProvider(&config) assert.NoError(t, provider.StartupCheck()) ok, err := provider.CheckUserPassword("fake", "password") assert.Error(t, err) assert.Equal(t, false, ok) assert.EqualError(t, err, "user not found") }) } func TestShouldRetrieveUserDetails(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("john") assert.NoError(t, err) assert.Equal(t, "john", details.Username) assert.Equal(t, []string{"john.doe@authelia.com"}, details.Emails) assert.Equal(t, []string{"admins", "dev"}, details.Groups) }) } 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) }) } func TestShouldUpdatePassword(t *testing.T) { WithDatabase(t, UserDatabaseContent, func(path string) { config := DefaultFileAuthenticationBackendConfiguration config.Path = path provider := NewFileUserProvider(&config) assert.NoError(t, provider.StartupCheck()) err := provider.UpdatePassword("harry", "newpassword") assert.NoError(t, err) // Reset the provider to force a read from disk. provider = NewFileUserProvider(&config) assert.NoError(t, provider.StartupCheck()) 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) { WithDatabase(t, UserDatabaseContent, func(path string) { config := DefaultFileAuthenticationBackendConfiguration config.Path = path provider := NewFileUserProvider(&config) assert.NoError(t, provider.StartupCheck()) 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) // Reset the provider to force a read from disk. provider = NewFileUserProvider(&config) assert.NoError(t, provider.StartupCheck()) ok, err = provider.CheckUserPassword("harry", "newpassword") assert.NoError(t, err) assert.True(t, ok) assert.True(t, strings.HasPrefix(db.Users["harry"].Digest.Encode(), "$argon2id$")) }) } func TestShouldUpdatePasswordHashingAlgorithmToSHA512(t *testing.T) { WithDatabase(t, UserDatabaseContent, func(path string) { config := DefaultFileAuthenticationBackendConfiguration config.Path = path config.Password.Algorithm = "sha2crypt" config.Password.SHA2Crypt.Iterations = 50000 provider := NewFileUserProvider(&config) assert.NoError(t, provider.StartupCheck()) 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) // Reset the provider to force a read from disk. provider = NewFileUserProvider(&config) assert.NoError(t, provider.StartupCheck()) ok, err = provider.CheckUserPassword("john", "newpassword") assert.NoError(t, err) assert.True(t, ok) assert.True(t, strings.HasPrefix(db.Users["john"].Digest.Encode(), "$6$")) }) } 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) }) } func TestShouldRaiseWhenLoadingMalformedDatabaseForFirstTime(t *testing.T) { WithDatabase(t, MalformedUserDatabaseContent, func(path string) { config := DefaultFileAuthenticationBackendConfiguration config.Path = path 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") }) } func TestShouldRaiseWhenLoadingDatabaseWithBadSchemaForFirstTime(t *testing.T) { WithDatabase(t, BadSchemaUserDatabaseContent, func(path string) { config := DefaultFileAuthenticationBackendConfiguration config.Path = path provider := NewFileUserProvider(&config) assert.EqualError(t, provider.StartupCheck(), "error reading the authentication database: could not validate the schema: Users: non zero value required") }) } func TestShouldRaiseWhenLoadingDatabaseWithBadSHA512HashesForTheFirstTime(t *testing.T) { WithDatabase(t, BadSHA512HashContent, func(path string) { config := DefaultFileAuthenticationBackendConfiguration config.Path = path provider := NewFileUserProvider(&config) 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 '='") }) } func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashSettingsForTheFirstTime(t *testing.T) { WithDatabase(t, BadArgon2idHashSettingsContent, func(path string) { config := DefaultFileAuthenticationBackendConfiguration config.Path = path provider := NewFileUserProvider(&config) 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 '='") }) } func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashKeyForTheFirstTime(t *testing.T) { WithDatabase(t, BadArgon2idHashKeyContent, func(path string) { config := DefaultFileAuthenticationBackendConfiguration config.Path = path 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") }) } func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashSaltForTheFirstTime(t *testing.T) { WithDatabase(t, BadArgon2idHashSaltContent, func(path string) { config := DefaultFileAuthenticationBackendConfiguration config.Path = path 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") }) } func TestShouldSupportHashPasswordWithoutCRYPT(t *testing.T) { WithDatabase(t, UserDatabaseWithoutCryptContent, func(path string) { config := DefaultFileAuthenticationBackendConfiguration config.Path = path provider := NewFileUserProvider(&config) assert.NoError(t, provider.StartupCheck()) ok, err := provider.CheckUserPassword("john", "password") assert.NoError(t, err) assert.True(t, ok) }) } func TestShouldNotAllowLoginOfDisabledUsers(t *testing.T) { WithDatabase(t, UserDatabaseContent, func(path string) { 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) { WithDatabase(t, UserDatabaseContentInvalidSearchCaseInsenstive, func(path string) { 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) { WithDatabase(t, UserDatabaseContentInvalidSearchEmail, func(path string) { 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) { WithDatabase(t, UserDatabaseContentSearchEmailAsUsername, func(path string) { 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) { WithDatabase(t, UserDatabaseContentInvalidSearchEmailAsUsername, func(path string) { 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) { WithDatabase(t, UserDatabaseContentInvalidSearchEmailAsUsernameCase, func(path string) { 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) { WithDatabase(t, UserDatabaseContent, func(path string) { 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) { WithDatabase(t, UserDatabaseContent, func(path string) { 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) }) } func TestNewFileCryptoHashFromConfig(t *testing.T) { testCases := []struct { name string have schema.Password expected any err string }{ { "ShouldCreatePBKDF2", schema.Password{ Algorithm: "pbkdf2", PBKDF2: schema.PBKDF2Password{ Variant: "sha256", Iterations: 100000, SaltLength: 16, }, }, &pbkdf2.Hasher{}, "", }, { "ShouldCreateSCrypt", schema.Password{ Algorithm: "scrypt", SCrypt: schema.SCryptPassword{ Iterations: 12, SaltLength: 16, Parallelism: 1, BlockSize: 1, KeyLength: 32, }, }, &scrypt.Hasher{}, "", }, { "ShouldCreateBCrypt", schema.Password{ Algorithm: "bcrypt", BCrypt: schema.BCryptPassword{ Variant: "standard", Cost: 12, }, }, &bcrypt.Hasher{}, "", }, { "ShouldFailToCreateSCryptInvalidParameter", schema.Password{ 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", schema.Password{ 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) } }) } } 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: "", Password: schema.DefaultCIPasswordConfig, } ) var UserDatabaseContent = []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: harry.potter@authelia.com groups: [] bob: displayname: "Bob Dylan" password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/" email: bob.dylan@authelia.com groups: - dev james: displayname: "James Dean" password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/" email: james.dean@authelia.com enumeration: displayname: "Enumeration" password: "$argon2id$v=19$m=131072,p=8$BpLnfgDsc2WD8F2q$O126GHPeZ5fwj7OLSs7PndXsTbje76R+QW9/EGfhkJg" 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: [] `) var MalformedUserDatabaseContent = []byte(` users john email: john.doe@authelia.com groups: - admins - dev `) // The YAML is valid but the root key is user instead of users. var BadSchemaUserDatabaseContent = []byte(` user: john: displayname: "John Doe" password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/" email: john.doe@authelia.com groups: - admins - dev `) var UserDatabaseWithoutCryptContent = []byte(` users: john: displayname: "John Doe" password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/" email: john.doe@authelia.com groups: - admins - dev james: displayname: "James Dean" password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/" email: james.dean@authelia.com `) var BadSHA512HashContent = []byte(` users: john: displayname: "John Doe" password: "$6$rounds00000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/" email: john.doe@authelia.com groups: - admins - dev james: displayname: "James Dean" password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/" email: james.dean@authelia.com `) var BadArgon2idHashSettingsContent = []byte(` users: john: displayname: "John Doe" password: "$argon2id$v=19$m65536,t3,p2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM" email: john.doe@authelia.com groups: - admins - dev james: displayname: "James Dean" 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: displayname: "John Doe" password: "$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$^^vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM" email: john.doe@authelia.com groups: - admins - dev `) var BadArgon2idHashSaltContent = []byte(` users: john: displayname: "John Doe" password: "$argon2id$v=19$m=65536,t=3,p=2$^^LnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM" email: john.doe@authelia.com groups: - admins - dev `) 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()) }