[Buildkite] Introduce CI linting with golangci-lint and reviewdog (#832)

* [Buildkite] Introduce CI linting with golangci-lint and reviewdog

* Initial pass of golangci-lint

* Add gosimple (megacheck) recommendations

* Add golint recommendations

* [BUGFIX] Migrate authentication traces from v3 mongodb

* Add deadcode recommendations

* [BUGFIX] Fix ShortTimeouts suite when run in dev workflow

* Add unused recommendations

* Add unparam recommendations

* Disable linting on unfixable errors instead of skipping files

* Adjust nolint notation for unparam

* Fix ineffectual assignment to err raised by linter.

* Export environment variable in agent hook

* Add ineffassign recommendations

* Add staticcheck recommendations

* Add gocyclo recommendations

* Adjust ineffassign recommendations

Co-authored-by: Clement Michaud <clement.michaud34@gmail.com>
This commit is contained in:
Amir Zarrinkafsh 2020-04-09 11:05:17 +10:00 committed by GitHub
parent 2fed503e5e
commit de2c5836fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 326 additions and 352 deletions

View File

@ -2,6 +2,13 @@
set +u set +u
if [[ $BUILDKITE_PULL_REQUEST != "false" ]]; then
if [[ $BUILDKITE_LABEL == ":hammer_and_wrench: Unit Test" ]]; then
echo "--- :go::service_dog: Linting pull request"
reviewdog -reporter=github-pr-review
fi
fi
if [[ $BUILDKITE_LABEL =~ ":selenium:" ]] || [[ $BUILDKITE_LABEL =~ ":docker: Build Image" ]]; then if [[ $BUILDKITE_LABEL =~ ":selenium:" ]] || [[ $BUILDKITE_LABEL =~ ":docker: Build Image" ]]; then
CONTAINERS=$(docker ps -a -q) CONTAINERS=$(docker ps -a -q)
if [[ ${CONTAINERS} != "" ]]; then if [[ ${CONTAINERS} != "" ]]; then

25
.golangci.yml Normal file
View File

@ -0,0 +1,25 @@
run:
timeout: 3m
linters-settings:
gocyclo:
min-complexity: 15
goimports:
local-prefixes: github.com/authelia/authelia
linters:
enable:
- gocyclo
- gofmt
- goimports
- golint
- interfacer
- maligned
- misspell
- prealloc
- unparam
- whitespace
issues:
max-issues-per-linter: 0
max-same-issues: 0

8
.reviewdog.yml Normal file
View File

@ -0,0 +1,8 @@
runner:
golangci:
cmd: golangci-lint run
errorformat:
- '%E%f:%l:%c: %m'
- '%E%f:%l: %m'
- '%C%.%#'
level: warning

View File

@ -21,33 +21,33 @@ type HostEntry struct {
var hostEntries = []HostEntry{ var hostEntries = []HostEntry{
// For authelia backend // For authelia backend
HostEntry{Domain: "authelia.example.com", IP: "192.168.240.50"}, {Domain: "authelia.example.com", IP: "192.168.240.50"},
// For common tests // For common tests
HostEntry{Domain: "login.example.com", IP: "192.168.240.100"}, {Domain: "login.example.com", IP: "192.168.240.100"},
HostEntry{Domain: "admin.example.com", IP: "192.168.240.100"}, {Domain: "admin.example.com", IP: "192.168.240.100"},
HostEntry{Domain: "singlefactor.example.com", IP: "192.168.240.100"}, {Domain: "singlefactor.example.com", IP: "192.168.240.100"},
HostEntry{Domain: "dev.example.com", IP: "192.168.240.100"}, {Domain: "dev.example.com", IP: "192.168.240.100"},
HostEntry{Domain: "home.example.com", IP: "192.168.240.100"}, {Domain: "home.example.com", IP: "192.168.240.100"},
HostEntry{Domain: "mx1.mail.example.com", IP: "192.168.240.100"}, {Domain: "mx1.mail.example.com", IP: "192.168.240.100"},
HostEntry{Domain: "mx2.mail.example.com", IP: "192.168.240.100"}, {Domain: "mx2.mail.example.com", IP: "192.168.240.100"},
HostEntry{Domain: "public.example.com", IP: "192.168.240.100"}, {Domain: "public.example.com", IP: "192.168.240.100"},
HostEntry{Domain: "secure.example.com", IP: "192.168.240.100"}, {Domain: "secure.example.com", IP: "192.168.240.100"},
HostEntry{Domain: "mail.example.com", IP: "192.168.240.100"}, {Domain: "mail.example.com", IP: "192.168.240.100"},
HostEntry{Domain: "duo.example.com", IP: "192.168.240.100"}, {Domain: "duo.example.com", IP: "192.168.240.100"},
// For Traefik suite // For Traefik suite
HostEntry{Domain: "traefik.example.com", IP: "192.168.240.100"}, {Domain: "traefik.example.com", IP: "192.168.240.100"},
// For HAProxy suite // For HAProxy suite
HostEntry{Domain: "haproxy.example.com", IP: "192.168.240.100"}, {Domain: "haproxy.example.com", IP: "192.168.240.100"},
// For testing network ACLs // For testing network ACLs
HostEntry{Domain: "proxy-client1.example.com", IP: "192.168.240.201"}, {Domain: "proxy-client1.example.com", IP: "192.168.240.201"},
HostEntry{Domain: "proxy-client2.example.com", IP: "192.168.240.202"}, {Domain: "proxy-client2.example.com", IP: "192.168.240.202"},
HostEntry{Domain: "proxy-client3.example.com", IP: "192.168.240.203"}, {Domain: "proxy-client3.example.com", IP: "192.168.240.203"},
// Kubernetes dashboard // Kubernetes dashboard
HostEntry{Domain: "kubernetes.example.com", IP: "192.168.240.110"}, {Domain: "kubernetes.example.com", IP: "192.168.240.110"},
} }
func runCommand(cmd string, args ...string) { func runCommand(cmd string, args ...string) {
@ -71,16 +71,6 @@ func checkCommandExist(cmd string) {
fmt.Println(" OK") fmt.Println(" OK")
} }
func installClientNpmPackages() {
command := utils.CommandWithStdout("yarn", "install")
command.Dir = "client"
err := command.Run()
if err != nil {
panic(err)
}
}
func createTemporaryDirectory() { func createTemporaryDirectory() {
err := os.MkdirAll("/tmp/authelia", 0755) err := os.MkdirAll("/tmp/authelia", 0755)

View File

@ -7,16 +7,6 @@ import (
"github.com/authelia/authelia/internal/utils" "github.com/authelia/authelia/internal/utils"
) )
const dockerPullCommandLine = "docker-compose -p authelia -f internal/suites/docker-compose.yml " +
"-f internal/suites/example/compose/mariadb/docker-compose.yml " +
"-f internal/suites/example/compose/redis/docker-compose.yml " +
"-f internal/suites/example/compose/nginx/portal/docker-compose.yml " +
"-f internal/suites/example/compose/smtp/docker-compose.yml " +
"-f internal/suites/example/compose/httpbin/docker-compose.yml " +
"-f internal/suites/example/compose/ldap/docker-compose.admin.yml " +
"-f internal/suites/example/compose/ldap/docker-compose.yml " +
"pull"
// RunCI run the CI scripts // RunCI run the CI scripts
func RunCI(cmd *cobra.Command, args []string) { func RunCI(cmd *cobra.Command, args []string) {
log.Info("=====> Build stage <=====") log.Info("=====> Build stage <=====")

View File

@ -106,9 +106,7 @@ var SuitesTestCmd = &cobra.Command{
func listSuites() []string { func listSuites() []string {
suiteNames := make([]string, 0) suiteNames := make([]string, 0)
for _, k := range suites.GlobalRegistry.Suites() { suiteNames = append(suiteNames, suites.GlobalRegistry.Suites()...)
suiteNames = append(suiteNames, k)
}
sort.Strings(suiteNames) sort.Strings(suiteNames)
return suiteNames return suiteNames
} }

View File

@ -28,35 +28,35 @@ type CobraCommands = []*cobra.Command
// Commands is the list of commands of authelia-scripts // Commands is the list of commands of authelia-scripts
var Commands = []AutheliaCommandDefinition{ var Commands = []AutheliaCommandDefinition{
AutheliaCommandDefinition{ {
Name: "bootstrap", Name: "bootstrap",
Short: "Prepare environment for development and testing.", Short: "Prepare environment for development and testing.",
Long: `Prepare environment for development and testing. This command prepares docker Long: `Prepare environment for development and testing. This command prepares docker
images and download tools like Kind for Kubernetes testing.`, images and download tools like Kind for Kubernetes testing.`,
Func: Bootstrap, Func: Bootstrap,
}, },
AutheliaCommandDefinition{ {
Name: "build", Name: "build",
Short: "Build Authelia binary and static assets", Short: "Build Authelia binary and static assets",
Func: Build, Func: Build,
}, },
AutheliaCommandDefinition{ {
Name: "clean", Name: "clean",
Short: "Clean build artifacts", Short: "Clean build artifacts",
Func: Clean, Func: Clean,
}, },
AutheliaCommandDefinition{ {
Name: "docker", Name: "docker",
Short: "Commands related to building and publishing docker image", Short: "Commands related to building and publishing docker image",
SubCommands: CobraCommands{DockerBuildCmd, DockerPushCmd, DockerManifestCmd}, SubCommands: CobraCommands{DockerBuildCmd, DockerPushCmd, DockerManifestCmd},
}, },
AutheliaCommandDefinition{ {
Name: "serve [config]", Name: "serve [config]",
Short: "Serve compiled version of Authelia", Short: "Serve compiled version of Authelia",
Func: ServeCmd, Func: ServeCmd,
Args: cobra.MinimumNArgs(1), Args: cobra.MinimumNArgs(1),
}, },
AutheliaCommandDefinition{ {
Name: "suites", Name: "suites",
Short: "Compute hash of a password for creating a file-based users database", Short: "Compute hash of a password for creating a file-based users database",
SubCommands: CobraCommands{ SubCommands: CobraCommands{
@ -66,12 +66,12 @@ var Commands = []AutheliaCommandDefinition{
SuitesTeardownCmd, SuitesTeardownCmd,
}, },
}, },
AutheliaCommandDefinition{ {
Name: "ci", Name: "ci",
Short: "Run continuous integration script", Short: "Run continuous integration script",
Func: RunCI, Func: RunCI,
}, },
AutheliaCommandDefinition{ {
Name: "unittest", Name: "unittest",
Short: "Run unit tests", Short: "Run unit tests",
Func: RunUnitTest, Func: RunUnitTest,

View File

@ -25,6 +25,7 @@ import (
var configPathFlag string var configPathFlag string
//nolint:gocyclo // TODO: Consider refactoring/simplifying, time permitting
func startServer() { func startServer() {
if configPathFlag == "" { if configPathFlag == "" {
log.Fatal(errors.New("No config file path provided")) log.Fatal(errors.New("No config file path provided"))
@ -47,7 +48,6 @@ func startServer() {
case "info": case "info":
logging.Logger().Info("Logging severity set to info") logging.Logger().Info("Logging severity set to info")
logging.SetLevel(logrus.InfoLevel) logging.SetLevel(logrus.InfoLevel)
break
case "debug": case "debug":
logging.Logger().Info("Logging severity set to debug") logging.Logger().Info("Logging severity set to debug")
logging.SetLevel(logrus.DebugLevel) logging.SetLevel(logrus.DebugLevel)

View File

@ -4,11 +4,11 @@ package authentication
type Level int type Level int
const ( const (
// NotAuthenticated if the user is not authenticated yet. // NotAuthenticated if the user is not authenticated yet
NotAuthenticated Level = iota NotAuthenticated Level = iota
// OneFactor if the user has passed first factor only. // OneFactor if the user has passed first factor only
OneFactor Level = iota OneFactor Level = iota
// TwoFactor if the user has passed two factors. // TwoFactor if the user has passed two factors
TwoFactor Level = iota TwoFactor Level = iota
) )
@ -17,11 +17,11 @@ const (
TOTP = "totp" TOTP = "totp"
// U2F Method using U2F devices like Yubikeys // U2F Method using U2F devices like Yubikeys
U2F = "u2f" U2F = "u2f"
// Push Method using Duo application to receive push notifications. // Push Method using Duo application to receive push notifications
Push = "mobile_push" Push = "mobile_push"
) )
// PossibleMethods is the set of all possible 2FA methods. // PossibleMethods is the set of all possible 2FA methods
var PossibleMethods = []string{TOTP, U2F, Push} var PossibleMethods = []string{TOTP, U2F, Push}
const ( const (
@ -40,5 +40,5 @@ const (
HashingDefaultSHA512Iterations = 5000 HashingDefaultSHA512Iterations = 5000
) )
// Valid Hashing runes // HashingPossibleSaltCharacters represents valid hashing runes
var HashingPossibleSaltCharacters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/") var HashingPossibleSaltCharacters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/")

View File

@ -172,7 +172,7 @@ func TestShouldRaiseWhenLoadingDatabaseWithBadSHA512HashesForTheFirstTime(t *tes
WithDatabase(BadSHA512HashContent, func(path string) { WithDatabase(BadSHA512HashContent, func(path string) {
config := DefaultFileAuthenticationBackendConfiguration config := DefaultFileAuthenticationBackendConfiguration
config.Path = path config.Path = path
assert.PanicsWithValue(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() { assert.PanicsWithValue(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() {
NewFileUserProvider(&config) NewFileUserProvider(&config)
}) })
}) })
@ -182,7 +182,7 @@ func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashSettingsForTheFirstTim
WithDatabase(BadArgon2idHashSettingsContent, func(path string) { WithDatabase(BadArgon2idHashSettingsContent, func(path string) {
config := DefaultFileAuthenticationBackendConfiguration config := DefaultFileAuthenticationBackendConfiguration
config.Path = path config.Path = path
assert.PanicsWithValue(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() { assert.PanicsWithValue(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() {
NewFileUserProvider(&config) NewFileUserProvider(&config)
}) })
}) })
@ -192,7 +192,7 @@ func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashKeyForTheFirstTime(t *
WithDatabase(BadArgon2idHashKeyContent, func(path string) { WithDatabase(BadArgon2idHashKeyContent, func(path string) {
config := DefaultFileAuthenticationBackendConfiguration config := DefaultFileAuthenticationBackendConfiguration
config.Path = path config.Path = path
assert.PanicsWithValue(t, "Unable to parse hash of user john: Hash key contains invalid base64 characters.", func() { assert.PanicsWithValue(t, "Unable to parse hash of user john: Hash key contains invalid base64 characters", func() {
NewFileUserProvider(&config) NewFileUserProvider(&config)
}) })
}) })
@ -202,7 +202,7 @@ func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashSaltForTheFirstTime(t
WithDatabase(BadArgon2idHashSaltContent, func(path string) { WithDatabase(BadArgon2idHashSaltContent, func(path string) {
config := DefaultFileAuthenticationBackendConfiguration config := DefaultFileAuthenticationBackendConfiguration
config.Path = path config.Path = path
assert.PanicsWithValue(t, "Unable to parse hash of user john: Salt contains invalid base64 characters.", func() { assert.PanicsWithValue(t, "Unable to parse hash of user john: Salt contains invalid base64 characters", func() {
NewFileUserProvider(&config) NewFileUserProvider(&config)
}) })
}) })

View File

@ -178,7 +178,7 @@ func (p *LDAPUserProvider) getUserProfile(conn LDAPConnection, inputUsername str
return &userProfile, nil return &userProfile, nil
} }
func (p *LDAPUserProvider) resolveGroupsFilter(inputUsername string, profile *ldapUserProfile) (string, error) { func (p *LDAPUserProvider) resolveGroupsFilter(inputUsername string, profile *ldapUserProfile) (string, error) { //nolint:unparam
inputUsername = p.ldapEscape(inputUsername) inputUsername = p.ldapEscape(inputUsername)
// We temporarily keep placeholder {0} for backward compatibility // We temporarily keep placeholder {0} for backward compatibility

View File

@ -180,7 +180,7 @@ func TestShouldCombineUsernameFilterAndUsersFilter(t *testing.T) {
func createSearchResultWithAttributes(attributes ...*ldap.EntryAttribute) *ldap.SearchResult { func createSearchResultWithAttributes(attributes ...*ldap.EntryAttribute) *ldap.SearchResult {
return &ldap.SearchResult{ return &ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
&ldap.Entry{Attributes: attributes}, {Attributes: attributes},
}, },
} }
} }
@ -227,14 +227,14 @@ func TestShouldNotCrashWhenGroupsAreNotRetrievedFromLDAP(t *testing.T) {
Search(gomock.Any()). Search(gomock.Any()).
Return(&ldap.SearchResult{ Return(&ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
&ldap.Entry{ {
DN: "uid=test,dc=example,dc=com", DN: "uid=test,dc=example,dc=com",
Attributes: []*ldap.EntryAttribute{ Attributes: []*ldap.EntryAttribute{
&ldap.EntryAttribute{ {
Name: "mail", Name: "mail",
Values: []string{"test@example.com"}, Values: []string{"test@example.com"},
}, },
&ldap.EntryAttribute{ {
Name: "uid", Name: "uid",
Values: []string{"john"}, Values: []string{"john"},
}, },
@ -288,10 +288,10 @@ func TestShouldNotCrashWhenEmailsAreNotRetrievedFromLDAP(t *testing.T) {
Search(gomock.Any()). Search(gomock.Any()).
Return(&ldap.SearchResult{ Return(&ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
&ldap.Entry{ {
DN: "uid=test,dc=example,dc=com", DN: "uid=test,dc=example,dc=com",
Attributes: []*ldap.EntryAttribute{ Attributes: []*ldap.EntryAttribute{
&ldap.EntryAttribute{ {
Name: "uid", Name: "uid",
Values: []string{"john"}, Values: []string{"john"},
}, },
@ -346,14 +346,14 @@ func TestShouldReturnUsernameFromLDAP(t *testing.T) {
Search(gomock.Any()). Search(gomock.Any()).
Return(&ldap.SearchResult{ Return(&ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
&ldap.Entry{ {
DN: "uid=test,dc=example,dc=com", DN: "uid=test,dc=example,dc=com",
Attributes: []*ldap.EntryAttribute{ Attributes: []*ldap.EntryAttribute{
&ldap.EntryAttribute{ {
Name: "mail", Name: "mail",
Values: []string{"test@example.com"}, Values: []string{"test@example.com"},
}, },
&ldap.EntryAttribute{ {
Name: "uid", Name: "uid",
Values: []string{"John"}, Values: []string{"John"},
}, },

View File

@ -12,7 +12,7 @@ import (
) )
// PasswordHash represents all characteristics of a password hash. // PasswordHash represents all characteristics of a password hash.
// Authelia only supports salted SHA512 or salted argon2id method, i.e., $6$ mode or $argon2id$ mode. // Authelia only supports salted SHA512 or salted argon2id method, i.e., $6$ mode or $argon2id$ mode
type PasswordHash struct { type PasswordHash struct {
Algorithm string Algorithm string
Iterations int Iterations int
@ -23,7 +23,7 @@ type PasswordHash struct {
Parallelism int Parallelism int
} }
// ParseHash extracts all characteristics of a hash given its string representation. // ParseHash extracts all characteristics of a hash given its string representation
func ParseHash(hash string) (passwordHash *PasswordHash, err error) { func ParseHash(hash string) (passwordHash *PasswordHash, err error) {
parts := strings.Split(hash, "$") parts := strings.Split(hash, "$")
@ -35,7 +35,7 @@ func ParseHash(hash string) (passwordHash *PasswordHash, err error) {
h.Key = key h.Key = key
if h.Key != parts[len(parts)-1] { if h.Key != parts[len(parts)-1] {
return nil, fmt.Errorf("Hash key is not the last parameter, the hash is likely malformed (%s).", hash) return nil, fmt.Errorf("Hash key is not the last parameter, the hash is likely malformed (%s)", hash)
} }
if h.Key == "" { if h.Key == "" {
return nil, fmt.Errorf("Hash key contains no characters or the field length is invalid (%s)", hash) return nil, fmt.Errorf("Hash key contains no characters or the field length is invalid (%s)", hash)
@ -43,14 +43,14 @@ func ParseHash(hash string) (passwordHash *PasswordHash, err error) {
_, err = crypt.Base64Encoding.DecodeString(h.Salt) _, err = crypt.Base64Encoding.DecodeString(h.Salt)
if err != nil { if err != nil {
return nil, errors.New("Salt contains invalid base64 characters.") return nil, errors.New("Salt contains invalid base64 characters")
} }
if code == HashingAlgorithmSHA512 { if code == HashingAlgorithmSHA512 {
h.Iterations = parameters.GetInt("rounds", HashingDefaultSHA512Iterations) h.Iterations = parameters.GetInt("rounds", HashingDefaultSHA512Iterations)
h.Algorithm = HashingAlgorithmSHA512 h.Algorithm = HashingAlgorithmSHA512
if parameters["rounds"] != "" && parameters["rounds"] != strconv.Itoa(h.Iterations) { if parameters["rounds"] != "" && parameters["rounds"] != strconv.Itoa(h.Iterations) {
return nil, fmt.Errorf("SHA512 iterations is not numeric (%s).", parameters["rounds"]) return nil, fmt.Errorf("SHA512 iterations is not numeric (%s)", parameters["rounds"])
} }
} else if code == HashingAlgorithmArgon2id { } else if code == HashingAlgorithmArgon2id {
version := parameters.GetInt("v", 0) version := parameters.GetInt("v", 0)
@ -58,9 +58,9 @@ func ParseHash(hash string) (passwordHash *PasswordHash, err error) {
if version == 0 { if version == 0 {
return nil, fmt.Errorf("Argon2id version parameter not found (%s)", hash) return nil, fmt.Errorf("Argon2id version parameter not found (%s)", hash)
} }
return nil, fmt.Errorf("Argon2id versions less than v19 are not supported (hash is version %d).", version) return nil, fmt.Errorf("Argon2id versions less than v19 are not supported (hash is version %d)", version)
} else if version > 19 { } else if version > 19 {
return nil, fmt.Errorf("Argon2id versions greater than v19 are not supported (hash is version %d).", version) return nil, fmt.Errorf("Argon2id versions greater than v19 are not supported (hash is version %d)", version)
} }
h.Algorithm = HashingAlgorithmArgon2id h.Algorithm = HashingAlgorithmArgon2id
h.Memory = parameters.GetInt("m", HashingDefaultArgon2idMemory) h.Memory = parameters.GetInt("m", HashingDefaultArgon2idMemory)
@ -70,10 +70,10 @@ func ParseHash(hash string) (passwordHash *PasswordHash, err error) {
decodedKey, err := crypt.Base64Encoding.DecodeString(h.Key) decodedKey, err := crypt.Base64Encoding.DecodeString(h.Key)
if err != nil { if err != nil {
return nil, errors.New("Hash key contains invalid base64 characters.") return nil, errors.New("Hash key contains invalid base64 characters")
} }
if len(decodedKey) != h.KeyLength { if len(decodedKey) != h.KeyLength {
return nil, fmt.Errorf("Argon2id key length parameter (%d) does not match the actual key length (%d).", h.KeyLength, len(decodedKey)) return nil, fmt.Errorf("Argon2id key length parameter (%d) does not match the actual key length (%d)", h.KeyLength, len(decodedKey))
} }
} else { } else {
return nil, fmt.Errorf("Authelia only supports salted SHA512 hashing ($6$) and salted argon2id ($argon2id$), not $%s$", code) return nil, fmt.Errorf("Authelia only supports salted SHA512 hashing ($6$) and salted argon2id ($argon2id$), not $%s$", code)
@ -81,47 +81,47 @@ func ParseHash(hash string) (passwordHash *PasswordHash, err error) {
return h, nil return h, nil
} }
// HashPassword generate a salt and hash the password with the salt and a constant // HashPassword generate a salt and hash the password with the salt and a constant number of rounds
// number of rounds. //nolint:gocyclo // TODO: Consider refactoring/simplifying, time permitting
func HashPassword(password, salt, algorithm string, iterations, memory, parallelism, keyLength, saltLength int) (hash string, err error) { func HashPassword(password, salt, algorithm string, iterations, memory, parallelism, keyLength, saltLength int) (hash string, err error) {
var settings string var settings string
if algorithm != HashingAlgorithmArgon2id && algorithm != HashingAlgorithmSHA512 { if algorithm != HashingAlgorithmArgon2id && algorithm != HashingAlgorithmSHA512 {
return "", fmt.Errorf("Hashing algorithm input of '%s' is invalid, only values of %s and %s are supported.", algorithm, HashingAlgorithmArgon2id, HashingAlgorithmSHA512) return "", fmt.Errorf("Hashing algorithm input of '%s' is invalid, only values of %s and %s are supported", algorithm, HashingAlgorithmArgon2id, HashingAlgorithmSHA512)
} }
if salt == "" { if salt == "" {
if saltLength < 2 { if saltLength < 2 {
return "", fmt.Errorf("Salt length input of %d is invalid, it must be 2 or higher.", saltLength) return "", fmt.Errorf("Salt length input of %d is invalid, it must be 2 or higher", saltLength)
} else if saltLength > 16 { } else if saltLength > 16 {
return "", fmt.Errorf("Salt length input of %d is invalid, it must be 16 or lower.", saltLength) return "", fmt.Errorf("Salt length input of %d is invalid, it must be 16 or lower", saltLength)
} }
} else if len(salt) > 16 { } else if len(salt) > 16 {
return "", fmt.Errorf("Salt input of %s is invalid (%d characters), it must be 16 or fewer characters.", salt, len(salt)) return "", fmt.Errorf("Salt input of %s is invalid (%d characters), it must be 16 or fewer characters", salt, len(salt))
} else if len(salt) < 2 { } else if len(salt) < 2 {
return "", fmt.Errorf("Salt input of %s is invalid (%d characters), it must be 2 or more characters.", salt, len(salt)) return "", fmt.Errorf("Salt input of %s is invalid (%d characters), it must be 2 or more characters", salt, len(salt))
} else if _, err = crypt.Base64Encoding.DecodeString(salt); err != nil { } else if _, err = crypt.Base64Encoding.DecodeString(salt); err != nil {
return "", fmt.Errorf("Salt input of %s is invalid, only characters [a-zA-Z0-9+/] are valid for input.", salt) return "", fmt.Errorf("Salt input of %s is invalid, only characters [a-zA-Z0-9+/] are valid for input", salt)
} }
if algorithm == HashingAlgorithmArgon2id { if algorithm == HashingAlgorithmArgon2id {
// Caution: Increasing any of the values in the below block has a high chance in old passwords that cannot be verified. // Caution: Increasing any of the values in the below block has a high chance in old passwords that cannot be verified
if memory < 8 { if memory < 8 {
return "", fmt.Errorf("Memory (argon2id) input of %d is invalid, it must be 8 or higher.", memory) return "", fmt.Errorf("Memory (argon2id) input of %d is invalid, it must be 8 or higher", memory)
} }
if parallelism < 1 { if parallelism < 1 {
return "", fmt.Errorf("Parallelism (argon2id) input of %d is invalid, it must be 1 or higher.", parallelism) return "", fmt.Errorf("Parallelism (argon2id) input of %d is invalid, it must be 1 or higher", parallelism)
} }
if memory < parallelism*8 { if memory < parallelism*8 {
return "", fmt.Errorf("Memory (argon2id) input of %d is invalid with a paraellelism input of %d, it must be %d (parallelism * 8) or higher.", memory, parallelism, parallelism*8) return "", fmt.Errorf("Memory (argon2id) input of %d is invalid with a parallelism input of %d, it must be %d (parallelism * 8) or higher", memory, parallelism, parallelism*8)
} }
if keyLength < 16 { if keyLength < 16 {
return "", fmt.Errorf("Key length (argon2id) input of %d is invalid, it must be 16 or higher.", keyLength) return "", fmt.Errorf("Key length (argon2id) input of %d is invalid, it must be 16 or higher", keyLength)
} }
if iterations < 1 { if iterations < 1 {
return "", fmt.Errorf("Iterations (argon2id) input of %d is invalid, it must be 1 or more.", iterations) return "", fmt.Errorf("Iterations (argon2id) input of %d is invalid, it must be 1 or more", iterations)
} }
// Caution: Increasing any of the values in the above block has a high chance in old passwords that cannot be verified. // Caution: Increasing any of the values in the above block has a high chance in old passwords that cannot be verified
} }
if salt == "" { if salt == "" {
@ -138,7 +138,7 @@ func HashPassword(password, salt, algorithm string, iterations, memory, parallel
return hash, nil return hash, nil
} }
// CheckPassword check a password against a hash. // CheckPassword check a password against a hash
func CheckPassword(password, hash string) (ok bool, err error) { func CheckPassword(password, hash string) (ok bool, err error) {
passwordHash, err := ParseHash(hash) passwordHash, err := ParseHash(hash)
if err != nil { if err != nil {

View File

@ -17,7 +17,7 @@ func TestShouldHashSHA512Password(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
code, parameters, salt, hash, err := crypt.DecodeSettings(hash) code, parameters, salt, hash, _ := crypt.DecodeSettings(hash)
assert.Equal(t, "6", code) assert.Equal(t, "6", code)
assert.Equal(t, "aFr56HjK3DrB8t3S", salt) assert.Equal(t, "aFr56HjK3DrB8t3S", salt)
@ -78,7 +78,7 @@ func TestShouldNotHashPasswordWithNonExistentAlgorithm(t *testing.T) {
schema.DefaultCIPasswordOptionsConfiguration.SaltLength) schema.DefaultCIPasswordOptionsConfiguration.SaltLength)
assert.Equal(t, "", hash) assert.Equal(t, "", hash)
assert.EqualError(t, err, "Hashing algorithm input of 'bogus' is invalid, only values of argon2id and 6 are supported.") assert.EqualError(t, err, "Hashing algorithm input of 'bogus' is invalid, only values of argon2id and 6 are supported")
} }
func TestShouldNotHashArgon2idPasswordDueToMemoryParallelismMismatch(t *testing.T) { func TestShouldNotHashArgon2idPasswordDueToMemoryParallelismMismatch(t *testing.T) {
@ -87,7 +87,7 @@ func TestShouldNotHashArgon2idPasswordDueToMemoryParallelismMismatch(t *testing.
schema.DefaultCIPasswordOptionsConfiguration.KeyLength, schema.DefaultCIPasswordOptionsConfiguration.SaltLength) schema.DefaultCIPasswordOptionsConfiguration.KeyLength, schema.DefaultCIPasswordOptionsConfiguration.SaltLength)
assert.Equal(t, "", hash) assert.Equal(t, "", hash)
assert.EqualError(t, err, "Memory (argon2id) input of 8 is invalid with a paraellelism input of 2, it must be 16 (parallelism * 8) or higher.") assert.EqualError(t, err, "Memory (argon2id) input of 8 is invalid with a parallelism input of 2, it must be 16 (parallelism * 8) or higher")
} }
func TestShouldNotHashArgon2idPasswordDueToMemoryLessThanEight(t *testing.T) { func TestShouldNotHashArgon2idPasswordDueToMemoryLessThanEight(t *testing.T) {
@ -96,7 +96,7 @@ func TestShouldNotHashArgon2idPasswordDueToMemoryLessThanEight(t *testing.T) {
schema.DefaultCIPasswordOptionsConfiguration.KeyLength, schema.DefaultCIPasswordOptionsConfiguration.SaltLength) schema.DefaultCIPasswordOptionsConfiguration.KeyLength, schema.DefaultCIPasswordOptionsConfiguration.SaltLength)
assert.Equal(t, "", hash) assert.Equal(t, "", hash)
assert.EqualError(t, err, "Memory (argon2id) input of 1 is invalid, it must be 8 or higher.") assert.EqualError(t, err, "Memory (argon2id) input of 1 is invalid, it must be 8 or higher")
} }
func TestShouldNotHashArgon2idPasswordDueToKeyLengthLessThanSixteen(t *testing.T) { func TestShouldNotHashArgon2idPasswordDueToKeyLengthLessThanSixteen(t *testing.T) {
@ -105,7 +105,7 @@ func TestShouldNotHashArgon2idPasswordDueToKeyLengthLessThanSixteen(t *testing.T
schema.DefaultCIPasswordOptionsConfiguration.Parallelism, 5, schema.DefaultCIPasswordOptionsConfiguration.SaltLength) schema.DefaultCIPasswordOptionsConfiguration.Parallelism, 5, schema.DefaultCIPasswordOptionsConfiguration.SaltLength)
assert.Equal(t, "", hash) assert.Equal(t, "", hash)
assert.EqualError(t, err, "Key length (argon2id) input of 5 is invalid, it must be 16 or higher.") assert.EqualError(t, err, "Key length (argon2id) input of 5 is invalid, it must be 16 or higher")
} }
func TestShouldNotHashArgon2idPasswordDueParallelismLessThanOne(t *testing.T) { func TestShouldNotHashArgon2idPasswordDueParallelismLessThanOne(t *testing.T) {
@ -114,7 +114,7 @@ func TestShouldNotHashArgon2idPasswordDueParallelismLessThanOne(t *testing.T) {
schema.DefaultCIPasswordOptionsConfiguration.KeyLength, schema.DefaultCIPasswordOptionsConfiguration.SaltLength) schema.DefaultCIPasswordOptionsConfiguration.KeyLength, schema.DefaultCIPasswordOptionsConfiguration.SaltLength)
assert.Equal(t, "", hash) assert.Equal(t, "", hash)
assert.EqualError(t, err, "Parallelism (argon2id) input of -1 is invalid, it must be 1 or higher.") assert.EqualError(t, err, "Parallelism (argon2id) input of -1 is invalid, it must be 1 or higher")
} }
func TestShouldNotHashArgon2idPasswordDueIterationsLessThanOne(t *testing.T) { func TestShouldNotHashArgon2idPasswordDueIterationsLessThanOne(t *testing.T) {
@ -123,7 +123,7 @@ func TestShouldNotHashArgon2idPasswordDueIterationsLessThanOne(t *testing.T) {
schema.DefaultCIPasswordOptionsConfiguration.KeyLength, schema.DefaultCIPasswordOptionsConfiguration.SaltLength) schema.DefaultCIPasswordOptionsConfiguration.KeyLength, schema.DefaultCIPasswordOptionsConfiguration.SaltLength)
assert.Equal(t, "", hash) assert.Equal(t, "", hash)
assert.EqualError(t, err, "Iterations (argon2id) input of 0 is invalid, it must be 1 or more.") assert.EqualError(t, err, "Iterations (argon2id) input of 0 is invalid, it must be 1 or more")
} }
func TestShouldNotHashPasswordDueToSaltLength(t *testing.T) { func TestShouldNotHashPasswordDueToSaltLength(t *testing.T) {
@ -132,14 +132,14 @@ func TestShouldNotHashPasswordDueToSaltLength(t *testing.T) {
schema.DefaultCIPasswordOptionsConfiguration.Parallelism, schema.DefaultCIPasswordOptionsConfiguration.KeyLength, 0) schema.DefaultCIPasswordOptionsConfiguration.Parallelism, schema.DefaultCIPasswordOptionsConfiguration.KeyLength, 0)
assert.Equal(t, "", hash) assert.Equal(t, "", hash)
assert.EqualError(t, err, "Salt length input of 0 is invalid, it must be 2 or higher.") assert.EqualError(t, err, "Salt length input of 0 is invalid, it must be 2 or higher")
hash, err = HashPassword("password", "", HashingAlgorithmArgon2id, hash, err = HashPassword("password", "", HashingAlgorithmArgon2id,
schema.DefaultCIPasswordOptionsConfiguration.Iterations, schema.DefaultCIPasswordOptionsConfiguration.Memory*1024, schema.DefaultCIPasswordOptionsConfiguration.Iterations, schema.DefaultCIPasswordOptionsConfiguration.Memory*1024,
schema.DefaultCIPasswordOptionsConfiguration.Parallelism, schema.DefaultCIPasswordOptionsConfiguration.KeyLength, 20) schema.DefaultCIPasswordOptionsConfiguration.Parallelism, schema.DefaultCIPasswordOptionsConfiguration.KeyLength, 20)
assert.Equal(t, "", hash) assert.Equal(t, "", hash)
assert.EqualError(t, err, "Salt length input of 20 is invalid, it must be 16 or lower.") assert.EqualError(t, err, "Salt length input of 20 is invalid, it must be 16 or lower")
} }
func TestShouldNotHashPasswordDueToSaltCharLengthTooLong(t *testing.T) { func TestShouldNotHashPasswordDueToSaltCharLengthTooLong(t *testing.T) {
@ -148,7 +148,7 @@ func TestShouldNotHashPasswordDueToSaltCharLengthTooLong(t *testing.T) {
schema.DefaultCIPasswordOptionsConfiguration.Parallelism, schema.DefaultCIPasswordOptionsConfiguration.KeyLength, schema.DefaultCIPasswordOptionsConfiguration.Parallelism, schema.DefaultCIPasswordOptionsConfiguration.KeyLength,
schema.DefaultCIPasswordOptionsConfiguration.SaltLength) schema.DefaultCIPasswordOptionsConfiguration.SaltLength)
assert.Equal(t, "", hash) assert.Equal(t, "", hash)
assert.EqualError(t, err, "Salt input of abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 is invalid (62 characters), it must be 16 or fewer characters.") assert.EqualError(t, err, "Salt input of abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 is invalid (62 characters), it must be 16 or fewer characters")
} }
func TestShouldNotHashPasswordDueToSaltCharLengthTooShort(t *testing.T) { func TestShouldNotHashPasswordDueToSaltCharLengthTooShort(t *testing.T) {
@ -157,7 +157,7 @@ func TestShouldNotHashPasswordDueToSaltCharLengthTooShort(t *testing.T) {
schema.DefaultCIPasswordOptionsConfiguration.Parallelism, schema.DefaultCIPasswordOptionsConfiguration.KeyLength, schema.DefaultCIPasswordOptionsConfiguration.Parallelism, schema.DefaultCIPasswordOptionsConfiguration.KeyLength,
schema.DefaultCIPasswordOptionsConfiguration.SaltLength) schema.DefaultCIPasswordOptionsConfiguration.SaltLength)
assert.Equal(t, "", hash) assert.Equal(t, "", hash)
assert.EqualError(t, err, "Salt input of a is invalid (1 characters), it must be 2 or more characters.") assert.EqualError(t, err, "Salt input of a is invalid (1 characters), it must be 2 or more characters")
} }
func TestShouldNotHashPasswordWithNonBase64CharsInSalt(t *testing.T) { func TestShouldNotHashPasswordWithNonBase64CharsInSalt(t *testing.T) {
@ -166,18 +166,18 @@ func TestShouldNotHashPasswordWithNonBase64CharsInSalt(t *testing.T) {
schema.DefaultCIPasswordOptionsConfiguration.Parallelism, schema.DefaultCIPasswordOptionsConfiguration.KeyLength, schema.DefaultCIPasswordOptionsConfiguration.Parallelism, schema.DefaultCIPasswordOptionsConfiguration.KeyLength,
schema.DefaultCIPasswordOptionsConfiguration.SaltLength) schema.DefaultCIPasswordOptionsConfiguration.SaltLength)
assert.Equal(t, "", hash) assert.Equal(t, "", hash)
assert.EqualError(t, err, "Salt input of abc&123 is invalid, only characters [a-zA-Z0-9+/] are valid for input.") assert.EqualError(t, err, "Salt input of abc&123 is invalid, only characters [a-zA-Z0-9+/] are valid for input")
} }
func TestShouldNotParseHashWithNoneBase64CharsInKey(t *testing.T) { func TestShouldNotParseHashWithNoneBase64CharsInKey(t *testing.T) {
passwordHash, err := ParseHash("$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$^^vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM") passwordHash, err := ParseHash("$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$^^vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM")
assert.EqualError(t, err, "Hash key contains invalid base64 characters.") assert.EqualError(t, err, "Hash key contains invalid base64 characters")
assert.Nil(t, passwordHash) assert.Nil(t, passwordHash)
} }
func TestShouldNotParseHashWithNoneBase64CharsInSalt(t *testing.T) { func TestShouldNotParseHashWithNoneBase64CharsInSalt(t *testing.T) {
passwordHash, err := ParseHash("$argon2id$v=19$m=65536$^^wTFoFjITudo57a$Z4NH/EKkdv6PJ01Ye1twJ61fsmRJujZZn1IXdUOyrJY") passwordHash, err := ParseHash("$argon2id$v=19$m=65536$^^wTFoFjITudo57a$Z4NH/EKkdv6PJ01Ye1twJ61fsmRJujZZn1IXdUOyrJY")
assert.EqualError(t, err, "Salt contains invalid base64 characters.") assert.EqualError(t, err, "Salt contains invalid base64 characters")
assert.Nil(t, passwordHash) assert.Nil(t, passwordHash)
} }
@ -187,15 +187,15 @@ func TestShouldNotParseWithMalformedHash(t *testing.T) {
hashMissingSalt := "$argon2id$v=1$m=65536,t=3,p=2$2t9X8nNCN2n3/kFYJ3xWNBg5k/rO782Qr7JJoJIK7G4" hashMissingSalt := "$argon2id$v=1$m=65536,t=3,p=2$2t9X8nNCN2n3/kFYJ3xWNBg5k/rO782Qr7JJoJIK7G4"
passwordHash, err := ParseHash(hashExtraField) passwordHash, err := ParseHash(hashExtraField)
assert.EqualError(t, err, fmt.Sprintf("Hash key is not the last parameter, the hash is likely malformed (%s).", hashExtraField)) assert.EqualError(t, err, fmt.Sprintf("Hash key is not the last parameter, the hash is likely malformed (%s)", hashExtraField))
assert.Nil(t, passwordHash) assert.Nil(t, passwordHash)
passwordHash, err = ParseHash(hashMissingSaltAndParams) passwordHash, err = ParseHash(hashMissingSaltAndParams)
assert.EqualError(t, err, fmt.Sprintf("Hash key is not the last parameter, the hash is likely malformed (%s).", hashMissingSaltAndParams)) assert.EqualError(t, err, fmt.Sprintf("Hash key is not the last parameter, the hash is likely malformed (%s)", hashMissingSaltAndParams))
assert.Nil(t, passwordHash) assert.Nil(t, passwordHash)
passwordHash, err = ParseHash(hashMissingSalt) passwordHash, err = ParseHash(hashMissingSalt)
assert.EqualError(t, err, fmt.Sprintf("Hash key is not the last parameter, the hash is likely malformed (%s).", hashMissingSalt)) assert.EqualError(t, err, fmt.Sprintf("Hash key is not the last parameter, the hash is likely malformed (%s)", hashMissingSalt))
assert.Nil(t, passwordHash) assert.Nil(t, passwordHash)
} }
@ -216,7 +216,7 @@ func TestShouldNotParseArgon2idHashWithEmptyVersion(t *testing.T) {
func TestShouldNotParseArgon2idHashWithWrongKeyLength(t *testing.T) { func TestShouldNotParseArgon2idHashWithWrongKeyLength(t *testing.T) {
hash := "$argon2id$v=19$m=65536,k=50$fvwTFoFjITudo57a$Z4NH/EKkdv6PJ01Ye1twJ61fsmRJujZZn1IXdUOyrJY" hash := "$argon2id$v=19$m=65536,k=50$fvwTFoFjITudo57a$Z4NH/EKkdv6PJ01Ye1twJ61fsmRJujZZn1IXdUOyrJY"
passwordHash, err := ParseHash(hash) passwordHash, err := ParseHash(hash)
assert.EqualError(t, err, "Argon2id key length parameter (50) does not match the actual key length (32).") assert.EqualError(t, err, "Argon2id key length parameter (50) does not match the actual key length (32)")
assert.Nil(t, passwordHash) assert.Nil(t, passwordHash)
} }
@ -244,14 +244,14 @@ func TestShouldCheckArgon2idPassword(t *testing.T) {
func TestCannotParseSHA512Hash(t *testing.T) { func TestCannotParseSHA512Hash(t *testing.T) {
ok, err := CheckPassword("password", "$6$roSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1") ok, err := CheckPassword("password", "$6$roSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1")
assert.EqualError(t, err, "Hash key is not the last parameter, the hash is likely malformed ($6$roSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1).") assert.EqualError(t, err, "Hash key is not the last parameter, the hash is likely malformed ($6$roSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1)")
assert.False(t, ok) assert.False(t, ok)
} }
func TestCannotParseArgon2idHash(t *testing.T) { func TestCannotParseArgon2idHash(t *testing.T) {
ok, err := CheckPassword("password", "$argon2id$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM") ok, err := CheckPassword("password", "$argon2id$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM")
assert.EqualError(t, err, "Hash key is not the last parameter, the hash is likely malformed ($argon2id$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM).") assert.EqualError(t, err, "Hash key is not the last parameter, the hash is likely malformed ($argon2id$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM)")
assert.False(t, ok) assert.False(t, ok)
} }
@ -266,35 +266,35 @@ func TestCannotFindNumberOfRounds(t *testing.T) {
hash := "$6$rounds50000$aFr56HjK3DrB8t3S$zhPQiS85cgBlNhUKKE6n/AHMlpqrvYSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1" hash := "$6$rounds50000$aFr56HjK3DrB8t3S$zhPQiS85cgBlNhUKKE6n/AHMlpqrvYSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1"
ok, err := CheckPassword("password", hash) ok, err := CheckPassword("password", hash)
assert.EqualError(t, err, fmt.Sprintf("Hash key is not the last parameter, the hash is likely malformed (%s).", hash)) assert.EqualError(t, err, fmt.Sprintf("Hash key is not the last parameter, the hash is likely malformed (%s)", hash))
assert.False(t, ok) assert.False(t, ok)
} }
func TestCannotMatchArgon2idParamPattern(t *testing.T) { func TestCannotMatchArgon2idParamPattern(t *testing.T) {
ok, err := CheckPassword("password", "$argon2id$v=19$m65536,t3,p2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM") ok, err := CheckPassword("password", "$argon2id$v=19$m65536,t3,p2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM")
assert.EqualError(t, err, "Hash key is not the last parameter, the hash is likely malformed ($argon2id$v=19$m65536,t3,p2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM).") assert.EqualError(t, err, "Hash key is not the last parameter, the hash is likely malformed ($argon2id$v=19$m65536,t3,p2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM)")
assert.False(t, ok) assert.False(t, ok)
} }
func TestArgon2idVersionLessThanSupported(t *testing.T) { func TestArgon2idVersionLessThanSupported(t *testing.T) {
ok, err := CheckPassword("password", "$argon2id$v=18$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM") ok, err := CheckPassword("password", "$argon2id$v=18$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM")
assert.EqualError(t, err, "Argon2id versions less than v19 are not supported (hash is version 18).") assert.EqualError(t, err, "Argon2id versions less than v19 are not supported (hash is version 18)")
assert.False(t, ok) assert.False(t, ok)
} }
func TestArgon2idVersionGreaterThanSupported(t *testing.T) { func TestArgon2idVersionGreaterThanSupported(t *testing.T) {
ok, err := CheckPassword("password", "$argon2id$v=20$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM") ok, err := CheckPassword("password", "$argon2id$v=20$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM")
assert.EqualError(t, err, "Argon2id versions greater than v19 are not supported (hash is version 20).") assert.EqualError(t, err, "Argon2id versions greater than v19 are not supported (hash is version 20)")
assert.False(t, ok) assert.False(t, ok)
} }
func TestNumberOfRoundsNotInt(t *testing.T) { func TestNumberOfRoundsNotInt(t *testing.T) {
ok, err := CheckPassword("password", "$6$rounds=abc$aFr56HjK3DrB8t3S$zhPQiS85cgBlNhUKKE6n/AHMlpqrvYSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1") ok, err := CheckPassword("password", "$6$rounds=abc$aFr56HjK3DrB8t3S$zhPQiS85cgBlNhUKKE6n/AHMlpqrvYSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1")
assert.EqualError(t, err, "SHA512 iterations is not numeric (abc).") assert.EqualError(t, err, "SHA512 iterations is not numeric (abc)")
assert.False(t, ok) assert.False(t, ok)
} }

View File

@ -12,12 +12,6 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
var NoNet = []string{}
var LocalNet = []string{"127.0.0.1"}
var PrivateNet = []string{"192.168.1.0/24"}
var MultipleNet = []string{"192.168.1.0/24", "10.0.0.0/8"}
var MixedNetIP = []string{"192.168.1.0/24", "192.168.2.4"}
type AuthorizerSuite struct { type AuthorizerSuite struct {
suite.Suite suite.Suite
} }

View File

@ -62,6 +62,7 @@ func publicKey(priv interface{}) interface{} {
} }
} }
//nolint:gocyclo // TODO: Consider refactoring/simplifying, time permitting
func generateSelfSignedCertificate(cmd *cobra.Command, args []string) { func generateSelfSignedCertificate(cmd *cobra.Command, args []string) {
// implementation retrieved from https://golang.org/src/crypto/tls/generate_cert.go // implementation retrieved from https://golang.org/src/crypto/tls/generate_cert.go
var priv interface{} var priv interface{}

View File

@ -46,9 +46,9 @@ var HashPasswordCmd = &cobra.Command{
hash, err = authentication.HashPassword(args[0], salt, algorithm, iterations, memory*1024, parallelism, keyLength, saltLength) hash, err = authentication.HashPassword(args[0], salt, algorithm, iterations, memory*1024, parallelism, keyLength, saltLength)
if err != nil { if err != nil {
fmt.Println(fmt.Sprintf("Error occured during hashing: %s", err)) fmt.Printf("Error occurred during hashing: %s", err)
} else { } else {
fmt.Println(fmt.Sprintf("Password hash: %s", hash)) fmt.Printf("Password hash: %s", hash)
} }
}, },
Args: cobra.MinimumNArgs(1), Args: cobra.MinimumNArgs(1),

View File

@ -103,7 +103,7 @@ func migrateLocalU2FSecret(dbProvider storage.Provider) {
} }
func migrateLocalPreferences(dbProvider storage.Provider) { func migrateLocalPreferences(dbProvider storage.Provider) {
file, err := os.Open(path.Join(localDatabasePath, "prefered_2fa_method")) file, err := os.Open(path.Join(localDatabasePath, "prefered_2fa_method")) //nolint:misspell
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -54,6 +54,7 @@ func migrateMongo(cmd *cobra.Command, args []string) {
migrateMongoU2FDevices(db, dbProvider) migrateMongoU2FDevices(db, dbProvider)
migrateMongoTOTPDevices(db, dbProvider) migrateMongoTOTPDevices(db, dbProvider)
migrateMongoPreferences(db, dbProvider) migrateMongoPreferences(db, dbProvider)
migrateMongoAuthenticationTraces(db, dbProvider)
log.Println("Migration done!") log.Println("Migration done!")
} }
@ -125,7 +126,7 @@ func migrateMongoTOTPDevices(db *mongo.Database, dbProvider storage.Provider) {
} }
func migrateMongoPreferences(db *mongo.Database, dbProvider storage.Provider) { func migrateMongoPreferences(db *mongo.Database, dbProvider storage.Provider) {
u2fCollection := db.Collection("prefered_2fa_method") u2fCollection := db.Collection("prefered_2fa_method") //nolint:misspell
cur, err := u2fCollection.Find(context.Background(), bson.D{}) cur, err := u2fCollection.Find(context.Background(), bson.D{})
if err != nil { if err != nil {

View File

@ -10,12 +10,6 @@ import (
"github.com/authelia/authelia/internal/configuration/validator" "github.com/authelia/authelia/internal/configuration/validator"
) )
func check(e error) {
if e != nil {
panic(e)
}
}
// Read a YAML configuration and create a Configuration object out of it. // Read a YAML configuration and create a Configuration object out of it.
func Read(configPath string) (*schema.Configuration, []error) { func Read(configPath string) (*schema.Configuration, []error) {
viper.SetEnvPrefix("AUTHELIA") viper.SetEnvPrefix("AUTHELIA")

View File

@ -1,6 +1,6 @@
package schema package schema
// LDAPAuthenticationBackendConfiguration represents the configuration related to LDAP server. // LDAPAuthenticationBackendConfiguration represents the configuration related to LDAP server
type LDAPAuthenticationBackendConfiguration struct { type LDAPAuthenticationBackendConfiguration struct {
URL string `mapstructure:"url"` URL string `mapstructure:"url"`
SkipVerify bool `mapstructure:"skip_verify"` SkipVerify bool `mapstructure:"skip_verify"`
@ -31,7 +31,7 @@ type PasswordHashingConfiguration struct {
Parallelism int `mapstructure:"parallelism"` Parallelism int `mapstructure:"parallelism"`
} }
// Default Argon2id Configuration // DefaultPasswordOptionsConfiguration represents the default configuration related to Argon2id hashing
var DefaultPasswordOptionsConfiguration = PasswordHashingConfiguration{ var DefaultPasswordOptionsConfiguration = PasswordHashingConfiguration{
Iterations: 1, Iterations: 1,
KeyLength: 32, KeyLength: 32,
@ -41,7 +41,7 @@ var DefaultPasswordOptionsConfiguration = PasswordHashingConfiguration{
Parallelism: 8, Parallelism: 8,
} }
// Default Argon2id Configuration for CI testing when calling HashPassword() // DefaultCIPasswordOptionsConfiguration represents the default configuration related to Argon2id hashing for CI
var DefaultCIPasswordOptionsConfiguration = PasswordHashingConfiguration{ var DefaultCIPasswordOptionsConfiguration = PasswordHashingConfiguration{
Iterations: 1, Iterations: 1,
KeyLength: 32, KeyLength: 32,
@ -51,14 +51,14 @@ var DefaultCIPasswordOptionsConfiguration = PasswordHashingConfiguration{
Parallelism: 8, Parallelism: 8,
} }
// Default SHA512 Cofniguration // DefaultPasswordOptionsSHA512Configuration represents the default configuration related to SHA512 hashing
var DefaultPasswordOptionsSHA512Configuration = PasswordHashingConfiguration{ var DefaultPasswordOptionsSHA512Configuration = PasswordHashingConfiguration{
Iterations: 50000, Iterations: 50000,
SaltLength: 16, SaltLength: 16,
Algorithm: "sha512", Algorithm: "sha512",
} }
// AuthenticationBackendConfiguration represents the configuration related to the authentication backend. // AuthenticationBackendConfiguration represents the configuration related to the authentication backend
type AuthenticationBackendConfiguration struct { type AuthenticationBackendConfiguration struct {
DisableResetPassword bool `mapstructure:"disable_reset_password"` DisableResetPassword bool `mapstructure:"disable_reset_password"`
Ldap *LDAPAuthenticationBackendConfiguration `mapstructure:"ldap"` Ldap *LDAPAuthenticationBackendConfiguration `mapstructure:"ldap"`

View File

@ -32,7 +32,7 @@ type QueueItem struct {
path string path string
} }
func (v *Validator) validateOne(item QueueItem, q *queue.Queue) error { func (v *Validator) validateOne(item QueueItem, q *queue.Queue) error { //nolint:unparam
if item.value.Type().Kind() == reflect.Ptr { if item.value.Type().Kind() == reflect.Ptr {
if item.value.IsNil() { if item.value.IsNil() {
return nil return nil

View File

@ -9,9 +9,7 @@ import (
"github.com/authelia/authelia/internal/configuration/schema" "github.com/authelia/authelia/internal/configuration/schema"
) )
var ldapProtocolPrefix = "ldap://" //nolint:gocyclo // TODO: Consider refactoring/simplifying, time permitting
var ldapsProtocolPrefix = "ldaps://"
func validateFileAuthenticationBackend(configuration *schema.FileAuthenticationBackendConfiguration, validator *schema.StructValidator) { func validateFileAuthenticationBackend(configuration *schema.FileAuthenticationBackendConfiguration, validator *schema.StructValidator) {
if configuration.Path == "" { if configuration.Path == "" {
validator.Push(errors.New("Please provide a `path` for the users database in `authentication_backend`")) validator.Push(errors.New("Please provide a `path` for the users database in `authentication_backend`"))
@ -50,7 +48,6 @@ func validateFileAuthenticationBackend(configuration *schema.FileAuthenticationB
} }
if configuration.PasswordHashing.Algorithm == "argon2id" { if configuration.PasswordHashing.Algorithm == "argon2id" {
// Parallelism // Parallelism
if configuration.PasswordHashing.Parallelism == 0 { if configuration.PasswordHashing.Parallelism == 0 {
configuration.PasswordHashing.Parallelism = schema.DefaultPasswordOptionsConfiguration.Parallelism configuration.PasswordHashing.Parallelism = schema.DefaultPasswordOptionsConfiguration.Parallelism
@ -101,6 +98,7 @@ func validateLdapURL(ldapURL string, validator *schema.StructValidator) string {
return u.String() return u.String()
} }
//nolint:gocyclo // TODO: Consider refactoring/simplifying, time permitting
func validateLdapAuthenticationBackend(configuration *schema.LDAPAuthenticationBackendConfiguration, validator *schema.StructValidator) { func validateLdapAuthenticationBackend(configuration *schema.LDAPAuthenticationBackendConfiguration, validator *schema.StructValidator) {
if configuration.URL == "" { if configuration.URL == "" {
validator.Push(errors.New("Please provide a URL to the LDAP server")) validator.Push(errors.New("Please provide a URL to the LDAP server"))

View File

@ -1,7 +1,6 @@
package validator package validator
import ( import (
"errors"
"fmt" "fmt"
"github.com/authelia/authelia/internal/configuration/schema" "github.com/authelia/authelia/internal/configuration/schema"
@ -18,13 +17,13 @@ func ValidateRegulation(configuration *schema.RegulationConfiguration, validator
} }
findTime, err := utils.ParseDurationString(configuration.FindTime) findTime, err := utils.ParseDurationString(configuration.FindTime)
if err != nil { if err != nil {
validator.Push(errors.New(fmt.Sprintf("Error occurred parsing regulation find_time string: %s", err))) validator.Push(fmt.Errorf("Error occurred parsing regulation find_time string: %s", err))
} }
banTime, err := utils.ParseDurationString(configuration.BanTime) banTime, err := utils.ParseDurationString(configuration.BanTime)
if err != nil { if err != nil {
validator.Push(errors.New(fmt.Sprintf("Error occurred parsing regulation ban_time string: %s", err))) validator.Push(fmt.Errorf("Error occurred parsing regulation ban_time string: %s", err))
} }
if findTime > banTime { if findTime > banTime {
validator.Push(errors.New(fmt.Sprintf("find_time cannot be greater than ban_time"))) validator.Push(fmt.Errorf("find_time cannot be greater than ban_time"))
} }
} }

View File

@ -21,19 +21,19 @@ func ValidateSession(configuration *schema.SessionConfiguration, validator *sche
if configuration.Expiration == "" { if configuration.Expiration == "" {
configuration.Expiration = schema.DefaultSessionConfiguration.Expiration // 1 hour configuration.Expiration = schema.DefaultSessionConfiguration.Expiration // 1 hour
} else if _, err := utils.ParseDurationString(configuration.Expiration); err != nil { } else if _, err := utils.ParseDurationString(configuration.Expiration); err != nil {
validator.Push(errors.New(fmt.Sprintf("Error occurred parsing session expiration string: %s", err))) validator.Push(fmt.Errorf("Error occurred parsing session expiration string: %s", err))
} }
if configuration.Inactivity == "" { if configuration.Inactivity == "" {
configuration.Inactivity = schema.DefaultSessionConfiguration.Inactivity // 5 min configuration.Inactivity = schema.DefaultSessionConfiguration.Inactivity // 5 min
} else if _, err := utils.ParseDurationString(configuration.Inactivity); err != nil { } else if _, err := utils.ParseDurationString(configuration.Inactivity); err != nil {
validator.Push(errors.New(fmt.Sprintf("Error occurred parsing session inactivity string: %s", err))) validator.Push(fmt.Errorf("Error occurred parsing session inactivity string: %s", err))
} }
if configuration.RememberMeDuration == "" { if configuration.RememberMeDuration == "" {
configuration.RememberMeDuration = schema.DefaultSessionConfiguration.RememberMeDuration // 1 month configuration.RememberMeDuration = schema.DefaultSessionConfiguration.RememberMeDuration // 1 month
} else if _, err := utils.ParseDurationString(configuration.RememberMeDuration); err != nil { } else if _, err := utils.ParseDurationString(configuration.RememberMeDuration); err != nil {
validator.Push(errors.New(fmt.Sprintf("Error occurred parsing session remember_me_duration string: %s", err))) validator.Push(fmt.Errorf("Error occurred parsing session remember_me_duration string: %s", err))
} }
if configuration.Domain == "" { if configuration.Domain == "" {

View File

@ -84,8 +84,8 @@ func TestShouldRaiseErrorWhenBadInactivityAndExpirationSet(t *testing.T) {
ValidateSession(&config, validator) ValidateSession(&config, validator)
assert.Len(t, validator.Errors(), 2) assert.Len(t, validator.Errors(), 2)
assert.EqualError(t, validator.Errors()[0], "Error occurred parsing session expiration string: could not convert the input string of -1 into a duration") assert.EqualError(t, validator.Errors()[0], "Error occurred parsing session expiration string: Could not convert the input string of -1 into a duration")
assert.EqualError(t, validator.Errors()[1], "Error occurred parsing session inactivity string: could not convert the input string of -1 into a duration") assert.EqualError(t, validator.Errors()[1], "Error occurred parsing session inactivity string: Could not convert the input string of -1 into a duration")
} }
func TestShouldRaiseErrorWhenBadRememberMeDurationSet(t *testing.T) { func TestShouldRaiseErrorWhenBadRememberMeDurationSet(t *testing.T) {
@ -96,7 +96,7 @@ func TestShouldRaiseErrorWhenBadRememberMeDurationSet(t *testing.T) {
ValidateSession(&config, validator) ValidateSession(&config, validator)
assert.Len(t, validator.Errors(), 1) assert.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "Error occurred parsing session remember_me_duration string: could not convert the input string of 1 year into a duration") assert.EqualError(t, validator.Errors()[0], "Error occurred parsing session remember_me_duration string: Could not convert the input string of 1 year into a duration")
} }
func TestShouldSetDefaultRememberMeDuration(t *testing.T) { func TestShouldSetDefaultRememberMeDuration(t *testing.T) {

View File

@ -32,5 +32,4 @@ func TestShouldRaiseErrorWhenInvalidTOTPMinimumValues(t *testing.T) {
assert.Len(t, validator.Errors(), 2) assert.Len(t, validator.Errors(), 2)
assert.EqualError(t, validator.Errors()[0], "TOTP Period must be 1 or more") assert.EqualError(t, validator.Errors()[0], "TOTP Period must be 1 or more")
assert.EqualError(t, validator.Errors()[1], "TOTP Skew must be 0 or more") assert.EqualError(t, validator.Errors()[1], "TOTP Skew must be 0 or more")
} }

View File

@ -33,4 +33,3 @@ const unableToRegisterOneTimePasswordMessage = "Unable to set up one-time passwo
const unableToRegisterSecurityKeyMessage = "Unable to register your security key." const unableToRegisterSecurityKeyMessage = "Unable to register your security key."
const unableToResetPasswordMessage = "Unable to reset your password." const unableToResetPasswordMessage = "Unable to reset your password."
const mfaValidationFailedMessage = "Authentication failed, please retry later." const mfaValidationFailedMessage = "Authentication failed, please retry later."
const badBasicAuthFormatMessage = "Content of Proxy-Authorization header is wrong."

View File

@ -67,15 +67,15 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldCheckSecondFactorIsDisab
s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{ s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{
DefaultPolicy: "bypass", DefaultPolicy: "bypass",
Rules: []schema.ACLRule{ Rules: []schema.ACLRule{
schema.ACLRule{ {
Domain: "example.com", Domain: "example.com",
Policy: "deny", Policy: "deny",
}, },
schema.ACLRule{ {
Domain: "abc.example.com", Domain: "abc.example.com",
Policy: "single_factor", Policy: "single_factor",
}, },
schema.ACLRule{ {
Domain: "def.example.com", Domain: "def.example.com",
Policy: "bypass", Policy: "bypass",
}, },
@ -98,15 +98,15 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldCheckSecondFactorIsEnabl
s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{ s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{
DefaultPolicy: "two_factor", DefaultPolicy: "two_factor",
Rules: []schema.ACLRule{ Rules: []schema.ACLRule{
schema.ACLRule{ {
Domain: "example.com", Domain: "example.com",
Policy: "deny", Policy: "deny",
}, },
schema.ACLRule{ {
Domain: "abc.example.com", Domain: "abc.example.com",
Policy: "single_factor", Policy: "single_factor",
}, },
schema.ACLRule{ {
Domain: "def.example.com", Domain: "def.example.com",
Policy: "bypass", Policy: "bypass",
}, },
@ -129,15 +129,15 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldCheckSecondFactorIsEnabl
s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{ s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{
DefaultPolicy: "bypass", DefaultPolicy: "bypass",
Rules: []schema.ACLRule{ Rules: []schema.ACLRule{
schema.ACLRule{ {
Domain: "example.com", Domain: "example.com",
Policy: "deny", Policy: "deny",
}, },
schema.ACLRule{ {
Domain: "abc.example.com", Domain: "abc.example.com",
Policy: "two_factor", Policy: "two_factor",
}, },
schema.ACLRule{ {
Domain: "def.example.com", Domain: "def.example.com",
Policy: "bypass", Policy: "bypass",
}, },

View File

@ -10,7 +10,6 @@ import (
"github.com/authelia/authelia/internal/models" "github.com/authelia/authelia/internal/models"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
@ -31,13 +30,6 @@ func (s *FirstFactorSuite) TearDownTest() {
s.mock.Close() s.mock.Close()
} }
func (s *FirstFactorSuite) assertError500(err string) {
assert.Equal(s.T(), 500, s.mock.Ctx.Response.StatusCode())
assert.Equal(s.T(), []byte(InternalError), s.mock.Ctx.Response.Body())
assert.Equal(s.T(), err, s.mock.Hook.LastEntry().Message)
assert.Equal(s.T(), logrus.ErrorLevel, s.mock.Hook.LastEntry().Level)
}
func (s *FirstFactorSuite) TestShouldFailIfBodyIsNil() { func (s *FirstFactorSuite) TestShouldFailIfBodyIsNil() {
FirstFactorPost(s.mock.Ctx) FirstFactorPost(s.mock.Ctx)
@ -288,7 +280,7 @@ func (s *FirstFactorRedirectionSuite) SetupTest() {
s.mock.Ctx.Configuration.DefaultRedirectionURL = "https://default.local" s.mock.Ctx.Configuration.DefaultRedirectionURL = "https://default.local"
s.mock.Ctx.Configuration.AccessControl.DefaultPolicy = "bypass" s.mock.Ctx.Configuration.AccessControl.DefaultPolicy = "bypass"
s.mock.Ctx.Configuration.AccessControl.Rules = []schema.ACLRule{ s.mock.Ctx.Configuration.AccessControl.Rules = []schema.ACLRule{
schema.ACLRule{ {
Domain: "default.local", Domain: "default.local",
Policy: "one_factor", Policy: "one_factor",
}, },
@ -384,11 +376,11 @@ func (s *FirstFactorRedirectionSuite) TestShouldReply200WhenUnsafeTargetURLProvi
s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{ s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{
DefaultPolicy: "one_factor", DefaultPolicy: "one_factor",
Rules: []schema.ACLRule{ Rules: []schema.ACLRule{
schema.ACLRule{ {
Domain: "test.example.com", Domain: "test.example.com",
Policy: "one_factor", Policy: "one_factor",
}, },
schema.ACLRule{ {
Domain: "example.com", Domain: "example.com",
Policy: "two_factor", Policy: "two_factor",
}, },

View File

@ -46,13 +46,6 @@ func createToken(secret string, username string, action string, expiresAt time.T
return ss return ss
} }
func newFinishArgs() middlewares.IdentityVerificationFinishArgs {
return middlewares.IdentityVerificationFinishArgs{
ActionClaim: U2FRegistrationAction,
IsTokenUserValidFunc: func(ctx *middlewares.AutheliaCtx, username string) bool { return true },
}
}
func (s *HandlerRegisterU2FStep1Suite) TestShouldRaiseWhenXForwardedProtoIsMissing() { func (s *HandlerRegisterU2FStep1Suite) TestShouldRaiseWhenXForwardedProtoIsMissing() {
token := createToken(s.mock.Ctx.Configuration.JWTSecret, "john", U2FRegistrationAction, token := createToken(s.mock.Ctx.Configuration.JWTSecret, "john", U2FRegistrationAction,
time.Now().Add(1*time.Minute)) time.Now().Add(1*time.Minute))

View File

@ -15,6 +15,10 @@ func SecondFactorU2FRegister(ctx *middlewares.AutheliaCtx) {
responseBody := u2f.RegisterResponse{} responseBody := u2f.RegisterResponse{}
err := ctx.ParseBody(&responseBody) err := ctx.ParseBody(&responseBody)
if err != nil {
ctx.Error(fmt.Errorf("Unable to parse response body: %v", err), unableToRegisterSecurityKeyMessage)
}
userSession := ctx.GetSession() userSession := ctx.GetSession()
if userSession.U2FChallenge == nil { if userSession.U2FChallenge == nil {

View File

@ -62,25 +62,24 @@ func setPreferencesExpectations(preferences UserPreferences, provider *storage.M
LoadTOTPSecret(gomock.Eq("john")). LoadTOTPSecret(gomock.Eq("john")).
Return("", storage.ErrNoTOTPSecret) Return("", storage.ErrNoTOTPSecret)
} }
} }
func TestMethodSetToU2F(t *testing.T) { func TestMethodSetToU2F(t *testing.T) {
table := []UserPreferences{ table := []UserPreferences{
UserPreferences{ {
Method: "totp", Method: "totp",
}, },
UserPreferences{ {
Method: "u2f", Method: "u2f",
HasU2F: true, HasU2F: true,
HasTOTP: true, HasTOTP: true,
}, },
UserPreferences{ {
Method: "u2f", Method: "u2f",
HasU2F: true, HasU2F: true,
HasTOTP: false, HasTOTP: false,
}, },
UserPreferences{ {
Method: "mobile_push", Method: "mobile_push",
HasU2F: false, HasU2F: false,
HasTOTP: false, HasTOTP: false,

View File

@ -27,7 +27,7 @@ func isSchemeWSS(url *url.URL) bool {
return url.Scheme == "wss" return url.Scheme == "wss"
} }
// getOriginalURL extract the URL from the request headers (X-Original-URI or X-Forwarded-* headers). // getOriginalURL extract the URL from the request headers (X-Original-URI or X-Forwarded-* headers)
func getOriginalURL(ctx *middlewares.AutheliaCtx) (*url.URL, error) { func getOriginalURL(ctx *middlewares.AutheliaCtx) (*url.URL, error) {
originalURL := ctx.XOriginalURL() originalURL := ctx.XOriginalURL()
if originalURL != nil { if originalURL != nil {
@ -65,8 +65,8 @@ func getOriginalURL(ctx *middlewares.AutheliaCtx) (*url.URL, error) {
return url, nil return url, nil
} }
// parseBasicAuth parses an HTTP Basic Authentication string. // parseBasicAuth parses an HTTP Basic Authentication string
// "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ("Aladdin", "open sesame", true). // "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ("Aladdin", "open sesame", true)
func parseBasicAuth(auth string) (username, password string, err error) { func parseBasicAuth(auth string) (username, password string, err error) {
if !strings.HasPrefix(auth, authPrefix) { if !strings.HasPrefix(auth, authPrefix) {
return "", "", fmt.Errorf("%s prefix not found in %s header", strings.Trim(authPrefix, " "), AuthorizationHeader) return "", "", fmt.Errorf("%s prefix not found in %s header", strings.Trim(authPrefix, " "), AuthorizationHeader)
@ -83,10 +83,9 @@ func parseBasicAuth(auth string) (username, password string, err error) {
return cs[:s], cs[s+1:], nil return cs[:s], cs[s+1:], nil
} }
// isTargetURLAuthorized check whether the given user is authorized to access the resource. // isTargetURLAuthorized check whether the given user is authorized to access the resource
func isTargetURLAuthorized(authorizer *authorization.Authorizer, targetURL url.URL, func isTargetURLAuthorized(authorizer *authorization.Authorizer, targetURL url.URL,
username string, userGroups []string, clientIP net.IP, authLevel authentication.Level) authorizationMatching { username string, userGroups []string, clientIP net.IP, authLevel authentication.Level) authorizationMatching {
level := authorizer.GetRequiredLevel(authorization.Subject{ level := authorizer.GetRequiredLevel(authorization.Subject{
Username: username, Username: username,
Groups: userGroups, Groups: userGroups,
@ -98,10 +97,10 @@ func isTargetURLAuthorized(authorizer *authorization.Authorizer, targetURL url.U
} else if username != "" && level == authorization.Denied { } else if username != "" && level == authorization.Denied {
// If the user is not anonymous, it means that we went through // If the user is not anonymous, it means that we went through
// all the rules related to that user and knowing who he is we can // all the rules related to that user and knowing who he is we can
// deduce the access is forbidden. // deduce the access is forbidden
// For anonymous users though, we cannot be sure that she // For anonymous users though, we cannot be sure that she
// could not be granted the rights to access the resource. Consequently // could not be granted the rights to access the resource. Consequently
// for anonymous users we send Unauthorized instead of Forbidden. // for anonymous users we send Unauthorized instead of Forbidden
return Forbidden return Forbidden
} else { } else {
if level == authorization.OneFactor && if level == authorization.OneFactor &&
@ -116,8 +115,8 @@ func isTargetURLAuthorized(authorizer *authorization.Authorizer, targetURL url.U
} }
// verifyBasicAuth verify that the provided username and password are correct and // verifyBasicAuth verify that the provided username and password are correct and
// that the user is authorized to target the resource. // that the user is authorized to target the resource
func verifyBasicAuth(auth []byte, targetURL url.URL, ctx *middlewares.AutheliaCtx) (username string, groups []string, authLevel authentication.Level, err error) { func verifyBasicAuth(auth []byte, targetURL url.URL, ctx *middlewares.AutheliaCtx) (username string, groups []string, authLevel authentication.Level, err error) { //nolint:unparam
username, password, err := parseBasicAuth(string(auth)) username, password, err := parseBasicAuth(string(auth))
if err != nil { if err != nil {
@ -130,7 +129,7 @@ func verifyBasicAuth(auth []byte, targetURL url.URL, ctx *middlewares.AutheliaCt
return "", nil, authentication.NotAuthenticated, fmt.Errorf("Unable to check credentials extracted from %s header: %s", AuthorizationHeader, err) return "", nil, authentication.NotAuthenticated, fmt.Errorf("Unable to check credentials extracted from %s header: %s", AuthorizationHeader, err)
} }
// If the user is not correctly authenticated, send a 401. // If the user is not correctly authenticated, send a 401
if !authenticated { if !authenticated {
// Request Basic Authentication otherwise // Request Basic Authentication otherwise
return "", nil, authentication.NotAuthenticated, fmt.Errorf("User %s is not authenticated", username) return "", nil, authentication.NotAuthenticated, fmt.Errorf("User %s is not authenticated", username)
@ -145,7 +144,7 @@ func verifyBasicAuth(auth []byte, targetURL url.URL, ctx *middlewares.AutheliaCt
return username, details.Groups, authentication.OneFactor, nil return username, details.Groups, authentication.OneFactor, nil
} }
// setForwardedHeaders set the forwarded User and Groups headers. // setForwardedHeaders set the forwarded User and Groups headers
func setForwardedHeaders(headers *fasthttp.ResponseHeader, username string, groups []string) { func setForwardedHeaders(headers *fasthttp.ResponseHeader, username string, groups []string) {
if username != "" { if username != "" {
headers.Set(remoteUserHeader, username) headers.Set(remoteUserHeader, username)
@ -153,9 +152,8 @@ func setForwardedHeaders(headers *fasthttp.ResponseHeader, username string, grou
} }
} }
// hasUserBeenInactiveLongEnough check whether the user has been inactive for too long. // hasUserBeenInactiveLongEnough check whether the user has been inactive for too long
func hasUserBeenInactiveLongEnough(ctx *middlewares.AutheliaCtx) (bool, error) { func hasUserBeenInactiveLongEnough(ctx *middlewares.AutheliaCtx) (bool, error) { //nolint:unparam
maxInactivityPeriod := int64(ctx.Providers.SessionProvider.Inactivity.Seconds()) maxInactivityPeriod := int64(ctx.Providers.SessionProvider.Inactivity.Seconds())
if maxInactivityPeriod == 0 { if maxInactivityPeriod == 0 {
return false, nil return false, nil
@ -174,10 +172,10 @@ func hasUserBeenInactiveLongEnough(ctx *middlewares.AutheliaCtx) (bool, error) {
return false, nil return false, nil
} }
// verifyFromSessionCookie verify if a user identified by a cookie is allowed to access target URL. // verifyFromSessionCookie verify if a user identified by a cookie is allowed to access target URL
func verifyFromSessionCookie(targetURL url.URL, ctx *middlewares.AutheliaCtx) (username string, groups []string, authLevel authentication.Level, err error) { func verifyFromSessionCookie(targetURL url.URL, ctx *middlewares.AutheliaCtx) (username string, groups []string, authLevel authentication.Level, err error) { //nolint:unparam
userSession := ctx.GetSession() userSession := ctx.GetSession()
// No username in the session means the user is anonymous. // No username in the session means the user is anonymous
isUserAnonymous := userSession.Username == "" isUserAnonymous := userSession.Username == ""
if isUserAnonymous && userSession.AuthenticationLevel != authentication.NotAuthenticated { if isUserAnonymous && userSession.AuthenticationLevel != authentication.NotAuthenticated {
@ -191,7 +189,7 @@ func verifyFromSessionCookie(targetURL url.URL, ctx *middlewares.AutheliaCtx) (u
} }
if inactiveLongEnough { if inactiveLongEnough {
// Destroy the session a new one will be regenerated on next request. // Destroy the session a new one will be regenerated on next request
err := ctx.Providers.SessionProvider.DestroySession(ctx.RequestCtx) err := ctx.Providers.SessionProvider.DestroySession(ctx.RequestCtx)
if err != nil { if err != nil {
return "", nil, authentication.NotAuthenticated, fmt.Errorf("Unable to destroy user session after long inactivity: %s", err) return "", nil, authentication.NotAuthenticated, fmt.Errorf("Unable to destroy user session after long inactivity: %s", err)
@ -203,7 +201,7 @@ func verifyFromSessionCookie(targetURL url.URL, ctx *middlewares.AutheliaCtx) (u
return userSession.Username, userSession.Groups, userSession.AuthenticationLevel, nil return userSession.Username, userSession.Groups, userSession.AuthenticationLevel, nil
} }
// VerifyGet is the handler verifying if a request is allowed to go through. // VerifyGet is the handler verifying if a request is allowed to go through
func VerifyGet(ctx *middlewares.AutheliaCtx) { func VerifyGet(ctx *middlewares.AutheliaCtx) {
ctx.Logger.Tracef("Headers=%s", ctx.Request.Header.String()) ctx.Logger.Tracef("Headers=%s", ctx.Request.Header.String())
targetURL, err := getOriginalURL(ctx) targetURL, err := getOriginalURL(ctx)
@ -256,7 +254,7 @@ func VerifyGet(ctx *middlewares.AutheliaCtx) {
} else if authorization == NotAuthorized { } else if authorization == NotAuthorized {
// Kubernetes ingress controller and Traefik use the rd parameter of the verify // Kubernetes ingress controller and Traefik use the rd parameter of the verify
// endpoint to provide the URL of the login portal. The target URL of the user // endpoint to provide the URL of the login portal. The target URL of the user
// is computed from X-Fowarded-* headers or X-Original-URL. // is computed from X-Fowarded-* headers or X-Original-URL
rd := string(ctx.QueryArgs().Peek("rd")) rd := string(ctx.QueryArgs().Peek("rd"))
if rd != "" { if rd != "" {
redirectionURL := fmt.Sprintf("%s?rd=%s", rd, url.QueryEscape(targetURL.String())) redirectionURL := fmt.Sprintf("%s?rd=%s", rd, url.QueryEscape(targetURL.String()))
@ -273,7 +271,7 @@ func VerifyGet(ctx *middlewares.AutheliaCtx) {
setForwardedHeaders(&ctx.Response.Header, username, groups) setForwardedHeaders(&ctx.Response.Header, username, groups)
} }
// We mark activity of the current user if he comes with a session cookie. // We mark activity of the current user if he comes with a session cookie
if !hasBasicAuth && username != "" { if !hasBasicAuth && username != "" {
// Mark current activity // Mark current activity
userSession := ctx.GetSession() userSession := ctx.GetSession()

View File

@ -147,21 +147,21 @@ func TestShouldCheckAuthorizationMatching(t *testing.T) {
ExpectedMatching authorizationMatching ExpectedMatching authorizationMatching
} }
rules := []Rule{ rules := []Rule{
Rule{"bypass", authentication.NotAuthenticated, Authorized}, {"bypass", authentication.NotAuthenticated, Authorized},
Rule{"bypass", authentication.OneFactor, Authorized}, {"bypass", authentication.OneFactor, Authorized},
Rule{"bypass", authentication.TwoFactor, Authorized}, {"bypass", authentication.TwoFactor, Authorized},
Rule{"one_factor", authentication.NotAuthenticated, NotAuthorized}, {"one_factor", authentication.NotAuthenticated, NotAuthorized},
Rule{"one_factor", authentication.OneFactor, Authorized}, {"one_factor", authentication.OneFactor, Authorized},
Rule{"one_factor", authentication.TwoFactor, Authorized}, {"one_factor", authentication.TwoFactor, Authorized},
Rule{"two_factor", authentication.NotAuthenticated, NotAuthorized}, {"two_factor", authentication.NotAuthenticated, NotAuthorized},
Rule{"two_factor", authentication.OneFactor, NotAuthorized}, {"two_factor", authentication.OneFactor, NotAuthorized},
Rule{"two_factor", authentication.TwoFactor, Authorized}, {"two_factor", authentication.TwoFactor, Authorized},
Rule{"deny", authentication.NotAuthenticated, NotAuthorized}, {"deny", authentication.NotAuthenticated, NotAuthorized},
Rule{"deny", authentication.OneFactor, Forbidden}, {"deny", authentication.OneFactor, Forbidden},
Rule{"deny", authentication.TwoFactor, Forbidden}, {"deny", authentication.TwoFactor, Forbidden},
} }
url, _ := url.ParseRequestURI("https://test.example.com") url, _ := url.ParseRequestURI("https://test.example.com")
@ -169,7 +169,7 @@ func TestShouldCheckAuthorizationMatching(t *testing.T) {
for _, rule := range rules { for _, rule := range rules {
authorizer := authorization.NewAuthorizer(schema.AccessControlConfiguration{ authorizer := authorization.NewAuthorizer(schema.AccessControlConfiguration{
DefaultPolicy: "deny", DefaultPolicy: "deny",
Rules: []schema.ACLRule{schema.ACLRule{ Rules: []schema.ACLRule{{
Domain: "test.example.com", Domain: "test.example.com",
Policy: rule.Policy, Policy: rule.Policy,
}}, }},
@ -419,23 +419,23 @@ func (p Pair) String() string {
func TestShouldVerifyAuthorizationsUsingSessionCookie(t *testing.T) { func TestShouldVerifyAuthorizationsUsingSessionCookie(t *testing.T) {
testCases := []Pair{ testCases := []Pair{
Pair{"https://test.example.com", "", authentication.NotAuthenticated, 401}, {"https://test.example.com", "", authentication.NotAuthenticated, 401},
Pair{"https://bypass.example.com", "", authentication.NotAuthenticated, 200}, {"https://bypass.example.com", "", authentication.NotAuthenticated, 200},
Pair{"https://one-factor.example.com", "", authentication.NotAuthenticated, 401}, {"https://one-factor.example.com", "", authentication.NotAuthenticated, 401},
Pair{"https://two-factor.example.com", "", authentication.NotAuthenticated, 401}, {"https://two-factor.example.com", "", authentication.NotAuthenticated, 401},
Pair{"https://deny.example.com", "", authentication.NotAuthenticated, 401}, {"https://deny.example.com", "", authentication.NotAuthenticated, 401},
Pair{"https://test.example.com", "john", authentication.OneFactor, 403}, {"https://test.example.com", "john", authentication.OneFactor, 403},
Pair{"https://bypass.example.com", "john", authentication.OneFactor, 200}, {"https://bypass.example.com", "john", authentication.OneFactor, 200},
Pair{"https://one-factor.example.com", "john", authentication.OneFactor, 200}, {"https://one-factor.example.com", "john", authentication.OneFactor, 200},
Pair{"https://two-factor.example.com", "john", authentication.OneFactor, 401}, {"https://two-factor.example.com", "john", authentication.OneFactor, 401},
Pair{"https://deny.example.com", "john", authentication.OneFactor, 403}, {"https://deny.example.com", "john", authentication.OneFactor, 403},
Pair{"https://test.example.com", "john", authentication.TwoFactor, 403}, {"https://test.example.com", "john", authentication.TwoFactor, 403},
Pair{"https://bypass.example.com", "john", authentication.TwoFactor, 200}, {"https://bypass.example.com", "john", authentication.TwoFactor, 200},
Pair{"https://one-factor.example.com", "john", authentication.TwoFactor, 200}, {"https://one-factor.example.com", "john", authentication.TwoFactor, 200},
Pair{"https://two-factor.example.com", "john", authentication.TwoFactor, 200}, {"https://two-factor.example.com", "john", authentication.TwoFactor, 200},
Pair{"https://deny.example.com", "john", authentication.TwoFactor, 403}, {"https://deny.example.com", "john", authentication.TwoFactor, 403},
} }
for _, testCase := range testCases { for _, testCase := range testCases {
@ -628,7 +628,6 @@ func TestSchemeIsHTTPS(t *testing.T) {
GetURL("wss://mytest.example.com/abc/?query=abc"))) GetURL("wss://mytest.example.com/abc/?query=abc")))
assert.True(t, isSchemeHTTPS( assert.True(t, isSchemeHTTPS(
GetURL("https://mytest.example.com/abc/?query=abc"))) GetURL("https://mytest.example.com/abc/?query=abc")))
} }
func TestSchemeIsWSS(t *testing.T) { func TestSchemeIsWSS(t *testing.T) {
@ -646,5 +645,4 @@ func TestSchemeIsWSS(t *testing.T) {
GetURL("https://mytest.example.com/abc/?query=abc"))) GetURL("https://mytest.example.com/abc/?query=abc")))
assert.True(t, isSchemeWSS( assert.True(t, isSchemeWSS(
GetURL("wss://mytest.example.com/abc/?query=abc"))) GetURL("wss://mytest.example.com/abc/?query=abc")))
} }

View File

@ -49,12 +49,6 @@ type firstFactorRequestBody struct {
KeepMeLoggedIn *bool `json:"keepMeLoggedIn"` KeepMeLoggedIn *bool `json:"keepMeLoggedIn"`
} }
// FirstFactorMessageResponse represents the response sent by the first factor endpoint
// when no redirection URL has been provided by the user.
type firstFactorMessageResponse struct {
Message string `json:"message"`
}
// redirectResponse represent the response sent by the first factor endpoint // redirectResponse represent the response sent by the first factor endpoint
// when a redirection URL has been provided. // when a redirection URL has been provided.
type redirectResponse struct { type redirectResponse struct {

View File

@ -72,16 +72,16 @@ func NewMockAutheliaCtx(t *testing.T) *MockAutheliaCtx {
configuration.Session.RememberMeDuration = schema.DefaultSessionConfiguration.RememberMeDuration configuration.Session.RememberMeDuration = schema.DefaultSessionConfiguration.RememberMeDuration
configuration.Session.Name = "authelia_session" configuration.Session.Name = "authelia_session"
configuration.AccessControl.DefaultPolicy = "deny" configuration.AccessControl.DefaultPolicy = "deny"
configuration.AccessControl.Rules = []schema.ACLRule{schema.ACLRule{ configuration.AccessControl.Rules = []schema.ACLRule{{
Domain: "bypass.example.com", Domain: "bypass.example.com",
Policy: "bypass", Policy: "bypass",
}, schema.ACLRule{ }, {
Domain: "one-factor.example.com", Domain: "one-factor.example.com",
Policy: "one_factor", Policy: "one_factor",
}, schema.ACLRule{ }, {
Domain: "two-factor.example.com", Domain: "two-factor.example.com",
Policy: "two_factor", Policy: "two_factor",
}, schema.ACLRule{ }, {
Domain: "deny.example.com", Domain: "deny.example.com",
Policy: "deny", Policy: "deny",
}} }}

View File

@ -15,7 +15,7 @@ import (
"github.com/authelia/authelia/internal/utils" "github.com/authelia/authelia/internal/utils"
) )
// SMTPNotifier a notifier to send emails to SMTP servers. // SMTPNotifier a notifier to send emails to SMTP servers
type SMTPNotifier struct { type SMTPNotifier struct {
username string username string
password string password string
@ -31,7 +31,7 @@ type SMTPNotifier struct {
tlsConfig *tls.Config tlsConfig *tls.Config
} }
// NewSMTPNotifier create an SMTPNotifier targeting a given address. // NewSMTPNotifier create an SMTPNotifier targeting a given address
func NewSMTPNotifier(configuration schema.SMTPNotifierConfiguration) *SMTPNotifier { func NewSMTPNotifier(configuration schema.SMTPNotifierConfiguration) *SMTPNotifier {
notifier := &SMTPNotifier{ notifier := &SMTPNotifier{
username: configuration.Username, username: configuration.Username,
@ -50,15 +50,15 @@ func NewSMTPNotifier(configuration schema.SMTPNotifierConfiguration) *SMTPNotifi
} }
func (n *SMTPNotifier) initializeTLSConfig() { func (n *SMTPNotifier) initializeTLSConfig() {
// Do not allow users to disable verification of certs if they have also set a trusted cert that was loaded. // Do not allow users to disable verification of certs if they have also set a trusted cert that was loaded
// The second part of this check happens in the Configure Cert Pool code block. // The second part of this check happens in the Configure Cert Pool code block
log.Debug("Notifier SMTP client initializing TLS configuration") log.Debug("Notifier SMTP client initializing TLS configuration")
insecureSkipVerify := false insecureSkipVerify := false
if n.disableVerifyCert { if n.disableVerifyCert {
insecureSkipVerify = true insecureSkipVerify = true
} }
//Configure Cert Pool. //Configure Cert Pool
certPool, err := x509.SystemCertPool() certPool, err := x509.SystemCertPool()
if err != nil || certPool == nil { if err != nil || certPool == nil {
certPool = x509.NewCertPool() certPool = x509.NewCertPool()
@ -92,9 +92,9 @@ func (n *SMTPNotifier) initializeTLSConfig() {
} }
} }
// Do startTLS if available (some servers only provide the auth extension after, and encryption is preferred). // Do startTLS if available (some servers only provide the auth extension after, and encryption is preferred)
func (n *SMTPNotifier) startTLS() (bool, error) { func (n *SMTPNotifier) startTLS() (bool, error) {
// Only start if not already encrypted. // Only start if not already encrypted
if _, ok := n.client.TLSConnectionState(); ok { if _, ok := n.client.TLSConnectionState(); ok {
log.Debugf("Notifier SMTP connection is already encrypted, skipping STARTTLS") log.Debugf("Notifier SMTP connection is already encrypted, skipping STARTTLS")
return ok, nil return ok, nil
@ -107,9 +107,8 @@ func (n *SMTPNotifier) startTLS() (bool, error) {
err := n.client.StartTLS(n.tlsConfig) err := n.client.StartTLS(n.tlsConfig)
if err != nil { if err != nil {
return ok, err return ok, err
} else {
log.Debug("Notifier SMTP STARTTLS completed without error")
} }
log.Debug("Notifier SMTP STARTTLS completed without error")
} else if n.disableRequireTLS { } else if n.disableRequireTLS {
log.Warn("Notifier SMTP server does not support STARTTLS and SMTP configuration is set to disable the TLS requirement (only useful for unauthenticated emails over plain text)") log.Warn("Notifier SMTP server does not support STARTTLS and SMTP configuration is set to disable the TLS requirement (only useful for unauthenticated emails over plain text)")
} else { } else {
@ -118,16 +117,16 @@ func (n *SMTPNotifier) startTLS() (bool, error) {
return ok, nil return ok, nil
} }
// Attempt Authentication. // Attempt Authentication
func (n *SMTPNotifier) auth() (bool, error) { func (n *SMTPNotifier) auth() (bool, error) {
// Attempt AUTH if password is specified only. // Attempt AUTH if password is specified only
if n.password != "" { if n.password != "" {
_, ok := n.client.TLSConnectionState() _, ok := n.client.TLSConnectionState()
if !ok { if !ok {
return false, errors.New("Notifier SMTP client does not support authentication over plain text and the connection is currently plain text") return false, errors.New("Notifier SMTP client does not support authentication over plain text and the connection is currently plain text")
} }
// Check the server supports AUTH, and get the mechanisms. // Check the server supports AUTH, and get the mechanisms
ok, m := n.client.Extension("AUTH") ok, m := n.client.Extension("AUTH")
if ok { if ok {
log.Debugf("Notifier SMTP server supports authentication with the following mechanisms: %s", m) log.Debugf("Notifier SMTP server supports authentication with the following mechanisms: %s", m)
@ -143,26 +142,23 @@ func (n *SMTPNotifier) auth() (bool, error) {
log.Debug("Notifier SMTP client attempting AUTH LOGIN with server") log.Debug("Notifier SMTP client attempting AUTH LOGIN with server")
} }
// Throw error since AUTH extension is not supported. // Throw error since AUTH extension is not supported
if auth == nil { if auth == nil {
return false, fmt.Errorf("notifier SMTP server does not advertise a AUTH mechanism that are supported by Authelia (PLAIN or LOGIN are supported, but server advertised %s mechanisms)", m) return false, fmt.Errorf("notifier SMTP server does not advertise a AUTH mechanism that are supported by Authelia (PLAIN or LOGIN are supported, but server advertised %s mechanisms)", m)
} }
// Authenticate. // Authenticate
err := n.client.Auth(auth) err := n.client.Auth(auth)
if err != nil { if err != nil {
return false, err return false, err
} else {
log.Debug("Notifier SMTP client authenticated successfully with the server")
return true, nil
} }
} else { log.Debug("Notifier SMTP client authenticated successfully with the server")
return false, errors.New("Notifier SMTP server does not advertise the AUTH extension but config requires AUTH (password specified), either disable AUTH, or use an SMTP host that supports AUTH PLAIN or AUTH LOGIN") return true, nil
} }
} else { return false, errors.New("Notifier SMTP server does not advertise the AUTH extension but config requires AUTH (password specified), either disable AUTH, or use an SMTP host that supports AUTH PLAIN or AUTH LOGIN")
log.Debug("Notifier SMTP config has no password specified so authentication is being skipped")
return false, nil
} }
log.Debug("Notifier SMTP config has no password specified so authentication is being skipped")
return false, nil
} }
func (n *SMTPNotifier) compose(recipient, subject, body string) error { func (n *SMTPNotifier) compose(recipient, subject, body string) error {
@ -185,7 +181,7 @@ func (n *SMTPNotifier) compose(recipient, subject, body string) error {
"MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n" + "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n" +
body body
_, err = fmt.Fprintf(wc, msg) _, err = fmt.Fprint(wc, msg)
if err != nil { if err != nil {
log.Debugf("Notifier SMTP client error while sending email body over WriteCloser: %s", err) log.Debugf("Notifier SMTP client error while sending email body over WriteCloser: %s", err)
return err return err
@ -199,7 +195,7 @@ func (n *SMTPNotifier) compose(recipient, subject, body string) error {
return nil return nil
} }
// Dial the SMTP server with the SMTPNotifier config. // Dial the SMTP server with the SMTPNotifier config
func (n *SMTPNotifier) dial() error { func (n *SMTPNotifier) dial() error {
log.Debugf("Notifier SMTP client attempting connection to %s", n.address) log.Debugf("Notifier SMTP client attempting connection to %s", n.address)
if n.port == 465 { if n.port == 465 {
@ -224,7 +220,7 @@ func (n *SMTPNotifier) dial() error {
return nil return nil
} }
// Closes the connection properly. // Closes the connection properly
func (n *SMTPNotifier) cleanup() { func (n *SMTPNotifier) cleanup() {
err := n.client.Quit() err := n.client.Quit()
if err != nil { if err != nil {
@ -239,10 +235,10 @@ func (n *SMTPNotifier) Send(recipient, title, body string) error {
return err return err
} }
// Always execute QUIT at the end once we're connected. // Always execute QUIT at the end once we're connected
defer n.cleanup() defer n.cleanup()
// Start TLS and then Authenticate. // Start TLS and then Authenticate
if _, err := n.startTLS(); err != nil { if _, err := n.startTLS(); err != nil {
return err return err
} }
@ -250,7 +246,7 @@ func (n *SMTPNotifier) Send(recipient, title, body string) error {
return err return err
} }
// Set the sender and recipient first. // Set the sender and recipient first
if err := n.client.Mail(n.sender); err != nil { if err := n.client.Mail(n.sender); err != nil {
log.Debugf("Notifier SMTP failed while sending MAIL FROM (using sender) with error: %s", err) log.Debugf("Notifier SMTP failed while sending MAIL FROM (using sender) with error: %s", err)
return err return err
@ -260,7 +256,7 @@ func (n *SMTPNotifier) Send(recipient, title, body string) error {
return err return err
} }
// Compose and send the email body to the server. // Compose and send the email body to the server
if err := n.compose(recipient, subject, body); err != nil { if err := n.compose(recipient, subject, body); err != nil {
return err return err
} }

View File

@ -42,7 +42,7 @@ func (s *RegulatorSuite) TearDownTest() {
func (s *RegulatorSuite) TestShouldNotThrowWhenUserIsLegitimate() { func (s *RegulatorSuite) TestShouldNotThrowWhenUserIsLegitimate() {
attemptsInDB := []models.AuthenticationAttempt{ attemptsInDB := []models.AuthenticationAttempt{
models.AuthenticationAttempt{ {
Username: "john", Username: "john",
Successful: true, Successful: true,
Time: s.clock.Now().Add(-4 * time.Minute), Time: s.clock.Now().Add(-4 * time.Minute),
@ -63,17 +63,17 @@ func (s *RegulatorSuite) TestShouldNotThrowWhenUserIsLegitimate() {
// with a certain amount of time larger than FindTime. Meaning the user should not be banned. // with a certain amount of time larger than FindTime. Meaning the user should not be banned.
func (s *RegulatorSuite) TestShouldNotThrowWhenFailedAuthenticationNotInFindTime() { func (s *RegulatorSuite) TestShouldNotThrowWhenFailedAuthenticationNotInFindTime() {
attemptsInDB := []models.AuthenticationAttempt{ attemptsInDB := []models.AuthenticationAttempt{
models.AuthenticationAttempt{ {
Username: "john", Username: "john",
Successful: false, Successful: false,
Time: s.clock.Now().Add(-1 * time.Second), Time: s.clock.Now().Add(-1 * time.Second),
}, },
models.AuthenticationAttempt{ {
Username: "john", Username: "john",
Successful: false, Successful: false,
Time: s.clock.Now().Add(-90 * time.Second), Time: s.clock.Now().Add(-90 * time.Second),
}, },
models.AuthenticationAttempt{ {
Username: "john", Username: "john",
Successful: false, Successful: false,
Time: s.clock.Now().Add(-180 * time.Second), Time: s.clock.Now().Add(-180 * time.Second),
@ -94,22 +94,22 @@ func (s *RegulatorSuite) TestShouldNotThrowWhenFailedAuthenticationNotInFindTime
// seconds ago (meaning we are checking from now back to now-FindTime). // seconds ago (meaning we are checking from now back to now-FindTime).
func (s *RegulatorSuite) TestShouldBanUserIfLatestAttemptsAreWithinFinTime() { func (s *RegulatorSuite) TestShouldBanUserIfLatestAttemptsAreWithinFinTime() {
attemptsInDB := []models.AuthenticationAttempt{ attemptsInDB := []models.AuthenticationAttempt{
models.AuthenticationAttempt{ {
Username: "john", Username: "john",
Successful: false, Successful: false,
Time: s.clock.Now().Add(-1 * time.Second), Time: s.clock.Now().Add(-1 * time.Second),
}, },
models.AuthenticationAttempt{ {
Username: "john", Username: "john",
Successful: false, Successful: false,
Time: s.clock.Now().Add(-4 * time.Second), Time: s.clock.Now().Add(-4 * time.Second),
}, },
models.AuthenticationAttempt{ {
Username: "john", Username: "john",
Successful: false, Successful: false,
Time: s.clock.Now().Add(-6 * time.Second), Time: s.clock.Now().Add(-6 * time.Second),
}, },
models.AuthenticationAttempt{ {
Username: "john", Username: "john",
Successful: false, Successful: false,
Time: s.clock.Now().Add(-180 * time.Second), Time: s.clock.Now().Add(-180 * time.Second),
@ -132,17 +132,17 @@ func (s *RegulatorSuite) TestShouldBanUserIfLatestAttemptsAreWithinFinTime() {
// banned right now. // banned right now.
func (s *RegulatorSuite) TestShouldCheckUserIsStillBanned() { func (s *RegulatorSuite) TestShouldCheckUserIsStillBanned() {
attemptsInDB := []models.AuthenticationAttempt{ attemptsInDB := []models.AuthenticationAttempt{
models.AuthenticationAttempt{ {
Username: "john", Username: "john",
Successful: false, Successful: false,
Time: s.clock.Now().Add(-31 * time.Second), Time: s.clock.Now().Add(-31 * time.Second),
}, },
models.AuthenticationAttempt{ {
Username: "john", Username: "john",
Successful: false, Successful: false,
Time: s.clock.Now().Add(-34 * time.Second), Time: s.clock.Now().Add(-34 * time.Second),
}, },
models.AuthenticationAttempt{ {
Username: "john", Username: "john",
Successful: false, Successful: false,
Time: s.clock.Now().Add(-36 * time.Second), Time: s.clock.Now().Add(-36 * time.Second),
@ -161,12 +161,12 @@ func (s *RegulatorSuite) TestShouldCheckUserIsStillBanned() {
func (s *RegulatorSuite) TestShouldCheckUserIsNotYetBanned() { func (s *RegulatorSuite) TestShouldCheckUserIsNotYetBanned() {
attemptsInDB := []models.AuthenticationAttempt{ attemptsInDB := []models.AuthenticationAttempt{
models.AuthenticationAttempt{ {
Username: "john", Username: "john",
Successful: false, Successful: false,
Time: s.clock.Now().Add(-34 * time.Second), Time: s.clock.Now().Add(-34 * time.Second),
}, },
models.AuthenticationAttempt{ {
Username: "john", Username: "john",
Successful: false, Successful: false,
Time: s.clock.Now().Add(-36 * time.Second), Time: s.clock.Now().Add(-36 * time.Second),
@ -185,7 +185,7 @@ func (s *RegulatorSuite) TestShouldCheckUserIsNotYetBanned() {
func (s *RegulatorSuite) TestShouldCheckUserWasAboutToBeBanned() { func (s *RegulatorSuite) TestShouldCheckUserWasAboutToBeBanned() {
attemptsInDB := []models.AuthenticationAttempt{ attemptsInDB := []models.AuthenticationAttempt{
models.AuthenticationAttempt{ {
Username: "john", Username: "john",
Successful: false, Successful: false,
Time: s.clock.Now().Add(-14 * time.Second), Time: s.clock.Now().Add(-14 * time.Second),
@ -193,12 +193,12 @@ func (s *RegulatorSuite) TestShouldCheckUserWasAboutToBeBanned() {
// more than 30 seconds elapsed between this auth and the preceding one. // more than 30 seconds elapsed between this auth and the preceding one.
// In that case we don't need to regulate the user even though the number // In that case we don't need to regulate the user even though the number
// of retrieved attempts is 3. // of retrieved attempts is 3.
models.AuthenticationAttempt{ {
Username: "john", Username: "john",
Successful: false, Successful: false,
Time: s.clock.Now().Add(-94 * time.Second), Time: s.clock.Now().Add(-94 * time.Second),
}, },
models.AuthenticationAttempt{ {
Username: "john", Username: "john",
Successful: false, Successful: false,
Time: s.clock.Now().Add(-96 * time.Second), Time: s.clock.Now().Add(-96 * time.Second),
@ -217,24 +217,24 @@ func (s *RegulatorSuite) TestShouldCheckUserWasAboutToBeBanned() {
func (s *RegulatorSuite) TestShouldCheckRegulationHasBeenResetOnSuccessfulAttempt() { func (s *RegulatorSuite) TestShouldCheckRegulationHasBeenResetOnSuccessfulAttempt() {
attemptsInDB := []models.AuthenticationAttempt{ attemptsInDB := []models.AuthenticationAttempt{
models.AuthenticationAttempt{ {
Username: "john", Username: "john",
Successful: false, Successful: false,
Time: s.clock.Now().Add(-90 * time.Second), Time: s.clock.Now().Add(-90 * time.Second),
}, },
models.AuthenticationAttempt{ {
Username: "john", Username: "john",
Successful: true, Successful: true,
Time: s.clock.Now().Add(-93 * time.Second), Time: s.clock.Now().Add(-93 * time.Second),
}, },
// The user was almost banned but he did a successful attempt. Therefore, even if the next // The user was almost banned but he did a successful attempt. Therefore, even if the next
// failure happens within FindTime, he should not be banned. // failure happens within FindTime, he should not be banned.
models.AuthenticationAttempt{ {
Username: "john", Username: "john",
Successful: false, Successful: false,
Time: s.clock.Now().Add(-94 * time.Second), Time: s.clock.Now().Add(-94 * time.Second),
}, },
models.AuthenticationAttempt{ {
Username: "john", Username: "john",
Successful: false, Successful: false,
Time: s.clock.Now().Add(-96 * time.Second), Time: s.clock.Now().Add(-96 * time.Second),
@ -259,17 +259,17 @@ func TestRunRegulatorSuite(t *testing.T) {
// This test checks that the regulator is disabled when configuration is set to 0. // This test checks that the regulator is disabled when configuration is set to 0.
func (s *RegulatorSuite) TestShouldHaveRegulatorDisabled() { func (s *RegulatorSuite) TestShouldHaveRegulatorDisabled() {
attemptsInDB := []models.AuthenticationAttempt{ attemptsInDB := []models.AuthenticationAttempt{
models.AuthenticationAttempt{ {
Username: "john", Username: "john",
Successful: false, Successful: false,
Time: s.clock.Now().Add(-31 * time.Second), Time: s.clock.Now().Add(-31 * time.Second),
}, },
models.AuthenticationAttempt{ {
Username: "john", Username: "john",
Successful: false, Successful: false,
Time: s.clock.Now().Add(-34 * time.Second), Time: s.clock.Now().Add(-34 * time.Second),
}, },
models.AuthenticationAttempt{ {
Username: "john", Username: "john",
Successful: false, Successful: false,
Time: s.clock.Now().Add(-36 * time.Second), Time: s.clock.Now().Add(-36 * time.Second),

View File

@ -106,6 +106,6 @@ func TestShouldUseEncryptingSerializerWithRedis(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
decoded := session.Dict{} decoded := session.Dict{}
_, err = decoded.UnmarshalMsg(decrypted) _, _ = decoded.UnmarshalMsg(decrypted)
assert.Equal(t, "value", decoded.Get("key")) assert.Equal(t, "value", decoded.Get("key"))
} }

View File

@ -81,19 +81,16 @@ func (p *SQLProvider) initialize(db *sql.DB) error {
// LoadPreferred2FAMethod load the preferred method for 2FA from sqlite db. // LoadPreferred2FAMethod load the preferred method for 2FA from sqlite db.
func (p *SQLProvider) LoadPreferred2FAMethod(username string) (string, error) { func (p *SQLProvider) LoadPreferred2FAMethod(username string) (string, error) {
rows, err := p.db.Query(p.sqlGetPreferencesByUsername, username) rows, err := p.db.Query(p.sqlGetPreferencesByUsername, username)
defer rows.Close()
if err != nil { if err != nil {
return "", err return "", err
} }
if rows.Next() { defer rows.Close()
var method string if !rows.Next() {
err = rows.Scan(&method) return "", nil
if err != nil {
return "", err
}
return method, nil
} }
return "", nil var method string
err = rows.Scan(&method)
return method, err
} }
// SavePreferred2FAMethod save the preferred method for 2FA in sqlite db. // SavePreferred2FAMethod save the preferred method for 2FA in sqlite db.

View File

@ -64,7 +64,7 @@ access_control:
regulation: regulation:
max_retries: 3 max_retries: 3
find_time: 3 find_time: 3
ban_time: 5 ban_time: 10
notifier: notifier:
smtp: smtp:

View File

@ -50,7 +50,7 @@ func (wds *WebDriverSession) doLoginAndRegisterTOTP(ctx context.Context, t *test
} }
// Register a user with TOTP, logout and then authenticate until TOTP-2FA. // Register a user with TOTP, logout and then authenticate until TOTP-2FA.
func (wds *WebDriverSession) doRegisterAndLogin2FA(ctx context.Context, t *testing.T, username, password string, keepMeLoggedIn bool, targetURL string) string { func (wds *WebDriverSession) doRegisterAndLogin2FA(ctx context.Context, t *testing.T, username, password string, keepMeLoggedIn bool, targetURL string) string { //nolint:unparam
// Register TOTP secret and logout. // Register TOTP secret and logout.
secret := wds.doRegisterThenLogout(ctx, t, username, password) secret := wds.doRegisterThenLogout(ctx, t, username, password)
wds.doLoginTwoFactor(ctx, t, username, password, false, secret, targetURL) wds.doLoginTwoFactor(ctx, t, username, password, false, secret, targetURL)

View File

@ -2,6 +2,7 @@ package suites
import ( import (
"fmt" "fmt"
"os"
"strings" "strings"
"time" "time"
@ -10,6 +11,7 @@ import (
"github.com/authelia/authelia/internal/utils" "github.com/authelia/authelia/internal/utils"
) )
//nolint:unparam
func waitUntilServiceLogDetected( func waitUntilServiceLogDetected(
interval time.Duration, interval time.Duration,
timeout time.Duration, timeout time.Duration,
@ -61,8 +63,10 @@ func waitUntilAutheliaIsReady(dockerEnvironment *DockerEnvironment) error {
return err return err
} }
if err := waitUntilAutheliaFrontendIsReady(dockerEnvironment); err != nil { if os.Getenv("CI") != "true" {
return err if err := waitUntilAutheliaFrontendIsReady(dockerEnvironment); err != nil {
return err
}
} }
log.Info("Authelia is now ready!") log.Info("Authelia is now ready!")
return nil return nil

View File

@ -4579,7 +4579,7 @@ coredump_dir /var/spool/squid
# The default is to use HTTP request URL as the store ID. # The default is to use HTTP request URL as the store ID.
# #
# BH # BH
# An internal error occured in the helper, preventing # An internal error occurred in the helper, preventing
# a result being identified. # a result being identified.
# #
# In addition to the above kv-pairs Squid also understands the following # In addition to the above kv-pairs Squid also understands the following

View File

@ -67,7 +67,6 @@ func (s *RegulationScenario) TestShouldBanUserAfterTooManyAttempt() {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
s.verifyIsFirstFactorPage(ctx, s.T()) s.verifyIsFirstFactorPage(ctx, s.T())
time.Sleep(9 * time.Second) time.Sleep(9 * time.Second)
// Enter the correct password and test a successful login // Enter the correct password and test a successful login

View File

@ -25,7 +25,7 @@ func init() {
return err return err
} }
return waitUntilAutheliaBackendIsReady(dockerEnvironment) return waitUntilAutheliaIsReady(dockerEnvironment)
} }
onSetupTimeout := func() error { onSetupTimeout := func() error {

View File

@ -21,7 +21,7 @@ func init() {
return err return err
} }
return waitUntilAutheliaBackendIsReady(dockerEnvironment) return waitUntilAutheliaIsReady(dockerEnvironment)
} }
onSetupTimeout := func() error { onSetupTimeout := func() error {

View File

@ -23,7 +23,7 @@ func init() {
return err return err
} }
return waitUntilAutheliaBackendIsReady(dockerEnvironment) return waitUntilAutheliaIsReady(dockerEnvironment)
} }
onSetupTimeout := func() error { onSetupTimeout := func() error {

View File

@ -25,7 +25,7 @@ func init() {
return err return err
} }
return waitUntilAutheliaBackendIsReady(dockerEnvironment) return waitUntilAutheliaIsReady(dockerEnvironment)
} }
onSetupTimeout := func() error { onSetupTimeout := func() error {

View File

@ -28,7 +28,7 @@ func init() {
return err return err
} }
return waitUntilAutheliaBackendIsReady(haDockerEnvironment) return waitUntilAutheliaIsReady(haDockerEnvironment)
} }
displayAutheliaLogs := func() error { displayAutheliaLogs := func() error {

View File

@ -134,7 +134,7 @@ var expectedAuthorizations = map[string](map[string]bool){
} }
func (s *HighAvailabilityWebDriverSuite) TestShouldVerifyAccessControl() { func (s *HighAvailabilityWebDriverSuite) TestShouldVerifyAccessControl() {
verifyUserIsAuthorized := func(ctx context.Context, t *testing.T, username, targetURL string, authorized bool) { verifyUserIsAuthorized := func(ctx context.Context, t *testing.T, username, targetURL string, authorized bool) { //nolint:unparam
s.doVisit(t, targetURL) s.doVisit(t, targetURL)
s.verifyURLIs(ctx, t, targetURL) s.verifyURLIs(ctx, t, targetURL)
if authorized { if authorized {
@ -161,8 +161,8 @@ func (s *HighAvailabilityWebDriverSuite) TestShouldVerifyAccessControl() {
} }
} }
for _, user := range []string{UserJohn, UserBob, UserHarry} { for _, user := range Users {
s.T().Run(fmt.Sprintf("%s", user), verifyAuthorization(user)) s.T().Run(user, verifyAuthorization(user))
} }
} }

View File

@ -27,7 +27,7 @@ func init() {
return err return err
} }
return waitUntilAutheliaBackendIsReady(dockerEnvironment) return waitUntilAutheliaIsReady(dockerEnvironment)
} }
displayAutheliaLogs := func() error { displayAutheliaLogs := func() error {

View File

@ -25,7 +25,7 @@ func init() {
return err return err
} }
return waitUntilAutheliaBackendIsReady(dockerEnvironment) return waitUntilAutheliaIsReady(dockerEnvironment)
} }
onSetupTimeout := func() error { onSetupTimeout := func() error {

View File

@ -25,7 +25,7 @@ func init() {
return err return err
} }
return waitUntilAutheliaBackendIsReady(dockerEnvironment) return waitUntilAutheliaIsReady(dockerEnvironment)
} }
onSetupTimeout := func() error { onSetupTimeout := func() error {

View File

@ -26,7 +26,7 @@ func init() {
return err return err
} }
return waitUntilAutheliaBackendIsReady(dockerEnvironment) return waitUntilAutheliaIsReady(dockerEnvironment)
} }
onSetupTimeout := func() error { onSetupTimeout := func() error {

View File

@ -22,7 +22,7 @@ func init() {
return err return err
} }
return waitUntilAutheliaBackendIsReady(dockerEnvironment) return waitUntilAutheliaIsReady(dockerEnvironment)
} }
onSetupTimeout := func() error { onSetupTimeout := func() error {

View File

@ -25,7 +25,7 @@ func init() {
return err return err
} }
return waitUntilAutheliaBackendIsReady(dockerEnvironment) return waitUntilAutheliaIsReady(dockerEnvironment)
} }
onSetupTimeout := func() error { onSetupTimeout := func() error {

View File

@ -23,7 +23,7 @@ func init() {
return err return err
} }
return waitUntilAutheliaBackendIsReady(dockerEnvironment) return waitUntilAutheliaIsReady(dockerEnvironment)
} }
onSetupTimeout := func() error { onSetupTimeout := func() error {

View File

@ -25,7 +25,7 @@ func init() {
return err return err
} }
return waitUntilAutheliaBackendIsReady(dockerEnvironment) return waitUntilAutheliaIsReady(dockerEnvironment)
} }
displayAutheliaLogs := func() error { displayAutheliaLogs := func() error {

View File

@ -25,7 +25,7 @@ func init() {
return err return err
} }
return waitUntilAutheliaBackendIsReady(dockerEnvironment) return waitUntilAutheliaIsReady(dockerEnvironment)
} }
onSetupTimeout := func() error { onSetupTimeout := func() error {

View File

@ -25,7 +25,7 @@ func init() {
return err return err
} }
return waitUntilAutheliaBackendIsReady(dockerEnvironment) return waitUntilAutheliaIsReady(dockerEnvironment)
} }
onSetupTimeout := func() error { onSetupTimeout := func() error {

View File

@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func isURLSafe(requestURI string, domain string) bool { func isURLSafe(requestURI string, domain string) bool { //nolint:unparam
url, _ := url.ParseRequestURI(requestURI) url, _ := url.ParseRequestURI(requestURI)
return IsRedirectionSafe(*url, domain) return IsRedirectionSafe(*url, domain)
} }

View File

@ -1,19 +1,17 @@
package utils package utils
import ( import (
"errors"
"fmt" "fmt"
"strconv" "strconv"
"time" "time"
) )
// Parses a string to a duration // ParseDurationString parses a string to a duration
// Duration notations are an integer followed by a unit // Duration notations are an integer followed by a unit
// Units are s = second, m = minute, d = day, w = week, M = month, y = year // Units are s = second, m = minute, d = day, w = week, M = month, y = year
// Example 1y is the same as 1 year // Example 1y is the same as 1 year
func ParseDurationString(input string) (duration time.Duration, err error) { func ParseDurationString(input string) (time.Duration, error) {
duration = 0 var duration time.Duration
err = nil
matches := parseDurationRegexp.FindStringSubmatch(input) matches := parseDurationRegexp.FindStringSubmatch(input)
if len(matches) == 3 && matches[2] != "" { if len(matches) == 3 && matches[2] != "" {
d, _ := strconv.Atoi(matches[1]) d, _ := strconv.Atoi(matches[1])
@ -36,13 +34,12 @@ func ParseDurationString(input string) (duration time.Duration, err error) {
} else if input == "0" || len(matches) == 3 { } else if input == "0" || len(matches) == 3 {
seconds, err := strconv.Atoi(input) seconds, err := strconv.Atoi(input)
if err != nil { if err != nil {
err = errors.New(fmt.Sprintf("could not convert the input string of %s into a duration: %s", input, err)) return 0, fmt.Errorf("Could not convert the input string of %s into a duration: %s", input, err)
} else {
duration = time.Duration(seconds) * time.Second
} }
duration = time.Duration(seconds) * time.Second
} else if input != "" { } else if input != "" {
// Throw this error if input is anything other than a blank string, blank string will default to a duration of nothing // Throw this error if input is anything other than a blank string, blank string will default to a duration of nothing
err = errors.New(fmt.Sprintf("could not convert the input string of %s into a duration", input)) return 0, fmt.Errorf("Could not convert the input string of %s into a duration", input)
} }
return return duration, nil
} }

View File

@ -47,25 +47,25 @@ func TestShouldParseSecondsString(t *testing.T) {
func TestShouldNotParseDurationStringWithOutOfOrderQuantitiesAndUnits(t *testing.T) { func TestShouldNotParseDurationStringWithOutOfOrderQuantitiesAndUnits(t *testing.T) {
duration, err := ParseDurationString("h1") duration, err := ParseDurationString("h1")
assert.EqualError(t, err, "could not convert the input string of h1 into a duration") assert.EqualError(t, err, "Could not convert the input string of h1 into a duration")
assert.Equal(t, time.Duration(0), duration) assert.Equal(t, time.Duration(0), duration)
} }
func TestShouldNotParseBadDurationString(t *testing.T) { func TestShouldNotParseBadDurationString(t *testing.T) {
duration, err := ParseDurationString("10x") duration, err := ParseDurationString("10x")
assert.EqualError(t, err, "could not convert the input string of 10x into a duration") assert.EqualError(t, err, "Could not convert the input string of 10x into a duration")
assert.Equal(t, time.Duration(0), duration) assert.Equal(t, time.Duration(0), duration)
} }
func TestShouldNotParseDurationStringWithMultiValueUnits(t *testing.T) { func TestShouldNotParseDurationStringWithMultiValueUnits(t *testing.T) {
duration, err := ParseDurationString("10ms") duration, err := ParseDurationString("10ms")
assert.EqualError(t, err, "could not convert the input string of 10ms into a duration") assert.EqualError(t, err, "Could not convert the input string of 10ms into a duration")
assert.Equal(t, time.Duration(0), duration) assert.Equal(t, time.Duration(0), duration)
} }
func TestShouldNotParseDurationStringWithLeadingZero(t *testing.T) { func TestShouldNotParseDurationStringWithLeadingZero(t *testing.T) {
duration, err := ParseDurationString("005h") duration, err := ParseDurationString("005h")
assert.EqualError(t, err, "could not convert the input string of 005h into a duration") assert.EqualError(t, err, "Could not convert the input string of 005h into a duration")
assert.Equal(t, time.Duration(0), duration) assert.Equal(t, time.Duration(0), duration)
} }