[Buildkite] Introduce CI linting with golangci-lint and reviewdog (#832)
* [Buildkite] Introduce CI linting with golangci-lint and reviewdog * Initial pass of golangci-lint * Add gosimple (megacheck) recommendations * Add golint recommendations * [BUGFIX] Migrate authentication traces from v3 mongodb * Add deadcode recommendations * [BUGFIX] Fix ShortTimeouts suite when run in dev workflow * Add unused recommendations * Add unparam recommendations * Disable linting on unfixable errors instead of skipping files * Adjust nolint notation for unparam * Fix ineffectual assignment to err raised by linter. * Export environment variable in agent hook * Add ineffassign recommendations * Add staticcheck recommendations * Add gocyclo recommendations * Adjust ineffassign recommendations Co-authored-by: Clement Michaud <clement.michaud34@gmail.com>pull/839/head
parent
2fed503e5e
commit
de2c5836fd
|
@ -2,6 +2,13 @@
|
||||||
|
|
||||||
set +u
|
set +u
|
||||||
|
|
||||||
|
if [[ $BUILDKITE_PULL_REQUEST != "false" ]]; then
|
||||||
|
if [[ $BUILDKITE_LABEL == ":hammer_and_wrench: Unit Test" ]]; then
|
||||||
|
echo "--- :go::service_dog: Linting pull request"
|
||||||
|
reviewdog -reporter=github-pr-review
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ $BUILDKITE_LABEL =~ ":selenium:" ]] || [[ $BUILDKITE_LABEL =~ ":docker: Build Image" ]]; then
|
if [[ $BUILDKITE_LABEL =~ ":selenium:" ]] || [[ $BUILDKITE_LABEL =~ ":docker: Build Image" ]]; then
|
||||||
CONTAINERS=$(docker ps -a -q)
|
CONTAINERS=$(docker ps -a -q)
|
||||||
if [[ ${CONTAINERS} != "" ]]; then
|
if [[ ${CONTAINERS} != "" ]]; then
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
run:
|
||||||
|
timeout: 3m
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
gocyclo:
|
||||||
|
min-complexity: 15
|
||||||
|
goimports:
|
||||||
|
local-prefixes: github.com/authelia/authelia
|
||||||
|
|
||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
- gocyclo
|
||||||
|
- gofmt
|
||||||
|
- goimports
|
||||||
|
- golint
|
||||||
|
- interfacer
|
||||||
|
- maligned
|
||||||
|
- misspell
|
||||||
|
- prealloc
|
||||||
|
- unparam
|
||||||
|
- whitespace
|
||||||
|
|
||||||
|
issues:
|
||||||
|
max-issues-per-linter: 0
|
||||||
|
max-same-issues: 0
|
|
@ -0,0 +1,8 @@
|
||||||
|
runner:
|
||||||
|
golangci:
|
||||||
|
cmd: golangci-lint run
|
||||||
|
errorformat:
|
||||||
|
- '%E%f:%l:%c: %m'
|
||||||
|
- '%E%f:%l: %m'
|
||||||
|
- '%C%.%#'
|
||||||
|
level: warning
|
|
@ -21,33 +21,33 @@ type HostEntry struct {
|
||||||
|
|
||||||
var hostEntries = []HostEntry{
|
var hostEntries = []HostEntry{
|
||||||
// For authelia backend
|
// For authelia backend
|
||||||
HostEntry{Domain: "authelia.example.com", IP: "192.168.240.50"},
|
{Domain: "authelia.example.com", IP: "192.168.240.50"},
|
||||||
// For common tests
|
// For common tests
|
||||||
HostEntry{Domain: "login.example.com", IP: "192.168.240.100"},
|
{Domain: "login.example.com", IP: "192.168.240.100"},
|
||||||
HostEntry{Domain: "admin.example.com", IP: "192.168.240.100"},
|
{Domain: "admin.example.com", IP: "192.168.240.100"},
|
||||||
HostEntry{Domain: "singlefactor.example.com", IP: "192.168.240.100"},
|
{Domain: "singlefactor.example.com", IP: "192.168.240.100"},
|
||||||
HostEntry{Domain: "dev.example.com", IP: "192.168.240.100"},
|
{Domain: "dev.example.com", IP: "192.168.240.100"},
|
||||||
HostEntry{Domain: "home.example.com", IP: "192.168.240.100"},
|
{Domain: "home.example.com", IP: "192.168.240.100"},
|
||||||
HostEntry{Domain: "mx1.mail.example.com", IP: "192.168.240.100"},
|
{Domain: "mx1.mail.example.com", IP: "192.168.240.100"},
|
||||||
HostEntry{Domain: "mx2.mail.example.com", IP: "192.168.240.100"},
|
{Domain: "mx2.mail.example.com", IP: "192.168.240.100"},
|
||||||
HostEntry{Domain: "public.example.com", IP: "192.168.240.100"},
|
{Domain: "public.example.com", IP: "192.168.240.100"},
|
||||||
HostEntry{Domain: "secure.example.com", IP: "192.168.240.100"},
|
{Domain: "secure.example.com", IP: "192.168.240.100"},
|
||||||
HostEntry{Domain: "mail.example.com", IP: "192.168.240.100"},
|
{Domain: "mail.example.com", IP: "192.168.240.100"},
|
||||||
HostEntry{Domain: "duo.example.com", IP: "192.168.240.100"},
|
{Domain: "duo.example.com", IP: "192.168.240.100"},
|
||||||
|
|
||||||
// For Traefik suite
|
// For Traefik suite
|
||||||
HostEntry{Domain: "traefik.example.com", IP: "192.168.240.100"},
|
{Domain: "traefik.example.com", IP: "192.168.240.100"},
|
||||||
|
|
||||||
// For HAProxy suite
|
// For HAProxy suite
|
||||||
HostEntry{Domain: "haproxy.example.com", IP: "192.168.240.100"},
|
{Domain: "haproxy.example.com", IP: "192.168.240.100"},
|
||||||
|
|
||||||
// For testing network ACLs
|
// For testing network ACLs
|
||||||
HostEntry{Domain: "proxy-client1.example.com", IP: "192.168.240.201"},
|
{Domain: "proxy-client1.example.com", IP: "192.168.240.201"},
|
||||||
HostEntry{Domain: "proxy-client2.example.com", IP: "192.168.240.202"},
|
{Domain: "proxy-client2.example.com", IP: "192.168.240.202"},
|
||||||
HostEntry{Domain: "proxy-client3.example.com", IP: "192.168.240.203"},
|
{Domain: "proxy-client3.example.com", IP: "192.168.240.203"},
|
||||||
|
|
||||||
// Kubernetes dashboard
|
// Kubernetes dashboard
|
||||||
HostEntry{Domain: "kubernetes.example.com", IP: "192.168.240.110"},
|
{Domain: "kubernetes.example.com", IP: "192.168.240.110"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCommand(cmd string, args ...string) {
|
func runCommand(cmd string, args ...string) {
|
||||||
|
@ -71,16 +71,6 @@ func checkCommandExist(cmd string) {
|
||||||
fmt.Println(" OK")
|
fmt.Println(" OK")
|
||||||
}
|
}
|
||||||
|
|
||||||
func installClientNpmPackages() {
|
|
||||||
command := utils.CommandWithStdout("yarn", "install")
|
|
||||||
command.Dir = "client"
|
|
||||||
err := command.Run()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func createTemporaryDirectory() {
|
func createTemporaryDirectory() {
|
||||||
err := os.MkdirAll("/tmp/authelia", 0755)
|
err := os.MkdirAll("/tmp/authelia", 0755)
|
||||||
|
|
||||||
|
|
|
@ -7,16 +7,6 @@ import (
|
||||||
"github.com/authelia/authelia/internal/utils"
|
"github.com/authelia/authelia/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const dockerPullCommandLine = "docker-compose -p authelia -f internal/suites/docker-compose.yml " +
|
|
||||||
"-f internal/suites/example/compose/mariadb/docker-compose.yml " +
|
|
||||||
"-f internal/suites/example/compose/redis/docker-compose.yml " +
|
|
||||||
"-f internal/suites/example/compose/nginx/portal/docker-compose.yml " +
|
|
||||||
"-f internal/suites/example/compose/smtp/docker-compose.yml " +
|
|
||||||
"-f internal/suites/example/compose/httpbin/docker-compose.yml " +
|
|
||||||
"-f internal/suites/example/compose/ldap/docker-compose.admin.yml " +
|
|
||||||
"-f internal/suites/example/compose/ldap/docker-compose.yml " +
|
|
||||||
"pull"
|
|
||||||
|
|
||||||
// RunCI run the CI scripts
|
// RunCI run the CI scripts
|
||||||
func RunCI(cmd *cobra.Command, args []string) {
|
func RunCI(cmd *cobra.Command, args []string) {
|
||||||
log.Info("=====> Build stage <=====")
|
log.Info("=====> Build stage <=====")
|
||||||
|
|
|
@ -106,9 +106,7 @@ var SuitesTestCmd = &cobra.Command{
|
||||||
|
|
||||||
func listSuites() []string {
|
func listSuites() []string {
|
||||||
suiteNames := make([]string, 0)
|
suiteNames := make([]string, 0)
|
||||||
for _, k := range suites.GlobalRegistry.Suites() {
|
suiteNames = append(suiteNames, suites.GlobalRegistry.Suites()...)
|
||||||
suiteNames = append(suiteNames, k)
|
|
||||||
}
|
|
||||||
sort.Strings(suiteNames)
|
sort.Strings(suiteNames)
|
||||||
return suiteNames
|
return suiteNames
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,35 +28,35 @@ type CobraCommands = []*cobra.Command
|
||||||
|
|
||||||
// Commands is the list of commands of authelia-scripts
|
// Commands is the list of commands of authelia-scripts
|
||||||
var Commands = []AutheliaCommandDefinition{
|
var Commands = []AutheliaCommandDefinition{
|
||||||
AutheliaCommandDefinition{
|
{
|
||||||
Name: "bootstrap",
|
Name: "bootstrap",
|
||||||
Short: "Prepare environment for development and testing.",
|
Short: "Prepare environment for development and testing.",
|
||||||
Long: `Prepare environment for development and testing. This command prepares docker
|
Long: `Prepare environment for development and testing. This command prepares docker
|
||||||
images and download tools like Kind for Kubernetes testing.`,
|
images and download tools like Kind for Kubernetes testing.`,
|
||||||
Func: Bootstrap,
|
Func: Bootstrap,
|
||||||
},
|
},
|
||||||
AutheliaCommandDefinition{
|
{
|
||||||
Name: "build",
|
Name: "build",
|
||||||
Short: "Build Authelia binary and static assets",
|
Short: "Build Authelia binary and static assets",
|
||||||
Func: Build,
|
Func: Build,
|
||||||
},
|
},
|
||||||
AutheliaCommandDefinition{
|
{
|
||||||
Name: "clean",
|
Name: "clean",
|
||||||
Short: "Clean build artifacts",
|
Short: "Clean build artifacts",
|
||||||
Func: Clean,
|
Func: Clean,
|
||||||
},
|
},
|
||||||
AutheliaCommandDefinition{
|
{
|
||||||
Name: "docker",
|
Name: "docker",
|
||||||
Short: "Commands related to building and publishing docker image",
|
Short: "Commands related to building and publishing docker image",
|
||||||
SubCommands: CobraCommands{DockerBuildCmd, DockerPushCmd, DockerManifestCmd},
|
SubCommands: CobraCommands{DockerBuildCmd, DockerPushCmd, DockerManifestCmd},
|
||||||
},
|
},
|
||||||
AutheliaCommandDefinition{
|
{
|
||||||
Name: "serve [config]",
|
Name: "serve [config]",
|
||||||
Short: "Serve compiled version of Authelia",
|
Short: "Serve compiled version of Authelia",
|
||||||
Func: ServeCmd,
|
Func: ServeCmd,
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
},
|
},
|
||||||
AutheliaCommandDefinition{
|
{
|
||||||
Name: "suites",
|
Name: "suites",
|
||||||
Short: "Compute hash of a password for creating a file-based users database",
|
Short: "Compute hash of a password for creating a file-based users database",
|
||||||
SubCommands: CobraCommands{
|
SubCommands: CobraCommands{
|
||||||
|
@ -66,12 +66,12 @@ var Commands = []AutheliaCommandDefinition{
|
||||||
SuitesTeardownCmd,
|
SuitesTeardownCmd,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
AutheliaCommandDefinition{
|
{
|
||||||
Name: "ci",
|
Name: "ci",
|
||||||
Short: "Run continuous integration script",
|
Short: "Run continuous integration script",
|
||||||
Func: RunCI,
|
Func: RunCI,
|
||||||
},
|
},
|
||||||
AutheliaCommandDefinition{
|
{
|
||||||
Name: "unittest",
|
Name: "unittest",
|
||||||
Short: "Run unit tests",
|
Short: "Run unit tests",
|
||||||
Func: RunUnitTest,
|
Func: RunUnitTest,
|
||||||
|
|
|
@ -25,6 +25,7 @@ import (
|
||||||
|
|
||||||
var configPathFlag string
|
var configPathFlag string
|
||||||
|
|
||||||
|
//nolint:gocyclo // TODO: Consider refactoring/simplifying, time permitting
|
||||||
func startServer() {
|
func startServer() {
|
||||||
if configPathFlag == "" {
|
if configPathFlag == "" {
|
||||||
log.Fatal(errors.New("No config file path provided"))
|
log.Fatal(errors.New("No config file path provided"))
|
||||||
|
@ -47,7 +48,6 @@ func startServer() {
|
||||||
case "info":
|
case "info":
|
||||||
logging.Logger().Info("Logging severity set to info")
|
logging.Logger().Info("Logging severity set to info")
|
||||||
logging.SetLevel(logrus.InfoLevel)
|
logging.SetLevel(logrus.InfoLevel)
|
||||||
break
|
|
||||||
case "debug":
|
case "debug":
|
||||||
logging.Logger().Info("Logging severity set to debug")
|
logging.Logger().Info("Logging severity set to debug")
|
||||||
logging.SetLevel(logrus.DebugLevel)
|
logging.SetLevel(logrus.DebugLevel)
|
||||||
|
|
|
@ -4,11 +4,11 @@ package authentication
|
||||||
type Level int
|
type Level int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// NotAuthenticated if the user is not authenticated yet.
|
// NotAuthenticated if the user is not authenticated yet
|
||||||
NotAuthenticated Level = iota
|
NotAuthenticated Level = iota
|
||||||
// OneFactor if the user has passed first factor only.
|
// OneFactor if the user has passed first factor only
|
||||||
OneFactor Level = iota
|
OneFactor Level = iota
|
||||||
// TwoFactor if the user has passed two factors.
|
// TwoFactor if the user has passed two factors
|
||||||
TwoFactor Level = iota
|
TwoFactor Level = iota
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,11 +17,11 @@ const (
|
||||||
TOTP = "totp"
|
TOTP = "totp"
|
||||||
// U2F Method using U2F devices like Yubikeys
|
// U2F Method using U2F devices like Yubikeys
|
||||||
U2F = "u2f"
|
U2F = "u2f"
|
||||||
// Push Method using Duo application to receive push notifications.
|
// Push Method using Duo application to receive push notifications
|
||||||
Push = "mobile_push"
|
Push = "mobile_push"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PossibleMethods is the set of all possible 2FA methods.
|
// PossibleMethods is the set of all possible 2FA methods
|
||||||
var PossibleMethods = []string{TOTP, U2F, Push}
|
var PossibleMethods = []string{TOTP, U2F, Push}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -40,5 +40,5 @@ const (
|
||||||
HashingDefaultSHA512Iterations = 5000
|
HashingDefaultSHA512Iterations = 5000
|
||||||
)
|
)
|
||||||
|
|
||||||
// Valid Hashing runes
|
// HashingPossibleSaltCharacters represents valid hashing runes
|
||||||
var HashingPossibleSaltCharacters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/")
|
var HashingPossibleSaltCharacters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/")
|
||||||
|
|
|
@ -172,7 +172,7 @@ func TestShouldRaiseWhenLoadingDatabaseWithBadSHA512HashesForTheFirstTime(t *tes
|
||||||
WithDatabase(BadSHA512HashContent, func(path string) {
|
WithDatabase(BadSHA512HashContent, func(path string) {
|
||||||
config := DefaultFileAuthenticationBackendConfiguration
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
config.Path = path
|
config.Path = path
|
||||||
assert.PanicsWithValue(t, "Unable to parse hash of user john: Hash key is not the last parameter, the hash is likely malformed ($6$rounds00000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/).", func() {
|
assert.PanicsWithValue(t, "Unable to parse hash of user john: Hash key is not the last parameter, the hash is likely malformed ($6$rounds00000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/)", func() {
|
||||||
NewFileUserProvider(&config)
|
NewFileUserProvider(&config)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -182,7 +182,7 @@ func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashSettingsForTheFirstTim
|
||||||
WithDatabase(BadArgon2idHashSettingsContent, func(path string) {
|
WithDatabase(BadArgon2idHashSettingsContent, func(path string) {
|
||||||
config := DefaultFileAuthenticationBackendConfiguration
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
config.Path = path
|
config.Path = path
|
||||||
assert.PanicsWithValue(t, "Unable to parse hash of user john: Hash key is not the last parameter, the hash is likely malformed ($argon2id$v=19$m65536,t3,p2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM).", func() {
|
assert.PanicsWithValue(t, "Unable to parse hash of user john: Hash key is not the last parameter, the hash is likely malformed ($argon2id$v=19$m65536,t3,p2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM)", func() {
|
||||||
NewFileUserProvider(&config)
|
NewFileUserProvider(&config)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -192,7 +192,7 @@ func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashKeyForTheFirstTime(t *
|
||||||
WithDatabase(BadArgon2idHashKeyContent, func(path string) {
|
WithDatabase(BadArgon2idHashKeyContent, func(path string) {
|
||||||
config := DefaultFileAuthenticationBackendConfiguration
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
config.Path = path
|
config.Path = path
|
||||||
assert.PanicsWithValue(t, "Unable to parse hash of user john: Hash key contains invalid base64 characters.", func() {
|
assert.PanicsWithValue(t, "Unable to parse hash of user john: Hash key contains invalid base64 characters", func() {
|
||||||
NewFileUserProvider(&config)
|
NewFileUserProvider(&config)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -202,7 +202,7 @@ func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashSaltForTheFirstTime(t
|
||||||
WithDatabase(BadArgon2idHashSaltContent, func(path string) {
|
WithDatabase(BadArgon2idHashSaltContent, func(path string) {
|
||||||
config := DefaultFileAuthenticationBackendConfiguration
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
config.Path = path
|
config.Path = path
|
||||||
assert.PanicsWithValue(t, "Unable to parse hash of user john: Salt contains invalid base64 characters.", func() {
|
assert.PanicsWithValue(t, "Unable to parse hash of user john: Salt contains invalid base64 characters", func() {
|
||||||
NewFileUserProvider(&config)
|
NewFileUserProvider(&config)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -178,7 +178,7 @@ func (p *LDAPUserProvider) getUserProfile(conn LDAPConnection, inputUsername str
|
||||||
return &userProfile, nil
|
return &userProfile, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *LDAPUserProvider) resolveGroupsFilter(inputUsername string, profile *ldapUserProfile) (string, error) {
|
func (p *LDAPUserProvider) resolveGroupsFilter(inputUsername string, profile *ldapUserProfile) (string, error) { //nolint:unparam
|
||||||
inputUsername = p.ldapEscape(inputUsername)
|
inputUsername = p.ldapEscape(inputUsername)
|
||||||
|
|
||||||
// We temporarily keep placeholder {0} for backward compatibility
|
// We temporarily keep placeholder {0} for backward compatibility
|
||||||
|
|
|
@ -180,7 +180,7 @@ func TestShouldCombineUsernameFilterAndUsersFilter(t *testing.T) {
|
||||||
func createSearchResultWithAttributes(attributes ...*ldap.EntryAttribute) *ldap.SearchResult {
|
func createSearchResultWithAttributes(attributes ...*ldap.EntryAttribute) *ldap.SearchResult {
|
||||||
return &ldap.SearchResult{
|
return &ldap.SearchResult{
|
||||||
Entries: []*ldap.Entry{
|
Entries: []*ldap.Entry{
|
||||||
&ldap.Entry{Attributes: attributes},
|
{Attributes: attributes},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -227,14 +227,14 @@ func TestShouldNotCrashWhenGroupsAreNotRetrievedFromLDAP(t *testing.T) {
|
||||||
Search(gomock.Any()).
|
Search(gomock.Any()).
|
||||||
Return(&ldap.SearchResult{
|
Return(&ldap.SearchResult{
|
||||||
Entries: []*ldap.Entry{
|
Entries: []*ldap.Entry{
|
||||||
&ldap.Entry{
|
{
|
||||||
DN: "uid=test,dc=example,dc=com",
|
DN: "uid=test,dc=example,dc=com",
|
||||||
Attributes: []*ldap.EntryAttribute{
|
Attributes: []*ldap.EntryAttribute{
|
||||||
&ldap.EntryAttribute{
|
{
|
||||||
Name: "mail",
|
Name: "mail",
|
||||||
Values: []string{"test@example.com"},
|
Values: []string{"test@example.com"},
|
||||||
},
|
},
|
||||||
&ldap.EntryAttribute{
|
{
|
||||||
Name: "uid",
|
Name: "uid",
|
||||||
Values: []string{"john"},
|
Values: []string{"john"},
|
||||||
},
|
},
|
||||||
|
@ -288,10 +288,10 @@ func TestShouldNotCrashWhenEmailsAreNotRetrievedFromLDAP(t *testing.T) {
|
||||||
Search(gomock.Any()).
|
Search(gomock.Any()).
|
||||||
Return(&ldap.SearchResult{
|
Return(&ldap.SearchResult{
|
||||||
Entries: []*ldap.Entry{
|
Entries: []*ldap.Entry{
|
||||||
&ldap.Entry{
|
{
|
||||||
DN: "uid=test,dc=example,dc=com",
|
DN: "uid=test,dc=example,dc=com",
|
||||||
Attributes: []*ldap.EntryAttribute{
|
Attributes: []*ldap.EntryAttribute{
|
||||||
&ldap.EntryAttribute{
|
{
|
||||||
Name: "uid",
|
Name: "uid",
|
||||||
Values: []string{"john"},
|
Values: []string{"john"},
|
||||||
},
|
},
|
||||||
|
@ -346,14 +346,14 @@ func TestShouldReturnUsernameFromLDAP(t *testing.T) {
|
||||||
Search(gomock.Any()).
|
Search(gomock.Any()).
|
||||||
Return(&ldap.SearchResult{
|
Return(&ldap.SearchResult{
|
||||||
Entries: []*ldap.Entry{
|
Entries: []*ldap.Entry{
|
||||||
&ldap.Entry{
|
{
|
||||||
DN: "uid=test,dc=example,dc=com",
|
DN: "uid=test,dc=example,dc=com",
|
||||||
Attributes: []*ldap.EntryAttribute{
|
Attributes: []*ldap.EntryAttribute{
|
||||||
&ldap.EntryAttribute{
|
{
|
||||||
Name: "mail",
|
Name: "mail",
|
||||||
Values: []string{"test@example.com"},
|
Values: []string{"test@example.com"},
|
||||||
},
|
},
|
||||||
&ldap.EntryAttribute{
|
{
|
||||||
Name: "uid",
|
Name: "uid",
|
||||||
Values: []string{"John"},
|
Values: []string{"John"},
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// PasswordHash represents all characteristics of a password hash.
|
// PasswordHash represents all characteristics of a password hash.
|
||||||
// Authelia only supports salted SHA512 or salted argon2id method, i.e., $6$ mode or $argon2id$ mode.
|
// Authelia only supports salted SHA512 or salted argon2id method, i.e., $6$ mode or $argon2id$ mode
|
||||||
type PasswordHash struct {
|
type PasswordHash struct {
|
||||||
Algorithm string
|
Algorithm string
|
||||||
Iterations int
|
Iterations int
|
||||||
|
@ -23,7 +23,7 @@ type PasswordHash struct {
|
||||||
Parallelism int
|
Parallelism int
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseHash extracts all characteristics of a hash given its string representation.
|
// ParseHash extracts all characteristics of a hash given its string representation
|
||||||
func ParseHash(hash string) (passwordHash *PasswordHash, err error) {
|
func ParseHash(hash string) (passwordHash *PasswordHash, err error) {
|
||||||
parts := strings.Split(hash, "$")
|
parts := strings.Split(hash, "$")
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ func ParseHash(hash string) (passwordHash *PasswordHash, err error) {
|
||||||
h.Key = key
|
h.Key = key
|
||||||
|
|
||||||
if h.Key != parts[len(parts)-1] {
|
if h.Key != parts[len(parts)-1] {
|
||||||
return nil, fmt.Errorf("Hash key is not the last parameter, the hash is likely malformed (%s).", hash)
|
return nil, fmt.Errorf("Hash key is not the last parameter, the hash is likely malformed (%s)", hash)
|
||||||
}
|
}
|
||||||
if h.Key == "" {
|
if h.Key == "" {
|
||||||
return nil, fmt.Errorf("Hash key contains no characters or the field length is invalid (%s)", hash)
|
return nil, fmt.Errorf("Hash key contains no characters or the field length is invalid (%s)", hash)
|
||||||
|
@ -43,14 +43,14 @@ func ParseHash(hash string) (passwordHash *PasswordHash, err error) {
|
||||||
|
|
||||||
_, err = crypt.Base64Encoding.DecodeString(h.Salt)
|
_, err = crypt.Base64Encoding.DecodeString(h.Salt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("Salt contains invalid base64 characters.")
|
return nil, errors.New("Salt contains invalid base64 characters")
|
||||||
}
|
}
|
||||||
|
|
||||||
if code == HashingAlgorithmSHA512 {
|
if code == HashingAlgorithmSHA512 {
|
||||||
h.Iterations = parameters.GetInt("rounds", HashingDefaultSHA512Iterations)
|
h.Iterations = parameters.GetInt("rounds", HashingDefaultSHA512Iterations)
|
||||||
h.Algorithm = HashingAlgorithmSHA512
|
h.Algorithm = HashingAlgorithmSHA512
|
||||||
if parameters["rounds"] != "" && parameters["rounds"] != strconv.Itoa(h.Iterations) {
|
if parameters["rounds"] != "" && parameters["rounds"] != strconv.Itoa(h.Iterations) {
|
||||||
return nil, fmt.Errorf("SHA512 iterations is not numeric (%s).", parameters["rounds"])
|
return nil, fmt.Errorf("SHA512 iterations is not numeric (%s)", parameters["rounds"])
|
||||||
}
|
}
|
||||||
} else if code == HashingAlgorithmArgon2id {
|
} else if code == HashingAlgorithmArgon2id {
|
||||||
version := parameters.GetInt("v", 0)
|
version := parameters.GetInt("v", 0)
|
||||||
|
@ -58,9 +58,9 @@ func ParseHash(hash string) (passwordHash *PasswordHash, err error) {
|
||||||
if version == 0 {
|
if version == 0 {
|
||||||
return nil, fmt.Errorf("Argon2id version parameter not found (%s)", hash)
|
return nil, fmt.Errorf("Argon2id version parameter not found (%s)", hash)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("Argon2id versions less than v19 are not supported (hash is version %d).", version)
|
return nil, fmt.Errorf("Argon2id versions less than v19 are not supported (hash is version %d)", version)
|
||||||
} else if version > 19 {
|
} else if version > 19 {
|
||||||
return nil, fmt.Errorf("Argon2id versions greater than v19 are not supported (hash is version %d).", version)
|
return nil, fmt.Errorf("Argon2id versions greater than v19 are not supported (hash is version %d)", version)
|
||||||
}
|
}
|
||||||
h.Algorithm = HashingAlgorithmArgon2id
|
h.Algorithm = HashingAlgorithmArgon2id
|
||||||
h.Memory = parameters.GetInt("m", HashingDefaultArgon2idMemory)
|
h.Memory = parameters.GetInt("m", HashingDefaultArgon2idMemory)
|
||||||
|
@ -70,10 +70,10 @@ func ParseHash(hash string) (passwordHash *PasswordHash, err error) {
|
||||||
|
|
||||||
decodedKey, err := crypt.Base64Encoding.DecodeString(h.Key)
|
decodedKey, err := crypt.Base64Encoding.DecodeString(h.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("Hash key contains invalid base64 characters.")
|
return nil, errors.New("Hash key contains invalid base64 characters")
|
||||||
}
|
}
|
||||||
if len(decodedKey) != h.KeyLength {
|
if len(decodedKey) != h.KeyLength {
|
||||||
return nil, fmt.Errorf("Argon2id key length parameter (%d) does not match the actual key length (%d).", h.KeyLength, len(decodedKey))
|
return nil, fmt.Errorf("Argon2id key length parameter (%d) does not match the actual key length (%d)", h.KeyLength, len(decodedKey))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("Authelia only supports salted SHA512 hashing ($6$) and salted argon2id ($argon2id$), not $%s$", code)
|
return nil, fmt.Errorf("Authelia only supports salted SHA512 hashing ($6$) and salted argon2id ($argon2id$), not $%s$", code)
|
||||||
|
@ -81,47 +81,47 @@ func ParseHash(hash string) (passwordHash *PasswordHash, err error) {
|
||||||
return h, nil
|
return h, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HashPassword generate a salt and hash the password with the salt and a constant
|
// HashPassword generate a salt and hash the password with the salt and a constant number of rounds
|
||||||
// number of rounds.
|
//nolint:gocyclo // TODO: Consider refactoring/simplifying, time permitting
|
||||||
func HashPassword(password, salt, algorithm string, iterations, memory, parallelism, keyLength, saltLength int) (hash string, err error) {
|
func HashPassword(password, salt, algorithm string, iterations, memory, parallelism, keyLength, saltLength int) (hash string, err error) {
|
||||||
var settings string
|
var settings string
|
||||||
|
|
||||||
if algorithm != HashingAlgorithmArgon2id && algorithm != HashingAlgorithmSHA512 {
|
if algorithm != HashingAlgorithmArgon2id && algorithm != HashingAlgorithmSHA512 {
|
||||||
return "", fmt.Errorf("Hashing algorithm input of '%s' is invalid, only values of %s and %s are supported.", algorithm, HashingAlgorithmArgon2id, HashingAlgorithmSHA512)
|
return "", fmt.Errorf("Hashing algorithm input of '%s' is invalid, only values of %s and %s are supported", algorithm, HashingAlgorithmArgon2id, HashingAlgorithmSHA512)
|
||||||
}
|
}
|
||||||
|
|
||||||
if salt == "" {
|
if salt == "" {
|
||||||
if saltLength < 2 {
|
if saltLength < 2 {
|
||||||
return "", fmt.Errorf("Salt length input of %d is invalid, it must be 2 or higher.", saltLength)
|
return "", fmt.Errorf("Salt length input of %d is invalid, it must be 2 or higher", saltLength)
|
||||||
} else if saltLength > 16 {
|
} else if saltLength > 16 {
|
||||||
return "", fmt.Errorf("Salt length input of %d is invalid, it must be 16 or lower.", saltLength)
|
return "", fmt.Errorf("Salt length input of %d is invalid, it must be 16 or lower", saltLength)
|
||||||
}
|
}
|
||||||
} else if len(salt) > 16 {
|
} else if len(salt) > 16 {
|
||||||
return "", fmt.Errorf("Salt input of %s is invalid (%d characters), it must be 16 or fewer characters.", salt, len(salt))
|
return "", fmt.Errorf("Salt input of %s is invalid (%d characters), it must be 16 or fewer characters", salt, len(salt))
|
||||||
} else if len(salt) < 2 {
|
} else if len(salt) < 2 {
|
||||||
return "", fmt.Errorf("Salt input of %s is invalid (%d characters), it must be 2 or more characters.", salt, len(salt))
|
return "", fmt.Errorf("Salt input of %s is invalid (%d characters), it must be 2 or more characters", salt, len(salt))
|
||||||
} else if _, err = crypt.Base64Encoding.DecodeString(salt); err != nil {
|
} else if _, err = crypt.Base64Encoding.DecodeString(salt); err != nil {
|
||||||
return "", fmt.Errorf("Salt input of %s is invalid, only characters [a-zA-Z0-9+/] are valid for input.", salt)
|
return "", fmt.Errorf("Salt input of %s is invalid, only characters [a-zA-Z0-9+/] are valid for input", salt)
|
||||||
}
|
}
|
||||||
|
|
||||||
if algorithm == HashingAlgorithmArgon2id {
|
if algorithm == HashingAlgorithmArgon2id {
|
||||||
// Caution: Increasing any of the values in the below block has a high chance in old passwords that cannot be verified.
|
// Caution: Increasing any of the values in the below block has a high chance in old passwords that cannot be verified
|
||||||
if memory < 8 {
|
if memory < 8 {
|
||||||
return "", fmt.Errorf("Memory (argon2id) input of %d is invalid, it must be 8 or higher.", memory)
|
return "", fmt.Errorf("Memory (argon2id) input of %d is invalid, it must be 8 or higher", memory)
|
||||||
}
|
}
|
||||||
if parallelism < 1 {
|
if parallelism < 1 {
|
||||||
return "", fmt.Errorf("Parallelism (argon2id) input of %d is invalid, it must be 1 or higher.", parallelism)
|
return "", fmt.Errorf("Parallelism (argon2id) input of %d is invalid, it must be 1 or higher", parallelism)
|
||||||
}
|
}
|
||||||
if memory < parallelism*8 {
|
if memory < parallelism*8 {
|
||||||
return "", fmt.Errorf("Memory (argon2id) input of %d is invalid with a paraellelism input of %d, it must be %d (parallelism * 8) or higher.", memory, parallelism, parallelism*8)
|
return "", fmt.Errorf("Memory (argon2id) input of %d is invalid with a parallelism input of %d, it must be %d (parallelism * 8) or higher", memory, parallelism, parallelism*8)
|
||||||
}
|
}
|
||||||
if keyLength < 16 {
|
if keyLength < 16 {
|
||||||
return "", fmt.Errorf("Key length (argon2id) input of %d is invalid, it must be 16 or higher.", keyLength)
|
return "", fmt.Errorf("Key length (argon2id) input of %d is invalid, it must be 16 or higher", keyLength)
|
||||||
}
|
}
|
||||||
if iterations < 1 {
|
if iterations < 1 {
|
||||||
return "", fmt.Errorf("Iterations (argon2id) input of %d is invalid, it must be 1 or more.", iterations)
|
return "", fmt.Errorf("Iterations (argon2id) input of %d is invalid, it must be 1 or more", iterations)
|
||||||
}
|
}
|
||||||
// Caution: Increasing any of the values in the above block has a high chance in old passwords that cannot be verified.
|
// Caution: Increasing any of the values in the above block has a high chance in old passwords that cannot be verified
|
||||||
}
|
}
|
||||||
|
|
||||||
if salt == "" {
|
if salt == "" {
|
||||||
|
@ -138,7 +138,7 @@ func HashPassword(password, salt, algorithm string, iterations, memory, parallel
|
||||||
return hash, nil
|
return hash, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckPassword check a password against a hash.
|
// CheckPassword check a password against a hash
|
||||||
func CheckPassword(password, hash string) (ok bool, err error) {
|
func CheckPassword(password, hash string) (ok bool, err error) {
|
||||||
passwordHash, err := ParseHash(hash)
|
passwordHash, err := ParseHash(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -17,7 +17,7 @@ func TestShouldHashSHA512Password(t *testing.T) {
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
code, parameters, salt, hash, err := crypt.DecodeSettings(hash)
|
code, parameters, salt, hash, _ := crypt.DecodeSettings(hash)
|
||||||
|
|
||||||
assert.Equal(t, "6", code)
|
assert.Equal(t, "6", code)
|
||||||
assert.Equal(t, "aFr56HjK3DrB8t3S", salt)
|
assert.Equal(t, "aFr56HjK3DrB8t3S", salt)
|
||||||
|
@ -78,7 +78,7 @@ func TestShouldNotHashPasswordWithNonExistentAlgorithm(t *testing.T) {
|
||||||
schema.DefaultCIPasswordOptionsConfiguration.SaltLength)
|
schema.DefaultCIPasswordOptionsConfiguration.SaltLength)
|
||||||
|
|
||||||
assert.Equal(t, "", hash)
|
assert.Equal(t, "", hash)
|
||||||
assert.EqualError(t, err, "Hashing algorithm input of 'bogus' is invalid, only values of argon2id and 6 are supported.")
|
assert.EqualError(t, err, "Hashing algorithm input of 'bogus' is invalid, only values of argon2id and 6 are supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldNotHashArgon2idPasswordDueToMemoryParallelismMismatch(t *testing.T) {
|
func TestShouldNotHashArgon2idPasswordDueToMemoryParallelismMismatch(t *testing.T) {
|
||||||
|
@ -87,7 +87,7 @@ func TestShouldNotHashArgon2idPasswordDueToMemoryParallelismMismatch(t *testing.
|
||||||
schema.DefaultCIPasswordOptionsConfiguration.KeyLength, schema.DefaultCIPasswordOptionsConfiguration.SaltLength)
|
schema.DefaultCIPasswordOptionsConfiguration.KeyLength, schema.DefaultCIPasswordOptionsConfiguration.SaltLength)
|
||||||
|
|
||||||
assert.Equal(t, "", hash)
|
assert.Equal(t, "", hash)
|
||||||
assert.EqualError(t, err, "Memory (argon2id) input of 8 is invalid with a paraellelism input of 2, it must be 16 (parallelism * 8) or higher.")
|
assert.EqualError(t, err, "Memory (argon2id) input of 8 is invalid with a parallelism input of 2, it must be 16 (parallelism * 8) or higher")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldNotHashArgon2idPasswordDueToMemoryLessThanEight(t *testing.T) {
|
func TestShouldNotHashArgon2idPasswordDueToMemoryLessThanEight(t *testing.T) {
|
||||||
|
@ -96,7 +96,7 @@ func TestShouldNotHashArgon2idPasswordDueToMemoryLessThanEight(t *testing.T) {
|
||||||
schema.DefaultCIPasswordOptionsConfiguration.KeyLength, schema.DefaultCIPasswordOptionsConfiguration.SaltLength)
|
schema.DefaultCIPasswordOptionsConfiguration.KeyLength, schema.DefaultCIPasswordOptionsConfiguration.SaltLength)
|
||||||
|
|
||||||
assert.Equal(t, "", hash)
|
assert.Equal(t, "", hash)
|
||||||
assert.EqualError(t, err, "Memory (argon2id) input of 1 is invalid, it must be 8 or higher.")
|
assert.EqualError(t, err, "Memory (argon2id) input of 1 is invalid, it must be 8 or higher")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldNotHashArgon2idPasswordDueToKeyLengthLessThanSixteen(t *testing.T) {
|
func TestShouldNotHashArgon2idPasswordDueToKeyLengthLessThanSixteen(t *testing.T) {
|
||||||
|
@ -105,7 +105,7 @@ func TestShouldNotHashArgon2idPasswordDueToKeyLengthLessThanSixteen(t *testing.T
|
||||||
schema.DefaultCIPasswordOptionsConfiguration.Parallelism, 5, schema.DefaultCIPasswordOptionsConfiguration.SaltLength)
|
schema.DefaultCIPasswordOptionsConfiguration.Parallelism, 5, schema.DefaultCIPasswordOptionsConfiguration.SaltLength)
|
||||||
|
|
||||||
assert.Equal(t, "", hash)
|
assert.Equal(t, "", hash)
|
||||||
assert.EqualError(t, err, "Key length (argon2id) input of 5 is invalid, it must be 16 or higher.")
|
assert.EqualError(t, err, "Key length (argon2id) input of 5 is invalid, it must be 16 or higher")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldNotHashArgon2idPasswordDueParallelismLessThanOne(t *testing.T) {
|
func TestShouldNotHashArgon2idPasswordDueParallelismLessThanOne(t *testing.T) {
|
||||||
|
@ -114,7 +114,7 @@ func TestShouldNotHashArgon2idPasswordDueParallelismLessThanOne(t *testing.T) {
|
||||||
schema.DefaultCIPasswordOptionsConfiguration.KeyLength, schema.DefaultCIPasswordOptionsConfiguration.SaltLength)
|
schema.DefaultCIPasswordOptionsConfiguration.KeyLength, schema.DefaultCIPasswordOptionsConfiguration.SaltLength)
|
||||||
|
|
||||||
assert.Equal(t, "", hash)
|
assert.Equal(t, "", hash)
|
||||||
assert.EqualError(t, err, "Parallelism (argon2id) input of -1 is invalid, it must be 1 or higher.")
|
assert.EqualError(t, err, "Parallelism (argon2id) input of -1 is invalid, it must be 1 or higher")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldNotHashArgon2idPasswordDueIterationsLessThanOne(t *testing.T) {
|
func TestShouldNotHashArgon2idPasswordDueIterationsLessThanOne(t *testing.T) {
|
||||||
|
@ -123,7 +123,7 @@ func TestShouldNotHashArgon2idPasswordDueIterationsLessThanOne(t *testing.T) {
|
||||||
schema.DefaultCIPasswordOptionsConfiguration.KeyLength, schema.DefaultCIPasswordOptionsConfiguration.SaltLength)
|
schema.DefaultCIPasswordOptionsConfiguration.KeyLength, schema.DefaultCIPasswordOptionsConfiguration.SaltLength)
|
||||||
|
|
||||||
assert.Equal(t, "", hash)
|
assert.Equal(t, "", hash)
|
||||||
assert.EqualError(t, err, "Iterations (argon2id) input of 0 is invalid, it must be 1 or more.")
|
assert.EqualError(t, err, "Iterations (argon2id) input of 0 is invalid, it must be 1 or more")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldNotHashPasswordDueToSaltLength(t *testing.T) {
|
func TestShouldNotHashPasswordDueToSaltLength(t *testing.T) {
|
||||||
|
@ -132,14 +132,14 @@ func TestShouldNotHashPasswordDueToSaltLength(t *testing.T) {
|
||||||
schema.DefaultCIPasswordOptionsConfiguration.Parallelism, schema.DefaultCIPasswordOptionsConfiguration.KeyLength, 0)
|
schema.DefaultCIPasswordOptionsConfiguration.Parallelism, schema.DefaultCIPasswordOptionsConfiguration.KeyLength, 0)
|
||||||
|
|
||||||
assert.Equal(t, "", hash)
|
assert.Equal(t, "", hash)
|
||||||
assert.EqualError(t, err, "Salt length input of 0 is invalid, it must be 2 or higher.")
|
assert.EqualError(t, err, "Salt length input of 0 is invalid, it must be 2 or higher")
|
||||||
|
|
||||||
hash, err = HashPassword("password", "", HashingAlgorithmArgon2id,
|
hash, err = HashPassword("password", "", HashingAlgorithmArgon2id,
|
||||||
schema.DefaultCIPasswordOptionsConfiguration.Iterations, schema.DefaultCIPasswordOptionsConfiguration.Memory*1024,
|
schema.DefaultCIPasswordOptionsConfiguration.Iterations, schema.DefaultCIPasswordOptionsConfiguration.Memory*1024,
|
||||||
schema.DefaultCIPasswordOptionsConfiguration.Parallelism, schema.DefaultCIPasswordOptionsConfiguration.KeyLength, 20)
|
schema.DefaultCIPasswordOptionsConfiguration.Parallelism, schema.DefaultCIPasswordOptionsConfiguration.KeyLength, 20)
|
||||||
|
|
||||||
assert.Equal(t, "", hash)
|
assert.Equal(t, "", hash)
|
||||||
assert.EqualError(t, err, "Salt length input of 20 is invalid, it must be 16 or lower.")
|
assert.EqualError(t, err, "Salt length input of 20 is invalid, it must be 16 or lower")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldNotHashPasswordDueToSaltCharLengthTooLong(t *testing.T) {
|
func TestShouldNotHashPasswordDueToSaltCharLengthTooLong(t *testing.T) {
|
||||||
|
@ -148,7 +148,7 @@ func TestShouldNotHashPasswordDueToSaltCharLengthTooLong(t *testing.T) {
|
||||||
schema.DefaultCIPasswordOptionsConfiguration.Parallelism, schema.DefaultCIPasswordOptionsConfiguration.KeyLength,
|
schema.DefaultCIPasswordOptionsConfiguration.Parallelism, schema.DefaultCIPasswordOptionsConfiguration.KeyLength,
|
||||||
schema.DefaultCIPasswordOptionsConfiguration.SaltLength)
|
schema.DefaultCIPasswordOptionsConfiguration.SaltLength)
|
||||||
assert.Equal(t, "", hash)
|
assert.Equal(t, "", hash)
|
||||||
assert.EqualError(t, err, "Salt input of abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 is invalid (62 characters), it must be 16 or fewer characters.")
|
assert.EqualError(t, err, "Salt input of abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 is invalid (62 characters), it must be 16 or fewer characters")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldNotHashPasswordDueToSaltCharLengthTooShort(t *testing.T) {
|
func TestShouldNotHashPasswordDueToSaltCharLengthTooShort(t *testing.T) {
|
||||||
|
@ -157,7 +157,7 @@ func TestShouldNotHashPasswordDueToSaltCharLengthTooShort(t *testing.T) {
|
||||||
schema.DefaultCIPasswordOptionsConfiguration.Parallelism, schema.DefaultCIPasswordOptionsConfiguration.KeyLength,
|
schema.DefaultCIPasswordOptionsConfiguration.Parallelism, schema.DefaultCIPasswordOptionsConfiguration.KeyLength,
|
||||||
schema.DefaultCIPasswordOptionsConfiguration.SaltLength)
|
schema.DefaultCIPasswordOptionsConfiguration.SaltLength)
|
||||||
assert.Equal(t, "", hash)
|
assert.Equal(t, "", hash)
|
||||||
assert.EqualError(t, err, "Salt input of a is invalid (1 characters), it must be 2 or more characters.")
|
assert.EqualError(t, err, "Salt input of a is invalid (1 characters), it must be 2 or more characters")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldNotHashPasswordWithNonBase64CharsInSalt(t *testing.T) {
|
func TestShouldNotHashPasswordWithNonBase64CharsInSalt(t *testing.T) {
|
||||||
|
@ -166,18 +166,18 @@ func TestShouldNotHashPasswordWithNonBase64CharsInSalt(t *testing.T) {
|
||||||
schema.DefaultCIPasswordOptionsConfiguration.Parallelism, schema.DefaultCIPasswordOptionsConfiguration.KeyLength,
|
schema.DefaultCIPasswordOptionsConfiguration.Parallelism, schema.DefaultCIPasswordOptionsConfiguration.KeyLength,
|
||||||
schema.DefaultCIPasswordOptionsConfiguration.SaltLength)
|
schema.DefaultCIPasswordOptionsConfiguration.SaltLength)
|
||||||
assert.Equal(t, "", hash)
|
assert.Equal(t, "", hash)
|
||||||
assert.EqualError(t, err, "Salt input of abc&123 is invalid, only characters [a-zA-Z0-9+/] are valid for input.")
|
assert.EqualError(t, err, "Salt input of abc&123 is invalid, only characters [a-zA-Z0-9+/] are valid for input")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldNotParseHashWithNoneBase64CharsInKey(t *testing.T) {
|
func TestShouldNotParseHashWithNoneBase64CharsInKey(t *testing.T) {
|
||||||
passwordHash, err := ParseHash("$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$^^vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM")
|
passwordHash, err := ParseHash("$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$^^vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM")
|
||||||
assert.EqualError(t, err, "Hash key contains invalid base64 characters.")
|
assert.EqualError(t, err, "Hash key contains invalid base64 characters")
|
||||||
assert.Nil(t, passwordHash)
|
assert.Nil(t, passwordHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldNotParseHashWithNoneBase64CharsInSalt(t *testing.T) {
|
func TestShouldNotParseHashWithNoneBase64CharsInSalt(t *testing.T) {
|
||||||
passwordHash, err := ParseHash("$argon2id$v=19$m=65536$^^wTFoFjITudo57a$Z4NH/EKkdv6PJ01Ye1twJ61fsmRJujZZn1IXdUOyrJY")
|
passwordHash, err := ParseHash("$argon2id$v=19$m=65536$^^wTFoFjITudo57a$Z4NH/EKkdv6PJ01Ye1twJ61fsmRJujZZn1IXdUOyrJY")
|
||||||
assert.EqualError(t, err, "Salt contains invalid base64 characters.")
|
assert.EqualError(t, err, "Salt contains invalid base64 characters")
|
||||||
assert.Nil(t, passwordHash)
|
assert.Nil(t, passwordHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,15 +187,15 @@ func TestShouldNotParseWithMalformedHash(t *testing.T) {
|
||||||
hashMissingSalt := "$argon2id$v=1$m=65536,t=3,p=2$2t9X8nNCN2n3/kFYJ3xWNBg5k/rO782Qr7JJoJIK7G4"
|
hashMissingSalt := "$argon2id$v=1$m=65536,t=3,p=2$2t9X8nNCN2n3/kFYJ3xWNBg5k/rO782Qr7JJoJIK7G4"
|
||||||
|
|
||||||
passwordHash, err := ParseHash(hashExtraField)
|
passwordHash, err := ParseHash(hashExtraField)
|
||||||
assert.EqualError(t, err, fmt.Sprintf("Hash key is not the last parameter, the hash is likely malformed (%s).", hashExtraField))
|
assert.EqualError(t, err, fmt.Sprintf("Hash key is not the last parameter, the hash is likely malformed (%s)", hashExtraField))
|
||||||
assert.Nil(t, passwordHash)
|
assert.Nil(t, passwordHash)
|
||||||
|
|
||||||
passwordHash, err = ParseHash(hashMissingSaltAndParams)
|
passwordHash, err = ParseHash(hashMissingSaltAndParams)
|
||||||
assert.EqualError(t, err, fmt.Sprintf("Hash key is not the last parameter, the hash is likely malformed (%s).", hashMissingSaltAndParams))
|
assert.EqualError(t, err, fmt.Sprintf("Hash key is not the last parameter, the hash is likely malformed (%s)", hashMissingSaltAndParams))
|
||||||
assert.Nil(t, passwordHash)
|
assert.Nil(t, passwordHash)
|
||||||
|
|
||||||
passwordHash, err = ParseHash(hashMissingSalt)
|
passwordHash, err = ParseHash(hashMissingSalt)
|
||||||
assert.EqualError(t, err, fmt.Sprintf("Hash key is not the last parameter, the hash is likely malformed (%s).", hashMissingSalt))
|
assert.EqualError(t, err, fmt.Sprintf("Hash key is not the last parameter, the hash is likely malformed (%s)", hashMissingSalt))
|
||||||
assert.Nil(t, passwordHash)
|
assert.Nil(t, passwordHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,7 +216,7 @@ func TestShouldNotParseArgon2idHashWithEmptyVersion(t *testing.T) {
|
||||||
func TestShouldNotParseArgon2idHashWithWrongKeyLength(t *testing.T) {
|
func TestShouldNotParseArgon2idHashWithWrongKeyLength(t *testing.T) {
|
||||||
hash := "$argon2id$v=19$m=65536,k=50$fvwTFoFjITudo57a$Z4NH/EKkdv6PJ01Ye1twJ61fsmRJujZZn1IXdUOyrJY"
|
hash := "$argon2id$v=19$m=65536,k=50$fvwTFoFjITudo57a$Z4NH/EKkdv6PJ01Ye1twJ61fsmRJujZZn1IXdUOyrJY"
|
||||||
passwordHash, err := ParseHash(hash)
|
passwordHash, err := ParseHash(hash)
|
||||||
assert.EqualError(t, err, "Argon2id key length parameter (50) does not match the actual key length (32).")
|
assert.EqualError(t, err, "Argon2id key length parameter (50) does not match the actual key length (32)")
|
||||||
assert.Nil(t, passwordHash)
|
assert.Nil(t, passwordHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,14 +244,14 @@ func TestShouldCheckArgon2idPassword(t *testing.T) {
|
||||||
func TestCannotParseSHA512Hash(t *testing.T) {
|
func TestCannotParseSHA512Hash(t *testing.T) {
|
||||||
ok, err := CheckPassword("password", "$6$roSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1")
|
ok, err := CheckPassword("password", "$6$roSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1")
|
||||||
|
|
||||||
assert.EqualError(t, err, "Hash key is not the last parameter, the hash is likely malformed ($6$roSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1).")
|
assert.EqualError(t, err, "Hash key is not the last parameter, the hash is likely malformed ($6$roSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1)")
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCannotParseArgon2idHash(t *testing.T) {
|
func TestCannotParseArgon2idHash(t *testing.T) {
|
||||||
ok, err := CheckPassword("password", "$argon2id$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM")
|
ok, err := CheckPassword("password", "$argon2id$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM")
|
||||||
|
|
||||||
assert.EqualError(t, err, "Hash key is not the last parameter, the hash is likely malformed ($argon2id$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM).")
|
assert.EqualError(t, err, "Hash key is not the last parameter, the hash is likely malformed ($argon2id$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM)")
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,35 +266,35 @@ func TestCannotFindNumberOfRounds(t *testing.T) {
|
||||||
hash := "$6$rounds50000$aFr56HjK3DrB8t3S$zhPQiS85cgBlNhUKKE6n/AHMlpqrvYSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1"
|
hash := "$6$rounds50000$aFr56HjK3DrB8t3S$zhPQiS85cgBlNhUKKE6n/AHMlpqrvYSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1"
|
||||||
ok, err := CheckPassword("password", hash)
|
ok, err := CheckPassword("password", hash)
|
||||||
|
|
||||||
assert.EqualError(t, err, fmt.Sprintf("Hash key is not the last parameter, the hash is likely malformed (%s).", hash))
|
assert.EqualError(t, err, fmt.Sprintf("Hash key is not the last parameter, the hash is likely malformed (%s)", hash))
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCannotMatchArgon2idParamPattern(t *testing.T) {
|
func TestCannotMatchArgon2idParamPattern(t *testing.T) {
|
||||||
ok, err := CheckPassword("password", "$argon2id$v=19$m65536,t3,p2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM")
|
ok, err := CheckPassword("password", "$argon2id$v=19$m65536,t3,p2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM")
|
||||||
|
|
||||||
assert.EqualError(t, err, "Hash key is not the last parameter, the hash is likely malformed ($argon2id$v=19$m65536,t3,p2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM).")
|
assert.EqualError(t, err, "Hash key is not the last parameter, the hash is likely malformed ($argon2id$v=19$m65536,t3,p2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM)")
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestArgon2idVersionLessThanSupported(t *testing.T) {
|
func TestArgon2idVersionLessThanSupported(t *testing.T) {
|
||||||
ok, err := CheckPassword("password", "$argon2id$v=18$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM")
|
ok, err := CheckPassword("password", "$argon2id$v=18$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM")
|
||||||
|
|
||||||
assert.EqualError(t, err, "Argon2id versions less than v19 are not supported (hash is version 18).")
|
assert.EqualError(t, err, "Argon2id versions less than v19 are not supported (hash is version 18)")
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestArgon2idVersionGreaterThanSupported(t *testing.T) {
|
func TestArgon2idVersionGreaterThanSupported(t *testing.T) {
|
||||||
ok, err := CheckPassword("password", "$argon2id$v=20$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM")
|
ok, err := CheckPassword("password", "$argon2id$v=20$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM")
|
||||||
|
|
||||||
assert.EqualError(t, err, "Argon2id versions greater than v19 are not supported (hash is version 20).")
|
assert.EqualError(t, err, "Argon2id versions greater than v19 are not supported (hash is version 20)")
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNumberOfRoundsNotInt(t *testing.T) {
|
func TestNumberOfRoundsNotInt(t *testing.T) {
|
||||||
ok, err := CheckPassword("password", "$6$rounds=abc$aFr56HjK3DrB8t3S$zhPQiS85cgBlNhUKKE6n/AHMlpqrvYSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1")
|
ok, err := CheckPassword("password", "$6$rounds=abc$aFr56HjK3DrB8t3S$zhPQiS85cgBlNhUKKE6n/AHMlpqrvYSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1")
|
||||||
|
|
||||||
assert.EqualError(t, err, "SHA512 iterations is not numeric (abc).")
|
assert.EqualError(t, err, "SHA512 iterations is not numeric (abc)")
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,12 +12,6 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
var NoNet = []string{}
|
|
||||||
var LocalNet = []string{"127.0.0.1"}
|
|
||||||
var PrivateNet = []string{"192.168.1.0/24"}
|
|
||||||
var MultipleNet = []string{"192.168.1.0/24", "10.0.0.0/8"}
|
|
||||||
var MixedNetIP = []string{"192.168.1.0/24", "192.168.2.4"}
|
|
||||||
|
|
||||||
type AuthorizerSuite struct {
|
type AuthorizerSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,7 @@ func publicKey(priv interface{}) interface{} {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:gocyclo // TODO: Consider refactoring/simplifying, time permitting
|
||||||
func generateSelfSignedCertificate(cmd *cobra.Command, args []string) {
|
func generateSelfSignedCertificate(cmd *cobra.Command, args []string) {
|
||||||
// implementation retrieved from https://golang.org/src/crypto/tls/generate_cert.go
|
// implementation retrieved from https://golang.org/src/crypto/tls/generate_cert.go
|
||||||
var priv interface{}
|
var priv interface{}
|
||||||
|
|
|
@ -46,9 +46,9 @@ var HashPasswordCmd = &cobra.Command{
|
||||||
|
|
||||||
hash, err = authentication.HashPassword(args[0], salt, algorithm, iterations, memory*1024, parallelism, keyLength, saltLength)
|
hash, err = authentication.HashPassword(args[0], salt, algorithm, iterations, memory*1024, parallelism, keyLength, saltLength)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(fmt.Sprintf("Error occured during hashing: %s", err))
|
fmt.Printf("Error occurred during hashing: %s", err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Println(fmt.Sprintf("Password hash: %s", hash))
|
fmt.Printf("Password hash: %s", hash)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
|
|
@ -103,7 +103,7 @@ func migrateLocalU2FSecret(dbProvider storage.Provider) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func migrateLocalPreferences(dbProvider storage.Provider) {
|
func migrateLocalPreferences(dbProvider storage.Provider) {
|
||||||
file, err := os.Open(path.Join(localDatabasePath, "prefered_2fa_method"))
|
file, err := os.Open(path.Join(localDatabasePath, "prefered_2fa_method")) //nolint:misspell
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,7 @@ func migrateMongo(cmd *cobra.Command, args []string) {
|
||||||
migrateMongoU2FDevices(db, dbProvider)
|
migrateMongoU2FDevices(db, dbProvider)
|
||||||
migrateMongoTOTPDevices(db, dbProvider)
|
migrateMongoTOTPDevices(db, dbProvider)
|
||||||
migrateMongoPreferences(db, dbProvider)
|
migrateMongoPreferences(db, dbProvider)
|
||||||
|
migrateMongoAuthenticationTraces(db, dbProvider)
|
||||||
|
|
||||||
log.Println("Migration done!")
|
log.Println("Migration done!")
|
||||||
}
|
}
|
||||||
|
@ -125,7 +126,7 @@ func migrateMongoTOTPDevices(db *mongo.Database, dbProvider storage.Provider) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func migrateMongoPreferences(db *mongo.Database, dbProvider storage.Provider) {
|
func migrateMongoPreferences(db *mongo.Database, dbProvider storage.Provider) {
|
||||||
u2fCollection := db.Collection("prefered_2fa_method")
|
u2fCollection := db.Collection("prefered_2fa_method") //nolint:misspell
|
||||||
|
|
||||||
cur, err := u2fCollection.Find(context.Background(), bson.D{})
|
cur, err := u2fCollection.Find(context.Background(), bson.D{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -10,12 +10,6 @@ import (
|
||||||
"github.com/authelia/authelia/internal/configuration/validator"
|
"github.com/authelia/authelia/internal/configuration/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
func check(e error) {
|
|
||||||
if e != nil {
|
|
||||||
panic(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read a YAML configuration and create a Configuration object out of it.
|
// Read a YAML configuration and create a Configuration object out of it.
|
||||||
func Read(configPath string) (*schema.Configuration, []error) {
|
func Read(configPath string) (*schema.Configuration, []error) {
|
||||||
viper.SetEnvPrefix("AUTHELIA")
|
viper.SetEnvPrefix("AUTHELIA")
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package schema
|
package schema
|
||||||
|
|
||||||
// LDAPAuthenticationBackendConfiguration represents the configuration related to LDAP server.
|
// LDAPAuthenticationBackendConfiguration represents the configuration related to LDAP server
|
||||||
type LDAPAuthenticationBackendConfiguration struct {
|
type LDAPAuthenticationBackendConfiguration struct {
|
||||||
URL string `mapstructure:"url"`
|
URL string `mapstructure:"url"`
|
||||||
SkipVerify bool `mapstructure:"skip_verify"`
|
SkipVerify bool `mapstructure:"skip_verify"`
|
||||||
|
@ -31,7 +31,7 @@ type PasswordHashingConfiguration struct {
|
||||||
Parallelism int `mapstructure:"parallelism"`
|
Parallelism int `mapstructure:"parallelism"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default Argon2id Configuration
|
// DefaultPasswordOptionsConfiguration represents the default configuration related to Argon2id hashing
|
||||||
var DefaultPasswordOptionsConfiguration = PasswordHashingConfiguration{
|
var DefaultPasswordOptionsConfiguration = PasswordHashingConfiguration{
|
||||||
Iterations: 1,
|
Iterations: 1,
|
||||||
KeyLength: 32,
|
KeyLength: 32,
|
||||||
|
@ -41,7 +41,7 @@ var DefaultPasswordOptionsConfiguration = PasswordHashingConfiguration{
|
||||||
Parallelism: 8,
|
Parallelism: 8,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default Argon2id Configuration for CI testing when calling HashPassword()
|
// DefaultCIPasswordOptionsConfiguration represents the default configuration related to Argon2id hashing for CI
|
||||||
var DefaultCIPasswordOptionsConfiguration = PasswordHashingConfiguration{
|
var DefaultCIPasswordOptionsConfiguration = PasswordHashingConfiguration{
|
||||||
Iterations: 1,
|
Iterations: 1,
|
||||||
KeyLength: 32,
|
KeyLength: 32,
|
||||||
|
@ -51,14 +51,14 @@ var DefaultCIPasswordOptionsConfiguration = PasswordHashingConfiguration{
|
||||||
Parallelism: 8,
|
Parallelism: 8,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default SHA512 Cofniguration
|
// DefaultPasswordOptionsSHA512Configuration represents the default configuration related to SHA512 hashing
|
||||||
var DefaultPasswordOptionsSHA512Configuration = PasswordHashingConfiguration{
|
var DefaultPasswordOptionsSHA512Configuration = PasswordHashingConfiguration{
|
||||||
Iterations: 50000,
|
Iterations: 50000,
|
||||||
SaltLength: 16,
|
SaltLength: 16,
|
||||||
Algorithm: "sha512",
|
Algorithm: "sha512",
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthenticationBackendConfiguration represents the configuration related to the authentication backend.
|
// AuthenticationBackendConfiguration represents the configuration related to the authentication backend
|
||||||
type AuthenticationBackendConfiguration struct {
|
type AuthenticationBackendConfiguration struct {
|
||||||
DisableResetPassword bool `mapstructure:"disable_reset_password"`
|
DisableResetPassword bool `mapstructure:"disable_reset_password"`
|
||||||
Ldap *LDAPAuthenticationBackendConfiguration `mapstructure:"ldap"`
|
Ldap *LDAPAuthenticationBackendConfiguration `mapstructure:"ldap"`
|
||||||
|
|
|
@ -32,7 +32,7 @@ type QueueItem struct {
|
||||||
path string
|
path string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Validator) validateOne(item QueueItem, q *queue.Queue) error {
|
func (v *Validator) validateOne(item QueueItem, q *queue.Queue) error { //nolint:unparam
|
||||||
if item.value.Type().Kind() == reflect.Ptr {
|
if item.value.Type().Kind() == reflect.Ptr {
|
||||||
if item.value.IsNil() {
|
if item.value.IsNil() {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -9,9 +9,7 @@ import (
|
||||||
"github.com/authelia/authelia/internal/configuration/schema"
|
"github.com/authelia/authelia/internal/configuration/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ldapProtocolPrefix = "ldap://"
|
//nolint:gocyclo // TODO: Consider refactoring/simplifying, time permitting
|
||||||
var ldapsProtocolPrefix = "ldaps://"
|
|
||||||
|
|
||||||
func validateFileAuthenticationBackend(configuration *schema.FileAuthenticationBackendConfiguration, validator *schema.StructValidator) {
|
func validateFileAuthenticationBackend(configuration *schema.FileAuthenticationBackendConfiguration, validator *schema.StructValidator) {
|
||||||
if configuration.Path == "" {
|
if configuration.Path == "" {
|
||||||
validator.Push(errors.New("Please provide a `path` for the users database in `authentication_backend`"))
|
validator.Push(errors.New("Please provide a `path` for the users database in `authentication_backend`"))
|
||||||
|
@ -50,7 +48,6 @@ func validateFileAuthenticationBackend(configuration *schema.FileAuthenticationB
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.PasswordHashing.Algorithm == "argon2id" {
|
if configuration.PasswordHashing.Algorithm == "argon2id" {
|
||||||
|
|
||||||
// Parallelism
|
// Parallelism
|
||||||
if configuration.PasswordHashing.Parallelism == 0 {
|
if configuration.PasswordHashing.Parallelism == 0 {
|
||||||
configuration.PasswordHashing.Parallelism = schema.DefaultPasswordOptionsConfiguration.Parallelism
|
configuration.PasswordHashing.Parallelism = schema.DefaultPasswordOptionsConfiguration.Parallelism
|
||||||
|
@ -101,6 +98,7 @@ func validateLdapURL(ldapURL string, validator *schema.StructValidator) string {
|
||||||
return u.String()
|
return u.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:gocyclo // TODO: Consider refactoring/simplifying, time permitting
|
||||||
func validateLdapAuthenticationBackend(configuration *schema.LDAPAuthenticationBackendConfiguration, validator *schema.StructValidator) {
|
func validateLdapAuthenticationBackend(configuration *schema.LDAPAuthenticationBackendConfiguration, validator *schema.StructValidator) {
|
||||||
if configuration.URL == "" {
|
if configuration.URL == "" {
|
||||||
validator.Push(errors.New("Please provide a URL to the LDAP server"))
|
validator.Push(errors.New("Please provide a URL to the LDAP server"))
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package validator
|
package validator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/authelia/authelia/internal/configuration/schema"
|
"github.com/authelia/authelia/internal/configuration/schema"
|
||||||
|
@ -18,13 +17,13 @@ func ValidateRegulation(configuration *schema.RegulationConfiguration, validator
|
||||||
}
|
}
|
||||||
findTime, err := utils.ParseDurationString(configuration.FindTime)
|
findTime, err := utils.ParseDurationString(configuration.FindTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
validator.Push(errors.New(fmt.Sprintf("Error occurred parsing regulation find_time string: %s", err)))
|
validator.Push(fmt.Errorf("Error occurred parsing regulation find_time string: %s", err))
|
||||||
}
|
}
|
||||||
banTime, err := utils.ParseDurationString(configuration.BanTime)
|
banTime, err := utils.ParseDurationString(configuration.BanTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
validator.Push(errors.New(fmt.Sprintf("Error occurred parsing regulation ban_time string: %s", err)))
|
validator.Push(fmt.Errorf("Error occurred parsing regulation ban_time string: %s", err))
|
||||||
}
|
}
|
||||||
if findTime > banTime {
|
if findTime > banTime {
|
||||||
validator.Push(errors.New(fmt.Sprintf("find_time cannot be greater than ban_time")))
|
validator.Push(fmt.Errorf("find_time cannot be greater than ban_time"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,19 +21,19 @@ func ValidateSession(configuration *schema.SessionConfiguration, validator *sche
|
||||||
if configuration.Expiration == "" {
|
if configuration.Expiration == "" {
|
||||||
configuration.Expiration = schema.DefaultSessionConfiguration.Expiration // 1 hour
|
configuration.Expiration = schema.DefaultSessionConfiguration.Expiration // 1 hour
|
||||||
} else if _, err := utils.ParseDurationString(configuration.Expiration); err != nil {
|
} else if _, err := utils.ParseDurationString(configuration.Expiration); err != nil {
|
||||||
validator.Push(errors.New(fmt.Sprintf("Error occurred parsing session expiration string: %s", err)))
|
validator.Push(fmt.Errorf("Error occurred parsing session expiration string: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.Inactivity == "" {
|
if configuration.Inactivity == "" {
|
||||||
configuration.Inactivity = schema.DefaultSessionConfiguration.Inactivity // 5 min
|
configuration.Inactivity = schema.DefaultSessionConfiguration.Inactivity // 5 min
|
||||||
} else if _, err := utils.ParseDurationString(configuration.Inactivity); err != nil {
|
} else if _, err := utils.ParseDurationString(configuration.Inactivity); err != nil {
|
||||||
validator.Push(errors.New(fmt.Sprintf("Error occurred parsing session inactivity string: %s", err)))
|
validator.Push(fmt.Errorf("Error occurred parsing session inactivity string: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.RememberMeDuration == "" {
|
if configuration.RememberMeDuration == "" {
|
||||||
configuration.RememberMeDuration = schema.DefaultSessionConfiguration.RememberMeDuration // 1 month
|
configuration.RememberMeDuration = schema.DefaultSessionConfiguration.RememberMeDuration // 1 month
|
||||||
} else if _, err := utils.ParseDurationString(configuration.RememberMeDuration); err != nil {
|
} else if _, err := utils.ParseDurationString(configuration.RememberMeDuration); err != nil {
|
||||||
validator.Push(errors.New(fmt.Sprintf("Error occurred parsing session remember_me_duration string: %s", err)))
|
validator.Push(fmt.Errorf("Error occurred parsing session remember_me_duration string: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.Domain == "" {
|
if configuration.Domain == "" {
|
||||||
|
|
|
@ -84,8 +84,8 @@ func TestShouldRaiseErrorWhenBadInactivityAndExpirationSet(t *testing.T) {
|
||||||
ValidateSession(&config, validator)
|
ValidateSession(&config, validator)
|
||||||
|
|
||||||
assert.Len(t, validator.Errors(), 2)
|
assert.Len(t, validator.Errors(), 2)
|
||||||
assert.EqualError(t, validator.Errors()[0], "Error occurred parsing session expiration string: could not convert the input string of -1 into a duration")
|
assert.EqualError(t, validator.Errors()[0], "Error occurred parsing session expiration string: Could not convert the input string of -1 into a duration")
|
||||||
assert.EqualError(t, validator.Errors()[1], "Error occurred parsing session inactivity string: could not convert the input string of -1 into a duration")
|
assert.EqualError(t, validator.Errors()[1], "Error occurred parsing session inactivity string: Could not convert the input string of -1 into a duration")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldRaiseErrorWhenBadRememberMeDurationSet(t *testing.T) {
|
func TestShouldRaiseErrorWhenBadRememberMeDurationSet(t *testing.T) {
|
||||||
|
@ -96,7 +96,7 @@ func TestShouldRaiseErrorWhenBadRememberMeDurationSet(t *testing.T) {
|
||||||
ValidateSession(&config, validator)
|
ValidateSession(&config, validator)
|
||||||
|
|
||||||
assert.Len(t, validator.Errors(), 1)
|
assert.Len(t, validator.Errors(), 1)
|
||||||
assert.EqualError(t, validator.Errors()[0], "Error occurred parsing session remember_me_duration string: could not convert the input string of 1 year into a duration")
|
assert.EqualError(t, validator.Errors()[0], "Error occurred parsing session remember_me_duration string: Could not convert the input string of 1 year into a duration")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldSetDefaultRememberMeDuration(t *testing.T) {
|
func TestShouldSetDefaultRememberMeDuration(t *testing.T) {
|
||||||
|
|
|
@ -32,5 +32,4 @@ func TestShouldRaiseErrorWhenInvalidTOTPMinimumValues(t *testing.T) {
|
||||||
assert.Len(t, validator.Errors(), 2)
|
assert.Len(t, validator.Errors(), 2)
|
||||||
assert.EqualError(t, validator.Errors()[0], "TOTP Period must be 1 or more")
|
assert.EqualError(t, validator.Errors()[0], "TOTP Period must be 1 or more")
|
||||||
assert.EqualError(t, validator.Errors()[1], "TOTP Skew must be 0 or more")
|
assert.EqualError(t, validator.Errors()[1], "TOTP Skew must be 0 or more")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,4 +33,3 @@ const unableToRegisterOneTimePasswordMessage = "Unable to set up one-time passwo
|
||||||
const unableToRegisterSecurityKeyMessage = "Unable to register your security key."
|
const unableToRegisterSecurityKeyMessage = "Unable to register your security key."
|
||||||
const unableToResetPasswordMessage = "Unable to reset your password."
|
const unableToResetPasswordMessage = "Unable to reset your password."
|
||||||
const mfaValidationFailedMessage = "Authentication failed, please retry later."
|
const mfaValidationFailedMessage = "Authentication failed, please retry later."
|
||||||
const badBasicAuthFormatMessage = "Content of Proxy-Authorization header is wrong."
|
|
||||||
|
|
|
@ -67,15 +67,15 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldCheckSecondFactorIsDisab
|
||||||
s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{
|
s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{
|
||||||
DefaultPolicy: "bypass",
|
DefaultPolicy: "bypass",
|
||||||
Rules: []schema.ACLRule{
|
Rules: []schema.ACLRule{
|
||||||
schema.ACLRule{
|
{
|
||||||
Domain: "example.com",
|
Domain: "example.com",
|
||||||
Policy: "deny",
|
Policy: "deny",
|
||||||
},
|
},
|
||||||
schema.ACLRule{
|
{
|
||||||
Domain: "abc.example.com",
|
Domain: "abc.example.com",
|
||||||
Policy: "single_factor",
|
Policy: "single_factor",
|
||||||
},
|
},
|
||||||
schema.ACLRule{
|
{
|
||||||
Domain: "def.example.com",
|
Domain: "def.example.com",
|
||||||
Policy: "bypass",
|
Policy: "bypass",
|
||||||
},
|
},
|
||||||
|
@ -98,15 +98,15 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldCheckSecondFactorIsEnabl
|
||||||
s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{
|
s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{
|
||||||
DefaultPolicy: "two_factor",
|
DefaultPolicy: "two_factor",
|
||||||
Rules: []schema.ACLRule{
|
Rules: []schema.ACLRule{
|
||||||
schema.ACLRule{
|
{
|
||||||
Domain: "example.com",
|
Domain: "example.com",
|
||||||
Policy: "deny",
|
Policy: "deny",
|
||||||
},
|
},
|
||||||
schema.ACLRule{
|
{
|
||||||
Domain: "abc.example.com",
|
Domain: "abc.example.com",
|
||||||
Policy: "single_factor",
|
Policy: "single_factor",
|
||||||
},
|
},
|
||||||
schema.ACLRule{
|
{
|
||||||
Domain: "def.example.com",
|
Domain: "def.example.com",
|
||||||
Policy: "bypass",
|
Policy: "bypass",
|
||||||
},
|
},
|
||||||
|
@ -129,15 +129,15 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldCheckSecondFactorIsEnabl
|
||||||
s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{
|
s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{
|
||||||
DefaultPolicy: "bypass",
|
DefaultPolicy: "bypass",
|
||||||
Rules: []schema.ACLRule{
|
Rules: []schema.ACLRule{
|
||||||
schema.ACLRule{
|
{
|
||||||
Domain: "example.com",
|
Domain: "example.com",
|
||||||
Policy: "deny",
|
Policy: "deny",
|
||||||
},
|
},
|
||||||
schema.ACLRule{
|
{
|
||||||
Domain: "abc.example.com",
|
Domain: "abc.example.com",
|
||||||
Policy: "two_factor",
|
Policy: "two_factor",
|
||||||
},
|
},
|
||||||
schema.ACLRule{
|
{
|
||||||
Domain: "def.example.com",
|
Domain: "def.example.com",
|
||||||
Policy: "bypass",
|
Policy: "bypass",
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"github.com/authelia/authelia/internal/models"
|
"github.com/authelia/authelia/internal/models"
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
@ -31,13 +30,6 @@ func (s *FirstFactorSuite) TearDownTest() {
|
||||||
s.mock.Close()
|
s.mock.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FirstFactorSuite) assertError500(err string) {
|
|
||||||
assert.Equal(s.T(), 500, s.mock.Ctx.Response.StatusCode())
|
|
||||||
assert.Equal(s.T(), []byte(InternalError), s.mock.Ctx.Response.Body())
|
|
||||||
assert.Equal(s.T(), err, s.mock.Hook.LastEntry().Message)
|
|
||||||
assert.Equal(s.T(), logrus.ErrorLevel, s.mock.Hook.LastEntry().Level)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FirstFactorSuite) TestShouldFailIfBodyIsNil() {
|
func (s *FirstFactorSuite) TestShouldFailIfBodyIsNil() {
|
||||||
FirstFactorPost(s.mock.Ctx)
|
FirstFactorPost(s.mock.Ctx)
|
||||||
|
|
||||||
|
@ -288,7 +280,7 @@ func (s *FirstFactorRedirectionSuite) SetupTest() {
|
||||||
s.mock.Ctx.Configuration.DefaultRedirectionURL = "https://default.local"
|
s.mock.Ctx.Configuration.DefaultRedirectionURL = "https://default.local"
|
||||||
s.mock.Ctx.Configuration.AccessControl.DefaultPolicy = "bypass"
|
s.mock.Ctx.Configuration.AccessControl.DefaultPolicy = "bypass"
|
||||||
s.mock.Ctx.Configuration.AccessControl.Rules = []schema.ACLRule{
|
s.mock.Ctx.Configuration.AccessControl.Rules = []schema.ACLRule{
|
||||||
schema.ACLRule{
|
{
|
||||||
Domain: "default.local",
|
Domain: "default.local",
|
||||||
Policy: "one_factor",
|
Policy: "one_factor",
|
||||||
},
|
},
|
||||||
|
@ -384,11 +376,11 @@ func (s *FirstFactorRedirectionSuite) TestShouldReply200WhenUnsafeTargetURLProvi
|
||||||
s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{
|
s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{
|
||||||
DefaultPolicy: "one_factor",
|
DefaultPolicy: "one_factor",
|
||||||
Rules: []schema.ACLRule{
|
Rules: []schema.ACLRule{
|
||||||
schema.ACLRule{
|
{
|
||||||
Domain: "test.example.com",
|
Domain: "test.example.com",
|
||||||
Policy: "one_factor",
|
Policy: "one_factor",
|
||||||
},
|
},
|
||||||
schema.ACLRule{
|
{
|
||||||
Domain: "example.com",
|
Domain: "example.com",
|
||||||
Policy: "two_factor",
|
Policy: "two_factor",
|
||||||
},
|
},
|
||||||
|
|
|
@ -46,13 +46,6 @@ func createToken(secret string, username string, action string, expiresAt time.T
|
||||||
return ss
|
return ss
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFinishArgs() middlewares.IdentityVerificationFinishArgs {
|
|
||||||
return middlewares.IdentityVerificationFinishArgs{
|
|
||||||
ActionClaim: U2FRegistrationAction,
|
|
||||||
IsTokenUserValidFunc: func(ctx *middlewares.AutheliaCtx, username string) bool { return true },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *HandlerRegisterU2FStep1Suite) TestShouldRaiseWhenXForwardedProtoIsMissing() {
|
func (s *HandlerRegisterU2FStep1Suite) TestShouldRaiseWhenXForwardedProtoIsMissing() {
|
||||||
token := createToken(s.mock.Ctx.Configuration.JWTSecret, "john", U2FRegistrationAction,
|
token := createToken(s.mock.Ctx.Configuration.JWTSecret, "john", U2FRegistrationAction,
|
||||||
time.Now().Add(1*time.Minute))
|
time.Now().Add(1*time.Minute))
|
||||||
|
|
|
@ -15,6 +15,10 @@ func SecondFactorU2FRegister(ctx *middlewares.AutheliaCtx) {
|
||||||
responseBody := u2f.RegisterResponse{}
|
responseBody := u2f.RegisterResponse{}
|
||||||
err := ctx.ParseBody(&responseBody)
|
err := ctx.ParseBody(&responseBody)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(fmt.Errorf("Unable to parse response body: %v", err), unableToRegisterSecurityKeyMessage)
|
||||||
|
}
|
||||||
|
|
||||||
userSession := ctx.GetSession()
|
userSession := ctx.GetSession()
|
||||||
|
|
||||||
if userSession.U2FChallenge == nil {
|
if userSession.U2FChallenge == nil {
|
||||||
|
|
|
@ -62,25 +62,24 @@ func setPreferencesExpectations(preferences UserPreferences, provider *storage.M
|
||||||
LoadTOTPSecret(gomock.Eq("john")).
|
LoadTOTPSecret(gomock.Eq("john")).
|
||||||
Return("", storage.ErrNoTOTPSecret)
|
Return("", storage.ErrNoTOTPSecret)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMethodSetToU2F(t *testing.T) {
|
func TestMethodSetToU2F(t *testing.T) {
|
||||||
table := []UserPreferences{
|
table := []UserPreferences{
|
||||||
UserPreferences{
|
{
|
||||||
Method: "totp",
|
Method: "totp",
|
||||||
},
|
},
|
||||||
UserPreferences{
|
{
|
||||||
Method: "u2f",
|
Method: "u2f",
|
||||||
HasU2F: true,
|
HasU2F: true,
|
||||||
HasTOTP: true,
|
HasTOTP: true,
|
||||||
},
|
},
|
||||||
UserPreferences{
|
{
|
||||||
Method: "u2f",
|
Method: "u2f",
|
||||||
HasU2F: true,
|
HasU2F: true,
|
||||||
HasTOTP: false,
|
HasTOTP: false,
|
||||||
},
|
},
|
||||||
UserPreferences{
|
{
|
||||||
Method: "mobile_push",
|
Method: "mobile_push",
|
||||||
HasU2F: false,
|
HasU2F: false,
|
||||||
HasTOTP: false,
|
HasTOTP: false,
|
||||||
|
|
|
@ -27,7 +27,7 @@ func isSchemeWSS(url *url.URL) bool {
|
||||||
return url.Scheme == "wss"
|
return url.Scheme == "wss"
|
||||||
}
|
}
|
||||||
|
|
||||||
// getOriginalURL extract the URL from the request headers (X-Original-URI or X-Forwarded-* headers).
|
// getOriginalURL extract the URL from the request headers (X-Original-URI or X-Forwarded-* headers)
|
||||||
func getOriginalURL(ctx *middlewares.AutheliaCtx) (*url.URL, error) {
|
func getOriginalURL(ctx *middlewares.AutheliaCtx) (*url.URL, error) {
|
||||||
originalURL := ctx.XOriginalURL()
|
originalURL := ctx.XOriginalURL()
|
||||||
if originalURL != nil {
|
if originalURL != nil {
|
||||||
|
@ -65,8 +65,8 @@ func getOriginalURL(ctx *middlewares.AutheliaCtx) (*url.URL, error) {
|
||||||
return url, nil
|
return url, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseBasicAuth parses an HTTP Basic Authentication string.
|
// parseBasicAuth parses an HTTP Basic Authentication string
|
||||||
// "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ("Aladdin", "open sesame", true).
|
// "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ("Aladdin", "open sesame", true)
|
||||||
func parseBasicAuth(auth string) (username, password string, err error) {
|
func parseBasicAuth(auth string) (username, password string, err error) {
|
||||||
if !strings.HasPrefix(auth, authPrefix) {
|
if !strings.HasPrefix(auth, authPrefix) {
|
||||||
return "", "", fmt.Errorf("%s prefix not found in %s header", strings.Trim(authPrefix, " "), AuthorizationHeader)
|
return "", "", fmt.Errorf("%s prefix not found in %s header", strings.Trim(authPrefix, " "), AuthorizationHeader)
|
||||||
|
@ -83,10 +83,9 @@ func parseBasicAuth(auth string) (username, password string, err error) {
|
||||||
return cs[:s], cs[s+1:], nil
|
return cs[:s], cs[s+1:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// isTargetURLAuthorized check whether the given user is authorized to access the resource.
|
// isTargetURLAuthorized check whether the given user is authorized to access the resource
|
||||||
func isTargetURLAuthorized(authorizer *authorization.Authorizer, targetURL url.URL,
|
func isTargetURLAuthorized(authorizer *authorization.Authorizer, targetURL url.URL,
|
||||||
username string, userGroups []string, clientIP net.IP, authLevel authentication.Level) authorizationMatching {
|
username string, userGroups []string, clientIP net.IP, authLevel authentication.Level) authorizationMatching {
|
||||||
|
|
||||||
level := authorizer.GetRequiredLevel(authorization.Subject{
|
level := authorizer.GetRequiredLevel(authorization.Subject{
|
||||||
Username: username,
|
Username: username,
|
||||||
Groups: userGroups,
|
Groups: userGroups,
|
||||||
|
@ -98,10 +97,10 @@ func isTargetURLAuthorized(authorizer *authorization.Authorizer, targetURL url.U
|
||||||
} else if username != "" && level == authorization.Denied {
|
} else if username != "" && level == authorization.Denied {
|
||||||
// If the user is not anonymous, it means that we went through
|
// If the user is not anonymous, it means that we went through
|
||||||
// all the rules related to that user and knowing who he is we can
|
// all the rules related to that user and knowing who he is we can
|
||||||
// deduce the access is forbidden.
|
// deduce the access is forbidden
|
||||||
// For anonymous users though, we cannot be sure that she
|
// For anonymous users though, we cannot be sure that she
|
||||||
// could not be granted the rights to access the resource. Consequently
|
// could not be granted the rights to access the resource. Consequently
|
||||||
// for anonymous users we send Unauthorized instead of Forbidden.
|
// for anonymous users we send Unauthorized instead of Forbidden
|
||||||
return Forbidden
|
return Forbidden
|
||||||
} else {
|
} else {
|
||||||
if level == authorization.OneFactor &&
|
if level == authorization.OneFactor &&
|
||||||
|
@ -116,8 +115,8 @@ func isTargetURLAuthorized(authorizer *authorization.Authorizer, targetURL url.U
|
||||||
}
|
}
|
||||||
|
|
||||||
// verifyBasicAuth verify that the provided username and password are correct and
|
// verifyBasicAuth verify that the provided username and password are correct and
|
||||||
// that the user is authorized to target the resource.
|
// that the user is authorized to target the resource
|
||||||
func verifyBasicAuth(auth []byte, targetURL url.URL, ctx *middlewares.AutheliaCtx) (username string, groups []string, authLevel authentication.Level, err error) {
|
func verifyBasicAuth(auth []byte, targetURL url.URL, ctx *middlewares.AutheliaCtx) (username string, groups []string, authLevel authentication.Level, err error) { //nolint:unparam
|
||||||
username, password, err := parseBasicAuth(string(auth))
|
username, password, err := parseBasicAuth(string(auth))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -130,7 +129,7 @@ func verifyBasicAuth(auth []byte, targetURL url.URL, ctx *middlewares.AutheliaCt
|
||||||
return "", nil, authentication.NotAuthenticated, fmt.Errorf("Unable to check credentials extracted from %s header: %s", AuthorizationHeader, err)
|
return "", nil, authentication.NotAuthenticated, fmt.Errorf("Unable to check credentials extracted from %s header: %s", AuthorizationHeader, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user is not correctly authenticated, send a 401.
|
// If the user is not correctly authenticated, send a 401
|
||||||
if !authenticated {
|
if !authenticated {
|
||||||
// Request Basic Authentication otherwise
|
// Request Basic Authentication otherwise
|
||||||
return "", nil, authentication.NotAuthenticated, fmt.Errorf("User %s is not authenticated", username)
|
return "", nil, authentication.NotAuthenticated, fmt.Errorf("User %s is not authenticated", username)
|
||||||
|
@ -145,7 +144,7 @@ func verifyBasicAuth(auth []byte, targetURL url.URL, ctx *middlewares.AutheliaCt
|
||||||
return username, details.Groups, authentication.OneFactor, nil
|
return username, details.Groups, authentication.OneFactor, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// setForwardedHeaders set the forwarded User and Groups headers.
|
// setForwardedHeaders set the forwarded User and Groups headers
|
||||||
func setForwardedHeaders(headers *fasthttp.ResponseHeader, username string, groups []string) {
|
func setForwardedHeaders(headers *fasthttp.ResponseHeader, username string, groups []string) {
|
||||||
if username != "" {
|
if username != "" {
|
||||||
headers.Set(remoteUserHeader, username)
|
headers.Set(remoteUserHeader, username)
|
||||||
|
@ -153,9 +152,8 @@ func setForwardedHeaders(headers *fasthttp.ResponseHeader, username string, grou
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasUserBeenInactiveLongEnough check whether the user has been inactive for too long.
|
// hasUserBeenInactiveLongEnough check whether the user has been inactive for too long
|
||||||
func hasUserBeenInactiveLongEnough(ctx *middlewares.AutheliaCtx) (bool, error) {
|
func hasUserBeenInactiveLongEnough(ctx *middlewares.AutheliaCtx) (bool, error) { //nolint:unparam
|
||||||
|
|
||||||
maxInactivityPeriod := int64(ctx.Providers.SessionProvider.Inactivity.Seconds())
|
maxInactivityPeriod := int64(ctx.Providers.SessionProvider.Inactivity.Seconds())
|
||||||
if maxInactivityPeriod == 0 {
|
if maxInactivityPeriod == 0 {
|
||||||
return false, nil
|
return false, nil
|
||||||
|
@ -174,10 +172,10 @@ func hasUserBeenInactiveLongEnough(ctx *middlewares.AutheliaCtx) (bool, error) {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// verifyFromSessionCookie verify if a user identified by a cookie is allowed to access target URL.
|
// verifyFromSessionCookie verify if a user identified by a cookie is allowed to access target URL
|
||||||
func verifyFromSessionCookie(targetURL url.URL, ctx *middlewares.AutheliaCtx) (username string, groups []string, authLevel authentication.Level, err error) {
|
func verifyFromSessionCookie(targetURL url.URL, ctx *middlewares.AutheliaCtx) (username string, groups []string, authLevel authentication.Level, err error) { //nolint:unparam
|
||||||
userSession := ctx.GetSession()
|
userSession := ctx.GetSession()
|
||||||
// No username in the session means the user is anonymous.
|
// No username in the session means the user is anonymous
|
||||||
isUserAnonymous := userSession.Username == ""
|
isUserAnonymous := userSession.Username == ""
|
||||||
|
|
||||||
if isUserAnonymous && userSession.AuthenticationLevel != authentication.NotAuthenticated {
|
if isUserAnonymous && userSession.AuthenticationLevel != authentication.NotAuthenticated {
|
||||||
|
@ -191,7 +189,7 @@ func verifyFromSessionCookie(targetURL url.URL, ctx *middlewares.AutheliaCtx) (u
|
||||||
}
|
}
|
||||||
|
|
||||||
if inactiveLongEnough {
|
if inactiveLongEnough {
|
||||||
// Destroy the session a new one will be regenerated on next request.
|
// Destroy the session a new one will be regenerated on next request
|
||||||
err := ctx.Providers.SessionProvider.DestroySession(ctx.RequestCtx)
|
err := ctx.Providers.SessionProvider.DestroySession(ctx.RequestCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, authentication.NotAuthenticated, fmt.Errorf("Unable to destroy user session after long inactivity: %s", err)
|
return "", nil, authentication.NotAuthenticated, fmt.Errorf("Unable to destroy user session after long inactivity: %s", err)
|
||||||
|
@ -203,7 +201,7 @@ func verifyFromSessionCookie(targetURL url.URL, ctx *middlewares.AutheliaCtx) (u
|
||||||
return userSession.Username, userSession.Groups, userSession.AuthenticationLevel, nil
|
return userSession.Username, userSession.Groups, userSession.AuthenticationLevel, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyGet is the handler verifying if a request is allowed to go through.
|
// VerifyGet is the handler verifying if a request is allowed to go through
|
||||||
func VerifyGet(ctx *middlewares.AutheliaCtx) {
|
func VerifyGet(ctx *middlewares.AutheliaCtx) {
|
||||||
ctx.Logger.Tracef("Headers=%s", ctx.Request.Header.String())
|
ctx.Logger.Tracef("Headers=%s", ctx.Request.Header.String())
|
||||||
targetURL, err := getOriginalURL(ctx)
|
targetURL, err := getOriginalURL(ctx)
|
||||||
|
@ -256,7 +254,7 @@ func VerifyGet(ctx *middlewares.AutheliaCtx) {
|
||||||
} else if authorization == NotAuthorized {
|
} else if authorization == NotAuthorized {
|
||||||
// Kubernetes ingress controller and Traefik use the rd parameter of the verify
|
// Kubernetes ingress controller and Traefik use the rd parameter of the verify
|
||||||
// endpoint to provide the URL of the login portal. The target URL of the user
|
// endpoint to provide the URL of the login portal. The target URL of the user
|
||||||
// is computed from X-Fowarded-* headers or X-Original-URL.
|
// is computed from X-Fowarded-* headers or X-Original-URL
|
||||||
rd := string(ctx.QueryArgs().Peek("rd"))
|
rd := string(ctx.QueryArgs().Peek("rd"))
|
||||||
if rd != "" {
|
if rd != "" {
|
||||||
redirectionURL := fmt.Sprintf("%s?rd=%s", rd, url.QueryEscape(targetURL.String()))
|
redirectionURL := fmt.Sprintf("%s?rd=%s", rd, url.QueryEscape(targetURL.String()))
|
||||||
|
@ -273,7 +271,7 @@ func VerifyGet(ctx *middlewares.AutheliaCtx) {
|
||||||
setForwardedHeaders(&ctx.Response.Header, username, groups)
|
setForwardedHeaders(&ctx.Response.Header, username, groups)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We mark activity of the current user if he comes with a session cookie.
|
// We mark activity of the current user if he comes with a session cookie
|
||||||
if !hasBasicAuth && username != "" {
|
if !hasBasicAuth && username != "" {
|
||||||
// Mark current activity
|
// Mark current activity
|
||||||
userSession := ctx.GetSession()
|
userSession := ctx.GetSession()
|
||||||
|
|
|
@ -147,21 +147,21 @@ func TestShouldCheckAuthorizationMatching(t *testing.T) {
|
||||||
ExpectedMatching authorizationMatching
|
ExpectedMatching authorizationMatching
|
||||||
}
|
}
|
||||||
rules := []Rule{
|
rules := []Rule{
|
||||||
Rule{"bypass", authentication.NotAuthenticated, Authorized},
|
{"bypass", authentication.NotAuthenticated, Authorized},
|
||||||
Rule{"bypass", authentication.OneFactor, Authorized},
|
{"bypass", authentication.OneFactor, Authorized},
|
||||||
Rule{"bypass", authentication.TwoFactor, Authorized},
|
{"bypass", authentication.TwoFactor, Authorized},
|
||||||
|
|
||||||
Rule{"one_factor", authentication.NotAuthenticated, NotAuthorized},
|
{"one_factor", authentication.NotAuthenticated, NotAuthorized},
|
||||||
Rule{"one_factor", authentication.OneFactor, Authorized},
|
{"one_factor", authentication.OneFactor, Authorized},
|
||||||
Rule{"one_factor", authentication.TwoFactor, Authorized},
|
{"one_factor", authentication.TwoFactor, Authorized},
|
||||||
|
|
||||||
Rule{"two_factor", authentication.NotAuthenticated, NotAuthorized},
|
{"two_factor", authentication.NotAuthenticated, NotAuthorized},
|
||||||
Rule{"two_factor", authentication.OneFactor, NotAuthorized},
|
{"two_factor", authentication.OneFactor, NotAuthorized},
|
||||||
Rule{"two_factor", authentication.TwoFactor, Authorized},
|
{"two_factor", authentication.TwoFactor, Authorized},
|
||||||
|
|
||||||
Rule{"deny", authentication.NotAuthenticated, NotAuthorized},
|
{"deny", authentication.NotAuthenticated, NotAuthorized},
|
||||||
Rule{"deny", authentication.OneFactor, Forbidden},
|
{"deny", authentication.OneFactor, Forbidden},
|
||||||
Rule{"deny", authentication.TwoFactor, Forbidden},
|
{"deny", authentication.TwoFactor, Forbidden},
|
||||||
}
|
}
|
||||||
|
|
||||||
url, _ := url.ParseRequestURI("https://test.example.com")
|
url, _ := url.ParseRequestURI("https://test.example.com")
|
||||||
|
@ -169,7 +169,7 @@ func TestShouldCheckAuthorizationMatching(t *testing.T) {
|
||||||
for _, rule := range rules {
|
for _, rule := range rules {
|
||||||
authorizer := authorization.NewAuthorizer(schema.AccessControlConfiguration{
|
authorizer := authorization.NewAuthorizer(schema.AccessControlConfiguration{
|
||||||
DefaultPolicy: "deny",
|
DefaultPolicy: "deny",
|
||||||
Rules: []schema.ACLRule{schema.ACLRule{
|
Rules: []schema.ACLRule{{
|
||||||
Domain: "test.example.com",
|
Domain: "test.example.com",
|
||||||
Policy: rule.Policy,
|
Policy: rule.Policy,
|
||||||
}},
|
}},
|
||||||
|
@ -419,23 +419,23 @@ func (p Pair) String() string {
|
||||||
|
|
||||||
func TestShouldVerifyAuthorizationsUsingSessionCookie(t *testing.T) {
|
func TestShouldVerifyAuthorizationsUsingSessionCookie(t *testing.T) {
|
||||||
testCases := []Pair{
|
testCases := []Pair{
|
||||||
Pair{"https://test.example.com", "", authentication.NotAuthenticated, 401},
|
{"https://test.example.com", "", authentication.NotAuthenticated, 401},
|
||||||
Pair{"https://bypass.example.com", "", authentication.NotAuthenticated, 200},
|
{"https://bypass.example.com", "", authentication.NotAuthenticated, 200},
|
||||||
Pair{"https://one-factor.example.com", "", authentication.NotAuthenticated, 401},
|
{"https://one-factor.example.com", "", authentication.NotAuthenticated, 401},
|
||||||
Pair{"https://two-factor.example.com", "", authentication.NotAuthenticated, 401},
|
{"https://two-factor.example.com", "", authentication.NotAuthenticated, 401},
|
||||||
Pair{"https://deny.example.com", "", authentication.NotAuthenticated, 401},
|
{"https://deny.example.com", "", authentication.NotAuthenticated, 401},
|
||||||
|
|
||||||
Pair{"https://test.example.com", "john", authentication.OneFactor, 403},
|
{"https://test.example.com", "john", authentication.OneFactor, 403},
|
||||||
Pair{"https://bypass.example.com", "john", authentication.OneFactor, 200},
|
{"https://bypass.example.com", "john", authentication.OneFactor, 200},
|
||||||
Pair{"https://one-factor.example.com", "john", authentication.OneFactor, 200},
|
{"https://one-factor.example.com", "john", authentication.OneFactor, 200},
|
||||||
Pair{"https://two-factor.example.com", "john", authentication.OneFactor, 401},
|
{"https://two-factor.example.com", "john", authentication.OneFactor, 401},
|
||||||
Pair{"https://deny.example.com", "john", authentication.OneFactor, 403},
|
{"https://deny.example.com", "john", authentication.OneFactor, 403},
|
||||||
|
|
||||||
Pair{"https://test.example.com", "john", authentication.TwoFactor, 403},
|
{"https://test.example.com", "john", authentication.TwoFactor, 403},
|
||||||
Pair{"https://bypass.example.com", "john", authentication.TwoFactor, 200},
|
{"https://bypass.example.com", "john", authentication.TwoFactor, 200},
|
||||||
Pair{"https://one-factor.example.com", "john", authentication.TwoFactor, 200},
|
{"https://one-factor.example.com", "john", authentication.TwoFactor, 200},
|
||||||
Pair{"https://two-factor.example.com", "john", authentication.TwoFactor, 200},
|
{"https://two-factor.example.com", "john", authentication.TwoFactor, 200},
|
||||||
Pair{"https://deny.example.com", "john", authentication.TwoFactor, 403},
|
{"https://deny.example.com", "john", authentication.TwoFactor, 403},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
|
@ -628,7 +628,6 @@ func TestSchemeIsHTTPS(t *testing.T) {
|
||||||
GetURL("wss://mytest.example.com/abc/?query=abc")))
|
GetURL("wss://mytest.example.com/abc/?query=abc")))
|
||||||
assert.True(t, isSchemeHTTPS(
|
assert.True(t, isSchemeHTTPS(
|
||||||
GetURL("https://mytest.example.com/abc/?query=abc")))
|
GetURL("https://mytest.example.com/abc/?query=abc")))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSchemeIsWSS(t *testing.T) {
|
func TestSchemeIsWSS(t *testing.T) {
|
||||||
|
@ -646,5 +645,4 @@ func TestSchemeIsWSS(t *testing.T) {
|
||||||
GetURL("https://mytest.example.com/abc/?query=abc")))
|
GetURL("https://mytest.example.com/abc/?query=abc")))
|
||||||
assert.True(t, isSchemeWSS(
|
assert.True(t, isSchemeWSS(
|
||||||
GetURL("wss://mytest.example.com/abc/?query=abc")))
|
GetURL("wss://mytest.example.com/abc/?query=abc")))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,12 +49,6 @@ type firstFactorRequestBody struct {
|
||||||
KeepMeLoggedIn *bool `json:"keepMeLoggedIn"`
|
KeepMeLoggedIn *bool `json:"keepMeLoggedIn"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FirstFactorMessageResponse represents the response sent by the first factor endpoint
|
|
||||||
// when no redirection URL has been provided by the user.
|
|
||||||
type firstFactorMessageResponse struct {
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// redirectResponse represent the response sent by the first factor endpoint
|
// redirectResponse represent the response sent by the first factor endpoint
|
||||||
// when a redirection URL has been provided.
|
// when a redirection URL has been provided.
|
||||||
type redirectResponse struct {
|
type redirectResponse struct {
|
||||||
|
|
|
@ -72,16 +72,16 @@ func NewMockAutheliaCtx(t *testing.T) *MockAutheliaCtx {
|
||||||
configuration.Session.RememberMeDuration = schema.DefaultSessionConfiguration.RememberMeDuration
|
configuration.Session.RememberMeDuration = schema.DefaultSessionConfiguration.RememberMeDuration
|
||||||
configuration.Session.Name = "authelia_session"
|
configuration.Session.Name = "authelia_session"
|
||||||
configuration.AccessControl.DefaultPolicy = "deny"
|
configuration.AccessControl.DefaultPolicy = "deny"
|
||||||
configuration.AccessControl.Rules = []schema.ACLRule{schema.ACLRule{
|
configuration.AccessControl.Rules = []schema.ACLRule{{
|
||||||
Domain: "bypass.example.com",
|
Domain: "bypass.example.com",
|
||||||
Policy: "bypass",
|
Policy: "bypass",
|
||||||
}, schema.ACLRule{
|
}, {
|
||||||
Domain: "one-factor.example.com",
|
Domain: "one-factor.example.com",
|
||||||
Policy: "one_factor",
|
Policy: "one_factor",
|
||||||
}, schema.ACLRule{
|
}, {
|
||||||
Domain: "two-factor.example.com",
|
Domain: "two-factor.example.com",
|
||||||
Policy: "two_factor",
|
Policy: "two_factor",
|
||||||
}, schema.ACLRule{
|
}, {
|
||||||
Domain: "deny.example.com",
|
Domain: "deny.example.com",
|
||||||
Policy: "deny",
|
Policy: "deny",
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import (
|
||||||
"github.com/authelia/authelia/internal/utils"
|
"github.com/authelia/authelia/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SMTPNotifier a notifier to send emails to SMTP servers.
|
// SMTPNotifier a notifier to send emails to SMTP servers
|
||||||
type SMTPNotifier struct {
|
type SMTPNotifier struct {
|
||||||
username string
|
username string
|
||||||
password string
|
password string
|
||||||
|
@ -31,7 +31,7 @@ type SMTPNotifier struct {
|
||||||
tlsConfig *tls.Config
|
tlsConfig *tls.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSMTPNotifier create an SMTPNotifier targeting a given address.
|
// NewSMTPNotifier create an SMTPNotifier targeting a given address
|
||||||
func NewSMTPNotifier(configuration schema.SMTPNotifierConfiguration) *SMTPNotifier {
|
func NewSMTPNotifier(configuration schema.SMTPNotifierConfiguration) *SMTPNotifier {
|
||||||
notifier := &SMTPNotifier{
|
notifier := &SMTPNotifier{
|
||||||
username: configuration.Username,
|
username: configuration.Username,
|
||||||
|
@ -50,15 +50,15 @@ func NewSMTPNotifier(configuration schema.SMTPNotifierConfiguration) *SMTPNotifi
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *SMTPNotifier) initializeTLSConfig() {
|
func (n *SMTPNotifier) initializeTLSConfig() {
|
||||||
// Do not allow users to disable verification of certs if they have also set a trusted cert that was loaded.
|
// Do not allow users to disable verification of certs if they have also set a trusted cert that was loaded
|
||||||
// The second part of this check happens in the Configure Cert Pool code block.
|
// The second part of this check happens in the Configure Cert Pool code block
|
||||||
log.Debug("Notifier SMTP client initializing TLS configuration")
|
log.Debug("Notifier SMTP client initializing TLS configuration")
|
||||||
insecureSkipVerify := false
|
insecureSkipVerify := false
|
||||||
if n.disableVerifyCert {
|
if n.disableVerifyCert {
|
||||||
insecureSkipVerify = true
|
insecureSkipVerify = true
|
||||||
}
|
}
|
||||||
|
|
||||||
//Configure Cert Pool.
|
//Configure Cert Pool
|
||||||
certPool, err := x509.SystemCertPool()
|
certPool, err := x509.SystemCertPool()
|
||||||
if err != nil || certPool == nil {
|
if err != nil || certPool == nil {
|
||||||
certPool = x509.NewCertPool()
|
certPool = x509.NewCertPool()
|
||||||
|
@ -92,9 +92,9 @@ func (n *SMTPNotifier) initializeTLSConfig() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do startTLS if available (some servers only provide the auth extension after, and encryption is preferred).
|
// Do startTLS if available (some servers only provide the auth extension after, and encryption is preferred)
|
||||||
func (n *SMTPNotifier) startTLS() (bool, error) {
|
func (n *SMTPNotifier) startTLS() (bool, error) {
|
||||||
// Only start if not already encrypted.
|
// Only start if not already encrypted
|
||||||
if _, ok := n.client.TLSConnectionState(); ok {
|
if _, ok := n.client.TLSConnectionState(); ok {
|
||||||
log.Debugf("Notifier SMTP connection is already encrypted, skipping STARTTLS")
|
log.Debugf("Notifier SMTP connection is already encrypted, skipping STARTTLS")
|
||||||
return ok, nil
|
return ok, nil
|
||||||
|
@ -107,9 +107,8 @@ func (n *SMTPNotifier) startTLS() (bool, error) {
|
||||||
err := n.client.StartTLS(n.tlsConfig)
|
err := n.client.StartTLS(n.tlsConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ok, err
|
return ok, err
|
||||||
} else {
|
|
||||||
log.Debug("Notifier SMTP STARTTLS completed without error")
|
|
||||||
}
|
}
|
||||||
|
log.Debug("Notifier SMTP STARTTLS completed without error")
|
||||||
} else if n.disableRequireTLS {
|
} else if n.disableRequireTLS {
|
||||||
log.Warn("Notifier SMTP server does not support STARTTLS and SMTP configuration is set to disable the TLS requirement (only useful for unauthenticated emails over plain text)")
|
log.Warn("Notifier SMTP server does not support STARTTLS and SMTP configuration is set to disable the TLS requirement (only useful for unauthenticated emails over plain text)")
|
||||||
} else {
|
} else {
|
||||||
|
@ -118,16 +117,16 @@ func (n *SMTPNotifier) startTLS() (bool, error) {
|
||||||
return ok, nil
|
return ok, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt Authentication.
|
// Attempt Authentication
|
||||||
func (n *SMTPNotifier) auth() (bool, error) {
|
func (n *SMTPNotifier) auth() (bool, error) {
|
||||||
// Attempt AUTH if password is specified only.
|
// Attempt AUTH if password is specified only
|
||||||
if n.password != "" {
|
if n.password != "" {
|
||||||
_, ok := n.client.TLSConnectionState()
|
_, ok := n.client.TLSConnectionState()
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, errors.New("Notifier SMTP client does not support authentication over plain text and the connection is currently plain text")
|
return false, errors.New("Notifier SMTP client does not support authentication over plain text and the connection is currently plain text")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the server supports AUTH, and get the mechanisms.
|
// Check the server supports AUTH, and get the mechanisms
|
||||||
ok, m := n.client.Extension("AUTH")
|
ok, m := n.client.Extension("AUTH")
|
||||||
if ok {
|
if ok {
|
||||||
log.Debugf("Notifier SMTP server supports authentication with the following mechanisms: %s", m)
|
log.Debugf("Notifier SMTP server supports authentication with the following mechanisms: %s", m)
|
||||||
|
@ -143,27 +142,24 @@ func (n *SMTPNotifier) auth() (bool, error) {
|
||||||
log.Debug("Notifier SMTP client attempting AUTH LOGIN with server")
|
log.Debug("Notifier SMTP client attempting AUTH LOGIN with server")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Throw error since AUTH extension is not supported.
|
// Throw error since AUTH extension is not supported
|
||||||
if auth == nil {
|
if auth == nil {
|
||||||
return false, fmt.Errorf("notifier SMTP server does not advertise a AUTH mechanism that are supported by Authelia (PLAIN or LOGIN are supported, but server advertised %s mechanisms)", m)
|
return false, fmt.Errorf("notifier SMTP server does not advertise a AUTH mechanism that are supported by Authelia (PLAIN or LOGIN are supported, but server advertised %s mechanisms)", m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authenticate.
|
// Authenticate
|
||||||
err := n.client.Auth(auth)
|
err := n.client.Auth(auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
} else {
|
}
|
||||||
log.Debug("Notifier SMTP client authenticated successfully with the server")
|
log.Debug("Notifier SMTP client authenticated successfully with the server")
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return false, errors.New("Notifier SMTP server does not advertise the AUTH extension but config requires AUTH (password specified), either disable AUTH, or use an SMTP host that supports AUTH PLAIN or AUTH LOGIN")
|
return false, errors.New("Notifier SMTP server does not advertise the AUTH extension but config requires AUTH (password specified), either disable AUTH, or use an SMTP host that supports AUTH PLAIN or AUTH LOGIN")
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
log.Debug("Notifier SMTP config has no password specified so authentication is being skipped")
|
log.Debug("Notifier SMTP config has no password specified so authentication is being skipped")
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func (n *SMTPNotifier) compose(recipient, subject, body string) error {
|
func (n *SMTPNotifier) compose(recipient, subject, body string) error {
|
||||||
log.Debugf("Notifier SMTP client attempting to send email body to %s", recipient)
|
log.Debugf("Notifier SMTP client attempting to send email body to %s", recipient)
|
||||||
|
@ -185,7 +181,7 @@ func (n *SMTPNotifier) compose(recipient, subject, body string) error {
|
||||||
"MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n" +
|
"MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n" +
|
||||||
body
|
body
|
||||||
|
|
||||||
_, err = fmt.Fprintf(wc, msg)
|
_, err = fmt.Fprint(wc, msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("Notifier SMTP client error while sending email body over WriteCloser: %s", err)
|
log.Debugf("Notifier SMTP client error while sending email body over WriteCloser: %s", err)
|
||||||
return err
|
return err
|
||||||
|
@ -199,7 +195,7 @@ func (n *SMTPNotifier) compose(recipient, subject, body string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dial the SMTP server with the SMTPNotifier config.
|
// Dial the SMTP server with the SMTPNotifier config
|
||||||
func (n *SMTPNotifier) dial() error {
|
func (n *SMTPNotifier) dial() error {
|
||||||
log.Debugf("Notifier SMTP client attempting connection to %s", n.address)
|
log.Debugf("Notifier SMTP client attempting connection to %s", n.address)
|
||||||
if n.port == 465 {
|
if n.port == 465 {
|
||||||
|
@ -224,7 +220,7 @@ func (n *SMTPNotifier) dial() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Closes the connection properly.
|
// Closes the connection properly
|
||||||
func (n *SMTPNotifier) cleanup() {
|
func (n *SMTPNotifier) cleanup() {
|
||||||
err := n.client.Quit()
|
err := n.client.Quit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -239,10 +235,10 @@ func (n *SMTPNotifier) Send(recipient, title, body string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always execute QUIT at the end once we're connected.
|
// Always execute QUIT at the end once we're connected
|
||||||
defer n.cleanup()
|
defer n.cleanup()
|
||||||
|
|
||||||
// Start TLS and then Authenticate.
|
// Start TLS and then Authenticate
|
||||||
if _, err := n.startTLS(); err != nil {
|
if _, err := n.startTLS(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -250,7 +246,7 @@ func (n *SMTPNotifier) Send(recipient, title, body string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the sender and recipient first.
|
// Set the sender and recipient first
|
||||||
if err := n.client.Mail(n.sender); err != nil {
|
if err := n.client.Mail(n.sender); err != nil {
|
||||||
log.Debugf("Notifier SMTP failed while sending MAIL FROM (using sender) with error: %s", err)
|
log.Debugf("Notifier SMTP failed while sending MAIL FROM (using sender) with error: %s", err)
|
||||||
return err
|
return err
|
||||||
|
@ -260,7 +256,7 @@ func (n *SMTPNotifier) Send(recipient, title, body string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compose and send the email body to the server.
|
// Compose and send the email body to the server
|
||||||
if err := n.compose(recipient, subject, body); err != nil {
|
if err := n.compose(recipient, subject, body); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ func (s *RegulatorSuite) TearDownTest() {
|
||||||
|
|
||||||
func (s *RegulatorSuite) TestShouldNotThrowWhenUserIsLegitimate() {
|
func (s *RegulatorSuite) TestShouldNotThrowWhenUserIsLegitimate() {
|
||||||
attemptsInDB := []models.AuthenticationAttempt{
|
attemptsInDB := []models.AuthenticationAttempt{
|
||||||
models.AuthenticationAttempt{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: true,
|
Successful: true,
|
||||||
Time: s.clock.Now().Add(-4 * time.Minute),
|
Time: s.clock.Now().Add(-4 * time.Minute),
|
||||||
|
@ -63,17 +63,17 @@ func (s *RegulatorSuite) TestShouldNotThrowWhenUserIsLegitimate() {
|
||||||
// with a certain amount of time larger than FindTime. Meaning the user should not be banned.
|
// with a certain amount of time larger than FindTime. Meaning the user should not be banned.
|
||||||
func (s *RegulatorSuite) TestShouldNotThrowWhenFailedAuthenticationNotInFindTime() {
|
func (s *RegulatorSuite) TestShouldNotThrowWhenFailedAuthenticationNotInFindTime() {
|
||||||
attemptsInDB := []models.AuthenticationAttempt{
|
attemptsInDB := []models.AuthenticationAttempt{
|
||||||
models.AuthenticationAttempt{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-1 * time.Second),
|
Time: s.clock.Now().Add(-1 * time.Second),
|
||||||
},
|
},
|
||||||
models.AuthenticationAttempt{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-90 * time.Second),
|
Time: s.clock.Now().Add(-90 * time.Second),
|
||||||
},
|
},
|
||||||
models.AuthenticationAttempt{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-180 * time.Second),
|
Time: s.clock.Now().Add(-180 * time.Second),
|
||||||
|
@ -94,22 +94,22 @@ func (s *RegulatorSuite) TestShouldNotThrowWhenFailedAuthenticationNotInFindTime
|
||||||
// seconds ago (meaning we are checking from now back to now-FindTime).
|
// seconds ago (meaning we are checking from now back to now-FindTime).
|
||||||
func (s *RegulatorSuite) TestShouldBanUserIfLatestAttemptsAreWithinFinTime() {
|
func (s *RegulatorSuite) TestShouldBanUserIfLatestAttemptsAreWithinFinTime() {
|
||||||
attemptsInDB := []models.AuthenticationAttempt{
|
attemptsInDB := []models.AuthenticationAttempt{
|
||||||
models.AuthenticationAttempt{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-1 * time.Second),
|
Time: s.clock.Now().Add(-1 * time.Second),
|
||||||
},
|
},
|
||||||
models.AuthenticationAttempt{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-4 * time.Second),
|
Time: s.clock.Now().Add(-4 * time.Second),
|
||||||
},
|
},
|
||||||
models.AuthenticationAttempt{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-6 * time.Second),
|
Time: s.clock.Now().Add(-6 * time.Second),
|
||||||
},
|
},
|
||||||
models.AuthenticationAttempt{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-180 * time.Second),
|
Time: s.clock.Now().Add(-180 * time.Second),
|
||||||
|
@ -132,17 +132,17 @@ func (s *RegulatorSuite) TestShouldBanUserIfLatestAttemptsAreWithinFinTime() {
|
||||||
// banned right now.
|
// banned right now.
|
||||||
func (s *RegulatorSuite) TestShouldCheckUserIsStillBanned() {
|
func (s *RegulatorSuite) TestShouldCheckUserIsStillBanned() {
|
||||||
attemptsInDB := []models.AuthenticationAttempt{
|
attemptsInDB := []models.AuthenticationAttempt{
|
||||||
models.AuthenticationAttempt{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-31 * time.Second),
|
Time: s.clock.Now().Add(-31 * time.Second),
|
||||||
},
|
},
|
||||||
models.AuthenticationAttempt{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-34 * time.Second),
|
Time: s.clock.Now().Add(-34 * time.Second),
|
||||||
},
|
},
|
||||||
models.AuthenticationAttempt{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-36 * time.Second),
|
Time: s.clock.Now().Add(-36 * time.Second),
|
||||||
|
@ -161,12 +161,12 @@ func (s *RegulatorSuite) TestShouldCheckUserIsStillBanned() {
|
||||||
|
|
||||||
func (s *RegulatorSuite) TestShouldCheckUserIsNotYetBanned() {
|
func (s *RegulatorSuite) TestShouldCheckUserIsNotYetBanned() {
|
||||||
attemptsInDB := []models.AuthenticationAttempt{
|
attemptsInDB := []models.AuthenticationAttempt{
|
||||||
models.AuthenticationAttempt{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-34 * time.Second),
|
Time: s.clock.Now().Add(-34 * time.Second),
|
||||||
},
|
},
|
||||||
models.AuthenticationAttempt{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-36 * time.Second),
|
Time: s.clock.Now().Add(-36 * time.Second),
|
||||||
|
@ -185,7 +185,7 @@ func (s *RegulatorSuite) TestShouldCheckUserIsNotYetBanned() {
|
||||||
|
|
||||||
func (s *RegulatorSuite) TestShouldCheckUserWasAboutToBeBanned() {
|
func (s *RegulatorSuite) TestShouldCheckUserWasAboutToBeBanned() {
|
||||||
attemptsInDB := []models.AuthenticationAttempt{
|
attemptsInDB := []models.AuthenticationAttempt{
|
||||||
models.AuthenticationAttempt{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-14 * time.Second),
|
Time: s.clock.Now().Add(-14 * time.Second),
|
||||||
|
@ -193,12 +193,12 @@ func (s *RegulatorSuite) TestShouldCheckUserWasAboutToBeBanned() {
|
||||||
// more than 30 seconds elapsed between this auth and the preceding one.
|
// more than 30 seconds elapsed between this auth and the preceding one.
|
||||||
// In that case we don't need to regulate the user even though the number
|
// In that case we don't need to regulate the user even though the number
|
||||||
// of retrieved attempts is 3.
|
// of retrieved attempts is 3.
|
||||||
models.AuthenticationAttempt{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-94 * time.Second),
|
Time: s.clock.Now().Add(-94 * time.Second),
|
||||||
},
|
},
|
||||||
models.AuthenticationAttempt{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-96 * time.Second),
|
Time: s.clock.Now().Add(-96 * time.Second),
|
||||||
|
@ -217,24 +217,24 @@ func (s *RegulatorSuite) TestShouldCheckUserWasAboutToBeBanned() {
|
||||||
|
|
||||||
func (s *RegulatorSuite) TestShouldCheckRegulationHasBeenResetOnSuccessfulAttempt() {
|
func (s *RegulatorSuite) TestShouldCheckRegulationHasBeenResetOnSuccessfulAttempt() {
|
||||||
attemptsInDB := []models.AuthenticationAttempt{
|
attemptsInDB := []models.AuthenticationAttempt{
|
||||||
models.AuthenticationAttempt{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-90 * time.Second),
|
Time: s.clock.Now().Add(-90 * time.Second),
|
||||||
},
|
},
|
||||||
models.AuthenticationAttempt{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: true,
|
Successful: true,
|
||||||
Time: s.clock.Now().Add(-93 * time.Second),
|
Time: s.clock.Now().Add(-93 * time.Second),
|
||||||
},
|
},
|
||||||
// The user was almost banned but he did a successful attempt. Therefore, even if the next
|
// The user was almost banned but he did a successful attempt. Therefore, even if the next
|
||||||
// failure happens within FindTime, he should not be banned.
|
// failure happens within FindTime, he should not be banned.
|
||||||
models.AuthenticationAttempt{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-94 * time.Second),
|
Time: s.clock.Now().Add(-94 * time.Second),
|
||||||
},
|
},
|
||||||
models.AuthenticationAttempt{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-96 * time.Second),
|
Time: s.clock.Now().Add(-96 * time.Second),
|
||||||
|
@ -259,17 +259,17 @@ func TestRunRegulatorSuite(t *testing.T) {
|
||||||
// This test checks that the regulator is disabled when configuration is set to 0.
|
// This test checks that the regulator is disabled when configuration is set to 0.
|
||||||
func (s *RegulatorSuite) TestShouldHaveRegulatorDisabled() {
|
func (s *RegulatorSuite) TestShouldHaveRegulatorDisabled() {
|
||||||
attemptsInDB := []models.AuthenticationAttempt{
|
attemptsInDB := []models.AuthenticationAttempt{
|
||||||
models.AuthenticationAttempt{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-31 * time.Second),
|
Time: s.clock.Now().Add(-31 * time.Second),
|
||||||
},
|
},
|
||||||
models.AuthenticationAttempt{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-34 * time.Second),
|
Time: s.clock.Now().Add(-34 * time.Second),
|
||||||
},
|
},
|
||||||
models.AuthenticationAttempt{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-36 * time.Second),
|
Time: s.clock.Now().Add(-36 * time.Second),
|
||||||
|
|
|
@ -106,6 +106,6 @@ func TestShouldUseEncryptingSerializerWithRedis(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
decoded := session.Dict{}
|
decoded := session.Dict{}
|
||||||
_, err = decoded.UnmarshalMsg(decrypted)
|
_, _ = decoded.UnmarshalMsg(decrypted)
|
||||||
assert.Equal(t, "value", decoded.Get("key"))
|
assert.Equal(t, "value", decoded.Get("key"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,19 +81,16 @@ func (p *SQLProvider) initialize(db *sql.DB) error {
|
||||||
// LoadPreferred2FAMethod load the preferred method for 2FA from sqlite db.
|
// LoadPreferred2FAMethod load the preferred method for 2FA from sqlite db.
|
||||||
func (p *SQLProvider) LoadPreferred2FAMethod(username string) (string, error) {
|
func (p *SQLProvider) LoadPreferred2FAMethod(username string) (string, error) {
|
||||||
rows, err := p.db.Query(p.sqlGetPreferencesByUsername, username)
|
rows, err := p.db.Query(p.sqlGetPreferencesByUsername, username)
|
||||||
defer rows.Close()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if rows.Next() {
|
defer rows.Close()
|
||||||
|
if !rows.Next() {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
var method string
|
var method string
|
||||||
err = rows.Scan(&method)
|
err = rows.Scan(&method)
|
||||||
if err != nil {
|
return method, err
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return method, nil
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SavePreferred2FAMethod save the preferred method for 2FA in sqlite db.
|
// SavePreferred2FAMethod save the preferred method for 2FA in sqlite db.
|
||||||
|
|
|
@ -64,7 +64,7 @@ access_control:
|
||||||
regulation:
|
regulation:
|
||||||
max_retries: 3
|
max_retries: 3
|
||||||
find_time: 3
|
find_time: 3
|
||||||
ban_time: 5
|
ban_time: 10
|
||||||
|
|
||||||
notifier:
|
notifier:
|
||||||
smtp:
|
smtp:
|
||||||
|
|
|
@ -50,7 +50,7 @@ func (wds *WebDriverSession) doLoginAndRegisterTOTP(ctx context.Context, t *test
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register a user with TOTP, logout and then authenticate until TOTP-2FA.
|
// Register a user with TOTP, logout and then authenticate until TOTP-2FA.
|
||||||
func (wds *WebDriverSession) doRegisterAndLogin2FA(ctx context.Context, t *testing.T, username, password string, keepMeLoggedIn bool, targetURL string) string {
|
func (wds *WebDriverSession) doRegisterAndLogin2FA(ctx context.Context, t *testing.T, username, password string, keepMeLoggedIn bool, targetURL string) string { //nolint:unparam
|
||||||
// Register TOTP secret and logout.
|
// Register TOTP secret and logout.
|
||||||
secret := wds.doRegisterThenLogout(ctx, t, username, password)
|
secret := wds.doRegisterThenLogout(ctx, t, username, password)
|
||||||
wds.doLoginTwoFactor(ctx, t, username, password, false, secret, targetURL)
|
wds.doLoginTwoFactor(ctx, t, username, password, false, secret, targetURL)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package suites
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -10,6 +11,7 @@ import (
|
||||||
"github.com/authelia/authelia/internal/utils"
|
"github.com/authelia/authelia/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//nolint:unparam
|
||||||
func waitUntilServiceLogDetected(
|
func waitUntilServiceLogDetected(
|
||||||
interval time.Duration,
|
interval time.Duration,
|
||||||
timeout time.Duration,
|
timeout time.Duration,
|
||||||
|
@ -61,9 +63,11 @@ func waitUntilAutheliaIsReady(dockerEnvironment *DockerEnvironment) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if os.Getenv("CI") != "true" {
|
||||||
if err := waitUntilAutheliaFrontendIsReady(dockerEnvironment); err != nil {
|
if err := waitUntilAutheliaFrontendIsReady(dockerEnvironment); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
log.Info("Authelia is now ready!")
|
log.Info("Authelia is now ready!")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4579,7 +4579,7 @@ coredump_dir /var/spool/squid
|
||||||
# The default is to use HTTP request URL as the store ID.
|
# The default is to use HTTP request URL as the store ID.
|
||||||
#
|
#
|
||||||
# BH
|
# BH
|
||||||
# An internal error occured in the helper, preventing
|
# An internal error occurred in the helper, preventing
|
||||||
# a result being identified.
|
# a result being identified.
|
||||||
#
|
#
|
||||||
# In addition to the above kv-pairs Squid also understands the following
|
# In addition to the above kv-pairs Squid also understands the following
|
||||||
|
|
|
@ -67,7 +67,6 @@ func (s *RegulationScenario) TestShouldBanUserAfterTooManyAttempt() {
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
s.verifyIsFirstFactorPage(ctx, s.T())
|
s.verifyIsFirstFactorPage(ctx, s.T())
|
||||||
|
|
||||||
time.Sleep(9 * time.Second)
|
time.Sleep(9 * time.Second)
|
||||||
|
|
||||||
// Enter the correct password and test a successful login
|
// Enter the correct password and test a successful login
|
||||||
|
|
|
@ -25,7 +25,7 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaBackendIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment)
|
||||||
}
|
}
|
||||||
|
|
||||||
onSetupTimeout := func() error {
|
onSetupTimeout := func() error {
|
||||||
|
|
|
@ -21,7 +21,7 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaBackendIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment)
|
||||||
}
|
}
|
||||||
|
|
||||||
onSetupTimeout := func() error {
|
onSetupTimeout := func() error {
|
||||||
|
|
|
@ -23,7 +23,7 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaBackendIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment)
|
||||||
}
|
}
|
||||||
|
|
||||||
onSetupTimeout := func() error {
|
onSetupTimeout := func() error {
|
||||||
|
|
|
@ -25,7 +25,7 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaBackendIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment)
|
||||||
}
|
}
|
||||||
|
|
||||||
onSetupTimeout := func() error {
|
onSetupTimeout := func() error {
|
||||||
|
|
|
@ -28,7 +28,7 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaBackendIsReady(haDockerEnvironment)
|
return waitUntilAutheliaIsReady(haDockerEnvironment)
|
||||||
}
|
}
|
||||||
|
|
||||||
displayAutheliaLogs := func() error {
|
displayAutheliaLogs := func() error {
|
||||||
|
|
|
@ -134,7 +134,7 @@ var expectedAuthorizations = map[string](map[string]bool){
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HighAvailabilityWebDriverSuite) TestShouldVerifyAccessControl() {
|
func (s *HighAvailabilityWebDriverSuite) TestShouldVerifyAccessControl() {
|
||||||
verifyUserIsAuthorized := func(ctx context.Context, t *testing.T, username, targetURL string, authorized bool) {
|
verifyUserIsAuthorized := func(ctx context.Context, t *testing.T, username, targetURL string, authorized bool) { //nolint:unparam
|
||||||
s.doVisit(t, targetURL)
|
s.doVisit(t, targetURL)
|
||||||
s.verifyURLIs(ctx, t, targetURL)
|
s.verifyURLIs(ctx, t, targetURL)
|
||||||
if authorized {
|
if authorized {
|
||||||
|
@ -161,8 +161,8 @@ func (s *HighAvailabilityWebDriverSuite) TestShouldVerifyAccessControl() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, user := range []string{UserJohn, UserBob, UserHarry} {
|
for _, user := range Users {
|
||||||
s.T().Run(fmt.Sprintf("%s", user), verifyAuthorization(user))
|
s.T().Run(user, verifyAuthorization(user))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaBackendIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment)
|
||||||
}
|
}
|
||||||
|
|
||||||
displayAutheliaLogs := func() error {
|
displayAutheliaLogs := func() error {
|
||||||
|
|
|
@ -25,7 +25,7 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaBackendIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment)
|
||||||
}
|
}
|
||||||
|
|
||||||
onSetupTimeout := func() error {
|
onSetupTimeout := func() error {
|
||||||
|
|
|
@ -25,7 +25,7 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaBackendIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment)
|
||||||
}
|
}
|
||||||
|
|
||||||
onSetupTimeout := func() error {
|
onSetupTimeout := func() error {
|
||||||
|
|
|
@ -26,7 +26,7 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaBackendIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment)
|
||||||
}
|
}
|
||||||
|
|
||||||
onSetupTimeout := func() error {
|
onSetupTimeout := func() error {
|
||||||
|
|
|
@ -22,7 +22,7 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaBackendIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment)
|
||||||
}
|
}
|
||||||
|
|
||||||
onSetupTimeout := func() error {
|
onSetupTimeout := func() error {
|
||||||
|
|
|
@ -25,7 +25,7 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaBackendIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment)
|
||||||
}
|
}
|
||||||
|
|
||||||
onSetupTimeout := func() error {
|
onSetupTimeout := func() error {
|
||||||
|
|
|
@ -23,7 +23,7 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaBackendIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment)
|
||||||
}
|
}
|
||||||
|
|
||||||
onSetupTimeout := func() error {
|
onSetupTimeout := func() error {
|
||||||
|
|
|
@ -25,7 +25,7 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaBackendIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment)
|
||||||
}
|
}
|
||||||
|
|
||||||
displayAutheliaLogs := func() error {
|
displayAutheliaLogs := func() error {
|
||||||
|
|
|
@ -25,7 +25,7 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaBackendIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment)
|
||||||
}
|
}
|
||||||
|
|
||||||
onSetupTimeout := func() error {
|
onSetupTimeout := func() error {
|
||||||
|
|
|
@ -25,7 +25,7 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaBackendIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment)
|
||||||
}
|
}
|
||||||
|
|
||||||
onSetupTimeout := func() error {
|
onSetupTimeout := func() error {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func isURLSafe(requestURI string, domain string) bool {
|
func isURLSafe(requestURI string, domain string) bool { //nolint:unparam
|
||||||
url, _ := url.ParseRequestURI(requestURI)
|
url, _ := url.ParseRequestURI(requestURI)
|
||||||
return IsRedirectionSafe(*url, domain)
|
return IsRedirectionSafe(*url, domain)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,17 @@
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Parses a string to a duration
|
// ParseDurationString parses a string to a duration
|
||||||
// Duration notations are an integer followed by a unit
|
// Duration notations are an integer followed by a unit
|
||||||
// Units are s = second, m = minute, d = day, w = week, M = month, y = year
|
// Units are s = second, m = minute, d = day, w = week, M = month, y = year
|
||||||
// Example 1y is the same as 1 year
|
// Example 1y is the same as 1 year
|
||||||
func ParseDurationString(input string) (duration time.Duration, err error) {
|
func ParseDurationString(input string) (time.Duration, error) {
|
||||||
duration = 0
|
var duration time.Duration
|
||||||
err = nil
|
|
||||||
matches := parseDurationRegexp.FindStringSubmatch(input)
|
matches := parseDurationRegexp.FindStringSubmatch(input)
|
||||||
if len(matches) == 3 && matches[2] != "" {
|
if len(matches) == 3 && matches[2] != "" {
|
||||||
d, _ := strconv.Atoi(matches[1])
|
d, _ := strconv.Atoi(matches[1])
|
||||||
|
@ -36,13 +34,12 @@ func ParseDurationString(input string) (duration time.Duration, err error) {
|
||||||
} else if input == "0" || len(matches) == 3 {
|
} else if input == "0" || len(matches) == 3 {
|
||||||
seconds, err := strconv.Atoi(input)
|
seconds, err := strconv.Atoi(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.New(fmt.Sprintf("could not convert the input string of %s into a duration: %s", input, err))
|
return 0, fmt.Errorf("Could not convert the input string of %s into a duration: %s", input, err)
|
||||||
} else {
|
|
||||||
duration = time.Duration(seconds) * time.Second
|
|
||||||
}
|
}
|
||||||
|
duration = time.Duration(seconds) * time.Second
|
||||||
} else if input != "" {
|
} else if input != "" {
|
||||||
// Throw this error if input is anything other than a blank string, blank string will default to a duration of nothing
|
// Throw this error if input is anything other than a blank string, blank string will default to a duration of nothing
|
||||||
err = errors.New(fmt.Sprintf("could not convert the input string of %s into a duration", input))
|
return 0, fmt.Errorf("Could not convert the input string of %s into a duration", input)
|
||||||
}
|
}
|
||||||
return
|
return duration, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,25 +47,25 @@ func TestShouldParseSecondsString(t *testing.T) {
|
||||||
|
|
||||||
func TestShouldNotParseDurationStringWithOutOfOrderQuantitiesAndUnits(t *testing.T) {
|
func TestShouldNotParseDurationStringWithOutOfOrderQuantitiesAndUnits(t *testing.T) {
|
||||||
duration, err := ParseDurationString("h1")
|
duration, err := ParseDurationString("h1")
|
||||||
assert.EqualError(t, err, "could not convert the input string of h1 into a duration")
|
assert.EqualError(t, err, "Could not convert the input string of h1 into a duration")
|
||||||
assert.Equal(t, time.Duration(0), duration)
|
assert.Equal(t, time.Duration(0), duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldNotParseBadDurationString(t *testing.T) {
|
func TestShouldNotParseBadDurationString(t *testing.T) {
|
||||||
duration, err := ParseDurationString("10x")
|
duration, err := ParseDurationString("10x")
|
||||||
assert.EqualError(t, err, "could not convert the input string of 10x into a duration")
|
assert.EqualError(t, err, "Could not convert the input string of 10x into a duration")
|
||||||
assert.Equal(t, time.Duration(0), duration)
|
assert.Equal(t, time.Duration(0), duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldNotParseDurationStringWithMultiValueUnits(t *testing.T) {
|
func TestShouldNotParseDurationStringWithMultiValueUnits(t *testing.T) {
|
||||||
duration, err := ParseDurationString("10ms")
|
duration, err := ParseDurationString("10ms")
|
||||||
assert.EqualError(t, err, "could not convert the input string of 10ms into a duration")
|
assert.EqualError(t, err, "Could not convert the input string of 10ms into a duration")
|
||||||
assert.Equal(t, time.Duration(0), duration)
|
assert.Equal(t, time.Duration(0), duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldNotParseDurationStringWithLeadingZero(t *testing.T) {
|
func TestShouldNotParseDurationStringWithLeadingZero(t *testing.T) {
|
||||||
duration, err := ParseDurationString("005h")
|
duration, err := ParseDurationString("005h")
|
||||||
assert.EqualError(t, err, "could not convert the input string of 005h into a duration")
|
assert.EqualError(t, err, "Could not convert the input string of 005h into a duration")
|
||||||
assert.Equal(t, time.Duration(0), duration)
|
assert.Equal(t, time.Duration(0), duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue