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 a943edee4..49b0d229c 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 4aac50763..33c3448e2 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 8e6b98c28..9c955d6e1 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 92f7f90cb..da9e8572c 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 9a9bb2b64..1d9e99421 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 ae4e231d7..aa2842855 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 f09947dd6..df4368ba6 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 d81f8a15d..44905e3f4 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 66f9fac8b..5e64e5a20 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 6b740c347..0b49a8921 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 bb6d41d0b..bbfa9da90 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 bf5df2fd3..7d635b4bb 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 eebbdace9..9d8baf0dd 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 9586147f0..c2e87aea1 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 b169cb465..9017cee00 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 e138c3c24..5395afdf3 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 e4a11f390..c34c8249d 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 c5814a537..ec8822417 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 b2b3b11af..ea90c8e75 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 5161dc884..d47093b95 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 67ac099f4..fa50684af 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 f963fe55a..039e902a9 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 75a716c48..e2fe399b3 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 c69bd37ff..bd290b91e 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 8e0bc09df..f410ff2e2 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 fe3b2cd73..3d3dc9af4 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 78bc39a29..bc20effcf 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 d5b156ce6..4ec5a2c3b 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 8227506aa..ccb455f1a 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 1dbf2989a..037d386a9 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 66af45363..9481a548c 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 830c6b849..2478edf34 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 68c94a44e..cbf91b89e 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 3357fa639..c95759f4e 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 67979b826..7bf80806c 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 e109a6f31..b974b0758 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 54561d4f7..1ae05b558 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 9476fa3fb..736cba804 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 fd7d470f6..3887f2e3e 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 98624e7a0..6962ce9a8 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 fe91a657c..c06181af0 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 abeb74b47..640298961 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 7f6dc4d3b..7066c6402 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 a3e64f1b1..4ee4afb76 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 512a60d3f..a7a81b8b8 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 0119f6703..25d4961cc 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 a8bfacfd9..fe4e7c2a3 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 034da4275..1638e8423 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 0438d53aa..8746e156d 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 6a92ca874..24e3e052d 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 7afa86961..c4f8a2274 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 c56cd7f86..cebcd3bf6 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 4634eed27..1c0069934 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 4b521efb6..ee32b986b 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 05d412d6b..9f9aa1259 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 8115a29be..9a54d681a 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 0cf307b3e..f3a00c9cf 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 90f720318..4251178d3 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 f694d8f4c..e6f22e582 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 2a7069dfe..2056ef33c 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 6495f0ba0..0db06fc01 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 68b57b690..1232ddca6 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 90af6643e..212952104 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 7551b0567..9ebfa1c65 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 9646bd23f..c9f328181 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 ba766ccdd..fc4ce2fb6 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 993895c8a..f71bcff6d 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 e568c56cc..5f823c6aa 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 7b0e33a2e..3f10965bf 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 ffca64066..399363ad4 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 e2f8772b6..e127fdbea 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 4278e9492..728e721d1 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 3d88c3527..bfb4d71de 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 e9be4f574..56edca00b 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 eb6e78e48..5adf1dab6 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 c47c6dc64..70498327b 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 a4f07e99f..522562afb 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 af2085dde..5f154ba96 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 524eb7e5f..de5c5db21 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 a0cacdc81..328cac3d8 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 bfca5dd5e..83ca67283 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 d00f620ec..6a71c2bd0 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 94030a0ba..3f1937d18 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 dd17ca74b..170bd989c 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 115e32cca..f6a53f1e0 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 df76888b1..4fe94f01e 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 66d22aa63..df0faf414 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 683f48f10..a87dbc8d1 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 a6c40d4f7..a7af84f36 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 736ff0e4e..be2d21610 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 004f07881..d837f4aa7 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 c2557e8ce..7f4922646 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 6101be075..a4cbf6523 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 5626e0f2e..a9e3e8174 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 42cf856a4..23fdf6a29 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 65e7d4728..50e19e263 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 fc12ed863..7088d234a 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 783f5eba0..95d729f68 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 1fd5655ba..99cb46067 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 ae574e96a..85e5ce0d3 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 52834cb09..54fdce6fd 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 2cfb64811..3fdfcc3fb 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 1f1fad265..ce48c4b49 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 841c6ca78..a70d11c04 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 37b3e48c0..d161c8b36 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 d1cb06d62..9ee77b15c 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 26a819e6c..b1f0c1241 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 }