2019-04-25 04:52:08 +07:00
package authentication
import (
"log"
"os"
2021-03-22 16:04:09 +07:00
"runtime"
2020-03-06 08:38:02 +07:00
"strings"
2019-04-25 04:52:08 +07:00
"testing"
"github.com/stretchr/testify/assert"
2020-06-17 13:25:35 +07:00
"github.com/stretchr/testify/require"
2020-04-05 19:37:21 +07:00
2021-08-11 08:04:35 +07:00
"github.com/authelia/authelia/v4/internal/configuration/schema"
2019-04-25 04:52:08 +07:00
)
func WithDatabase ( content [ ] byte , f func ( path string ) ) {
2021-12-01 20:14:15 +07:00
tmpfile , err := os . CreateTemp ( "" , "users_database.*.yaml" )
2019-04-25 04:52:08 +07:00
if err != nil {
log . Fatal ( err )
}
2020-09-18 19:05:43 +07:00
defer os . Remove ( tmpfile . Name ( ) ) // Clean up
2019-04-25 04:52:08 +07:00
if _ , err := tmpfile . Write ( content ) ; err != nil {
tmpfile . Close ( )
2020-09-18 19:05:43 +07:00
log . Panic ( err )
2019-04-25 04:52:08 +07:00
}
f ( tmpfile . Name ( ) )
if err := tmpfile . Close ( ) ; err != nil {
2020-09-18 19:05:43 +07:00
log . Panic ( err )
2019-04-25 04:52:08 +07:00
}
}
2020-06-17 13:25:35 +07:00
func TestShouldErrorPermissionsOnLocalFS ( t * testing . T ) {
2021-03-22 16:04:09 +07:00
if runtime . GOOS == "windows" {
t . Skip ( "skipping test due to being on windows" )
}
2020-06-17 13:25:35 +07:00
_ = os . Mkdir ( "/tmp/noperms/" , 0000 )
errors := checkDatabase ( "/tmp/noperms/users_database.yml" )
require . Len ( t , errors , 3 )
require . EqualError ( t , errors [ 0 ] , "Unable to find database file: /tmp/noperms/users_database.yml" )
require . EqualError ( t , errors [ 1 ] , "Generating database file: /tmp/noperms/users_database.yml" )
require . EqualError ( t , errors [ 2 ] , "Unable to generate /tmp/noperms/users_database.yml: open /tmp/noperms/users_database.yml: permission denied" )
}
func TestShouldErrorAndGenerateUserDB ( t * testing . T ) {
errors := checkDatabase ( "./nonexistent.yml" )
_ = os . Remove ( "./nonexistent.yml" )
require . Len ( t , errors , 3 )
require . EqualError ( t , errors [ 0 ] , "Unable to find database file: ./nonexistent.yml" )
require . EqualError ( t , errors [ 1 ] , "Generating database file: ./nonexistent.yml" )
require . EqualError ( t , errors [ 2 ] , "Generated database at: ./nonexistent.yml" )
}
2020-03-06 08:38:02 +07:00
func TestShouldCheckUserArgon2idPasswordIsCorrect ( t * testing . T ) {
2019-04-25 04:52:08 +07:00
WithDatabase ( UserDatabaseContent , func ( path string ) {
2020-03-06 08:38:02 +07:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
provider := NewFileUserProvider ( & config )
2019-04-25 04:52:08 +07:00
ok , err := provider . CheckUserPassword ( "john" , "password" )
assert . NoError ( t , err )
assert . True ( t , ok )
} )
}
2020-03-06 08:38:02 +07:00
func TestShouldCheckUserSHA512PasswordIsCorrect ( t * testing . T ) {
WithDatabase ( UserDatabaseContent , func ( path string ) {
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
provider := NewFileUserProvider ( & config )
ok , err := provider . CheckUserPassword ( "harry" , "password" )
assert . NoError ( t , err )
assert . True ( t , ok )
} )
}
2019-04-25 04:52:08 +07:00
func TestShouldCheckUserPasswordIsWrong ( t * testing . T ) {
WithDatabase ( UserDatabaseContent , func ( path string ) {
2020-03-06 08:38:02 +07:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
provider := NewFileUserProvider ( & config )
2019-04-25 04:52:08 +07:00
ok , err := provider . CheckUserPassword ( "john" , "wrong_password" )
assert . NoError ( t , err )
assert . False ( t , ok )
} )
}
2020-05-02 05:32:09 +07:00
func TestShouldCheckUserPasswordIsWrongForEnumerationCompare ( t * testing . T ) {
2019-04-25 04:52:08 +07:00
WithDatabase ( UserDatabaseContent , func ( path string ) {
2020-03-06 08:38:02 +07:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
provider := NewFileUserProvider ( & config )
2020-05-02 05:32:09 +07: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
provider := NewFileUserProvider ( & config )
ok , err := provider . CheckUserPassword ( "fake" , "password" )
2020-05-08 10:38:22 +07:00
assert . Error ( t , err )
2020-05-02 05:32:09 +07:00
assert . Equal ( t , false , ok )
2020-05-08 10:38:22 +07:00
assert . EqualError ( t , err , "user not found" )
2019-04-25 04:52:08 +07:00
} )
}
func TestShouldRetrieveUserDetails ( t * testing . T ) {
WithDatabase ( UserDatabaseContent , func ( path string ) {
2020-03-06 08:38:02 +07:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
provider := NewFileUserProvider ( & config )
2019-04-25 04:52:08 +07:00
details , err := provider . GetDetails ( "john" )
assert . NoError ( t , err )
2020-03-15 14:10:25 +07:00
assert . Equal ( t , details . Username , "john" )
2019-04-25 04:52:08 +07:00
assert . Equal ( t , details . Emails , [ ] string { "john.doe@authelia.com" } )
assert . Equal ( t , details . Groups , [ ] string { "admins" , "dev" } )
} )
}
func TestShouldUpdatePassword ( t * testing . T ) {
WithDatabase ( UserDatabaseContent , func ( path string ) {
2020-03-06 08:38:02 +07:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
provider := NewFileUserProvider ( & config )
err := provider . UpdatePassword ( "harry" , "newpassword" )
assert . NoError ( t , err )
// Reset the provider to force a read from disk.
provider = NewFileUserProvider ( & config )
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
provider := NewFileUserProvider ( & config )
2020-05-08 10:38:22 +07:00
assert . True ( t , strings . HasPrefix ( provider . database . Users [ "harry" ] . HashedPassword , "$6$" ) )
2020-03-06 08:38:02 +07:00
err := provider . UpdatePassword ( "harry" , "newpassword" )
assert . NoError ( t , err )
// Reset the provider to force a read from disk.
provider = NewFileUserProvider ( & config )
ok , err := provider . CheckUserPassword ( "harry" , "newpassword" )
assert . NoError ( t , err )
assert . True ( t , ok )
assert . True ( t , strings . HasPrefix ( provider . database . Users [ "harry" ] . HashedPassword , "$argon2id$" ) )
} )
}
func TestShouldUpdatePasswordHashingAlgorithmToSHA512 ( t * testing . T ) {
WithDatabase ( UserDatabaseContent , func ( path string ) {
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
2020-04-11 10:54:18 +07:00
config . Password . Algorithm = "sha512"
config . Password . Iterations = 50000
2020-03-06 08:38:02 +07:00
provider := NewFileUserProvider ( & config )
2020-05-08 10:38:22 +07:00
assert . True ( t , strings . HasPrefix ( provider . database . Users [ "john" ] . HashedPassword , "$argon2id$" ) )
2019-04-25 04:52:08 +07:00
err := provider . UpdatePassword ( "john" , "newpassword" )
assert . NoError ( t , err )
// Reset the provider to force a read from disk.
2020-03-06 08:38:02 +07:00
provider = NewFileUserProvider ( & config )
2019-04-25 04:52:08 +07:00
ok , err := provider . CheckUserPassword ( "john" , "newpassword" )
assert . NoError ( t , err )
assert . True ( t , ok )
2020-03-06 08:38:02 +07:00
assert . True ( t , strings . HasPrefix ( provider . database . Users [ "john" ] . HashedPassword , "$6$" ) )
2019-04-25 04:52:08 +07:00
} )
}
func TestShouldRaiseWhenLoadingMalformedDatabaseForFirstTime ( t * testing . T ) {
WithDatabase ( MalformedUserDatabaseContent , func ( path string ) {
2020-03-06 08:38:02 +07:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
2020-05-08 10:38:22 +07:00
assert . PanicsWithError ( t , "Unable to parse database: yaml: line 4: mapping values are not allowed in this context" , func ( ) {
2020-03-06 08:38:02 +07:00
NewFileUserProvider ( & config )
2019-04-25 04:52:08 +07:00
} )
} )
}
func TestShouldRaiseWhenLoadingDatabaseWithBadSchemaForFirstTime ( t * testing . T ) {
WithDatabase ( BadSchemaUserDatabaseContent , func ( path string ) {
2020-03-06 08:38:02 +07:00
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
2020-05-08 10:38:22 +07:00
assert . PanicsWithError ( t , "Invalid schema of database: Users: non zero value required" , func ( ) {
2020-03-06 08:38:02 +07:00
NewFileUserProvider ( & config )
} )
} )
}
func TestShouldRaiseWhenLoadingDatabaseWithBadSHA512HashesForTheFirstTime ( t * testing . T ) {
WithDatabase ( BadSHA512HashContent , func ( path string ) {
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
2020-05-08 10:38:22 +07:00
assert . PanicsWithError ( t , "Unable to parse hash of user john: Hash key is not the last parameter, the hash is likely malformed ($6$rounds00000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/)" , func ( ) {
2020-03-06 08:38:02 +07:00
NewFileUserProvider ( & config )
} )
} )
}
func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashSettingsForTheFirstTime ( t * testing . T ) {
WithDatabase ( BadArgon2idHashSettingsContent , func ( path string ) {
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
2020-05-08 10:38:22 +07:00
assert . PanicsWithError ( t , "Unable to parse hash of user john: Hash key is not the last parameter, the hash is likely malformed ($argon2id$v=19$m65536,t3,p2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM)" , func ( ) {
2020-03-06 08:38:02 +07:00
NewFileUserProvider ( & config )
2019-12-28 00:09:57 +07:00
} )
} )
}
2020-03-06 08:38:02 +07:00
func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashKeyForTheFirstTime ( t * testing . T ) {
WithDatabase ( BadArgon2idHashKeyContent , func ( path string ) {
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
2020-05-08 10:38:22 +07:00
assert . PanicsWithError ( t , "Unable to parse hash of user john: Hash key contains invalid base64 characters" , func ( ) {
2020-03-06 08:38:02 +07:00
NewFileUserProvider ( & config )
} )
} )
}
func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashSaltForTheFirstTime ( t * testing . T ) {
WithDatabase ( BadArgon2idHashSaltContent , func ( path string ) {
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
2020-05-08 10:38:22 +07:00
assert . PanicsWithError ( t , "Unable to parse hash of user john: Salt contains invalid base64 characters" , func ( ) {
2020-03-06 08:38:02 +07:00
NewFileUserProvider ( & config )
2019-04-25 04:52:08 +07:00
} )
} )
}
2019-12-27 23:55:00 +07:00
func TestShouldSupportHashPasswordWithoutCRYPT ( t * testing . T ) {
2020-03-06 08:38:02 +07:00
WithDatabase ( UserDatabaseWithoutCryptContent , func ( path string ) {
config := DefaultFileAuthenticationBackendConfiguration
config . Path = path
provider := NewFileUserProvider ( & config )
2019-12-27 23:55:00 +07:00
ok , err := provider . CheckUserPassword ( "john" , "password" )
assert . NoError ( t , err )
assert . True ( t , ok )
} )
}
2020-03-06 08:38:02 +07:00
var (
DefaultFileAuthenticationBackendConfiguration = schema . FileAuthenticationBackendConfiguration {
Path : "" ,
2020-04-11 10:54:18 +07:00
Password : & schema . PasswordConfiguration {
Iterations : schema . DefaultCIPasswordConfiguration . Iterations ,
KeyLength : schema . DefaultCIPasswordConfiguration . KeyLength ,
SaltLength : schema . DefaultCIPasswordConfiguration . SaltLength ,
Algorithm : schema . DefaultCIPasswordConfiguration . Algorithm ,
Memory : schema . DefaultCIPasswordConfiguration . Memory ,
Parallelism : schema . DefaultCIPasswordConfiguration . Parallelism ,
2020-03-06 08:38:02 +07:00
} ,
}
)
2019-04-25 04:52:08 +07:00
var UserDatabaseContent = [ ] byte ( `
users :
john :
2020-06-19 17:50:21 +07:00
displayname : "John Doe"
2020-03-06 08:38:02 +07:00
password : "{CRYPT}$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
2019-04-25 04:52:08 +07:00
email : john . doe @ authelia . com
groups :
- admins
- dev
harry :
2020-06-19 17:50:21 +07:00
displayname : "Harry Potter"
2019-04-25 04:52:08 +07:00
password : "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email : harry . potter @ authelia . com
groups : [ ]
bob :
2020-06-19 17:50:21 +07:00
displayname : "Bob Dylan"
2019-04-25 04:52:08 +07:00
password : "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email : bob . dylan @ authelia . com
groups :
- dev
james :
2020-06-19 17:50:21 +07:00
displayname : "James Dean"
2019-04-25 04:52:08 +07:00
password : "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email : james . dean @ authelia . com
2020-05-02 05:32:09 +07:00
enumeration :
2020-06-19 17:50:21 +07:00
displayname : "Enumeration"
2020-05-02 05:32:09 +07:00
password : "$argon2id$v=19$m=131072,p=8$BpLnfgDsc2WD8F2q$O126GHPeZ5fwj7OLSs7PndXsTbje76R+QW9/EGfhkJg"
email : james . dean @ authelia . com
2019-04-25 04:52:08 +07:00
` )
var MalformedUserDatabaseContent = [ ] byte ( `
users
john
email : john . doe @ authelia . com
groups :
2019-11-30 23:49:52 +07:00
- admins
2019-04-25 04:52:08 +07:00
- dev
` )
2020-05-02 12:06:39 +07:00
// The YAML is valid but the root key is user instead of users.
2019-04-25 04:52:08 +07:00
var BadSchemaUserDatabaseContent = [ ] byte ( `
user :
john :
2020-06-19 17:50:21 +07:00
displayname : "John Doe"
2019-04-25 04:52:08 +07:00
password : "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email : john . doe @ authelia . com
groups :
- admins
- dev
` )
2019-12-27 23:55:00 +07:00
2020-03-06 08:38:02 +07:00
var UserDatabaseWithoutCryptContent = [ ] byte ( `
2019-12-27 23:55:00 +07:00
users :
john :
2020-06-19 17:50:21 +07:00
displayname : "John Doe"
2019-12-27 23:55:00 +07:00
password : "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email : john . doe @ authelia . com
groups :
- admins
- dev
james :
2020-06-19 17:50:21 +07:00
displayname : "James Dean"
2019-12-27 23:55:00 +07:00
password : "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email : james . dean @ authelia . com
` )
2019-12-28 00:09:57 +07:00
2020-03-06 08:38:02 +07:00
var BadSHA512HashContent = [ ] byte ( `
2019-12-28 00:09:57 +07:00
users :
john :
2020-06-19 17:50:21 +07:00
displayname : "John Doe"
2019-12-28 00:09:57 +07:00
password : "$6$rounds00000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email : john . doe @ authelia . com
groups :
- admins
- dev
james :
2020-06-19 17:50:21 +07:00
displayname : "James Dean"
2019-12-28 00:09:57 +07:00
password : "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email : james . dean @ authelia . com
` )
2020-03-06 08:38:02 +07:00
var BadArgon2idHashSettingsContent = [ ] byte ( `
users :
john :
2020-06-19 17:50:21 +07:00
displayname : "John Doe"
2020-03-06 08:38:02 +07: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 17:50:21 +07:00
displayname : "James Dean"
2020-03-06 08:38:02 +07: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 17:50:21 +07:00
displayname : "John Doe"
2020-03-06 08:38:02 +07:00
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 :
2020-06-19 17:50:21 +07:00
displayname : "John Doe"
2020-03-06 08:38:02 +07:00
password : "$argon2id$v=19$m=65536,t=3,p=2$^^LnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
email : john . doe @ authelia . com
groups :
- admins
- dev
` )