From 1600e0f7da7219e2398fbef68b982d0b466db266 Mon Sep 17 00:00:00 2001 From: Amir Zarrinkafsh Date: Wed, 6 May 2020 05:35:32 +1000 Subject: [PATCH] [CI] Add wsl linter (#980) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [CI] Add wsl linter * Implement wsl recommendations Co-authored-by: Clément Michaud --- .golangci.yml | 1 + cmd/authelia-scripts/cmd_bootstrap.go | 3 ++ cmd/authelia-scripts/cmd_build.go | 2 ++ cmd/authelia-scripts/cmd_ci.go | 2 ++ cmd/authelia-scripts/cmd_docker.go | 6 ++-- cmd/authelia-scripts/cmd_suites.go | 17 ++++++++++ cmd/authelia-scripts/cmd_unittest.go | 3 ++ cmd/authelia-scripts/main.go | 4 +++ cmd/authelia-suites/main.go | 3 ++ cmd/authelia/main.go | 2 ++ internal/authentication/file_user_provider.go | 13 +++++++- .../authentication/ldap_connection_factory.go | 2 ++ internal/authentication/ldap_user_provider.go | 11 +++++++ internal/authentication/password_hash.go | 13 +++++++- internal/authentication/password_hash_test.go | 14 ++++++--- internal/authorization/authorizer.go | 3 ++ internal/authorization/domain_matcher.go | 1 + internal/authorization/ip_matcher.go | 4 +++ internal/authorization/path_matcher.go | 1 + internal/authorization/subject_matcher.go | 1 + internal/commands/certificates.go | 14 +++++++++ internal/configuration/reader.go | 1 + internal/configuration/reader_test.go | 4 +++ internal/configuration/schema/validator.go | 8 +++++ .../configuration/validator/configuration.go | 2 ++ .../validator/configuration_test.go | 1 + internal/configuration/validator/keys.go | 2 ++ internal/configuration/validator/keys_test.go | 2 ++ internal/configuration/validator/notifier.go | 3 ++ .../configuration/validator/regulation.go | 4 +++ internal/configuration/validator/secrets.go | 3 ++ .../configuration/validator/session_test.go | 1 + internal/configuration/validator/totp.go | 1 + internal/configuration/validator/totp_test.go | 1 + internal/duo/duo.go | 6 ++-- .../handler_extended_configuration_test.go | 2 ++ internal/handlers/handler_firstfactor.go | 6 ++++ .../handlers/handler_register_u2f_step1.go | 1 + .../handler_register_u2f_step1_test.go | 1 + internal/handlers/handler_sign_duo.go | 1 + internal/handlers/handler_sign_totp.go | 1 + internal/handlers/handler_sign_u2f_step1.go | 3 ++ internal/handlers/handler_user_info.go | 16 ++++++++++ internal/handlers/handler_verify.go | 31 ++++++++++++++++++- internal/handlers/handler_verify_test.go | 6 ++++ internal/handlers/response.go | 5 +++ internal/handlers/totp.go | 1 + internal/handlers/u2f.go | 1 + internal/logging/logger.go | 2 ++ internal/logging/logger_test.go | 1 + internal/middlewares/authelia_context.go | 7 ++++- internal/middlewares/identity_verification.go | 5 +++ .../middlewares/identity_verification_test.go | 1 + internal/middlewares/log_request_test.go | 1 + internal/middlewares/require_first_factor.go | 1 + internal/mocks/mock_authelia_ctx.go | 2 ++ internal/notification/file_notifier.go | 3 ++ internal/notification/smtp_login_auth.go | 3 ++ internal/notification/smtp_notifier.go | 29 ++++++++++++++++- internal/regulation/regulator.go | 6 ++++ internal/server/index.go | 3 ++ internal/session/encrypting_serializer.go | 3 ++ internal/session/provider.go | 6 ++++ internal/session/provider_config.go | 2 ++ internal/storage/mysql_provider.go | 3 +- internal/storage/postgres_provider.go | 1 + internal/storage/sql_provider.go | 19 ++++++++++-- internal/storage/sqlite_provider.go | 1 + internal/suites/action_http.go | 1 + internal/suites/action_login.go | 2 ++ internal/suites/action_mail.go | 1 + internal/suites/action_register.go | 1 + internal/suites/action_totp.go | 1 + internal/suites/action_visit.go | 1 + internal/suites/docker.go | 4 +++ internal/suites/environment.go | 4 +++ internal/suites/http.go | 1 + internal/suites/kubernetes.go | 2 ++ internal/suites/registry.go | 3 ++ .../suites/scenario_available_methods_test.go | 3 ++ internal/suites/scenario_inactivity_test.go | 6 ++-- internal/suites/scenario_two_factor_test.go | 2 +- internal/suites/suite_bypass_all.go | 3 ++ internal/suites/suite_docker.go | 3 ++ internal/suites/suite_duo_push.go | 3 ++ internal/suites/suite_haproxy.go | 3 ++ internal/suites/suite_high_availability.go | 3 ++ .../suites/suite_high_availability_test.go | 2 ++ internal/suites/suite_kubernetes.go | 13 ++++++++ internal/suites/suite_ldap.go | 3 ++ internal/suites/suite_mariadb.go | 3 ++ internal/suites/suite_mysql.go | 3 ++ internal/suites/suite_network_acl.go | 3 ++ internal/suites/suite_network_acl_test.go | 3 ++ internal/suites/suite_one_factor_only.go | 3 ++ internal/suites/suite_postgres.go | 3 ++ internal/suites/suite_short_timeouts.go | 3 ++ internal/suites/suite_standalone.go | 3 ++ internal/suites/suite_traefik.go | 3 ++ internal/suites/suite_traefik2.go | 3 ++ internal/suites/webdriver.go | 5 +++ internal/utils/aes.go | 1 + internal/utils/exec.go | 11 +++++++ internal/utils/files.go | 2 ++ internal/utils/safe_redirection.go | 1 + internal/utils/strings.go | 9 ++++++ internal/utils/time.go | 3 ++ 107 files changed, 441 insertions(+), 19 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index a943edee..49b0d229 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -30,6 +30,7 @@ linters: - unconvert - unparam - whitespace + - wsl issues: exclude: diff --git a/cmd/authelia-scripts/cmd_bootstrap.go b/cmd/authelia-scripts/cmd_bootstrap.go index 4aac5076..33c3448e 100644 --- a/cmd/authelia-scripts/cmd_bootstrap.go +++ b/cmd/authelia-scripts/cmd_bootstrap.go @@ -99,6 +99,7 @@ func prepareHostsFile() { for _, entry := range hostEntries { domainInHostFile := false + for i, line := range lines { domainFound := strings.Contains(line, entry.Domain) ipFound := strings.Contains(line, entry.IP) @@ -154,6 +155,7 @@ func readHostsFile() ([]byte, error) { if err != nil { return nil, err } + return bs, nil } @@ -188,6 +190,7 @@ func Bootstrap(cobraCmd *cobra.Command, args []string) { bootstrapPrintln("Checking if GOPATH is set") goPathFound := false + for _, v := range os.Environ() { if strings.HasPrefix(v, "GOPATH=") { goPathFound = true diff --git a/cmd/authelia-scripts/cmd_build.go b/cmd/authelia-scripts/cmd_build.go index 8e6b98c2..9c955d6e 100644 --- a/cmd/authelia-scripts/cmd_build.go +++ b/cmd/authelia-scripts/cmd_build.go @@ -12,6 +12,7 @@ import ( func buildAutheliaBinary() { cmd := utils.CommandWithStdout("go", "build", "-o", "../../"+OutputDir+"/authelia") cmd.Dir = "cmd/authelia" + cmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH=amd64", "CGO_ENABLED=1") @@ -34,6 +35,7 @@ func buildFrontend() { // Then build the frontend. cmd = utils.CommandWithStdout("yarn", "build") cmd.Dir = webDirectory + cmd.Env = append(os.Environ(), "INLINE_RUNTIME_CHUNK=false") if err := cmd.Run(); err != nil { diff --git a/cmd/authelia-scripts/cmd_ci.go b/cmd/authelia-scripts/cmd_ci.go index 92f7f90c..da9e8572 100644 --- a/cmd/authelia-scripts/cmd_ci.go +++ b/cmd/authelia-scripts/cmd_ci.go @@ -10,11 +10,13 @@ import ( // RunCI run the CI scripts. func RunCI(cmd *cobra.Command, args []string) { log.Info("=====> Build stage <=====") + if err := utils.CommandWithStdout("authelia-scripts", "--log-level", "debug", "build").Run(); err != nil { log.Fatal(err) } log.Info("=====> Unit testing stage <=====") + if err := utils.CommandWithStdout("authelia-scripts", "--log-level", "debug", "unittest").Run(); err != nil { log.Fatal(err) } diff --git a/cmd/authelia-scripts/cmd_docker.go b/cmd/authelia-scripts/cmd_docker.go index 9a9bb2b6..1d9e9942 100644 --- a/cmd/authelia-scripts/cmd_docker.go +++ b/cmd/authelia-scripts/cmd_docker.go @@ -37,6 +37,7 @@ func checkArchIsSupported(arch string) { return } } + log.Fatal("Architecture is not supported. Please select one of " + strings.Join(supportedArch, ", ") + ".") } @@ -90,9 +91,11 @@ func dockerBuildOfficialImage(arch string) error { cmd.Stdout = nil cmd.Stderr = nil commitBytes, err := cmd.Output() + if err != nil { log.Fatal(err) } + commitHash := strings.Trim(string(commitBytes), "\n") return docker.Build(IntermediateDockerImageName, dockerfile, ".", gitTag, commitHash) @@ -202,9 +205,9 @@ func publishDockerImage(arch string) { if ciTag != "" { if len(tags) == 4 { log.Infof("Detected tags: '%s' | '%s' | '%s'", tags[1], tags[2], tags[3]) - login(docker) deploy(docker, tags[1]+"-"+arch) + if !ignoredSuffixes.MatchString(ciTag) { deploy(docker, tags[2]+"-"+arch) deploy(docker, tags[3]+"-"+arch) @@ -233,7 +236,6 @@ func publishDockerManifest() { if ciTag != "" { if len(tags) == 4 { log.Infof("Detected tags: '%s' | '%s' | '%s'", tags[1], tags[2], tags[3]) - login(docker) deployManifest(docker, tags[1], tags[1]+"-amd64", tags[1]+"-arm32v7", tags[1]+"-arm64v8") publishDockerReadme(docker) diff --git a/cmd/authelia-scripts/cmd_suites.go b/cmd/authelia-scripts/cmd_suites.go index ae4e231d..aa284285 100644 --- a/cmd/authelia-scripts/cmd_suites.go +++ b/cmd/authelia-scripts/cmd_suites.go @@ -108,6 +108,7 @@ func listSuites() []string { suiteNames := make([]string, 0) suiteNames = append(suiteNames, suites.GlobalRegistry.Suites()...) sort.Strings(suiteNames) + return suiteNames } @@ -119,6 +120,7 @@ func checkSuiteAvailable(suite string) error { return nil } } + return ErrNotAvailableSuite } @@ -130,6 +132,7 @@ func runSuiteSetupTeardown(command string, suite string) error { if err == ErrNotAvailableSuite { log.Fatal(errors.New("Suite named " + selectedSuite + " does not exist")) } + log.Fatal(err) } @@ -139,6 +142,7 @@ func runSuiteSetupTeardown(command string, suite string) error { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Env = os.Environ() + return utils.RunCommandWithTimeout(cmd, s.SetUpTimeout) } @@ -147,6 +151,7 @@ func runOnSetupTimeout(suite string) error { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Env = os.Environ() + return utils.RunCommandWithTimeout(cmd, 15*time.Second) } @@ -155,11 +160,13 @@ func runOnError(suite string) error { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Env = os.Environ() + return utils.RunCommandWithTimeout(cmd, 15*time.Second) } func setupSuite(suiteName string) error { log.Infof("Setup environment for suite %s...", suiteName) + signalChannel := make(chan os.Signal) signal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM) @@ -167,6 +174,7 @@ func setupSuite(suiteName string) error { go func() { <-signalChannel + interrupted = true }() @@ -174,7 +182,9 @@ func setupSuite(suiteName string) error { if errSetup == utils.ErrTimeoutReached { runOnSetupTimeout(suiteName) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. } + teardownSuite(suiteName) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. + return errSetup } @@ -230,6 +240,7 @@ func getRunningSuite() (string, error) { } b, err := ioutil.ReadFile(runningSuiteFile) + return string(b), err } @@ -247,6 +258,7 @@ func runSuiteTests(suiteName string, withEnv bool) error { if suite.TestTimeout > 0 { timeout = fmt.Sprintf("%ds", int64(suite.TestTimeout/time.Second)) } + testCmdLine := fmt.Sprintf("go test -count=1 -v ./internal/suites -timeout %s ", timeout) if testPattern != "" { @@ -262,6 +274,7 @@ func runSuiteTests(suiteName string, withEnv bool) error { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Env = os.Environ() + if headless { cmd.Env = append(cmd.Env, "HEADLESS=y") } @@ -293,16 +306,20 @@ func runMultipleSuitesTests(suiteNames []string, withEnv bool) error { return err } } + return nil } func runAllSuites() error { log.Info("Start running all suites") + for _, s := range listSuites() { if err := runSuiteTests(s, true); err != nil { return err } } + log.Info("All suites passed successfully") + return nil } diff --git a/cmd/authelia-scripts/cmd_unittest.go b/cmd/authelia-scripts/cmd_unittest.go index f09947dd..df4368ba 100644 --- a/cmd/authelia-scripts/cmd_unittest.go +++ b/cmd/authelia-scripts/cmd_unittest.go @@ -12,13 +12,16 @@ import ( // RunUnitTest run the unit tests. func RunUnitTest(cobraCmd *cobra.Command, args []string) { log.SetLevel(log.TraceLevel) + if err := utils.Shell("go test $(go list ./... | grep -v suites)").Run(); err != nil { log.Fatal(err) } cmd := utils.Shell("yarn test") cmd.Dir = webDirectory + cmd.Env = append(os.Environ(), "CI=true") + if err := cmd.Run(); err != nil { log.Fatal(err) } diff --git a/cmd/authelia-scripts/main.go b/cmd/authelia-scripts/main.go index d81f8a15..44905e3f 100755 --- a/cmd/authelia-scripts/main.go +++ b/cmd/authelia-scripts/main.go @@ -85,11 +85,13 @@ func levelStringToLevel(level string) log.Level { } else if level == "warning" { return log.WarnLevel } + return log.InfoLevel } func main() { var rootCmd = &cobra.Command{Use: "authelia-scripts"} + cobraCommands := make([]*cobra.Command, 0) for _, autheliaCommand := range Commands { @@ -99,6 +101,7 @@ func main() { cmdline := autheliaCommand.CommandLine fn = func(cobraCmd *cobra.Command, args []string) { cmd := utils.CommandWithStdout(cmdline, args...) + err := cmd.Run() if err != nil { panic(err) @@ -131,6 +134,7 @@ func main() { cobraCommands = append(cobraCommands, command) } + cobraCommands = append(cobraCommands, commands.HashPasswordCmd) rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "Set the log level for the command") diff --git a/cmd/authelia-suites/main.go b/cmd/authelia-suites/main.go index 66f9fac8..5e64e5a2 100644 --- a/cmd/authelia-suites/main.go +++ b/cmd/authelia-suites/main.go @@ -55,6 +55,7 @@ func main() { rootCmd.AddCommand(setupTimeoutCmd) rootCmd.AddCommand(errorCmd) rootCmd.AddCommand(stopCmd) + if err := rootCmd.Execute(); err != nil { log.Fatal(err) } @@ -125,6 +126,7 @@ func setupTimeoutSuite(cmd *cobra.Command, args []string) { if s.OnSetupTimeout == nil { return } + if err := s.OnSetupTimeout(); err != nil { log.Fatal(err) } @@ -137,6 +139,7 @@ func runErrorCallback(cmd *cobra.Command, args []string) { if s.OnError == nil { return } + if err := s.OnError(); err != nil { log.Fatal(err) } diff --git a/cmd/authelia/main.go b/cmd/authelia/main.go index 6b740c34..0b49a892 100644 --- a/cmd/authelia/main.go +++ b/cmd/authelia/main.go @@ -37,6 +37,7 @@ func startServer() { for _, err := range errs { logging.Logger().Error(err) } + panic(errors.New("Some errors have been reported")) } @@ -89,6 +90,7 @@ func startServer() { } else { log.Fatalf("Unrecognized notifier") } + if !config.Notifier.DisableStartupCheck { _, err := notifier.StartupCheck() if err != nil { diff --git a/internal/authentication/file_user_provider.go b/internal/authentication/file_user_provider.go index bb6d41d0..bbfa9da9 100644 --- a/internal/authentication/file_user_provider.go +++ b/internal/authentication/file_user_provider.go @@ -57,6 +57,7 @@ func NewFileUserProvider(configuration *schema.FileAuthenticationBackendConfigur if configuration.Password.Algorithm == sha512 { cryptAlgo = HashingAlgorithmSHA512 } + settings := getCryptSettings(utils.RandomString(configuration.Password.SaltLength, HashingPossibleSaltCharacters), cryptAlgo, configuration.Password.Iterations, configuration.Password.Memory*1024, configuration.Password.Parallelism, configuration.Password.KeyLength) @@ -78,6 +79,7 @@ func checkPasswordHashes(database *DatabaseModel) error { return fmt.Errorf("Unable to parse hash of user %s: %s", u, err) } } + return nil } @@ -86,7 +88,9 @@ func readDatabase(path string) (*DatabaseModel, error) { if err != nil { return nil, fmt.Errorf("Unable to read database from file %s: %s", path, err) } + db := DatabaseModel{} + err = yaml.Unmarshal(content, &db) if err != nil { return nil, fmt.Errorf("Unable to parse database: %s", err) @@ -100,6 +104,7 @@ func readDatabase(path string) (*DatabaseModel, error) { if !ok { return nil, fmt.Errorf("The database format is invalid: %s", err) } + return &db, nil } @@ -107,10 +112,12 @@ func readDatabase(path string) (*DatabaseModel, error) { func (p *FileUserProvider) CheckUserPassword(username string, password string) (bool, error) { if details, ok := p.database.Users[username]; ok { hashedPassword := strings.ReplaceAll(details.HashedPassword, "{CRYPT}", "") + ok, err := CheckPassword(password, hashedPassword) if err != nil { return false, err } + return ok, nil } @@ -130,6 +137,7 @@ func (p *FileUserProvider) GetDetails(username string) (*UserDetails, error) { Emails: []string{details.Email}, }, nil } + return nil, fmt.Errorf("User '%s' does not exist in database", username) } @@ -153,11 +161,12 @@ func (p *FileUserProvider) UpdatePassword(username string, newPassword string) e newPassword, "", algorithm, p.configuration.Password.Iterations, p.configuration.Password.Memory*1024, p.configuration.Password.Parallelism, p.configuration.Password.KeyLength, p.configuration.Password.SaltLength) - if err != nil { return err } + details.HashedPassword = hash + p.lock.Lock() p.database.Users[username] = details @@ -166,7 +175,9 @@ func (p *FileUserProvider) UpdatePassword(username string, newPassword string) e p.lock.Unlock() return err } + err = ioutil.WriteFile(p.configuration.Path, b, 0644) //nolint:gosec // Fixed in future PR. p.lock.Unlock() + return err } diff --git a/internal/authentication/ldap_connection_factory.go b/internal/authentication/ldap_connection_factory.go index bf5df2fd..7d635b4b 100644 --- a/internal/authentication/ldap_connection_factory.go +++ b/internal/authentication/ldap_connection_factory.go @@ -69,6 +69,7 @@ func (lcf *LDAPConnectionFactoryImpl) DialTLS(network, addr string, config *tls. if err != nil { return nil, err } + return NewLDAPConnectionImpl(conn), nil } @@ -78,5 +79,6 @@ func (lcf *LDAPConnectionFactoryImpl) Dial(network, addr string) (LDAPConnection if err != nil { return nil, err } + return NewLDAPConnectionImpl(conn), nil } diff --git a/internal/authentication/ldap_user_provider.go b/internal/authentication/ldap_user_provider.go index eebbdace..9d8baf0d 100644 --- a/internal/authentication/ldap_user_provider.go +++ b/internal/authentication/ldap_user_provider.go @@ -47,12 +47,14 @@ func (p *LDAPUserProvider) connect(userDN string, password string) (LDAPConnecti if url.Scheme == "ldaps" { logging.Logger().Trace("LDAP client starts a TLS session") + conn, err := p.connectionFactory.DialTLS("tcp", url.Host, &tls.Config{ InsecureSkipVerify: p.configuration.SkipVerify, //nolint:gosec // This is a configurable option, is desirable in some situations and is off by default }) if err != nil { return nil, err } + newConnection = conn } else { logging.Logger().Trace("LDAP client starts a session over raw TCP") @@ -66,6 +68,7 @@ func (p *LDAPUserProvider) connect(userDN string, password string) (LDAPConnecti if err := newConnection.Bind(userDN, password); err != nil { return nil, err } + return newConnection, nil } @@ -100,6 +103,7 @@ func (p *LDAPUserProvider) ldapEscape(inputUsername string) string { for _, c := range specialLDAPRunes { inputUsername = strings.ReplaceAll(inputUsername, string(c), fmt.Sprintf("\\%c", c)) } + return inputUsername } @@ -122,6 +126,7 @@ func (p *LDAPUserProvider) resolveUsersFilter(userFilter string, inputUsername s // in configuration. userFilter = strings.ReplaceAll(userFilter, "{username_attribute}", p.configuration.UsernameAttribute) userFilter = strings.ReplaceAll(userFilter, "{mail_attribute}", p.configuration.MailAttribute) + return userFilter } @@ -160,15 +165,18 @@ func (p *LDAPUserProvider) getUserProfile(conn LDAPConnection, inputUsername str userProfile := ldapUserProfile{ DN: sr.Entries[0].DN, } + for _, attr := range sr.Entries[0].Attributes { if attr.Name == p.configuration.MailAttribute { userProfile.Emails = attr.Values } + if attr.Name == p.configuration.UsernameAttribute { if len(attr.Values) != 1 { return nil, fmt.Errorf("User %s cannot have multiple value for attribute %s", inputUsername, p.configuration.UsernameAttribute) } + userProfile.Username = attr.Values[0] } } @@ -186,6 +194,7 @@ func (p *LDAPUserProvider) resolveGroupsFilter(inputUsername string, profile *ld // We temporarily keep placeholder {0} for backward compatibility. groupFilter := strings.ReplaceAll(p.configuration.GroupsFilter, "{0}", inputUsername) groupFilter = strings.ReplaceAll(groupFilter, "{input}", inputUsername) + if profile != nil { // We temporarily keep placeholder {1} for backward compatibility. groupFilter = strings.ReplaceAll(groupFilter, "{1}", ldap.EscapeFilter(profile.Username)) @@ -213,6 +222,7 @@ func (p *LDAPUserProvider) GetDetails(inputUsername string) (*UserDetails, error if err != nil { return nil, fmt.Errorf("Unable to create group filter for user %s. Cause: %s", inputUsername, err) } + logging.Logger().Tracef("Computed groups filter is %s", groupsFilter) groupBaseDN := p.configuration.BaseDN @@ -233,6 +243,7 @@ func (p *LDAPUserProvider) GetDetails(inputUsername string) (*UserDetails, error } groups := make([]string, 0) + for _, res := range sr.Entries { if len(res.Attributes) == 0 { logging.Logger().Warningf("No groups retrieved from LDAP for user %s", inputUsername) diff --git a/internal/authentication/password_hash.go b/internal/authentication/password_hash.go index 9586147f..c2e87aea 100644 --- a/internal/authentication/password_hash.go +++ b/internal/authentication/password_hash.go @@ -38,6 +38,7 @@ func ParseHash(hash string) (passwordHash *PasswordHash, err error) { 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) } + if h.Key == "" { return nil, fmt.Errorf("Hash key contains no characters or the field length is invalid (%s)", hash) } @@ -50,6 +51,7 @@ func ParseHash(hash string) (passwordHash *PasswordHash, err error) { if code == HashingAlgorithmSHA512 { h.Iterations = parameters.GetInt("rounds", HashingDefaultSHA512Iterations) h.Algorithm = HashingAlgorithmSHA512 + if parameters["rounds"] != "" && parameters["rounds"] != strconv.Itoa(h.Iterations) { return nil, fmt.Errorf("SHA512 iterations is not numeric (%s)", parameters["rounds"]) } @@ -79,6 +81,7 @@ func ParseHash(hash string) (passwordHash *PasswordHash, err error) { } else { return nil, fmt.Errorf("Authelia only supports salted SHA512 hashing ($6$) and salted argon2id ($argon2id$), not $%s$", code) } + return h, nil } @@ -110,28 +113,33 @@ func HashPassword(password, salt string, algorithm CryptAlgo, iterations, memory if memory < 8 { return "", fmt.Errorf("Memory (argon2id) input of %d is invalid, it must be 8 or higher", memory) } + if parallelism < 1 { return "", fmt.Errorf("Parallelism (argon2id) input of %d is invalid, it must be 1 or higher", parallelism) } + if memory < 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 { return "", fmt.Errorf("Key length (argon2id) input of %d is invalid, it must be 16 or higher", keyLength) } + if iterations < 1 { 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. } if salt == "" { salt = utils.RandomString(saltLength, HashingPossibleSaltCharacters) } + settings = getCryptSettings(salt, algorithm, iterations, memory, parallelism, keyLength) // This error can be ignored because we check for it before a user gets here. hash, _ = crypt.Crypt(password, settings) + return hash, nil } @@ -141,10 +149,12 @@ func CheckPassword(password, hash string) (ok bool, err error) { if err != nil { return false, err } + expectedHash, err := HashPassword(password, passwordHash.Salt, passwordHash.Algorithm, passwordHash.Iterations, passwordHash.Memory, passwordHash.Parallelism, passwordHash.KeyLength, len(passwordHash.Salt)) if err != nil { return false, err } + return hash == expectedHash, nil } @@ -156,5 +166,6 @@ func getCryptSettings(salt string, algorithm CryptAlgo, iterations, memory, para } else { panic("invalid password hashing algorithm provided") } + return settings } diff --git a/internal/authentication/password_hash_test.go b/internal/authentication/password_hash_test.go index b169cb46..9017cee0 100644 --- a/internal/authentication/password_hash_test.go +++ b/internal/authentication/password_hash_test.go @@ -47,10 +47,13 @@ func TestShouldHashArgon2idPassword(t *testing.T) { // This checks the method of hashing (for argon2id) supports all the characters we allow in Authelia's hash function. func TestArgon2idHashSaltValidValues(t *testing.T) { + var err error + + var hash string + data := string(HashingPossibleSaltCharacters) datas := utils.SliceString(data, 16) - var hash string - var err error + for _, salt := range datas { hash, err = HashPassword("password", salt, HashingAlgorithmArgon2id, 1, 8, 1, 32, 16) assert.NoError(t, err) @@ -60,10 +63,13 @@ func TestArgon2idHashSaltValidValues(t *testing.T) { // This checks the method of hashing (for sha512) supports all the characters we allow in Authelia's hash function. func TestSHA512HashSaltValidValues(t *testing.T) { + var err error + + var hash string + data := string(HashingPossibleSaltCharacters) datas := utils.SliceString(data, 16) - var hash string - var err error + for _, salt := range datas { hash, err = HashPassword("password", salt, HashingAlgorithmSHA512, 1000, 0, 0, 0, 16) assert.NoError(t, err) diff --git a/internal/authorization/authorizer.go b/internal/authorization/authorizer.go index e138c3c2..5395afdf 100644 --- a/internal/authorization/authorizer.go +++ b/internal/authorization/authorizer.go @@ -71,6 +71,7 @@ func selectMatchingObjectRules(rules []schema.ACLRule, object Object) []schema.A selectedRules = append(selectedRules, rule) } } + return selectedRules } @@ -123,6 +124,7 @@ func (p *Authorizer) GetRequiredLevel(subject Subject, requestURL url.URL) Level if len(matchingRules) > 0 { return PolicyToLevel(matchingRules[0].Policy) } + logging.Logger().Tracef("No matching rule for subject %s and url %s... Applying default policy.", subject.String(), requestURL.String()) @@ -141,5 +143,6 @@ func (p *Authorizer) IsURLMatchingRuleWithGroupSubjects(requestURL url.URL) (has } } } + return false } diff --git a/internal/authorization/domain_matcher.go b/internal/authorization/domain_matcher.go index e4a11f39..c34c8249 100644 --- a/internal/authorization/domain_matcher.go +++ b/internal/authorization/domain_matcher.go @@ -10,5 +10,6 @@ func isDomainMatching(domain string, domainRules []string) bool { return true } } + return false } diff --git a/internal/authorization/ip_matcher.go b/internal/authorization/ip_matcher.go index c5814a53..ec882241 100644 --- a/internal/authorization/ip_matcher.go +++ b/internal/authorization/ip_matcher.go @@ -17,9 +17,12 @@ func isIPMatching(ip net.IP, networks []string) bool { if ip.String() == network { return true } + continue } + _, ipNet, err := net.ParseCIDR(network) + if err != nil { // TODO(c.michaud): make sure the rule is valid at startup to // to such a case here. @@ -30,5 +33,6 @@ func isIPMatching(ip net.IP, networks []string) bool { return true } } + return false } diff --git a/internal/authorization/path_matcher.go b/internal/authorization/path_matcher.go index b2b3b11a..ea90c8e7 100644 --- a/internal/authorization/path_matcher.go +++ b/internal/authorization/path_matcher.go @@ -20,5 +20,6 @@ func isPathMatching(path string, pathRegexps []string) bool { return true } } + return false } diff --git a/internal/authorization/subject_matcher.go b/internal/authorization/subject_matcher.go index 5161dc88..d47093b9 100644 --- a/internal/authorization/subject_matcher.go +++ b/internal/authorization/subject_matcher.go @@ -25,5 +25,6 @@ func isSubjectMatching(subject Subject, subjectRule string) bool { return true } } + return false } diff --git a/internal/commands/certificates.go b/internal/commands/certificates.go index 67ac099f..fa50684a 100644 --- a/internal/commands/certificates.go +++ b/internal/commands/certificates.go @@ -34,6 +34,7 @@ var ( func init() { CertificatesGenerateCmd.PersistentFlags().StringVar(&host, "host", "", "Comma-separated hostnames and IPs to generate a certificate for") err := CertificatesGenerateCmd.MarkPersistentFlagRequired("host") + if err != nil { log.Fatal(err) } @@ -66,7 +67,9 @@ func publicKey(priv interface{}) interface{} { func generateSelfSignedCertificate(cmd *cobra.Command, args []string) { // implementation retrieved from https://golang.org/src/crypto/tls/generate_cert.go var priv interface{} + var err error + switch ecdsaCurve { case "": if ed25519Key { @@ -85,6 +88,7 @@ func generateSelfSignedCertificate(cmd *cobra.Command, args []string) { default: log.Fatalf("Unrecognized elliptic curve: %q", ecdsaCurve) } + if err != nil { log.Fatalf("Failed to generate private key: %v", err) } @@ -103,6 +107,7 @@ func generateSelfSignedCertificate(cmd *cobra.Command, args []string) { serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { log.Fatalf("Failed to generate serial number: %v", err) } @@ -141,33 +146,42 @@ func generateSelfSignedCertificate(cmd *cobra.Command, args []string) { certPath := path.Join(targetDirectory, "cert.pem") certOut, err := os.Create(certPath) + if err != nil { log.Fatalf("Failed to open %s for writing: %v", certPath, err) } + if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { log.Fatalf("Failed to write data to cert.pem: %v", err) } + if err := certOut.Close(); err != nil { log.Fatalf("Error closing %s: %v", certPath, err) } + log.Printf("wrote %s\n", certPath) keyPath := path.Join(targetDirectory, "key.pem") keyOut, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { log.Fatalf("Failed to open %s for writing: %v", keyPath, err) return } + privBytes, err := x509.MarshalPKCS8PrivateKey(priv) if err != nil { log.Fatalf("Unable to marshal private key: %v", err) } + if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil { log.Fatalf("Failed to write data to %s: %v", keyPath, err) } + if err := keyOut.Close(); err != nil { log.Fatalf("Error closing %s: %v", keyPath, err) } + log.Printf("wrote %s\n", keyPath) } diff --git a/internal/configuration/reader.go b/internal/configuration/reader.go index f963fe55..039e902a 100644 --- a/internal/configuration/reader.go +++ b/internal/configuration/reader.go @@ -43,6 +43,7 @@ func Read(configPath string) (*schema.Configuration, []error) { } var configuration schema.Configuration + viper.Unmarshal(&configuration) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. val := schema.NewStructValidator() diff --git a/internal/configuration/reader_test.go b/internal/configuration/reader_test.go index 75a716c4..e2fe399b 100644 --- a/internal/configuration/reader_test.go +++ b/internal/configuration/reader_test.go @@ -58,6 +58,7 @@ func TestShouldParseConfigFile(t *testing.T) { func TestShouldParseAltConfigFile(t *testing.T) { require.NoError(t, os.Setenv("AUTHELIA_STORAGE_POSTGRES_PASSWORD", "postgres_secret_from_env")) + config, errors := Read("./test_resources/config_alt.yml") require.Len(t, errors, 0) @@ -98,6 +99,7 @@ func TestShouldNotParseConfigFileWithOldOrUnexpectedKeys(t *testing.T) { func TestShouldValidateConfigurationTemplate(t *testing.T) { resetEnv() + _, errors := Read("../../config.template.yml") assert.Len(t, errors, 0) } @@ -112,6 +114,7 @@ func TestShouldOnlyAllowOneEnvType(t *testing.T) { require.NoError(t, os.Setenv("AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD", "ldap_secret_from_env")) require.NoError(t, os.Setenv("AUTHELIA_NOTIFIER_SMTP_PASSWORD", "smtp_secret_from_env")) require.NoError(t, os.Setenv("AUTHELIA_SESSION_REDIS_PASSWORD", "redis_secret_from_env")) + _, errors := Read("./test_resources/config_alt.yml") require.Len(t, errors, 2) @@ -128,6 +131,7 @@ func TestShouldOnlyAllowEnvOrConfig(t *testing.T) { require.NoError(t, os.Setenv("AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD", "ldap_secret_from_env")) require.NoError(t, os.Setenv("AUTHELIA_NOTIFIER_SMTP_PASSWORD", "smtp_secret_from_env")) require.NoError(t, os.Setenv("AUTHELIA_SESSION_REDIS_PASSWORD", "redis_secret_from_env")) + _, errors := Read("./test_resources/config_with_secret.yml") require.Len(t, errors, 1) diff --git a/internal/configuration/schema/validator.go b/internal/configuration/schema/validator.go index c69bd37f..bd290b91 100644 --- a/internal/configuration/schema/validator.go +++ b/internal/configuration/schema/validator.go @@ -23,6 +23,7 @@ type Validator struct { func NewValidator() *Validator { validator := new(Validator) validator.errors = make(map[string][]error) + return validator } @@ -39,6 +40,7 @@ func (v *Validator) validateOne(item QueueItem, q *queue.Queue) error { //nolint } elem := item.value.Elem() + q.Put(QueueItem{ //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. value: elem, path: item.path, @@ -64,6 +66,7 @@ func (v *Validator) validateOne(item QueueItem, q *queue.Queue) error { //nolint }) } } + return nil } @@ -77,12 +80,15 @@ func (v *Validator) Validate(s interface{}) error { if err != nil { return err } + item, ok := val[0].(QueueItem) if !ok { return fmt.Errorf("Cannot convert item into QueueItem") } + v.validateOne(item, q) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. } + return nil } @@ -90,6 +96,7 @@ func (v *Validator) Validate(s interface{}) error { func (v *Validator) PrintErrors() { for path, errs := range v.errors { fmt.Printf("Errors at %s:\n", path) + for _, err := range errs { fmt.Printf("--> %s\n", err) } @@ -110,6 +117,7 @@ type StructValidator struct { func NewStructValidator() *StructValidator { val := new(StructValidator) val.errors = make([]error, 0) + return val } diff --git a/internal/configuration/validator/configuration.go b/internal/configuration/validator/configuration.go index 8e0bc09d..f410ff2e 100644 --- a/internal/configuration/validator/configuration.go +++ b/internal/configuration/validator/configuration.go @@ -45,6 +45,7 @@ func ValidateConfiguration(configuration *schema.Configuration, validator *schem if configuration.TOTP == nil { configuration.TOTP = &schema.DefaultTOTPConfiguration } + ValidateTOTP(configuration.TOTP, validator) ValidateAuthenticationBackend(&configuration.AuthenticationBackend, validator) @@ -58,6 +59,7 @@ func ValidateConfiguration(configuration *schema.Configuration, validator *schem if configuration.Regulation == nil { configuration.Regulation = &schema.DefaultRegulationConfiguration } + ValidateRegulation(configuration.Regulation, validator) ValidateServer(&configuration.Server, validator) diff --git a/internal/configuration/validator/configuration_test.go b/internal/configuration/validator/configuration_test.go index fe3b2cd7..3d3dc9af 100644 --- a/internal/configuration/validator/configuration_test.go +++ b/internal/configuration/validator/configuration_test.go @@ -30,6 +30,7 @@ func newDefaultConfig() schema.Configuration { Filename: "/tmp/file", }, } + return config } diff --git a/internal/configuration/validator/keys.go b/internal/configuration/validator/keys.go index 78bc39a2..bc20effc 100644 --- a/internal/configuration/validator/keys.go +++ b/internal/configuration/validator/keys.go @@ -11,6 +11,7 @@ import ( // ValidateKeys determines if a provided key is valid. func ValidateKeys(validator *schema.StructValidator, keys []string) { var errStrings []string + for _, key := range keys { if utils.IsStringInSlice(key, validKeys) { continue @@ -24,6 +25,7 @@ func ValidateKeys(validator *schema.StructValidator, keys []string) { validator.Push(fmt.Errorf("config key not expected: %s", key)) } } + for _, err := range errStrings { validator.Push(errors.New(err)) } diff --git a/internal/configuration/validator/keys_test.go b/internal/configuration/validator/keys_test.go index d5b156ce..4ec5a2c3 100644 --- a/internal/configuration/validator/keys_test.go +++ b/internal/configuration/validator/keys_test.go @@ -34,11 +34,13 @@ func TestShouldNotValidateBadKeys(t *testing.T) { func TestAllSpecificErrorKeys(t *testing.T) { var configKeys []string //nolint:prealloc // This is because the test is dynamic based on the keys that exist in the map + var uniqueValues []string // Setup configKeys and uniqueValues expected. for key, value := range specificErrorKeys { configKeys = append(configKeys, key) + if !utils.IsStringInSlice(value, uniqueValues) { uniqueValues = append(uniqueValues, value) } diff --git a/internal/configuration/validator/notifier.go b/internal/configuration/validator/notifier.go index 8227506a..ccb455f1 100644 --- a/internal/configuration/validator/notifier.go +++ b/internal/configuration/validator/notifier.go @@ -22,6 +22,7 @@ func ValidateNotifier(configuration *schema.NotifierConfiguration, validator *sc if configuration.FileSystem.Filename == "" { validator.Push(fmt.Errorf("Filename of filesystem notifier must not be empty")) } + return } @@ -29,6 +30,7 @@ func ValidateNotifier(configuration *schema.NotifierConfiguration, validator *sc if configuration.SMTP.StartupCheckAddress == "" { configuration.SMTP.StartupCheckAddress = "test@authelia.com" } + if configuration.SMTP.Host == "" { validator.Push(fmt.Errorf("Host of SMTP notifier must be provided")) } @@ -44,6 +46,7 @@ func ValidateNotifier(configuration *schema.NotifierConfiguration, validator *sc if configuration.SMTP.Subject == "" { configuration.SMTP.Subject = schema.DefaultSMTPNotifierConfiguration.Subject } + return } } diff --git a/internal/configuration/validator/regulation.go b/internal/configuration/validator/regulation.go index 1dbf2989..037d386a 100644 --- a/internal/configuration/validator/regulation.go +++ b/internal/configuration/validator/regulation.go @@ -12,17 +12,21 @@ func ValidateRegulation(configuration *schema.RegulationConfiguration, validator if configuration.FindTime == "" { configuration.FindTime = schema.DefaultRegulationConfiguration.FindTime // 2 min } + if configuration.BanTime == "" { configuration.BanTime = schema.DefaultRegulationConfiguration.BanTime // 5 min } + findTime, err := utils.ParseDurationString(configuration.FindTime) if err != nil { validator.Push(fmt.Errorf("Error occurred parsing regulation find_time string: %s", err)) } + banTime, err := utils.ParseDurationString(configuration.BanTime) if err != nil { validator.Push(fmt.Errorf("Error occurred parsing regulation ban_time string: %s", err)) } + if findTime > banTime { validator.Push(fmt.Errorf("find_time cannot be greater than ban_time")) } diff --git a/internal/configuration/validator/secrets.go b/internal/configuration/validator/secrets.go index 66af4536..9481a548 100644 --- a/internal/configuration/validator/secrets.go +++ b/internal/configuration/validator/secrets.go @@ -50,6 +50,7 @@ func getSecretValue(name string, validator *schema.StructValidator, viper *viper if envValue != "" && fileEnvValue != "" { validator.Push(fmt.Errorf("secret is defined in multiple areas: %s", name)) } + if (envValue != "" || fileEnvValue != "") && configValue != "" { validator.Push(fmt.Errorf("error loading secret (%s): it's already defined in the config file", name)) } @@ -63,9 +64,11 @@ func getSecretValue(name string, validator *schema.StructValidator, viper *viper return strings.Replace(string(content), "\n", "", -1) } } + if envValue != "" { logging.Logger().Warnf("The following secret is defined as an environment variable, this is insecure and being removed in 4.18.0+, it's recommended to use the file secrets instead (https://docs.authelia.com/configuration/secrets.html): %s", name) return envValue } + return configValue } diff --git a/internal/configuration/validator/session_test.go b/internal/configuration/validator/session_test.go index 830c6b84..2478edf3 100644 --- a/internal/configuration/validator/session_test.go +++ b/internal/configuration/validator/session_test.go @@ -12,6 +12,7 @@ func newDefaultSessionConfig() schema.SessionConfiguration { config := schema.SessionConfiguration{} config.Secret = testJWTSecret config.Domain = "example.com" + return config } diff --git a/internal/configuration/validator/totp.go b/internal/configuration/validator/totp.go index 68c94a44..cbf91b89 100644 --- a/internal/configuration/validator/totp.go +++ b/internal/configuration/validator/totp.go @@ -11,6 +11,7 @@ func ValidateTOTP(configuration *schema.TOTPConfiguration, validator *schema.Str if configuration.Issuer == "" { configuration.Issuer = schema.DefaultTOTPConfiguration.Issuer } + if configuration.Period == 0 { configuration.Period = schema.DefaultTOTPConfiguration.Period } else if configuration.Period < 0 { diff --git a/internal/configuration/validator/totp_test.go b/internal/configuration/validator/totp_test.go index 3357fa63..c95759f4 100644 --- a/internal/configuration/validator/totp_test.go +++ b/internal/configuration/validator/totp_test.go @@ -23,6 +23,7 @@ func TestShouldSetDefaultTOTPValues(t *testing.T) { func TestShouldRaiseErrorWhenInvalidTOTPMinimumValues(t *testing.T) { var badSkew = -1 + validator := schema.NewStructValidator() config := schema.TOTPConfiguration{ Period: -5, diff --git a/internal/duo/duo.go b/internal/duo/duo.go index 67979b82..7bf80806 100644 --- a/internal/duo/duo.go +++ b/internal/duo/duo.go @@ -13,23 +13,25 @@ import ( func NewDuoAPI(duoAPI *duoapi.DuoApi) *APIImpl { api := new(APIImpl) api.DuoApi = duoAPI + return api } // Call call to the DuoAPI. func (d *APIImpl) Call(values url.Values, ctx *middlewares.AutheliaCtx) (*Response, error) { - _, responseBytes, err := d.DuoApi.SignedCall("POST", "/auth/v2/auth", values) + var response Response + _, responseBytes, err := d.DuoApi.SignedCall("POST", "/auth/v2/auth", values) if err != nil { return nil, err } ctx.Logger.Tracef("Duo Push Auth Response Raw Data for %s from IP %s: %s", ctx.GetSession().Username, ctx.RemoteIP().String(), string(responseBytes)) - var response Response err = json.Unmarshal(responseBytes, &response) if err != nil { return nil, err } + return &response, nil } diff --git a/internal/handlers/handler_extended_configuration_test.go b/internal/handlers/handler_extended_configuration_test.go index e109a6f3..b974b075 100644 --- a/internal/handlers/handler_extended_configuration_test.go +++ b/internal/handlers/handler_extended_configuration_test.go @@ -38,6 +38,7 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldServeDefaultMethods() { SecondFactorEnabled: false, TOTPPeriod: schema.DefaultTOTPConfiguration.Period, } + ExtendedConfigurationGet(s.mock.Ctx) s.mock.Assert200OK(s.T(), expectedBody) } @@ -54,6 +55,7 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldServeDefaultMethodsAndMo SecondFactorEnabled: false, TOTPPeriod: schema.DefaultTOTPConfiguration.Period, } + ExtendedConfigurationGet(s.mock.Ctx) s.mock.Assert200OK(s.T(), expectedBody) } diff --git a/internal/handlers/handler_firstfactor.go b/internal/handlers/handler_firstfactor.go index 54561d4f..1ae05b55 100644 --- a/internal/handlers/handler_firstfactor.go +++ b/internal/handlers/handler_firstfactor.go @@ -28,7 +28,9 @@ func FirstFactorPost(ctx *middlewares.AutheliaCtx) { ctx.Error(fmt.Errorf("User %s is banned until %s", bodyJSON.Username, bannedUntil), userBannedMessage) return } + ctx.Error(fmt.Errorf("Unable to regulate authentication: %s", err), authenticationFailedMessage) + return } @@ -39,6 +41,7 @@ func FirstFactorPost(ctx *middlewares.AutheliaCtx) { ctx.Providers.Regulator.Mark(bodyJSON.Username, false) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. ctx.Error(fmt.Errorf("Error while checking password for user %s: %s", bodyJSON.Username, err.Error()), authenticationFailedMessage) + return } @@ -47,6 +50,7 @@ func FirstFactorPost(ctx *middlewares.AutheliaCtx) { ctx.Providers.Regulator.Mark(bodyJSON.Username, false) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. ctx.ReplyError(fmt.Errorf("Credentials are wrong for user %s", bodyJSON.Username), authenticationFailedMessage) + return } @@ -106,9 +110,11 @@ func FirstFactorPost(ctx *middlewares.AutheliaCtx) { userSession.LastActivity = time.Now().Unix() userSession.KeepMeLoggedIn = keepMeLoggedIn refresh, refreshInterval := getProfileRefreshSettings(ctx.Configuration.AuthenticationBackend) + if refresh { userSession.RefreshTTL = ctx.Clock.Now().Add(refreshInterval) } + err = ctx.SaveSession(userSession) if err != nil { diff --git a/internal/handlers/handler_register_u2f_step1.go b/internal/handlers/handler_register_u2f_step1.go index 9476fa3f..736cba80 100644 --- a/internal/handlers/handler_register_u2f_step1.go +++ b/internal/handlers/handler_register_u2f_step1.go @@ -36,6 +36,7 @@ func secondFactorU2FIdentityFinish(ctx *middlewares.AutheliaCtx, username string appID := fmt.Sprintf("%s://%s", ctx.XForwardedProto(), ctx.XForwardedHost()) ctx.Logger.Tracef("U2F appID is %s", appID) + var trustedFacets = []string{appID} challenge, err := u2f.NewChallenge(appID, trustedFacets) diff --git a/internal/handlers/handler_register_u2f_step1_test.go b/internal/handlers/handler_register_u2f_step1_test.go index fd7d470f..3887f2e3 100644 --- a/internal/handlers/handler_register_u2f_step1_test.go +++ b/internal/handlers/handler_register_u2f_step1_test.go @@ -43,6 +43,7 @@ func createToken(secret string, username string, action string, expiresAt time.T } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) ss, _ := token.SignedString([]byte(secret)) + return ss } diff --git a/internal/handlers/handler_sign_duo.go b/internal/handlers/handler_sign_duo.go index 98624e7a..6962ce9a 100644 --- a/internal/handlers/handler_sign_duo.go +++ b/internal/handlers/handler_sign_duo.go @@ -31,6 +31,7 @@ func SecondFactorDuoPost(duoAPI duo.API) middlewares.RequestHandler { values.Set("ipaddr", remoteIP) values.Set("factor", "push") values.Set("device", "auto") + if requestBody.TargetURL != "" { values.Set("pushinfo", fmt.Sprintf("target%%20url=%s", requestBody.TargetURL)) } diff --git a/internal/handlers/handler_sign_totp.go b/internal/handlers/handler_sign_totp.go index fe91a657..c06181af 100644 --- a/internal/handlers/handler_sign_totp.go +++ b/internal/handlers/handler_sign_totp.go @@ -19,6 +19,7 @@ func SecondFactorTOTPPost(totpVerifier TOTPVerifier) middlewares.RequestHandler } userSession := ctx.GetSession() + secret, err := ctx.Providers.StorageProvider.LoadTOTPSecret(userSession.Username) if err != nil { ctx.Error(fmt.Errorf("Unable to load TOTP secret: %s", err), mfaValidationFailedMessage) diff --git a/internal/handlers/handler_sign_u2f_step1.go b/internal/handlers/handler_sign_u2f_step1.go index abeb74b4..64029896 100644 --- a/internal/handlers/handler_sign_u2f_step1.go +++ b/internal/handlers/handler_sign_u2f_step1.go @@ -24,6 +24,7 @@ func SecondFactorU2FSignGet(ctx *middlewares.AutheliaCtx) { } appID := fmt.Sprintf("%s://%s", ctx.XForwardedProto(), ctx.XForwardedHost()) + var trustedFacets = []string{appID} challenge, err := u2f.NewChallenge(appID, trustedFacets) @@ -40,7 +41,9 @@ func SecondFactorU2FSignGet(ctx *middlewares.AutheliaCtx) { ctx.Error(fmt.Errorf("No device handle found for user %s", userSession.Username), mfaValidationFailedMessage) return } + ctx.Error(fmt.Errorf("Unable to retrieve U2F device handle: %s", err), mfaValidationFailedMessage) + return } diff --git a/internal/handlers/handler_user_info.go b/internal/handlers/handler_user_info.go index 7f6dc4d3..7066c640 100644 --- a/internal/handlers/handler_user_info.go +++ b/internal/handlers/handler_user_info.go @@ -15,17 +15,22 @@ import ( func loadInfo(username string, storageProvider storage.Provider, preferences *UserPreferences, logger *logrus.Entry) []error { var wg sync.WaitGroup + wg.Add(3) errors := make([]error, 0) + go func() { defer wg.Done() + method, err := storageProvider.LoadPreferred2FAMethod(username) if err != nil { errors = append(errors, err) logger.Error(err) + return } + if method == "" { preferences.Method = authentication.PossibleMethods[0] } else { @@ -35,33 +40,42 @@ func loadInfo(username string, storageProvider storage.Provider, preferences *Us go func() { defer wg.Done() + _, _, err := storageProvider.LoadU2FDeviceHandle(username) if err != nil { if err == storage.ErrNoU2FDeviceHandle { return } + errors = append(errors, err) logger.Error(err) + return } + preferences.HasU2F = true }() go func() { defer wg.Done() + _, err := storageProvider.LoadTOTPSecret(username) if err != nil { if err == storage.ErrNoTOTPSecret { return } + errors = append(errors, err) logger.Error(err) + return } + preferences.HasTOTP = true }() wg.Wait() + return errors } @@ -76,6 +90,7 @@ func UserInfoGet(ctx *middlewares.AutheliaCtx) { ctx.Error(fmt.Errorf("Unable to load user information"), operationFailedMessage) return } + ctx.SetJSONBody(preferences) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. } @@ -87,6 +102,7 @@ type MethodBody struct { // MethodPreferencePost update the user preferences regarding 2FA method. func MethodPreferencePost(ctx *middlewares.AutheliaCtx) { bodyJSON := MethodBody{} + err := ctx.ParseBody(&bodyJSON) if err != nil { ctx.Error(err, operationFailedMessage) diff --git a/internal/handlers/handler_verify.go b/internal/handlers/handler_verify.go index a3e64f1b..4ee4afb7 100644 --- a/internal/handlers/handler_verify.go +++ b/internal/handlers/handler_verify.go @@ -38,7 +38,9 @@ func getOriginalURL(ctx *middlewares.AutheliaCtx) (*url.URL, error) { if err != nil { return nil, fmt.Errorf("Unable to parse URL extracted from X-Original-URL header: %v", err) } + ctx.Logger.Trace("Using X-Original-URL header content as targeted site URL") + return url, nil } @@ -55,6 +57,7 @@ func getOriginalURL(ctx *middlewares.AutheliaCtx) (*url.URL, error) { } var requestURI string + scheme := append(forwardedProto, protoHostSeparator...) requestURI = string(append(scheme, append(forwardedHost, forwardedURI...)...)) @@ -63,8 +66,10 @@ func getOriginalURL(ctx *middlewares.AutheliaCtx) (*url.URL, error) { if err != nil { return nil, fmt.Errorf("Unable to parse URL %s: %v", requestURI, err) } + ctx.Logger.Tracef("Using X-Fowarded-Proto, X-Forwarded-Host and X-Forwarded-URI headers " + "to construct targeted site URL") + return url, nil } @@ -74,15 +79,19 @@ func parseBasicAuth(auth string) (username, password string, err error) { if !strings.HasPrefix(auth, authPrefix) { return "", "", fmt.Errorf("%s prefix not found in %s header", strings.Trim(authPrefix, " "), AuthorizationHeader) } + c, err := base64.StdEncoding.DecodeString(auth[len(authPrefix):]) if err != nil { return "", "", err } + cs := string(c) s := strings.IndexByte(cs, ':') + if s < 0 { return "", "", fmt.Errorf("Format of %s header must be user:password", AuthorizationHeader) } + return cs[:s], cs[s+1:], nil } @@ -114,6 +123,7 @@ func isTargetURLAuthorized(authorizer *authorization.Authorizer, targetURL url.U return Authorized } } + return NotAuthorized } @@ -208,8 +218,10 @@ func verifySessionCookie(ctx *middlewares.AutheliaCtx, targetURL *url.URL, userS if err != nil { ctx.Logger.Error(fmt.Errorf("Unable to destroy user session after provider refresh didn't find the user: %s", err)) } + return userSession.Username, userSession.Groups, authentication.NotAuthenticated, err } + ctx.Logger.Warnf("Error occurred while attempting to update user details from LDAP: %s", err) } @@ -226,6 +238,7 @@ func handleUnauthorized(ctx *middlewares.AutheliaCtx, targetURL fmt.Stringer, us if strings.Contains(redirectionURL, "/%23/") { ctx.Logger.Warn("Characters /%23/ have been detected in redirection URL. This is not needed anymore, please strip it") } + ctx.Logger.Infof("Access to %s is not authorized to user %s, redirecting to %s", targetURL.String(), username, redirectionURL) ctx.Redirect(redirectionURL, 302) ctx.SetBodyString(fmt.Sprintf("Found. Redirecting to %s", redirectionURL)) @@ -248,6 +261,7 @@ func updateActivityTimestamp(ctx *middlewares.AutheliaCtx, isBasicAuth bool, use // Mark current activity. userSession.LastActivity = ctx.Clock.Now().Unix() + return ctx.SaveSession(userSession) } @@ -263,9 +277,11 @@ func generateVerifySessionHasUpToDateProfileTraceLogs(ctx *middlewares.AutheliaC if len(groupsAdded) != 0 { groupsDelta = append(groupsDelta, fmt.Sprintf("Added: %s.", strings.Join(groupsAdded, ", "))) } + if len(groupsRemoved) != 0 { groupsDelta = append(groupsDelta, fmt.Sprintf("Removed: %s.", strings.Join(groupsRemoved, ", "))) } + if len(groupsDelta) != 0 { ctx.Logger.Tracef("Updated groups detected for %s. %s", userSession.Username, strings.Join(groupsDelta, " ")) } else { @@ -277,9 +293,11 @@ func generateVerifySessionHasUpToDateProfileTraceLogs(ctx *middlewares.AutheliaC if len(emailsAdded) != 0 { emailsDelta = append(emailsDelta, fmt.Sprintf("Added: %s.", strings.Join(emailsAdded, ", "))) } + if len(emailsRemoved) != 0 { emailsDelta = append(emailsDelta, fmt.Sprintf("Removed: %s.", strings.Join(emailsRemoved, ", "))) } + if len(emailsDelta) != 0 { ctx.Logger.Tracef("Updated emails detected for %s. %s", userSession.Username, strings.Join(emailsDelta, " ")) } else { @@ -291,8 +309,8 @@ func verifySessionHasUpToDateProfile(ctx *middlewares.AutheliaCtx, targetURL *ur refreshProfile bool, refreshProfileInterval time.Duration) error { // TODO: Add a check for LDAP password changes based on a time format attribute. // See https://docs.authelia.com/security/threat-model.html#potential-future-guarantees - ctx.Logger.Tracef("Checking if we need check the authentication backend for an updated profile for %s.", userSession.Username) + if refreshProfile && userSession.Username != "" && targetURL != nil && ctx.Providers.Authorizer.IsURLMatchingRuleWithGroupSubjects(*targetURL) && (refreshProfileInterval == schema.RefreshIntervalAlways || userSession.RefreshTTL.Before(ctx.Clock.Now())) { @@ -305,6 +323,7 @@ func verifySessionHasUpToDateProfile(ctx *middlewares.AutheliaCtx, targetURL *ur groupsDiff := utils.IsStringSlicesDifferent(userSession.Groups, details.Groups) emailsDiff := utils.IsStringSlicesDifferent(userSession.Emails, details.Emails) + if !groupsDiff && !emailsDiff { ctx.Logger.Tracef("Updated profile not detected for %s.", userSession.Username) } else { @@ -329,6 +348,7 @@ func verifySessionHasUpToDateProfile(ctx *middlewares.AutheliaCtx, targetURL *ur return ctx.SaveSession(*userSession) } } + return nil } @@ -336,6 +356,7 @@ func getProfileRefreshSettings(cfg schema.AuthenticationBackendConfiguration) (r if cfg.Ldap != nil { if cfg.RefreshInterval != schema.ProfileRefreshDisabled { refresh = true + if cfg.RefreshInterval != schema.ProfileRefreshAlways { // Skip Error Check since validator checks it refreshInterval, _ = utils.ParseDurationString(cfg.RefreshInterval) @@ -344,6 +365,7 @@ func getProfileRefreshSettings(cfg schema.AuthenticationBackendConfiguration) (r } } } + return refresh, refreshInterval } @@ -364,6 +386,7 @@ func VerifyGet(cfg schema.AuthenticationBackendConfiguration) middlewares.Reques ctx.Logger.Error(fmt.Errorf("Scheme of target URL %s must be secure since cookies are "+ "only transported over a secure connection for security reasons", targetURL.String())) ctx.ReplyUnauthorized() + return } @@ -371,11 +394,14 @@ func VerifyGet(cfg schema.AuthenticationBackendConfiguration) middlewares.Reques ctx.Logger.Error(fmt.Errorf("The target URL %s is not under the protected domain %s", targetURL.String(), ctx.Configuration.Session.Domain)) ctx.ReplyUnauthorized() + return } var username string + var groups []string + var authLevel authentication.Level proxyAuthorization := ctx.Request.Header.Peek(AuthorizationHeader) @@ -391,11 +417,14 @@ func VerifyGet(cfg schema.AuthenticationBackendConfiguration) middlewares.Reques if err != nil { ctx.Logger.Error(fmt.Sprintf("Error caught when verifying user authorization: %s", err)) + if err := updateActivityTimestamp(ctx, isBasicAuth, username); err != nil { ctx.Error(fmt.Errorf("Unable to update last activity: %s", err), operationFailedMessage) return } + handleUnauthorized(ctx, targetURL, username) + return } diff --git a/internal/handlers/handler_verify_test.go b/internal/handlers/handler_verify_test.go index 512a60d3..a7a81b8b 100644 --- a/internal/handlers/handler_verify_test.go +++ b/internal/handlers/handler_verify_test.go @@ -153,6 +153,7 @@ func TestShouldCheckAuthorizationMatching(t *testing.T) { AuthLevel authentication.Level ExpectedMatching authorizationMatching } + rules := []Rule{ {"bypass", authentication.NotAuthenticated, Authorized}, {"bypass", authentication.OneFactor, Authorized}, @@ -679,6 +680,7 @@ func TestIsDomainProtected(t *testing.T) { GetURL := func(u string) *url.URL { x, err := url.ParseRequestURI(u) require.NoError(t, err) + return x } @@ -701,6 +703,7 @@ func TestSchemeIsHTTPS(t *testing.T) { GetURL := func(u string) *url.URL { x, err := url.ParseRequestURI(u) require.NoError(t, err) + return x } @@ -718,6 +721,7 @@ func TestSchemeIsWSS(t *testing.T) { GetURL := func(u string) *url.URL { x, err := url.ParseRequestURI(u) require.NoError(t, err) + return x } @@ -854,6 +858,7 @@ func TestShouldGetRemovedUserGroupsFromBackend(t *testing.T) { } verifyGet := VerifyGet(verifyGetCfg) + mock.UserProviderMock.EXPECT().GetDetails("john").Return(user, nil).Times(2) clock := mocks.TestingClock{} @@ -968,6 +973,7 @@ func TestShouldGetAddedUserGroupsFromBackend(t *testing.T) { // Reset otherwise we get the last 403 when we check the Response. Is there a better way to do this? mock.Close() + mock = mocks.NewMockAutheliaCtx(t) defer mock.Close() err = mock.Ctx.SaveSession(userSession) diff --git a/internal/handlers/response.go b/internal/handlers/response.go index 0119f670..25d4961c 100644 --- a/internal/handlers/response.go +++ b/internal/handlers/response.go @@ -17,6 +17,7 @@ func Handle1FAResponse(ctx *middlewares.AutheliaCtx, targetURI string, username } else { ctx.ReplyOK() } + return } @@ -37,6 +38,7 @@ func Handle1FAResponse(ctx *middlewares.AutheliaCtx, targetURI string, username if requiredLevel == authorization.TwoFactor { ctx.Logger.Warnf("%s requires 2FA, cannot be redirected yet", targetURI) ctx.ReplyOK() + return } @@ -48,10 +50,12 @@ func Handle1FAResponse(ctx *middlewares.AutheliaCtx, targetURI string, username } else { ctx.ReplyOK() } + return } ctx.Logger.Debugf("Redirection URL %s is safe", targetURI) + response := redirectResponse{Redirect: targetURI} ctx.SetJSONBody(response) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. } @@ -64,6 +68,7 @@ func Handle2FAResponse(ctx *middlewares.AutheliaCtx, targetURI string) { } else { ctx.ReplyOK() } + return } diff --git a/internal/handlers/totp.go b/internal/handlers/totp.go index a8bfacfd..fe4e7c2a 100644 --- a/internal/handlers/totp.go +++ b/internal/handlers/totp.go @@ -26,5 +26,6 @@ func (tv *TOTPVerifierImpl) Verify(token, secret string) (bool, error) { Digits: otp.DigitsSix, Algorithm: otp.AlgorithmSHA1, } + return totp.ValidateCustom(token, secret, time.Now().UTC(), opts) } diff --git a/internal/handlers/u2f.go b/internal/handlers/u2f.go index 034da427..1638e842 100644 --- a/internal/handlers/u2f.go +++ b/internal/handlers/u2f.go @@ -27,5 +27,6 @@ func (uv *U2FVerifierImpl) Verify(keyHandle []byte, publicKey []byte, // TODO(c.michaud): store the counter to help detecting cloned U2F keys. _, err := registration.Authenticate( signResponse, challenge, 0) + return err } diff --git a/internal/logging/logger.go b/internal/logging/logger.go index 0438d53a..8746e156 100644 --- a/internal/logging/logger.go +++ b/internal/logging/logger.go @@ -28,7 +28,9 @@ func InitializeLogger(filename string) error { if err != nil { return err } + logrus.SetOutput(f) } + return nil } diff --git a/internal/logging/logger_test.go b/internal/logging/logger_test.go index 6a92ca87..24e3e052 100644 --- a/internal/logging/logger_test.go +++ b/internal/logging/logger_test.go @@ -16,6 +16,7 @@ func TestShouldWriteLogsToFile(t *testing.T) { if err != nil { log.Fatal(err) } + defer os.RemoveAll(dir) path := fmt.Sprintf("%s/authelia.log", dir) diff --git a/internal/middlewares/authelia_context.go b/internal/middlewares/authelia_context.go index 7afa8696..c4f8a227 100644 --- a/internal/middlewares/authelia_context.go +++ b/internal/middlewares/authelia_context.go @@ -32,6 +32,7 @@ func NewAutheliaCtx(ctx *fasthttp.RequestCtx, configuration schema.Configuration autheliaCtx.Configuration = configuration autheliaCtx.Logger = NewRequestLogger(autheliaCtx) autheliaCtx.Clock = utils.RealClock{} + return autheliaCtx, nil } @@ -44,6 +45,7 @@ func AutheliaMiddleware(configuration schema.Configuration, providers Providers) autheliaCtx.Error(err, operationFailedMessage) return } + next(autheliaCtx) } } @@ -78,7 +80,6 @@ func (c *AutheliaCtx) ReplyError(err error, message string) { // ReplyUnauthorized response sent when user is unauthorized. func (c *AutheliaCtx) ReplyUnauthorized() { c.RequestCtx.Error(fasthttp.StatusMessage(fasthttp.StatusUnauthorized), fasthttp.StatusUnauthorized) - // c.Response.Header.Set("WWW-Authenticate", "Basic realm=Restricted") } // ReplyForbidden response sent when access is forbidden to user. @@ -113,6 +114,7 @@ func (c *AutheliaCtx) GetSession() session.UserSession { c.Logger.Error("Unable to retrieve user session") return session.NewDefaultUserSession() } + return userSession } @@ -144,6 +146,7 @@ func (c *AutheliaCtx) ParseBody(value interface{}) error { if !valid { return fmt.Errorf("Body is not valid") } + return nil } @@ -156,6 +159,7 @@ func (c *AutheliaCtx) SetJSONBody(value interface{}) error { c.SetContentType("application/json") c.SetBody(b) + return nil } @@ -169,5 +173,6 @@ func (c *AutheliaCtx) RemoteIP() net.IP { return net.ParseIP(strings.Trim(ips[0], " ")) } } + return c.RequestCtx.RemoteIP() } diff --git a/internal/middlewares/identity_verification.go b/internal/middlewares/identity_verification.go index c56cd7f8..cebcd3bf 100644 --- a/internal/middlewares/identity_verification.go +++ b/internal/middlewares/identity_verification.go @@ -24,6 +24,7 @@ func IdentityVerificationStart(args IdentityVerificationStartArgs) RequestHandle // In that case we reply ok to avoid user enumeration. ctx.Logger.Error(err) ctx.ReplyOK() + return } @@ -78,6 +79,7 @@ func IdentityVerificationStart(args IdentityVerificationStartArgs) RequestHandle ctx.Logger.Debugf("Sending an email to user %s (%s) to confirm identity for registering a device.", identity.Username, identity.Email) + err = ctx.Providers.Notifier.Send(identity.Email, args.MailTitle, buf.String()) if err != nil { @@ -93,6 +95,7 @@ func IdentityVerificationStart(args IdentityVerificationStartArgs) RequestHandle func IdentityVerificationFinish(args IdentityVerificationFinishArgs, next func(ctx *AutheliaCtx, username string)) RequestHandler { return func(ctx *AutheliaCtx) { var finishBody IdentityVerificationFinishBody + b := ctx.PostBody() err := json.Unmarshal(b, &finishBody) @@ -139,7 +142,9 @@ func IdentityVerificationFinish(args IdentityVerificationFinishArgs, next func(c return } } + ctx.Error(err, operationFailedMessage) + return } diff --git a/internal/middlewares/identity_verification_test.go b/internal/middlewares/identity_verification_test.go index 4634eed2..1c006993 100644 --- a/internal/middlewares/identity_verification_test.go +++ b/internal/middlewares/identity_verification_test.go @@ -174,6 +174,7 @@ func createToken(secret string, username string, action string, expiresAt time.T } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) ss, _ := token.SignedString([]byte(secret)) + return ss } diff --git a/internal/middlewares/log_request_test.go b/internal/middlewares/log_request_test.go index 4b521efb..ee32b986 100644 --- a/internal/middlewares/log_request_test.go +++ b/internal/middlewares/log_request_test.go @@ -9,6 +9,7 @@ import ( func TestShouldCallNextFunction(t *testing.T) { var val = false + f := func(ctx *fasthttp.RequestCtx) { val = true } context := &fasthttp.RequestCtx{} diff --git a/internal/middlewares/require_first_factor.go b/internal/middlewares/require_first_factor.go index 05d412d6..9f9aa125 100644 --- a/internal/middlewares/require_first_factor.go +++ b/internal/middlewares/require_first_factor.go @@ -11,6 +11,7 @@ func RequireFirstFactor(next RequestHandler) RequestHandler { ctx.ReplyForbidden() return } + next(ctx) } } diff --git a/internal/mocks/mock_authelia_ctx.go b/internal/mocks/mock_authelia_ctx.go index 8115a29b..9a54d681 100644 --- a/internal/mocks/mock_authelia_ctx.go +++ b/internal/mocks/mock_authelia_ctx.go @@ -123,6 +123,7 @@ func NewMockAutheliaCtx(t *testing.T) *MockAutheliaCtx { mockAuthelia.Hook = hook mockAuthelia.Ctx.Logger = logrus.NewEntry(logger) + return mockAuthelia } @@ -141,6 +142,7 @@ func (m *MockAutheliaCtx) Assert200KO(t *testing.T, message string) { // Assert200OK assert a successful response from the service. func (m *MockAutheliaCtx) Assert200OK(t *testing.T, data interface{}) { assert.Equal(t, 200, m.Ctx.Response.StatusCode()) + response := middlewares.OKResponse{ Status: "OK", Data: data, diff --git a/internal/notification/file_notifier.go b/internal/notification/file_notifier.go index 0cf307b3..f3a00c9c 100644 --- a/internal/notification/file_notifier.go +++ b/internal/notification/file_notifier.go @@ -42,9 +42,11 @@ func (n *FileNotifier) StartupCheck() (bool, error) { return false, err } } + if err := ioutil.WriteFile(n.path, []byte(""), fileNotifierMode); err != nil { return false, err } + return true, nil } @@ -57,5 +59,6 @@ func (n *FileNotifier) Send(recipient, subject, body string) error { if err != nil { return err } + return nil } diff --git a/internal/notification/smtp_login_auth.go b/internal/notification/smtp_login_auth.go index 90f72031..4251178d 100644 --- a/internal/notification/smtp_login_auth.go +++ b/internal/notification/smtp_login_auth.go @@ -21,9 +21,11 @@ func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) { if !server.TLS && !(server.Name == "localhost" || server.Name == "127.0.0.1" || server.Name == "::1") { return "", nil, errors.New("connection over plain-text") } + if server.Name != a.host { return "", nil, errors.New("unexpected hostname from server") } + return "LOGIN", []byte{}, nil } @@ -31,6 +33,7 @@ func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) { if !more { return nil, nil } + switch { case bytes.Equal(fromServer, []byte("Username:")): return []byte(a.username), nil diff --git a/internal/notification/smtp_notifier.go b/internal/notification/smtp_notifier.go index f694d8f4..e6f22e58 100644 --- a/internal/notification/smtp_notifier.go +++ b/internal/notification/smtp_notifier.go @@ -48,6 +48,7 @@ func NewSMTPNotifier(configuration schema.SMTPNotifierConfiguration) *SMTPNotifi startupCheckAddress: configuration.StartupCheckAddress, } notifier.initializeTLSConfig() + return notifier } @@ -64,6 +65,7 @@ func (n *SMTPNotifier) initializeTLSConfig() { if n.trustedCert != "" { log.Debugf("Notifier SMTP client attempting to load certificate from %s", n.trustedCert) + if exists, err := utils.FileExists(n.trustedCert); exists { pem, err := ioutil.ReadFile(n.trustedCert) if err != nil { @@ -83,6 +85,7 @@ func (n *SMTPNotifier) initializeTLSConfig() { log.Warnf("Notifier SMTP failed to load cert from file (file does not exist) with error: %s", err) } } + n.tlsConfig = &tls.Config{ InsecureSkipVerify: n.disableVerifyCert, //nolint:gosec // This is an intended config, we never default true, provide alternate options, and we constantly warn the user. ServerName: n.host, @@ -105,12 +108,14 @@ func (n *SMTPNotifier) startTLS() error { if err := n.client.StartTLS(n.tlsConfig); err != nil { return err } + log.Debug("Notifier SMTP STARTTLS completed without error") } 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)") } else { return errors.New("Notifier SMTP server does not support TLS and it is required by default (see documentation if you want to disable this highly recommended requirement)") } + return nil } @@ -126,16 +131,19 @@ func (n *SMTPNotifier) auth() error { // Check the server supports AUTH, and get the mechanisms. ok, m := n.client.Extension("AUTH") if ok { + var auth smtp.Auth + log.Debugf("Notifier SMTP server supports authentication with the following mechanisms: %s", m) mechanisms := strings.Split(m, " ") - var auth smtp.Auth // Adaptively select the AUTH mechanism to use based on what the server advertised. if utils.IsStringInSlice("PLAIN", mechanisms) { auth = smtp.PlainAuth("", n.username, n.password, n.host) + log.Debug("Notifier SMTP client attempting AUTH PLAIN with server") } else if utils.IsStringInSlice("LOGIN", mechanisms) { auth = newLoginAuth(n.username, n.password, n.host) + log.Debug("Notifier SMTP client attempting AUTH LOGIN with server") } @@ -148,23 +156,30 @@ func (n *SMTPNotifier) auth() error { if err := n.client.Auth(auth); err != nil { return err } + log.Debug("Notifier SMTP client authenticated successfully with the server") + return nil } + return 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 nil } func (n *SMTPNotifier) compose(recipient, subject, body string) error { log.Debugf("Notifier SMTP client attempting to send email body to %s", recipient) + if !n.disableRequireTLS { _, ok := n.client.TLSConnectionState() if !ok { return errors.New("Notifier SMTP client can't send an email over plain text connection") } } + wc, err := n.client.Data() if err != nil { log.Debugf("Notifier SMTP client error while obtaining WriteCloser: %s", err) @@ -188,31 +203,39 @@ func (n *SMTPNotifier) compose(recipient, subject, body string) error { log.Debugf("Notifier SMTP client error while closing the WriteCloser: %s", err) return err } + return nil } // Dial the SMTP server with the SMTPNotifier config. func (n *SMTPNotifier) dial() error { log.Debugf("Notifier SMTP client attempting connection to %s", n.address) + if n.port == 465 { log.Warnf("Notifier SMTP client configured to connect to a SMTPS server. It's highly recommended you use a non SMTPS port and STARTTLS instead of SMTPS, as the protocol is long deprecated.") + conn, err := tls.Dial("tcp", n.address, n.tlsConfig) if err != nil { return err } + client, err := smtp.NewClient(conn, n.host) if err != nil { return err } + n.client = client } else { client, err := smtp.Dial(n.address) if err != nil { return err } + n.client = client } + log.Debug("Notifier SMTP client connected successfully") + return nil } @@ -258,6 +281,7 @@ func (n *SMTPNotifier) StartupCheck() (bool, error) { // Send is used to send an email to a recipient. func (n *SMTPNotifier) Send(recipient, title, body string) error { subject := strings.ReplaceAll(n.subject, "{title}", title) + if err := n.dial(); err != nil { return err } @@ -269,6 +293,7 @@ func (n *SMTPNotifier) Send(recipient, title, body string) error { if err := n.startTLS(); err != nil { return err } + if err := n.auth(); err != nil { return err } @@ -278,6 +303,7 @@ func (n *SMTPNotifier) Send(recipient, title, body string) error { log.Debugf("Notifier SMTP failed while sending MAIL FROM (using sender) with error: %s", err) return err } + if err := n.client.Rcpt(recipient); err != nil { log.Debugf("Notifier SMTP failed while sending RCPT TO (using recipient) with error: %s", err) return err @@ -289,5 +315,6 @@ func (n *SMTPNotifier) Send(recipient, title, body string) error { } log.Debug("Notifier SMTP client successfully sent email") + return nil } diff --git a/internal/regulation/regulator.go b/internal/regulation/regulator.go index 2a7069df..2056ef33 100644 --- a/internal/regulation/regulator.go +++ b/internal/regulation/regulator.go @@ -14,11 +14,13 @@ import ( func NewRegulator(configuration *schema.RegulationConfiguration, provider storage.Provider, clock utils.Clock) *Regulator { regulator := &Regulator{storageProvider: provider} regulator.clock = clock + if configuration != nil { findTime, err := utils.ParseDurationString(configuration.FindTime) if err != nil { panic(err) } + banTime, err := utils.ParseDurationString(configuration.BanTime) if err != nil { panic(err) @@ -34,6 +36,7 @@ func NewRegulator(configuration *schema.RegulationConfiguration, provider storag regulator.findTime = findTime regulator.banTime = banTime } + return regulator } @@ -55,6 +58,7 @@ func (r *Regulator) Regulate(username string) (time.Time, error) { if !r.enabled { return time.Time{}, nil } + now := r.clock.Now() // TODO(c.michaud): make sure FindTime < BanTime. @@ -65,6 +69,7 @@ func (r *Regulator) Regulate(username string) (time.Time, error) { } latestFailedAttempts := make([]models.AuthenticationAttempt, 0, r.maxRetries) + for _, attempt := range attempts { if attempt.Successful || len(latestFailedAttempts) >= r.maxRetries { // We stop appending failed attempts once we find the first successful attempts or we reach @@ -90,5 +95,6 @@ func (r *Regulator) Regulate(username string) (time.Time, error) { bannedUntil := latestFailedAttempts[0].Time.Add(r.banTime) return bannedUntil, ErrUserIsBanned } + return time.Time{}, nil } diff --git a/internal/server/index.go b/internal/server/index.go index 6495f0ba..0db06fc0 100644 --- a/internal/server/index.go +++ b/internal/server/index.go @@ -34,12 +34,15 @@ func ServeIndex(publicDir string) fasthttp.RequestHandler { return func(ctx *fasthttp.RequestCtx) { nonce := utils.RandomString(32, alphaNumericRunes) + ctx.SetContentType("text/html; charset=utf-8") ctx.Response.Header.Add("Content-Security-Policy", fmt.Sprintf("default-src 'self'; style-src 'self' 'nonce-%s'", nonce)) + err := tmpl.Execute(ctx.Response.BodyWriter(), struct{ CSPNonce string }{CSPNonce: nonce}) if err != nil { ctx.Error("An error occurred", 503) logging.Logger().Errorf("Unable to execute template: %v", err) + return } } diff --git a/internal/session/encrypting_serializer.go b/internal/session/encrypting_serializer.go index 68b57b69..1232ddca 100644 --- a/internal/session/encrypting_serializer.go +++ b/internal/session/encrypting_serializer.go @@ -46,6 +46,7 @@ func (e *EncryptingSerializer) Decode(dst *session.Dict, src []byte) error { } dst.Reset() + decryptedSrc, err := utils.Decrypt(src, &e.key) if err != nil { // If an error is thrown while decrypting, it's probably an old unencrypted session @@ -56,9 +57,11 @@ func (e *EncryptingSerializer) Decode(dst *session.Dict, src []byte) error { if uerr != nil { return fmt.Errorf("Unable to decrypt session: %s", err) } + return nil } _, err = dst.UnmarshalMsg(decryptedSrc) + return err } diff --git a/internal/session/provider.go b/internal/session/provider.go index 90af6643..21295210 100644 --- a/internal/session/provider.go +++ b/internal/session/provider.go @@ -29,18 +29,21 @@ func NewProvider(configuration schema.SessionConfiguration) *Provider { if err != nil { panic(err) } + provider.RememberMe = duration duration, err = utils.ParseDurationString(configuration.Inactivity) if err != nil { panic(err) } + provider.Inactivity = duration err = provider.sessionHolder.SetProvider(providerConfig.providerName, providerConfig.providerConfig) if err != nil { panic(err) } + return provider } @@ -59,6 +62,7 @@ func (p *Provider) GetSession(ctx *fasthttp.RequestCtx) (UserSession, error) { if !ok { userSession := NewDefaultUserSession() store.Set(userSessionStorerKey, userSession) + return userSession, nil } @@ -88,6 +92,7 @@ func (p *Provider) SaveSession(ctx *fasthttp.RequestCtx, userSession UserSession store.Set(userSessionStorerKey, userSessionJSON) p.sessionHolder.Save(ctx, store) + return nil } @@ -117,6 +122,7 @@ func (p *Provider) UpdateExpiration(ctx *fasthttp.RequestCtx, expiration time.Du } p.sessionHolder.Save(ctx, store) + return nil } diff --git a/internal/session/provider_config.go b/internal/session/provider_config.go index 7551b056..9ebfa1c6 100644 --- a/internal/session/provider_config.go +++ b/internal/session/provider_config.go @@ -32,6 +32,7 @@ func NewProviderConfig(configuration schema.SessionConfiguration) ProviderConfig } var providerConfig session.ProviderConfig + var providerName string // If redis configuration is provided, then use the redis provider. @@ -54,6 +55,7 @@ func NewProviderConfig(configuration schema.SessionConfiguration) ProviderConfig providerName = "memory" providerConfig = &memory.Config{} } + return ProviderConfig{ config: config, providerName: providerName, diff --git a/internal/storage/mysql_provider.go b/internal/storage/mysql_provider.go index 9646bd23..c9f32818 100644 --- a/internal/storage/mysql_provider.go +++ b/internal/storage/mysql_provider.go @@ -31,8 +31,8 @@ func NewMySQLProvider(configuration schema.MySQLStorageConfiguration) *MySQLProv if configuration.Port > 0 { address += fmt.Sprintf(":%d", configuration.Port) } - connectionString += fmt.Sprintf("tcp(%s)", address) + connectionString += fmt.Sprintf("tcp(%s)", address) if configuration.Database != "" { connectionString += fmt.Sprintf("/%s", configuration.Database) } @@ -71,5 +71,6 @@ func NewMySQLProvider(configuration schema.MySQLStorageConfiguration) *MySQLProv if err := provider.initialize(db); err != nil { logging.Logger().Fatalf("Unable to initialize SQL database: %v", err) } + return &provider } diff --git a/internal/storage/postgres_provider.go b/internal/storage/postgres_provider.go index ba766ccd..fc4ce2fb 100644 --- a/internal/storage/postgres_provider.go +++ b/internal/storage/postgres_provider.go @@ -80,5 +80,6 @@ func NewPostgreSQLProvider(configuration schema.PostgreSQLStorageConfiguration) if err := provider.initialize(db); err != nil { logging.Logger().Fatalf("Unable to initialize SQL database: %v", err) } + return &provider } diff --git a/internal/storage/sql_provider.go b/internal/storage/sql_provider.go index 993895c8..f71bcff6 100644 --- a/internal/storage/sql_provider.go +++ b/internal/storage/sql_provider.go @@ -75,21 +75,26 @@ func (p *SQLProvider) initialize(db *sql.DB) error { return fmt.Errorf("Unable to create table %s: %v", authenticationLogsTableName, err) } } + return nil } // LoadPreferred2FAMethod load the preferred method for 2FA from sqlite db. func (p *SQLProvider) LoadPreferred2FAMethod(username string) (string, error) { + var method string + rows, err := p.db.Query(p.sqlGetPreferencesByUsername, username) if err != nil { return "", err } defer rows.Close() + if !rows.Next() { return "", nil } - var method string + err = rows.Scan(&method) + return method, err } @@ -102,10 +107,12 @@ func (p *SQLProvider) SavePreferred2FAMethod(username string, method string) err // FindIdentityVerificationToken look for an identity verification token in DB. func (p *SQLProvider) FindIdentityVerificationToken(token string) (bool, error) { var found bool + err := p.db.QueryRow(p.sqlTestIdentityVerificationTokenExistence, token).Scan(&found) if err != nil { return false, err } + return found, nil } @@ -134,8 +141,10 @@ func (p *SQLProvider) LoadTOTPSecret(username string) (string, error) { if err == sql.ErrNoRows { return "", ErrNoTOTPSecret } + return "", err } + return secret, nil } @@ -151,6 +160,7 @@ func (p *SQLProvider) SaveU2FDeviceHandle(username string, keyHandle []byte, pub username, base64.StdEncoding.EncodeToString(keyHandle), base64.StdEncoding.EncodeToString(publicKey)) + return err } @@ -161,6 +171,7 @@ func (p *SQLProvider) LoadU2FDeviceHandle(username string) ([]byte, []byte, erro if err == sql.ErrNoRows { return nil, nil, ErrNoU2FDeviceHandle } + return nil, nil, err } @@ -187,6 +198,8 @@ func (p *SQLProvider) AppendAuthenticationLog(attempt models.AuthenticationAttem // LoadLatestAuthenticationLogs retrieve the latest marks from the authentication log. func (p *SQLProvider) LoadLatestAuthenticationLogs(username string, fromDate time.Time) ([]models.AuthenticationAttempt, error) { + var t int64 + rows, err := p.db.Query(p.sqlGetLatestAuthenticationLogs, fromDate.Unix(), username) if err != nil { @@ -194,18 +207,20 @@ func (p *SQLProvider) LoadLatestAuthenticationLogs(username string, fromDate tim } attempts := make([]models.AuthenticationAttempt, 0, 10) + for rows.Next() { attempt := models.AuthenticationAttempt{ Username: username, } - var t int64 err = rows.Scan(&attempt.Successful, &t) attempt.Time = time.Unix(t, 0) if err != nil { return nil, err } + attempts = append(attempts, attempt) } + return attempts, nil } diff --git a/internal/storage/sqlite_provider.go b/internal/storage/sqlite_provider.go index e568c56c..5f823c6a 100644 --- a/internal/storage/sqlite_provider.go +++ b/internal/storage/sqlite_provider.go @@ -51,5 +51,6 @@ func NewSQLiteProvider(path string) *SQLiteProvider { if err := provider.initialize(db); err != nil { logging.Logger().Fatalf("Unable to initialize SQLite database %s: %s", path, err) } + return &provider } diff --git a/internal/suites/action_http.go b/internal/suites/action_http.go index 7b0e33a2..3f10965b 100644 --- a/internal/suites/action_http.go +++ b/internal/suites/action_http.go @@ -19,5 +19,6 @@ func doHTTPGetQuery(t *testing.T, url string) []byte { defer resp.Body.Close() body, _ := ioutil.ReadAll(resp.Body) + return body } diff --git a/internal/suites/action_login.go b/internal/suites/action_login.go index ffca6406..399363ad 100644 --- a/internal/suites/action_login.go +++ b/internal/suites/action_login.go @@ -51,6 +51,7 @@ func (wds *WebDriverSession) doLoginAndRegisterTOTP(ctx context.Context, t *test secret := wds.doRegisterTOTP(ctx, t) wds.doVisit(t, LoginBaseURL) wds.verifyIsSecondFactorPage(ctx, t) + return secret } @@ -59,5 +60,6 @@ func (wds *WebDriverSession) doRegisterAndLogin2FA(ctx context.Context, t *testi // Register TOTP secret and logout. secret := wds.doRegisterThenLogout(ctx, t, username, password) wds.doLoginTwoFactor(ctx, t, username, password, keepMeLoggedIn, secret, targetURL) + return secret } diff --git a/internal/suites/action_mail.go b/internal/suites/action_mail.go index e2f8772b..e127fdbe 100644 --- a/internal/suites/action_mail.go +++ b/internal/suites/action_mail.go @@ -28,5 +28,6 @@ func doGetLinkFromLastMail(t *testing.T) string { matches := re.FindStringSubmatch(string(res)) assert.Len(t, matches, 2, "Number of match for link in email is not equal to one") + return matches[1] } diff --git a/internal/suites/action_register.go b/internal/suites/action_register.go index 4278e949..728e721d 100644 --- a/internal/suites/action_register.go +++ b/internal/suites/action_register.go @@ -8,5 +8,6 @@ import ( func (wds *WebDriverSession) doRegisterThenLogout(ctx context.Context, t *testing.T, username, password string) string { secret := wds.doLoginAndRegisterTOTP(ctx, t, username, password, false) wds.doLogout(ctx, t) + return secret } diff --git a/internal/suites/action_totp.go b/internal/suites/action_totp.go index 3d88c352..bfb4d71d 100644 --- a/internal/suites/action_totp.go +++ b/internal/suites/action_totp.go @@ -18,6 +18,7 @@ func (wds *WebDriverSession) doRegisterTOTP(ctx context.Context, t *testing.T) s assert.NoError(t, err) assert.NotEqual(t, "", secret) assert.NotNil(t, secret) + return secret } diff --git a/internal/suites/action_visit.go b/internal/suites/action_visit.go index e9be4f57..56edca00 100644 --- a/internal/suites/action_visit.go +++ b/internal/suites/action_visit.go @@ -23,5 +23,6 @@ func (wds *WebDriverSession) doVisitLoginPage(ctx context.Context, t *testing.T, if targetURL != "" { suffix = fmt.Sprintf("?rd=%s", targetURL) } + wds.doVisitAndVerifyOneFactorStep(ctx, t, fmt.Sprintf("%s/%s", LoginBaseURL, suffix)) } diff --git a/internal/suites/docker.go b/internal/suites/docker.go index eb6e78e4..5adf1dab 100644 --- a/internal/suites/docker.go +++ b/internal/suites/docker.go @@ -27,18 +27,21 @@ func NewDockerEnvironment(files []string) *DockerEnvironment { files[i] = strings.ReplaceAll(files[i], "{}", "dev") } } + return &DockerEnvironment{dockerComposeFiles: files} } func (de *DockerEnvironment) createCommandWithStdout(cmd string) *exec.Cmd { dockerCmdLine := fmt.Sprintf("docker-compose -p authelia -f %s %s", strings.Join(de.dockerComposeFiles, " -f "), cmd) log.Trace(dockerCmdLine) + return utils.CommandWithStdout("bash", "-c", dockerCmdLine) } func (de *DockerEnvironment) createCommand(cmd string) *exec.Cmd { dockerCmdLine := fmt.Sprintf("docker-compose -p authelia -f %s %s", strings.Join(de.dockerComposeFiles, " -f "), cmd) log.Trace(dockerCmdLine) + return utils.Command("bash", "-c", dockerCmdLine) } @@ -61,5 +64,6 @@ func (de *DockerEnvironment) Down() error { func (de *DockerEnvironment) Logs(service string, flags []string) (string, error) { cmd := de.createCommand(fmt.Sprintf("logs %s %s", strings.Join(flags, " "), service)) content, err := cmd.Output() + return string(content), err } diff --git a/internal/suites/environment.go b/internal/suites/environment.go index c47c6dc6..70498327 100644 --- a/internal/suites/environment.go +++ b/internal/suites/environment.go @@ -19,6 +19,7 @@ func waitUntilServiceLogDetected( service string, logPatterns []string) error { log.Debug("Waiting for service " + service + " to be ready...") + err := utils.CheckUntil(5*time.Second, 1*time.Minute, func() (bool, error) { logs, err := dockerEnvironment.Logs(service, []string{"--tail", "20"}) fmt.Printf(".") @@ -35,6 +36,7 @@ func waitUntilServiceLogDetected( }) fmt.Print("\n") + return err } @@ -68,6 +70,8 @@ func waitUntilAutheliaIsReady(dockerEnvironment *DockerEnvironment) error { return err } } + log.Info("Authelia is now ready!") + return nil } diff --git a/internal/suites/http.go b/internal/suites/http.go index a4f07e99..522562af 100644 --- a/internal/suites/http.go +++ b/internal/suites/http.go @@ -12,6 +12,7 @@ func NewHTTPClient() *http.Client { InsecureSkipVerify: true, //nolint:gosec // Needs to be enabled in suites. Not used in production. }, } + return &http.Client{ Transport: tr, CheckRedirect: func(req *http.Request, via []*http.Request) error { diff --git a/internal/suites/kubernetes.go b/internal/suites/kubernetes.go index af2085dd..5f154ba9 100644 --- a/internal/suites/kubernetes.go +++ b/internal/suites/kubernetes.go @@ -39,6 +39,7 @@ func (k Kind) CreateCluster() error { if err := cmd.Run(); err != nil { return err } + return nil } @@ -92,6 +93,7 @@ func (k Kubectl) StartDashboard() error { if err := utils.Shell("docker-compose -p authelia -f internal/suites/docker-compose.yml -f internal/suites/example/compose/kind/docker-compose.yml up -d kube-dashboard").Run(); err != nil { return err } + return nil } diff --git a/internal/suites/registry.go b/internal/suites/registry.go index 524eb7e5..de5c5db2 100644 --- a/internal/suites/registry.go +++ b/internal/suites/registry.go @@ -49,6 +49,7 @@ func (sr *Registry) Register(name string, suite Suite) { if _, found := sr.registry[name]; found { log.Fatal(fmt.Sprintf("Trying to register the suite %s multiple times", name)) } + sr.registry[name] = suite } @@ -58,6 +59,7 @@ func (sr *Registry) Get(name string) Suite { if !found { log.Fatal(fmt.Sprintf("The suite %s does not exist", name)) } + return s } @@ -67,5 +69,6 @@ func (sr *Registry) Suites() []string { for k := range sr.registry { suites = append(suites, k) } + return suites } diff --git a/internal/suites/scenario_available_methods_test.go b/internal/suites/scenario_available_methods_test.go index a0cacdc8..328cac3d 100644 --- a/internal/suites/scenario_available_methods_test.go +++ b/internal/suites/scenario_available_methods_test.go @@ -54,6 +54,7 @@ func IsStringInList(str string, list []string) bool { return true } } + return false } @@ -73,9 +74,11 @@ func (s *AvailableMethodsScenario) TestShouldCheckAvailableMethods() { s.Assert().Len(options, len(s.methods)) optionsList := make([]string, 0) + for _, o := range options { txt, err := o.Text() s.Assert().NoError(err) + optionsList = append(optionsList, txt) } diff --git a/internal/suites/scenario_inactivity_test.go b/internal/suites/scenario_inactivity_test.go index bfca5dd5..83ca6728 100644 --- a/internal/suites/scenario_inactivity_test.go +++ b/internal/suites/scenario_inactivity_test.go @@ -60,8 +60,8 @@ func (s *InactivityScenario) TestShouldRequireReauthenticationAfterInactivityPer defer cancel() targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL) - s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, s.secret, "") + s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, s.secret, "") s.doVisit(s.T(), HomeBaseURL) s.verifyIsHome(ctx, s.T()) @@ -76,6 +76,7 @@ func (s *InactivityScenario) TestShouldRequireReauthenticationAfterCookieExpirat defer cancel() targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL) + s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, s.secret, "") for i := 0; i < 3; i++ { @@ -83,6 +84,7 @@ func (s *InactivityScenario) TestShouldRequireReauthenticationAfterCookieExpirat s.verifyIsHome(ctx, s.T()) time.Sleep(2 * time.Second) + s.doVisit(s.T(), targetURL) s.verifySecretAuthorized(ctx, s.T()) } @@ -101,8 +103,8 @@ func (s *InactivityScenario) TestShouldDisableCookieExpirationAndInactivity() { defer cancel() targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL) - s.doLoginTwoFactor(ctx, s.T(), "john", "password", true, s.secret, "") + s.doLoginTwoFactor(ctx, s.T(), "john", "password", true, s.secret, "") s.doVisit(s.T(), HomeBaseURL) s.verifyIsHome(ctx, s.T()) diff --git a/internal/suites/scenario_two_factor_test.go b/internal/suites/scenario_two_factor_test.go index d00f620e..6a71c2bd 100644 --- a/internal/suites/scenario_two_factor_test.go +++ b/internal/suites/scenario_two_factor_test.go @@ -90,10 +90,10 @@ func (s *TwoFactorSuite) TestShouldFailTwoFactor() { s.doRegisterThenLogout(ctx, s.T(), testUsername, testPassword) wrongPasscode := "123456" + s.doLoginOneFactor(ctx, s.T(), testUsername, testPassword, false, "") s.verifyIsSecondFactorPage(ctx, s.T()) s.doEnterOTP(ctx, s.T(), wrongPasscode) - s.verifyNotificationDisplayed(ctx, s.T(), "The one-time password might be wrong") } diff --git a/internal/suites/suite_bypass_all.go b/internal/suites/suite_bypass_all.go index 94030a0b..3f1937d1 100644 --- a/internal/suites/suite_bypass_all.go +++ b/internal/suites/suite_bypass_all.go @@ -33,13 +33,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/suite_docker.go b/internal/suites/suite_docker.go index dd17ca74..170bd989 100644 --- a/internal/suites/suite_docker.go +++ b/internal/suites/suite_docker.go @@ -29,13 +29,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/suite_duo_push.go b/internal/suites/suite_duo_push.go index 115e32cc..f6a53f1e 100644 --- a/internal/suites/suite_duo_push.go +++ b/internal/suites/suite_duo_push.go @@ -31,13 +31,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/suite_haproxy.go b/internal/suites/suite_haproxy.go index df76888b..4fe94f01 100644 --- a/internal/suites/suite_haproxy.go +++ b/internal/suites/suite_haproxy.go @@ -33,13 +33,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/suite_high_availability.go b/internal/suites/suite_high_availability.go index 66d22aa6..df0faf41 100644 --- a/internal/suites/suite_high_availability.go +++ b/internal/suites/suite_high_availability.go @@ -36,13 +36,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := haDockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/suite_high_availability_test.go b/internal/suites/suite_high_availability_test.go index 683f48f1..a87dbc8d 100644 --- a/internal/suites/suite_high_availability_test.go +++ b/internal/suites/suite_high_availability_test.go @@ -137,6 +137,7 @@ func (s *HighAvailabilityWebDriverSuite) TestShouldVerifyAccessControl() { verifyUserIsAuthorized := func(ctx context.Context, t *testing.T, username, targetURL string, authorized bool) { //nolint:unparam s.doVisit(t, targetURL) s.verifyURLIs(ctx, t, targetURL) + if authorized { s.verifySecretAuthorized(ctx, t) } else { @@ -182,6 +183,7 @@ func DoGetWithAuth(t *testing.T, username, password string) int { res, err := client.Do(req) assert.NoError(t, err) + return res.StatusCode } diff --git a/internal/suites/suite_kubernetes.go b/internal/suites/suite_kubernetes.go index a6c40d4f..a7af84f3 100644 --- a/internal/suites/suite_kubernetes.go +++ b/internal/suites/suite_kubernetes.go @@ -44,6 +44,7 @@ func init() { } log.Debug("Building authelia:dist image or use cache if already built...") + if os.Getenv("CI") != stringTrue { if err := utils.Shell("authelia-scripts docker build").Run(); err != nil { return err @@ -51,45 +52,54 @@ func init() { } log.Debug("Loading images into Kubernetes container...") + if err := loadDockerImages(); err != nil { return err } log.Debug("Starting Kubernetes dashboard...") + if err := kubectl.StartDashboard(); err != nil { return err } log.Debug("Deploying thirdparties...") + if err := kubectl.DeployThirdparties(); err != nil { return err } log.Debug("Waiting for services to be ready...") + if err := waitAllPodsAreReady(5 * time.Minute); err != nil { return err } log.Debug("Deploying Authelia...") + if err = kubectl.DeployAuthelia(); err != nil { return err } log.Debug("Waiting for services to be ready...") + if err := waitAllPodsAreReady(2 * time.Minute); err != nil { return err } log.Debug("Starting proxy...") + if err := kubectl.StartProxy(); err != nil { return err } + return nil } teardown := func(suitePath string) error { kubectl.StopDashboard() //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. kubectl.StopProxy() //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. + return kind.DeleteCluster() } @@ -123,9 +133,12 @@ func waitAllPodsAreReady(timeout time.Duration) error { // Wait in case the deployment has just been done and some services do not appear in kubectl logs. time.Sleep(1 * time.Second) fmt.Println("Check services are running") + if err := kubectl.WaitPodsReady(timeout); err != nil { return err } + fmt.Println("All pods are ready") + return nil } diff --git a/internal/suites/suite_ldap.go b/internal/suites/suite_ldap.go index 736ff0e4..be2d2161 100644 --- a/internal/suites/suite_ldap.go +++ b/internal/suites/suite_ldap.go @@ -35,13 +35,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/suite_mariadb.go b/internal/suites/suite_mariadb.go index 004f0788..d837f4aa 100644 --- a/internal/suites/suite_mariadb.go +++ b/internal/suites/suite_mariadb.go @@ -33,13 +33,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/suite_mysql.go b/internal/suites/suite_mysql.go index c2557e8c..7f492264 100644 --- a/internal/suites/suite_mysql.go +++ b/internal/suites/suite_mysql.go @@ -33,13 +33,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/suite_network_acl.go b/internal/suites/suite_network_acl.go index 6101be07..a4cbf652 100644 --- a/internal/suites/suite_network_acl.go +++ b/internal/suites/suite_network_acl.go @@ -34,13 +34,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/suite_network_acl_test.go b/internal/suites/suite_network_acl_test.go index 5626e0f2..a9e3e817 100644 --- a/internal/suites/suite_network_acl_test.go +++ b/internal/suites/suite_network_acl_test.go @@ -23,6 +23,7 @@ func (s *NetworkACLSuite) TestShouldAccessSecretUpon2FA() { wds, err := StartWebDriver() s.Require().NoError(err) + defer wds.Stop() //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. targetURL := fmt.Sprintf("%s/secret.html", SecureBaseURL) @@ -40,6 +41,7 @@ func (s *NetworkACLSuite) TestShouldAccessSecretUpon1FA() { wds, err := StartWebDriverWithProxy("http://proxy-client1.example.com:3128", 4444) s.Require().NoError(err) + defer wds.Stop() //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. targetURL := fmt.Sprintf("%s/secret.html", SecureBaseURL) @@ -58,6 +60,7 @@ func (s *NetworkACLSuite) TestShouldAccessSecretUpon0FA() { wds, err := StartWebDriverWithProxy("http://proxy-client2.example.com:3128", 4444) s.Require().NoError(err) + defer wds.Stop() //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. wds.doVisit(s.T(), fmt.Sprintf("%s/secret.html", SecureBaseURL)) diff --git a/internal/suites/suite_one_factor_only.go b/internal/suites/suite_one_factor_only.go index 42cf856a..23fdf6a2 100644 --- a/internal/suites/suite_one_factor_only.go +++ b/internal/suites/suite_one_factor_only.go @@ -30,13 +30,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/suite_postgres.go b/internal/suites/suite_postgres.go index 65e7d472..50e19e26 100644 --- a/internal/suites/suite_postgres.go +++ b/internal/suites/suite_postgres.go @@ -33,13 +33,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/suite_short_timeouts.go b/internal/suites/suite_short_timeouts.go index fc12ed86..7088d234 100644 --- a/internal/suites/suite_short_timeouts.go +++ b/internal/suites/suite_short_timeouts.go @@ -31,13 +31,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/suite_standalone.go b/internal/suites/suite_standalone.go index 783f5eba..95d729f6 100644 --- a/internal/suites/suite_standalone.go +++ b/internal/suites/suite_standalone.go @@ -33,13 +33,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/suite_traefik.go b/internal/suites/suite_traefik.go index 1fd5655b..99cb4606 100644 --- a/internal/suites/suite_traefik.go +++ b/internal/suites/suite_traefik.go @@ -33,13 +33,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/suite_traefik2.go b/internal/suites/suite_traefik2.go index ae574e96..85e5ce0d 100644 --- a/internal/suites/suite_traefik2.go +++ b/internal/suites/suite_traefik2.go @@ -33,13 +33,16 @@ func init() { if err != nil { return err } + fmt.Println(backendLogs) frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil) if err != nil { return err } + fmt.Println(frontendLogs) + return nil } diff --git a/internal/suites/webdriver.go b/internal/suites/webdriver.go index 52834cb0..54fdce6f 100644 --- a/internal/suites/webdriver.go +++ b/internal/suites/webdriver.go @@ -94,6 +94,7 @@ func WithWebdriver(fn func(webdriver selenium.WebDriver) error) error { // Wait wait until condition holds true. func (wds *WebDriverSession) Wait(ctx context.Context, condition selenium.Condition) error { done := make(chan error, 1) + go func() { done <- wds.WebDriver.Wait(condition) }() @@ -108,6 +109,7 @@ func (wds *WebDriverSession) Wait(ctx context.Context, condition selenium.Condit func (wds *WebDriverSession) waitElementLocated(ctx context.Context, t *testing.T, by, value string) selenium.WebElement { var el selenium.WebElement + err := wds.Wait(ctx, func(driver selenium.WebDriver) (bool, error) { var err error el, err = driver.FindElement(by, value) @@ -124,11 +126,13 @@ func (wds *WebDriverSession) waitElementLocated(ctx context.Context, t *testing. require.NoError(t, err) require.NotNil(t, el) + return el } func (wds *WebDriverSession) waitElementsLocated(ctx context.Context, t *testing.T, by, value string) []selenium.WebElement { var el []selenium.WebElement + err := wds.Wait(ctx, func(driver selenium.WebDriver) (bool, error) { var err error el, err = driver.FindElements(by, value) @@ -145,6 +149,7 @@ func (wds *WebDriverSession) waitElementsLocated(ctx context.Context, t *testing require.NoError(t, err) require.NotNil(t, el) + return el } diff --git a/internal/utils/aes.go b/internal/utils/aes.go index 2cfb6481..3fdfcc3f 100644 --- a/internal/utils/aes.go +++ b/internal/utils/aes.go @@ -26,6 +26,7 @@ func Encrypt(plaintext []byte, key *[32]byte) (ciphertext []byte, err error) { } nonce := make([]byte, gcm.NonceSize()) + _, err = io.ReadFull(rand.Reader, nonce) if err != nil { return nil, err diff --git a/internal/utils/exec.go b/internal/utils/exec.go index 1f1fad26..ce48c4b4 100644 --- a/internal/utils/exec.go +++ b/internal/utils/exec.go @@ -24,7 +24,9 @@ func Command(name string, args ...string) *exec.Cmd { for !strings.HasSuffix(wd, "authelia") { wd = filepath.Dir(wd) } + cmd.Dir = wd + return cmd } @@ -33,6 +35,7 @@ func CommandWithStdout(name string, args ...string) *exec.Cmd { cmd := Command(name, args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr + return cmd } @@ -52,6 +55,7 @@ func RunCommandUntilCtrlC(cmd *exec.Cmd) { go func() { mutex.Lock() + f := bufio.NewWriter(os.Stdout) defer f.Flush() @@ -63,6 +67,7 @@ func RunCommandUntilCtrlC(cmd *exec.Cmd) { fmt.Println(err) cond.Broadcast() mutex.Unlock() + return } @@ -86,6 +91,7 @@ func RunFuncUntilCtrlC(fn func() error) error { go func() { mutex.Lock() + f := bufio.NewWriter(os.Stdout) defer f.Flush() @@ -98,16 +104,19 @@ func RunFuncUntilCtrlC(fn func() error) error { fmt.Println(err) cond.Broadcast() mutex.Unlock() + return } errorChannel <- nil + <-signalChannel cond.Broadcast() mutex.Unlock() }() cond.Wait() + return <-errorChannel } @@ -120,6 +129,7 @@ func RunCommandWithTimeout(cmd *exec.Cmd, timeout time.Duration) error { // Wait for the process to finish or kill it after a timeout (whichever happens first): done := make(chan error, 1) + go func() { done <- cmd.Wait() }() @@ -131,6 +141,7 @@ func RunCommandWithTimeout(cmd *exec.Cmd, timeout time.Duration) error { if err := cmd.Process.Kill(); err != nil { return err } + return ErrTimeoutReached case err := <-done: return err diff --git a/internal/utils/files.go b/internal/utils/files.go index 841c6ca7..a70d11c0 100644 --- a/internal/utils/files.go +++ b/internal/utils/files.go @@ -10,8 +10,10 @@ func FileExists(path string) (bool, error) { if err == nil { return true, nil } + if os.IsNotExist(err) { return false, nil } + return true, err } diff --git a/internal/utils/safe_redirection.go b/internal/utils/safe_redirection.go index 37b3e48c..d161c8b3 100644 --- a/internal/utils/safe_redirection.go +++ b/internal/utils/safe_redirection.go @@ -14,5 +14,6 @@ func IsRedirectionSafe(url url.URL, protectedDomain string) bool { if !strings.HasSuffix(url.Hostname(), protectedDomain) { return false } + return true } diff --git a/internal/utils/strings.go b/internal/utils/strings.go index d1cb06d6..9ee77b15 100644 --- a/internal/utils/strings.go +++ b/internal/utils/strings.go @@ -12,6 +12,7 @@ func IsStringInSlice(a string, list []string) (inSlice bool) { return true } } + return false } @@ -21,12 +22,14 @@ func SliceString(s string, d int) (array []string) { n := len(s) q := n / d r := n % d + for i := 0; i < q; i++ { array = append(array, s[i*d:i*d+d]) if i+1 == q && r != 0 { array = append(array, s[i*d+d:]) } } + return } @@ -38,11 +41,13 @@ func IsStringSlicesDifferent(a, b []string) (different bool) { return true } } + for _, s := range b { if !IsStringInSlice(s, a) { return true } } + return false } @@ -53,20 +58,24 @@ func StringSlicesDelta(before, after []string) (added, removed []string) { removed = append(removed, s) } } + for _, s := range after { if !IsStringInSlice(s, before) { added = append(added, s) } } + return added, removed } // RandomString generate a random string of n characters. func RandomString(n int, characters []rune) (randomString string) { rand.Seed(time.Now().UnixNano()) + b := make([]rune, n) for i := range b { b[i] = characters[rand.Intn(len(characters))] } + return string(b) } diff --git a/internal/utils/time.go b/internal/utils/time.go index 26a819e6..b1f0c124 100644 --- a/internal/utils/time.go +++ b/internal/utils/time.go @@ -12,9 +12,11 @@ import ( // Example 1y is the same as 1 year. func ParseDurationString(input string) (time.Duration, error) { var duration time.Duration + matches := parseDurationRegexp.FindStringSubmatch(input) if len(matches) == 3 && matches[2] != "" { d, _ := strconv.Atoi(matches[1]) + switch matches[2] { case "y": duration = time.Duration(d) * Year @@ -41,5 +43,6 @@ func ParseDurationString(input string) (time.Duration, error) { // Throw this error if input is anything other than a blank string, blank string will default to a duration of nothing return 0, fmt.Errorf("Could not convert the input string of %s into a duration", input) } + return duration, nil }