2019-04-24 21:52:08 +00:00
package authentication
import (
"log"
"os"
2021-03-22 09:04:09 +00:00
"runtime"
2020-03-06 01:38:02 +00:00
"strings"
2019-04-24 21:52:08 +00:00
"testing"
"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
)
func WithDatabase ( content [ ] byte , f func ( path string ) ) {
2021-12-01 13:14:15 +00:00
tmpfile , err := os . CreateTemp ( "" , "users_database.*.yaml" )
2019-04-24 21:52:08 +00:00
if err != nil {
log . Fatal ( err )
}
2022-01-31 05:25:15 +00:00
defer os . Remove ( tmpfile . Name ( ) ) // Clean up.
2019-04-24 21:52:08 +00:00
if _ , err := tmpfile . Write ( content ) ; err != nil {
tmpfile . Close ( )
2020-09-18 12:05:43 +00:00
log . Panic ( err )
2019-04-24 21:52:08 +00:00
}
f ( tmpfile . Name ( ) )
if err := tmpfile . Close ( ) ; err != nil {
2020-09-18 12:05:43 +00:00
log . Panic ( err )
2019-04-24 21:52:08 +00:00
}
}
2020-06-17 06:25:35 +00:00
func TestShouldErrorPermissionsOnLocalFS ( t * testing . T ) {
2021-03-22 09:04:09 +00:00
if runtime . GOOS == "windows" {
t . Skip ( "skipping test due to being on windows" )
}
2020-06-17 06:25:35 +00:00
_ = os . Mkdir ( "/tmp/noperms/" , 0000 )
2022-10-17 10:51:59 +00:00
err := checkDatabase ( "/tmp/noperms/users_database.yml" )
2020-06-17 06:25:35 +00:00
2022-10-17 10:51:59 +00:00
require . EqualError ( t , err , "error checking user authentication database file: stat /tmp/noperms/users_database.yml: permission denied" )
2020-06-17 06:25:35 +00:00
}
func TestShouldErrorAndGenerateUserDB ( t * testing . T ) {
2022-10-17 10:51:59 +00:00
err := checkDatabase ( "./nonexistent.yml" )
2020-06-17 06:25:35 +00:00
_ = os . Remove ( "./nonexistent.yml" )
2022-10-17 10:51:59 +00:00
require . EqualError ( t , err , "user authentication database file doesn't exist at path './nonexistent.yml' and has been generated" )
2020-06-17 06:25:35 +00:00
}
2020-03-06 01:38:02 +00:00
func TestShouldCheckUserArgon2idPasswordIsCorrect ( t * testing . T ) {
2019-04-24 21:52:08 +00:00
WithDatabase ( 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 ) {
WithDatabase ( UserDatabaseContent , func ( path string ) {
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 ) {
WithDatabase ( 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 ) {
2019-04-24 21:52:08 +00:00
WithDatabase ( 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 ) {
WithDatabase ( UserDatabaseContent , func ( path string ) {
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 ) {
WithDatabase ( 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
} )
}
func TestShouldUpdatePassword ( t * testing . T ) {
WithDatabase ( 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 ) {
WithDatabase ( UserDatabaseContent , func ( path string ) {
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 ( ) )
assert . True ( t , strings . HasPrefix ( provider . database . Users [ "harry" ] . Digest . 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 ( ) )
2020-03-06 01:38:02 +00:00
ok , err := provider . CheckUserPassword ( "harry" , "newpassword" )
assert . NoError ( t , err )
assert . True ( t , ok )
2022-10-17 10:51:59 +00:00
assert . True ( t , strings . HasPrefix ( provider . database . Users [ "harry" ] . Digest . Encode ( ) , "$argon2id$" ) )
2020-03-06 01:38:02 +00:00
} )
}
func TestShouldUpdatePasswordHashingAlgorithmToSHA512 ( t * testing . T ) {
WithDatabase ( UserDatabaseContent , func ( path string ) {
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 ( ) )
assert . True ( t , strings . HasPrefix ( provider . database . Users [ "john" ] . Digest . 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 ( ) )
2019-04-24 21:52:08 +00:00
ok , err := provider . CheckUserPassword ( "john" , "newpassword" )
assert . NoError ( t , err )
assert . True ( t , ok )
2022-10-17 10:51:59 +00:00
assert . True ( t , strings . HasPrefix ( provider . database . Users [ "john" ] . Digest . Encode ( ) , "$6$" ) )
2019-04-24 21:52:08 +00:00
} )
}
func TestShouldRaiseWhenLoadingMalformedDatabaseForFirstTime ( t * testing . T ) {
WithDatabase ( 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 ) {
WithDatabase ( 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 )
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 ) {
WithDatabase ( BadSHA512HashContent , func ( path string ) {
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': sha2crypt decode error: provided encoded hash has an invalid option: option 'rounds00000' is invalid" )
2020-03-06 01:38:02 +00:00
} )
}
func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashSettingsForTheFirstTime ( t * testing . T ) {
WithDatabase ( BadArgon2idHashSettingsContent , func ( path string ) {
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 an invalid option: option 'm65536' is invalid" )
2019-12-27 17:09:57 +00:00
} )
}
2020-03-06 01:38:02 +00:00
func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashKeyForTheFirstTime ( t * testing . T ) {
WithDatabase ( BadArgon2idHashKeyContent , func ( path string ) {
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 ) {
WithDatabase ( BadArgon2idHashSaltContent , func ( path string ) {
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 ) {
2020-03-06 01:38:02 +00:00
WithDatabase ( UserDatabaseWithoutCryptContent , func ( path string ) {
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 )
} )
}
2020-03-06 01:38:02 +00:00
var (
2022-10-17 10:51:59 +00:00
DefaultFileAuthenticationBackendConfiguration = schema . FileAuthenticationBackend {
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"
email : james . dean @ authelia . com
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
` )