summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorJames Elliott <james-d-elliott@users.noreply.github.com>2023-09-29 08:46:41 +1000
committerGitHub <noreply@github.com>2023-09-29 08:46:41 +1000
commit6a6059dc228b20fe13aee274188911d00458fe24 (patch)
tree775d837651f575671afdef6b89536cf85f7dad33 /internal
parent1a96b5c3c1997006a1830d9ca35ff806906f58b5 (diff)
feat(session): redirection by cookie domain (#6017)
This allows configuring the default redirection URL by session domain. In addition it makes the Authelia URL option in the new session config mandatory at least for the time being. Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
Diffstat (limited to 'internal')
-rw-r--r--internal/configuration/config.template.yml27
-rw-r--r--internal/configuration/schema/configuration.go14
-rw-r--r--internal/configuration/schema/keys.go2
-rw-r--r--internal/configuration/schema/session.go9
-rw-r--r--internal/configuration/schema/types.go2
-rw-r--r--internal/configuration/validator/configuration.go9
-rw-r--r--internal/configuration/validator/configuration_test.go8
-rw-r--r--internal/configuration/validator/const.go27
-rw-r--r--internal/configuration/validator/session.go110
-rw-r--r--internal/configuration/validator/session_test.go403
-rw-r--r--internal/handlers/const_test.go20
-rw-r--r--internal/handlers/handler_firstfactor_test.go7
-rw-r--r--internal/handlers/handler_sign_duo_test.go4
-rw-r--r--internal/handlers/handler_sign_totp_test.go6
-rw-r--r--internal/handlers/handler_state.go9
-rw-r--r--internal/handlers/handler_state_test.go4
-rw-r--r--internal/handlers/response.go18
-rw-r--r--internal/middlewares/authelia_context.go9
-rw-r--r--internal/middlewares/authelia_context_test.go20
-rw-r--r--internal/mocks/authelia_ctx.go12
-rw-r--r--internal/suites/ActiveDirectory/configuration.yml2
-rw-r--r--internal/suites/Docker/configuration.yml2
-rw-r--r--internal/suites/DuoPush/configuration.yml2
-rw-r--r--internal/suites/LDAP/configuration.yml2
-rw-r--r--internal/suites/MariaDB/configuration.yml2
-rw-r--r--internal/suites/MySQL/configuration.yml3
-rw-r--r--internal/suites/OneFactorOnly/configuration.yml2
-rw-r--r--internal/suites/Postgres/configuration.yml2
-rw-r--r--internal/suites/ShortTimeouts/configuration.yml2
-rw-r--r--internal/suites/example/kube/authelia/configs/configuration.yml3
-rw-r--r--internal/utils/url.go35
-rw-r--r--internal/utils/url_test.go23
32 files changed, 529 insertions, 271 deletions
diff --git a/internal/configuration/config.template.yml b/internal/configuration/config.template.yml
index 2f9641776..cfb9585ea 100644
--- a/internal/configuration/config.template.yml
+++ b/internal/configuration/config.template.yml
@@ -27,15 +27,6 @@ theme: 'light'
## set using a secret: https://www.authelia.com/c/secrets
jwt_secret: 'a_very_important_secret'
-## Default redirection URL
-##
-## If user tries to authenticate without any referer, Authelia does not know where to redirect the user to at the end
-## of the authentication process. This parameter allows you to specify the default redirection URL Authelia will use
-## in such a case.
-##
-## Note: this parameter is optional. If not provided, user won't be redirected upon successful authentication.
-# default_redirection_url: 'https://home.example.com/'
-
## Set the default 2FA method for new users and for when a user has a preferred method configured that has been
## disabled. This setting must be a method that is enabled.
## Options are totp, webauthn, mobile_push.
@@ -708,14 +699,28 @@ session:
## Note: the Authelia portal must also be in that domain.
# domain: 'example.com'
- ## Optional. The fully qualified URI of the portal to redirect users to on proxies that support redirections.
+ ## Required. The fully qualified URI of the portal to redirect users to on proxies that support redirections.
## Rules:
## - MUST use the secure scheme 'https://'
- ## - The above domain MUST either:
+ ## - The above 'domain' option MUST either:
## - Match the host portion of this URI.
## - Match the suffix of the host portion when prefixed with '.'.
# authelia_url: 'https://auth.example.com'
+ ## Optional. The fully qualified URI used as the redirection location if the portal is accessed directly. Not
+ ## configuring this option disables the automatic redirection behaviour.
+ ##
+ ## Note: this parameter is optional. If not provided, user won't be redirected upon successful authentication
+ ## unless they were redirected to Authelia by the proxy.
+ ##
+ ## Rules:
+ ## - MUST use the secure scheme 'https://'
+ ## - MUST not match the 'authelia_url' option.
+ ## - The above 'domain' option MUST either:
+ ## - Match the host portion of this URI.
+ ## - Match the suffix of the host portion when prefixed with '.'.
+ # default_redirection_url: 'https://www.example.com'
+
## Sets the Cookie SameSite value. Possible options are none, lax, or strict.
## Please read https://www.authelia.com/c/session#same_site
# same_site: 'lax'
diff --git a/internal/configuration/schema/configuration.go b/internal/configuration/schema/configuration.go
index 3761aad3d..3fdd09f79 100644
--- a/internal/configuration/schema/configuration.go
+++ b/internal/configuration/schema/configuration.go
@@ -1,12 +1,16 @@
package schema
+import (
+ "net/url"
+)
+
// Configuration object extracted from YAML configuration file.
type Configuration struct {
- Theme string `koanf:"theme" json:"theme" jsonschema:"default=light,enum=auto,enum=light,enum=dark,enum=grey,title=Theme Name" jsonschema_description:"The name of the theme to apply to the web UI"`
- CertificatesDirectory string `koanf:"certificates_directory" json:"certificates_directory" jsonschema:"title=Certificates Directory Path" jsonschema_description:"The path to a directory which is used to determine the certificates that are trusted"`
- JWTSecret string `koanf:"jwt_secret" json:"jwt_secret" jsonschema:"title=Secret Key for JWT's" jsonschema_description:"Used for signing HS256 JWT's for identity verification"`
- DefaultRedirectionURL string `koanf:"default_redirection_url" json:"default_redirection_url" jsonschema:"title=The default redirection URL" jsonschema_description:"Used to redirect users when they visit the portal directly"`
- Default2FAMethod string `koanf:"default_2fa_method" json:"default_2fa_method" jsonschema:"enum=totp,enum=webauthn,enum=mobile_push,title=Default 2FA method" jsonschema_description:"When a user logs in for the first time this is the 2FA method configured for them"`
+ Theme string `koanf:"theme" json:"theme" jsonschema:"default=light,enum=auto,enum=light,enum=dark,enum=grey,title=Theme Name" jsonschema_description:"The name of the theme to apply to the web UI"`
+ CertificatesDirectory string `koanf:"certificates_directory" json:"certificates_directory" jsonschema:"title=Certificates Directory Path" jsonschema_description:"The path to a directory which is used to determine the certificates that are trusted"`
+ JWTSecret string `koanf:"jwt_secret" json:"jwt_secret" jsonschema:"title=Secret Key for JWT's" jsonschema_description:"Used for signing HS256 JWT's for identity verification"`
+ DefaultRedirectionURL *url.URL `koanf:"default_redirection_url" json:"default_redirection_url" jsonschema:"format=uri,title=The default redirection URL" jsonschema_description:"Used to redirect users when they visit the portal directly"`
+ Default2FAMethod string `koanf:"default_2fa_method" json:"default_2fa_method" jsonschema:"enum=totp,enum=webauthn,enum=mobile_push,title=Default 2FA method" jsonschema_description:"When a user logs in for the first time this is the 2FA method configured for them"`
Log Log `koanf:"log" json:"log" jsonschema:"title=Log" jsonschema_description:"Logging Configuration"`
IdentityProviders IdentityProviders `koanf:"identity_providers" json:"identity_providers" jsonschema:"title=Identity Providers" jsonschema_description:"Identity Providers Configuration"`
diff --git a/internal/configuration/schema/keys.go b/internal/configuration/schema/keys.go
index 7d0ee6c68..ed755626f 100644
--- a/internal/configuration/schema/keys.go
+++ b/internal/configuration/schema/keys.go
@@ -188,6 +188,8 @@ var Keys = []string{
"session.cookies[]",
"session.cookies[].domain",
"session.cookies[].authelia_url",
+ "session.cookies[].default_redirection_url",
+ "session.cookies[]",
"session.redis.host",
"session.redis.port",
"session.redis.username",
diff --git a/internal/configuration/schema/session.go b/internal/configuration/schema/session.go
index 36048051a..71d66e916 100644
--- a/internal/configuration/schema/session.go
+++ b/internal/configuration/schema/session.go
@@ -27,15 +27,18 @@ type SessionCookieCommon struct {
Inactivity time.Duration `koanf:"inactivity" json:"inactivity" jsonschema:"default=5 minutes"`
RememberMe time.Duration `koanf:"remember_me" json:"remember_me" jsonschema:"default=30 days"`
- DisableRememberMe bool
+ DisableRememberMe bool `json:"-"`
}
// SessionCookie represents the configuration for a cookie domain.
type SessionCookie struct {
SessionCookieCommon `koanf:",squash"`
- Domain string `koanf:"domain" json:"domain" jsonschema:"format=hostname,title=Domain" jsonschema_description:"The domain for this session cookie"`
- AutheliaURL *url.URL `koanf:"authelia_url" json:"authelia_url" jsonschema:"format=uri,title=Authelia URL" jsonschema_description:"The Root Authelia URL to redirect users to for this session cookie"`
+ Domain string `koanf:"domain" json:"domain" jsonschema:"format=hostname,title=Domain" jsonschema_description:"The domain for this session cookie"`
+ AutheliaURL *url.URL `koanf:"authelia_url" json:"authelia_url" jsonschema:"format=uri,title=Authelia URL" jsonschema_description:"The Root Authelia URL to redirect users to for this session cookie"`
+ DefaultRedirectionURL *url.URL `koanf:"default_redirection_url" json:"default_redirection_url" jsonschema:"format=uri,title=Default Redirection URL" jsonschema_description:"The default redirection URL for this cookie domain"`
+
+ Legacy bool `json:"-"`
}
// SessionRedis represents the configuration related to redis session store.
diff --git a/internal/configuration/schema/types.go b/internal/configuration/schema/types.go
index 7c4969a97..be23104ba 100644
--- a/internal/configuration/schema/types.go
+++ b/internal/configuration/schema/types.go
@@ -19,7 +19,7 @@ import (
"github.com/go-crypt/crypt/algorithm"
"github.com/go-crypt/crypt/algorithm/plaintext"
"github.com/valyala/fasthttp"
- yaml "gopkg.in/yaml.v3"
+ "gopkg.in/yaml.v3"
)
var cdecoder algorithm.DecoderRegister
diff --git a/internal/configuration/validator/configuration.go b/internal/configuration/validator/configuration.go
index df3d5e328..e7e45fbfb 100644
--- a/internal/configuration/validator/configuration.go
+++ b/internal/configuration/validator/configuration.go
@@ -3,7 +3,6 @@ package validator
import (
"fmt"
"os"
- "strings"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/utils"
@@ -27,10 +26,8 @@ func ValidateConfiguration(config *schema.Configuration, validator *schema.Struc
validator.Push(fmt.Errorf("option 'jwt_secret' is required"))
}
- if config.DefaultRedirectionURL != "" {
- if err = utils.IsStringAbsURL(config.DefaultRedirectionURL); err != nil {
- validator.Push(fmt.Errorf("option 'default_redirection_url' is invalid: %s", strings.ReplaceAll(err.Error(), "like 'http://' or 'https://'", "like 'ldap://' or 'ldaps://'")))
- }
+ if config.DefaultRedirectionURL != nil && !config.DefaultRedirectionURL.IsAbs() {
+ validator.Push(fmt.Errorf("option 'default_redirection_url' is invalid: the url '%s' is not absolute", config.DefaultRedirectionURL.String()))
}
validateDefault2FAMethod(config, validator)
@@ -51,7 +48,7 @@ func ValidateConfiguration(config *schema.Configuration, validator *schema.Struc
ValidateRules(config, validator)
- ValidateSession(&config.Session, validator)
+ ValidateSession(config, validator)
ValidateRegulation(config, validator)
diff --git a/internal/configuration/validator/configuration_test.go b/internal/configuration/validator/configuration_test.go
index 6054e53c8..984aaf9f6 100644
--- a/internal/configuration/validator/configuration_test.go
+++ b/internal/configuration/validator/configuration_test.go
@@ -2,6 +2,7 @@ package validator
import (
"fmt"
+ "net/url"
"runtime"
"testing"
@@ -30,7 +31,8 @@ func newDefaultConfig() schema.Configuration {
SessionCookieCommon: schema.SessionCookieCommon{
Name: "authelia_session",
},
- Domain: exampleDotCom,
+ Domain: exampleDotCom,
+ AutheliaURL: &url.URL{Scheme: "https", Host: "auth." + exampleDotCom},
},
},
}
@@ -100,13 +102,13 @@ func TestShouldRaiseErrorWithUndefinedJWTSecretKey(t *testing.T) {
func TestShouldRaiseErrorWithBadDefaultRedirectionURL(t *testing.T) {
validator := schema.NewStructValidator()
config := newDefaultConfig()
- config.DefaultRedirectionURL = "bad_default_redirection_url"
+ config.DefaultRedirectionURL = &url.URL{Host: "localhost"}
ValidateConfiguration(&config, validator)
require.Len(t, validator.Errors(), 1)
require.Len(t, validator.Warnings(), 1)
- assert.EqualError(t, validator.Errors()[0], "option 'default_redirection_url' is invalid: could not parse 'bad_default_redirection_url' as a URL")
+ assert.EqualError(t, validator.Errors()[0], "option 'default_redirection_url' is invalid: the url '//localhost' is not absolute")
assert.EqualError(t, validator.Warnings()[0], "access control: no rules have been specified so the 'default_policy' of 'two_factor' is going to be applied to all requests")
}
diff --git a/internal/configuration/validator/const.go b/internal/configuration/validator/const.go
index fe5338563..01b7cd065 100644
--- a/internal/configuration/validator/const.go
+++ b/internal/configuration/validator/const.go
@@ -322,17 +322,19 @@ const (
errFmtSessionRedisSentinelMissingName = "session: redis: high_availability: option 'sentinel_name' is required"
errFmtSessionRedisSentinelNodeHostMissing = "session: redis: high_availability: option 'nodes': option 'host' is required for each node but one or more nodes are missing this"
- errFmtSessionDomainMustBeRoot = "session: domain config %s: option 'domain' must be the domain you wish to protect not a wildcard domain but it's configured as '%s'"
- errFmtSessionDomainSameSite = "session: domain config %s: option 'same_site' must be one of %s but it's configured as '%s'"
- errFmtSessionDomainRequired = "session: domain config %s: option 'domain' is required"
- errFmtSessionDomainHasPeriodPrefix = "session: domain config %s: option 'domain' has a prefix of '.' which is not supported or intended behaviour: you can use this at your own risk but we recommend removing it"
- errFmtSessionDomainDuplicate = "session: domain config %s: option 'domain' is a duplicate value for another configured session domain"
- errFmtSessionDomainDuplicateCookieScope = "session: domain config %s: option 'domain' shares the same cookie domain scope as another configured session domain"
- errFmtSessionDomainPortalURLInsecure = "session: domain config %s: option 'authelia_url' does not have a secure scheme with a value of '%s'"
- errFmtSessionDomainPortalURLNotInCookieScope = "session: domain config %s: option 'authelia_url' does not share a cookie scope with domain '%s' with a value of '%s'"
- errFmtSessionDomainInvalidDomain = "session: domain config %s: option 'domain' does not appear to be a valid cookie domain or an ip address"
- errFmtSessionDomainInvalidDomainNoDots = "session: domain config %s: option 'domain' is not a valid cookie domain: must have at least a single period or be an ip address"
- errFmtSessionDomainInvalidDomainPublic = "session: domain config %s: option 'domain' is not a valid cookie domain: the domain is part of the special public suffix list"
+ errFmtSessionDomainMustBeRoot = "session: domain config %s: option 'domain' must be the domain you wish to protect not a wildcard domain but it's configured as '%s'"
+ errFmtSessionDomainSameSite = "session: domain config %s: option 'same_site' must be one of %s but it's configured as '%s'"
+ errFmtSessionDomainOptionRequired = "session: domain config %s: option '%s' is required"
+ errFmtSessionDomainHasPeriodPrefix = "session: domain config %s: option 'domain' has a prefix of '.' which is not supported or intended behaviour: you can use this at your own risk but we recommend removing it"
+ errFmtSessionDomainDuplicate = "session: domain config %s: option 'domain' is a duplicate value for another configured session domain"
+ errFmtSessionDomainDuplicateCookieScope = "session: domain config %s: option 'domain' shares the same cookie domain scope as another configured session domain"
+ errFmtSessionDomainURLNotAbsolute = "session: domain config %s: option '%s' is not absolute with a value of '%s'"
+ errFmtSessionDomainURLInsecure = "session: domain config %s: option '%s' does not have a secure scheme with a value of '%s'"
+ errFmtSessionDomainURLNotInCookieScope = "session: domain config %s: option '%s' does not share a cookie scope with domain '%s' with a value of '%s'"
+ errFmtSessionDomainAutheliaURLAndRedirectionURLEqual = "session: domain config %s: option 'default_redirection_url' with value '%s' is effectively equal to option 'authelia_url' with value '%s' which is not permitted"
+ errFmtSessionDomainInvalidDomain = "session: domain config %s: option 'domain' does not appear to be a valid cookie domain or an ip address"
+ errFmtSessionDomainInvalidDomainNoDots = "session: domain config %s: option 'domain' is not a valid cookie domain: must have at least a single period or be an ip address"
+ errFmtSessionDomainInvalidDomainPublic = "session: domain config %s: option 'domain' is not a valid cookie domain: the domain is part of the special public suffix list"
)
// Regulation Error Consts.
@@ -482,6 +484,9 @@ const (
attrOIDCAccessTokenSigKID = "access_token_signed_response_key_id"
attrOIDCPKCEChallengeMethod = "pkce_challenge_method"
attrOIDCRequestedAudienceMode = "requested_audience_mode"
+ attrSessionAutheliaURL = "authelia_url"
+ attrSessionDomain = "domain"
+ attrDefaultRedirectionURL = "default_redirection_url"
)
var (
diff --git a/internal/configuration/validator/session.go b/internal/configuration/validator/session.go
index be0c28a44..24fcc54d5 100644
--- a/internal/configuration/validator/session.go
+++ b/internal/configuration/validator/session.go
@@ -11,65 +11,67 @@ import (
)
// ValidateSession validates and update session configuration.
-func ValidateSession(config *schema.Session, validator *schema.StructValidator) {
- if config.Name == "" {
- config.Name = schema.DefaultSessionConfiguration.Name
+func ValidateSession(config *schema.Configuration, validator *schema.StructValidator) {
+ if config.Session.Name == "" {
+ config.Session.Name = schema.DefaultSessionConfiguration.Name
}
- if config.Redis != nil {
- if config.Redis.HighAvailability != nil {
- validateRedisSentinel(config, validator)
+ if config.Session.Redis != nil {
+ if config.Session.Redis.HighAvailability != nil {
+ validateRedisSentinel(&config.Session, validator)
} else {
- validateRedis(config, validator)
+ validateRedis(&config.Session, validator)
}
}
validateSession(config, validator)
}
-func validateSession(config *schema.Session, validator *schema.StructValidator) {
- if config.Expiration <= 0 {
- config.Expiration = schema.DefaultSessionConfiguration.Expiration // 1 hour.
+func validateSession(config *schema.Configuration, validator *schema.StructValidator) {
+ if config.Session.Expiration <= 0 {
+ config.Session.Expiration = schema.DefaultSessionConfiguration.Expiration // 1 hour.
}
- if config.Inactivity <= 0 {
- config.Inactivity = schema.DefaultSessionConfiguration.Inactivity // 5 min.
+ if config.Session.Inactivity <= 0 {
+ config.Session.Inactivity = schema.DefaultSessionConfiguration.Inactivity // 5 min.
}
switch {
- case config.RememberMe == schema.RememberMeDisabled:
- config.DisableRememberMe = true
- case config.RememberMe <= 0:
- config.RememberMe = schema.DefaultSessionConfiguration.RememberMe // 1 month.
+ case config.Session.RememberMe == schema.RememberMeDisabled:
+ config.Session.DisableRememberMe = true
+ case config.Session.RememberMe <= 0:
+ config.Session.RememberMe = schema.DefaultSessionConfiguration.RememberMe // 1 month.
}
- if config.SameSite == "" {
- config.SameSite = schema.DefaultSessionConfiguration.SameSite
- } else if !utils.IsStringInSlice(config.SameSite, validSessionSameSiteValues) {
- validator.Push(fmt.Errorf(errFmtSessionSameSite, strJoinOr(validSessionSameSiteValues), config.SameSite))
+ if config.Session.SameSite == "" {
+ config.Session.SameSite = schema.DefaultSessionConfiguration.SameSite
+ } else if !utils.IsStringInSlice(config.Session.SameSite, validSessionSameSiteValues) {
+ validator.Push(fmt.Errorf(errFmtSessionSameSite, strJoinOr(validSessionSameSiteValues), config.Session.SameSite))
}
- cookies := len(config.Cookies)
+ cookies := len(config.Session.Cookies)
switch {
- case cookies == 0 && config.Domain != "": //nolint:staticcheck
+ case cookies == 0 && config.Session.Domain != "": //nolint:staticcheck
// Add legacy configuration to the domains list.
- config.Cookies = append(config.Cookies, schema.SessionCookie{
+ config.Session.Cookies = append(config.Session.Cookies, schema.SessionCookie{
SessionCookieCommon: schema.SessionCookieCommon{
- Name: config.Name,
- SameSite: config.SameSite,
- Expiration: config.Expiration,
- Inactivity: config.Inactivity,
- RememberMe: config.RememberMe,
- DisableRememberMe: config.DisableRememberMe,
+ Name: config.Session.Name,
+ SameSite: config.Session.SameSite,
+ Expiration: config.Session.Expiration,
+ Inactivity: config.Session.Inactivity,
+ RememberMe: config.Session.RememberMe,
+ DisableRememberMe: config.Session.DisableRememberMe,
},
- Domain: config.Domain, //nolint:staticcheck
+ Domain: config.Session.Domain, //nolint:staticcheck
+ DefaultRedirectionURL: config.DefaultRedirectionURL,
+ Legacy: true,
})
- case cookies != 0 && config.Domain != "": //nolint:staticcheck
+ case cookies != 0 && config.Session.Domain != "": //nolint:staticcheck
validator.Push(fmt.Errorf(errFmtSessionLegacyAndWarning))
}
- validateSessionCookieDomains(config, validator)
+ validateSessionCookieDomains(&config.Session, validator)
}
func validateSessionCookieDomains(config *schema.Session, validator *schema.StructValidator) {
@@ -86,7 +88,7 @@ func validateSessionCookieDomains(config *schema.Session, validator *schema.Stru
validateSessionCookieName(i, config)
- validateSessionCookiesAutheliaURL(i, config, validator)
+ validateSessionCookiesURLs(i, config, validator)
validateSessionExpiration(i, config)
@@ -104,7 +106,7 @@ func validateSessionDomainName(i int, config *schema.Session, validator *schema.
switch {
case d.Domain == "":
- validator.Push(fmt.Errorf(errFmtSessionDomainRequired, sessionDomainDescriptor(i, d)))
+ validator.Push(fmt.Errorf(errFmtSessionDomainOptionRequired, sessionDomainDescriptor(i, d), attrSessionDomain))
return
case strings.HasPrefix(d.Domain, "*."):
validator.Push(fmt.Errorf(errFmtSessionDomainMustBeRoot, sessionDomainDescriptor(i, d), d.Domain))
@@ -154,15 +156,39 @@ func validateSessionUniqueCookieDomain(i int, config *schema.Session, domains []
}
}
-// validateSessionCookiesAutheliaURL validates the AutheliaURL.
-func validateSessionCookiesAutheliaURL(index int, config *schema.Session, validator *schema.StructValidator) {
- var d = config.Cookies[index]
+// validateSessionCookiesURLs validates the AutheliaURL and DefaultRedirectionURL.
+func validateSessionCookiesURLs(i int, config *schema.Session, validator *schema.StructValidator) {
+ var d = config.Cookies[i]
- if d.AutheliaURL != nil && d.Domain != "" && !utils.IsURISafeRedirection(d.AutheliaURL, d.Domain) {
- if utils.IsURISecure(d.AutheliaURL) {
- validator.Push(fmt.Errorf(errFmtSessionDomainPortalURLNotInCookieScope, sessionDomainDescriptor(index, d), d.Domain, d.AutheliaURL))
- } else {
- validator.Push(fmt.Errorf(errFmtSessionDomainPortalURLInsecure, sessionDomainDescriptor(index, d), d.AutheliaURL))
+ if d.AutheliaURL == nil {
+ if !d.Legacy && d.Domain != "" {
+ validator.Push(fmt.Errorf(errFmtSessionDomainOptionRequired, sessionDomainDescriptor(i, d), attrSessionAutheliaURL))
+ }
+ } else {
+ if !d.AutheliaURL.IsAbs() {
+ validator.Push(fmt.Errorf(errFmtSessionDomainURLNotAbsolute, sessionDomainDescriptor(i, d), attrSessionAutheliaURL, d.AutheliaURL))
+ } else if !utils.IsURISecure(d.AutheliaURL) {
+ validator.Push(fmt.Errorf(errFmtSessionDomainURLInsecure, sessionDomainDescriptor(i, d), attrSessionAutheliaURL, d.AutheliaURL))
+ }
+
+ if d.Domain != "" && !utils.HasURIDomainSuffix(d.AutheliaURL, d.Domain) {
+ validator.Push(fmt.Errorf(errFmtSessionDomainURLNotInCookieScope, sessionDomainDescriptor(i, d), attrSessionAutheliaURL, d.Domain, d.AutheliaURL))
+ }
+ }
+
+ if d.DefaultRedirectionURL != nil {
+ if !d.DefaultRedirectionURL.IsAbs() {
+ validator.Push(fmt.Errorf(errFmtSessionDomainURLNotAbsolute, sessionDomainDescriptor(i, d), attrDefaultRedirectionURL, d.DefaultRedirectionURL))
+ } else if !utils.IsURISecure(d.DefaultRedirectionURL) {
+ validator.Push(fmt.Errorf(errFmtSessionDomainURLInsecure, sessionDomainDescriptor(i, d), attrDefaultRedirectionURL, d.DefaultRedirectionURL))
+ }
+
+ if d.Domain != "" && !utils.HasURIDomainSuffix(d.DefaultRedirectionURL, d.Domain) {
+ validator.Push(fmt.Errorf(errFmtSessionDomainURLNotInCookieScope, sessionDomainDescriptor(i, d), attrDefaultRedirectionURL, d.Domain, d.DefaultRedirectionURL))
+ }
+
+ if d.AutheliaURL != nil && utils.EqualURLs(d.AutheliaURL, d.DefaultRedirectionURL) {
+ validator.Push(fmt.Errorf(errFmtSessionDomainAutheliaURLAndRedirectionURLEqual, sessionDomainDescriptor(i, d), d.DefaultRedirectionURL, d.AutheliaURL))
}
}
}
diff --git a/internal/configuration/validator/session_test.go b/internal/configuration/validator/session_test.go
index e592a5223..ed8121610 100644
--- a/internal/configuration/validator/session_test.go
+++ b/internal/configuration/validator/session_test.go
@@ -13,13 +13,13 @@ import (
"github.com/authelia/authelia/v4/internal/configuration/schema"
)
-func newDefaultSessionConfig() schema.Session {
+func newDefaultSessionConfig() schema.Configuration {
config := schema.Session{}
config.Secret = testJWTSecret
config.Domain = exampleDotCom //nolint:staticcheck
config.Cookies = []schema.SessionCookie{}
- return config
+ return schema.Configuration{Session: config}
}
func TestShouldSetDefaultSessionValues(t *testing.T) {
@@ -30,40 +30,45 @@ func TestShouldSetDefaultSessionValues(t *testing.T) {
assert.False(t, validator.HasWarnings())
assert.False(t, validator.HasErrors())
- assert.Equal(t, schema.DefaultSessionConfiguration.Name, config.Name)
- assert.Equal(t, schema.DefaultSessionConfiguration.Inactivity, config.Inactivity)
- assert.Equal(t, schema.DefaultSessionConfiguration.Expiration, config.Expiration)
- assert.Equal(t, schema.DefaultSessionConfiguration.RememberMe, config.RememberMe)
- assert.Equal(t, schema.DefaultSessionConfiguration.SameSite, config.SameSite)
+ assert.Equal(t, schema.DefaultSessionConfiguration.Name, config.Session.Name)
+ assert.Equal(t, schema.DefaultSessionConfiguration.Inactivity, config.Session.Inactivity)
+ assert.Equal(t, schema.DefaultSessionConfiguration.Expiration, config.Session.Expiration)
+ assert.Equal(t, schema.DefaultSessionConfiguration.RememberMe, config.Session.RememberMe)
+ assert.Equal(t, schema.DefaultSessionConfiguration.SameSite, config.Session.SameSite)
}
func TestShouldSetDefaultSessionDomainsValues(t *testing.T) {
testCases := []struct {
name string
- have schema.Session
- expected schema.Session
+ have schema.Configuration
+ expected schema.Configuration
errs []string
}{
{
"ShouldSetGoodDefaultValues",
- schema.Session{
- SessionCookieCommon: schema.SessionCookieCommon{
- SameSite: "lax", Expiration: time.Hour, Inactivity: time.Minute, RememberMe: time.Hour * 2,
+ schema.Configuration{
+ Session: schema.Session{
+ SessionCookieCommon: schema.SessionCookieCommon{
+ SameSite: "lax", Expiration: time.Hour, Inactivity: time.Minute, RememberMe: time.Hour * 2,
+ },
+ Domain: exampleDotCom,
},
- Domain: exampleDotCom,
},
- schema.Session{
- SessionCookieCommon: schema.SessionCookieCommon{
- Name: "authelia_session", SameSite: "lax", Expiration: time.Hour, Inactivity: time.Minute, RememberMe: time.Hour * 2,
- },
- Domain: exampleDotCom,
- Cookies: []schema.SessionCookie{
- {
- SessionCookieCommon: schema.SessionCookieCommon{
- Name: "authelia_session", SameSite: "lax", Expiration: time.Hour,
- Inactivity: time.Minute, RememberMe: time.Hour * 2,
+ schema.Configuration{
+ Session: schema.Session{
+ SessionCookieCommon: schema.SessionCookieCommon{
+ Name: "authelia_session", SameSite: "lax", Expiration: time.Hour, Inactivity: time.Minute, RememberMe: time.Hour * 2,
+ },
+ Domain: exampleDotCom,
+ Cookies: []schema.SessionCookie{
+ {
+ SessionCookieCommon: schema.SessionCookieCommon{
+ Name: "authelia_session", SameSite: "lax", Expiration: time.Hour,
+ Inactivity: time.Minute, RememberMe: time.Hour * 2,
+ },
+ Domain: exampleDotCom,
+ Legacy: true,
},
- Domain: exampleDotCom,
},
},
},
@@ -71,31 +76,37 @@ func TestShouldSetDefaultSessionDomainsValues(t *testing.T) {
},
{
"ShouldNotSetBadDefaultValues",
- schema.Session{
- SessionCookieCommon: schema.SessionCookieCommon{
- SameSite: "BAD VALUE", Expiration: time.Hour, Inactivity: time.Minute, RememberMe: time.Hour * 2,
- },
- Cookies: []schema.SessionCookie{
- {
- SessionCookieCommon: schema.SessionCookieCommon{
- Name: "authelia_session",
- Expiration: time.Hour, Inactivity: time.Minute, RememberMe: time.Hour * 2,
+ schema.Configuration{
+ Session: schema.Session{
+ SessionCookieCommon: schema.SessionCookieCommon{
+ SameSite: "BAD VALUE", Expiration: time.Hour, Inactivity: time.Minute, RememberMe: time.Hour * 2,
+ },
+ Cookies: []schema.SessionCookie{
+ {
+ SessionCookieCommon: schema.SessionCookieCommon{
+ Name: "authelia_session",
+ Expiration: time.Hour, Inactivity: time.Minute, RememberMe: time.Hour * 2,
+ },
+ Domain: exampleDotCom,
+ AutheliaURL: &url.URL{Scheme: "https", Host: "auth." + exampleDotCom},
},
- Domain: exampleDotCom,
},
},
},
- schema.Session{
- SessionCookieCommon: schema.SessionCookieCommon{
- Name: "authelia_session", SameSite: "BAD VALUE", Expiration: time.Hour, Inactivity: time.Minute, RememberMe: time.Hour * 2,
- },
- Cookies: []schema.SessionCookie{
- {
- SessionCookieCommon: schema.SessionCookieCommon{
- Name: "authelia_session", SameSite: schema.DefaultSessionConfiguration.SameSite,
- Expiration: time.Hour, Inactivity: time.Minute, RememberMe: time.Hour * 2,
+ schema.Configuration{
+ Session: schema.Session{
+ SessionCookieCommon: schema.SessionCookieCommon{
+ Name: "authelia_session", SameSite: "BAD VALUE", Expiration: time.Hour, Inactivity: time.Minute, RememberMe: time.Hour * 2,
+ },
+ Cookies: []schema.SessionCookie{
+ {
+ SessionCookieCommon: schema.SessionCookieCommon{
+ Name: "authelia_session", SameSite: schema.DefaultSessionConfiguration.SameSite,
+ Expiration: time.Hour, Inactivity: time.Minute, RememberMe: time.Hour * 2,
+ },
+ Domain: exampleDotCom,
+ AutheliaURL: &url.URL{Scheme: "https", Host: "auth." + exampleDotCom},
},
- Domain: exampleDotCom,
},
},
},
@@ -105,42 +116,50 @@ func TestShouldSetDefaultSessionDomainsValues(t *testing.T) {
},
{
"ShouldSetDefaultValuesForEachConfig",
- schema.Session{
- SessionCookieCommon: schema.SessionCookieCommon{
- Name: "default_session", SameSite: "lax", Expiration: time.Hour, Inactivity: time.Minute,
- RememberMe: schema.RememberMeDisabled,
- },
- Cookies: []schema.SessionCookie{
- {
- Domain: exampleDotCom,
+ schema.Configuration{
+ Session: schema.Session{
+ SessionCookieCommon: schema.SessionCookieCommon{
+ Name: "default_session", SameSite: "lax", Expiration: time.Hour, Inactivity: time.Minute,
+ RememberMe: schema.RememberMeDisabled,
},
- {
- SessionCookieCommon: schema.SessionCookieCommon{
- Name: "authelia_session", SameSite: "strict",
+ Cookies: []schema.SessionCookie{
+ {
+ Domain: exampleDotCom,
+ AutheliaURL: &url.URL{Scheme: "https", Host: "auth." + exampleDotCom},
+ },
+ {
+ SessionCookieCommon: schema.SessionCookieCommon{
+ Name: "authelia_session", SameSite: "strict",
+ },
+ Domain: "example2.com",
+ AutheliaURL: &url.URL{Scheme: "https", Host: "auth.example2.com"},
},
- Domain: "example2.com",
},
},
},
- schema.Session{
- SessionCookieCommon: schema.SessionCookieCommon{
- Name: "default_session", SameSite: "lax", Expiration: time.Hour, Inactivity: time.Minute,
- RememberMe: schema.RememberMeDisabled, DisableRememberMe: true,
- },
- Cookies: []schema.SessionCookie{
- {
- SessionCookieCommon: schema.SessionCookieCommon{
- Name: "default_session", SameSite: "lax",
- Expiration: time.Hour, Inactivity: time.Minute, RememberMe: schema.RememberMeDisabled, DisableRememberMe: true,
- },
- Domain: exampleDotCom,
+ schema.Configuration{
+ Session: schema.Session{
+ SessionCookieCommon: schema.SessionCookieCommon{
+ Name: "default_session", SameSite: "lax", Expiration: time.Hour, Inactivity: time.Minute,
+ RememberMe: schema.RememberMeDisabled, DisableRememberMe: true,
},
- {
- SessionCookieCommon: schema.SessionCookieCommon{
- Name: "authelia_session", SameSite: "strict",
- Expiration: time.Hour, Inactivity: time.Minute, RememberMe: schema.RememberMeDisabled, DisableRememberMe: true,
+ Cookies: []schema.SessionCookie{
+ {
+ SessionCookieCommon: schema.SessionCookieCommon{
+ Name: "default_session", SameSite: "lax",
+ Expiration: time.Hour, Inactivity: time.Minute, RememberMe: schema.RememberMeDisabled, DisableRememberMe: true,
+ },
+ Domain: exampleDotCom,
+ AutheliaURL: &url.URL{Scheme: "https", Host: "auth." + exampleDotCom},
+ },
+ {
+ SessionCookieCommon: schema.SessionCookieCommon{
+ Name: "authelia_session", SameSite: "strict",
+ Expiration: time.Hour, Inactivity: time.Minute, RememberMe: schema.RememberMeDisabled, DisableRememberMe: true,
+ },
+ Domain: "example2.com",
+ AutheliaURL: &url.URL{Scheme: "https", Host: "auth.example2.com"},
},
- Domain: "example2.com",
},
},
},
@@ -148,18 +167,22 @@ func TestShouldSetDefaultSessionDomainsValues(t *testing.T) {
},
{
"ShouldErrorOnEmptyConfig",
- schema.Session{
- SessionCookieCommon: schema.SessionCookieCommon{
- Name: "", SameSite: "",
+ schema.Configuration{
+ Session: schema.Session{
+ SessionCookieCommon: schema.SessionCookieCommon{
+ Name: "", SameSite: "",
+ },
+ Domain: "",
+ Cookies: []schema.SessionCookie{},
},
- Domain: "",
- Cookies: []schema.SessionCookie{},
},
- schema.Session{
- SessionCookieCommon: schema.SessionCookieCommon{
- Name: "authelia_session", SameSite: "lax", Expiration: time.Hour, Inactivity: time.Minute * 5, RememberMe: time.Hour * 24 * 30,
+ schema.Configuration{
+ Session: schema.Session{
+ SessionCookieCommon: schema.SessionCookieCommon{
+ Name: "authelia_session", SameSite: "lax", Expiration: time.Hour, Inactivity: time.Minute * 5, RememberMe: time.Hour * 24 * 30,
+ },
+ Cookies: []schema.SessionCookie{},
},
- Cookies: []schema.SessionCookie{},
},
[]string{
"session: option 'cookies' is required",
@@ -186,7 +209,7 @@ func TestShouldSetDefaultSessionDomainsValues(t *testing.T) {
assert.EqualError(t, err, tc.errs[i])
}
- assert.Equal(t, tc.expected, have)
+ assert.Equal(t, tc.expected.Session, have.Session)
})
}
}
@@ -195,22 +218,22 @@ func TestShouldSetDefaultSessionValuesWhenNegative(t *testing.T) {
validator := schema.NewStructValidator()
config := newDefaultSessionConfig()
- config.Expiration, config.Inactivity, config.RememberMe = -1, -1, -2
+ config.Session.Expiration, config.Session.Inactivity, config.Session.RememberMe = -1, -1, -2
ValidateSession(&config, validator)
assert.Len(t, validator.Warnings(), 0)
assert.Len(t, validator.Errors(), 0)
- assert.Equal(t, schema.DefaultSessionConfiguration.Inactivity, config.Inactivity)
- assert.Equal(t, schema.DefaultSessionConfiguration.Expiration, config.Expiration)
- assert.Equal(t, schema.DefaultSessionConfiguration.RememberMe, config.RememberMe)
+ assert.Equal(t, schema.DefaultSessionConfiguration.Inactivity, config.Session.Inactivity)
+ assert.Equal(t, schema.DefaultSessionConfiguration.Expiration, config.Session.Expiration)
+ assert.Equal(t, schema.DefaultSessionConfiguration.RememberMe, config.Session.RememberMe)
}
func TestShouldWarnSessionValuesWhenPotentiallyInvalid(t *testing.T) {
validator := schema.NewStructValidator()
config := newDefaultSessionConfig()
- config.Domain = ".example.com" //nolint:staticcheck
+ config.Session.Domain = ".example.com" //nolint:staticcheck
ValidateSession(&config, validator)
@@ -232,7 +255,7 @@ func TestShouldHandleRedisConfigSuccessfully(t *testing.T) {
config = newDefaultSessionConfig()
// Set redis config because password must be set only when redis is used.
- config.Redis = &schema.SessionRedis{
+ config.Session.Redis = &schema.SessionRedis{
Host: "redis.localhost",
Port: 6379,
Password: "password",
@@ -243,14 +266,14 @@ func TestShouldHandleRedisConfigSuccessfully(t *testing.T) {
assert.Len(t, validator.Warnings(), 0)
assert.Len(t, validator.Errors(), 0)
- assert.Equal(t, 8, config.Redis.MaximumActiveConnections)
+ assert.Equal(t, 8, config.Session.Redis.MaximumActiveConnections)
}
func TestShouldRaiseErrorWithInvalidRedisPortLow(t *testing.T) {
validator := schema.NewStructValidator()
config := newDefaultSessionConfig()
- config.Redis = &schema.SessionRedis{
+ config.Session.Redis = &schema.SessionRedis{
Host: "authelia-port-1",
Port: -1,
}
@@ -267,7 +290,7 @@ func TestShouldRaiseErrorWithInvalidRedisPortHigh(t *testing.T) {
validator := schema.NewStructValidator()
config := newDefaultSessionConfig()
- config.Redis = &schema.SessionRedis{
+ config.Session.Redis = &schema.SessionRedis{
Host: "authelia-port-1",
Port: 65536,
}
@@ -283,7 +306,7 @@ func TestShouldRaiseErrorWithInvalidRedisPortHigh(t *testing.T) {
func TestShouldRaiseErrorWhenRedisIsUsedAndSecretNotSet(t *testing.T) {
validator := schema.NewStructValidator()
config := newDefaultSessionConfig()
- config.Secret = ""
+ config.Session.Secret = ""
ValidateSession(&config, validator)
@@ -291,10 +314,10 @@ func TestShouldRaiseErrorWhenRedisIsUsedAndSecretNotSet(t *testing.T) {
validator.Clear()
config = newDefaultSessionConfig()
- config.Secret = ""
+ config.Session.Secret = ""
// Set redis config because password must be set only when redis is used.
- config.Redis = &schema.SessionRedis{
+ config.Session.Redis = &schema.SessionRedis{
Host: "redis.localhost",
Port: 6379,
}
@@ -318,7 +341,7 @@ func TestShouldNotRaiseErrorsAndSetDefaultPortWhenRedisPortBlank(t *testing.T) {
config = newDefaultSessionConfig()
// Set redis config because password must be set only when redis is used.
- config.Redis = &schema.SessionRedis{
+ config.Session.Redis = &schema.SessionRedis{
Host: "redis.localhost",
Port: 0,
}
@@ -328,7 +351,7 @@ func TestShouldNotRaiseErrorsAndSetDefaultPortWhenRedisPortBlank(t *testing.T) {
assert.False(t, validator.HasWarnings())
assert.False(t, validator.HasErrors())
- assert.Equal(t, 6379, config.Redis.Port)
+ assert.Equal(t, 6379, config.Session.Redis.Port)
}
func TestShouldRaiseErrorWhenRedisPortInvalid(t *testing.T) {
@@ -343,7 +366,7 @@ func TestShouldRaiseErrorWhenRedisPortInvalid(t *testing.T) {
config = newDefaultSessionConfig()
// Set redis config because password must be set only when redis is used.
- config.Redis = &schema.SessionRedis{
+ config.Session.Redis = &schema.SessionRedis{
Host: "redis.localhost",
Port: -1,
}
@@ -359,7 +382,7 @@ func TestShouldRaiseOneErrorWhenRedisHighAvailabilityHasNodesWithNoHost(t *testi
validator := schema.NewStructValidator()
config := newDefaultSessionConfig()
- config.Redis = &schema.SessionRedis{
+ config.Session.Redis = &schema.SessionRedis{
Host: "redis",
Port: 6379,
HighAvailability: &schema.SessionRedisHighAvailability{
@@ -390,7 +413,7 @@ func TestShouldRaiseOneErrorWhenRedisHighAvailabilityDoesNotHaveSentinelName(t *
validator := schema.NewStructValidator()
config := newDefaultSessionConfig()
- config.Redis = &schema.SessionRedis{
+ config.Session.Redis = &schema.SessionRedis{
Host: "redis",
Port: 6379,
HighAvailability: &schema.SessionRedisHighAvailability{
@@ -412,7 +435,7 @@ func TestShouldUpdateDefaultPortWhenRedisSentinelHasNodes(t *testing.T) {
validator := schema.NewStructValidator()
config := newDefaultSessionConfig()
- config.Redis = &schema.SessionRedis{
+ config.Session.Redis = &schema.SessionRedis{
Host: "redis",
Port: 6379,
HighAvailability: &schema.SessionRedisHighAvailability{
@@ -438,17 +461,17 @@ func TestShouldUpdateDefaultPortWhenRedisSentinelHasNodes(t *testing.T) {
assert.False(t, validator.HasWarnings())
assert.False(t, validator.HasErrors())
- assert.Equal(t, 333, config.Redis.HighAvailability.Nodes[0].Port)
- assert.Equal(t, 26379, config.Redis.HighAvailability.Nodes[1].Port)
- assert.Equal(t, 26379, config.Redis.HighAvailability.Nodes[2].Port)
+ assert.Equal(t, 333, config.Session.Redis.HighAvailability.Nodes[0].Port)
+ assert.Equal(t, 26379, config.Session.Redis.HighAvailability.Nodes[1].Port)
+ assert.Equal(t, 26379, config.Session.Redis.HighAvailability.Nodes[2].Port)
}
func TestShouldRaiseErrorsWhenRedisSentinelOptionsIncorrectlyConfigured(t *testing.T) {
validator := schema.NewStructValidator()
config := newDefaultSessionConfig()
- config.Secret = ""
- config.Redis = &schema.SessionRedis{
+ config.Session.Secret = ""
+ config.Session.Redis = &schema.SessionRedis{
Port: 65536,
HighAvailability: &schema.SessionRedisHighAvailability{
SentinelName: "sentinel",
@@ -478,8 +501,8 @@ func TestShouldRaiseErrorsWhenRedisSentinelOptionsIncorrectlyConfigured(t *testi
config = newDefaultSessionConfig()
- config.Secret = ""
- config.Redis = &schema.SessionRedis{
+ config.Session.Secret = ""
+ config.Session.Redis = &schema.SessionRedis{
Port: -1,
HighAvailability: &schema.SessionRedisHighAvailability{
SentinelName: "sentinel",
@@ -510,7 +533,7 @@ func TestShouldNotRaiseErrorsAndSetDefaultPortWhenRedisSentinelPortBlank(t *test
validator := schema.NewStructValidator()
config := newDefaultSessionConfig()
- config.Redis = &schema.SessionRedis{
+ config.Session.Redis = &schema.SessionRedis{
Host: "mysentinelHost",
Port: 0,
HighAvailability: &schema.SessionRedisHighAvailability{
@@ -532,14 +555,14 @@ func TestShouldNotRaiseErrorsAndSetDefaultPortWhenRedisSentinelPortBlank(t *test
assert.False(t, validator.HasWarnings())
assert.False(t, validator.HasErrors())
- assert.Equal(t, 26379, config.Redis.Port)
+ assert.Equal(t, 26379, config.Session.Redis.Port)
}
func TestShouldRaiseErrorWhenRedisHostAndHighAvailabilityNodesEmpty(t *testing.T) {
validator := schema.NewStructValidator()
config := newDefaultSessionConfig()
- config.Redis = &schema.SessionRedis{
+ config.Session.Redis = &schema.SessionRedis{
Port: 26379,
HighAvailability: &schema.SessionRedisHighAvailability{
SentinelName: "sentinel",
@@ -561,7 +584,7 @@ func TestShouldRaiseErrorsWhenRedisHostNotSet(t *testing.T) {
validator := schema.NewStructValidator()
config := newDefaultSessionConfig()
- config.Redis = &schema.SessionRedis{
+ config.Session.Redis = &schema.SessionRedis{
Port: 6379,
}
@@ -579,7 +602,7 @@ func TestShouldSetDefaultRedisTLSOptions(t *testing.T) {
validator := schema.NewStructValidator()
config := newDefaultSessionConfig()
- config.Redis = &schema.SessionRedis{
+ config.Session.Redis = &schema.SessionRedis{
Host: "redis.local",
Port: 6379,
TLS: &schema.TLS{},
@@ -590,16 +613,16 @@ func TestShouldSetDefaultRedisTLSOptions(t *testing.T) {
assert.Len(t, validator.Warnings(), 0)
assert.Len(t, validator.Errors(), 0)
- assert.Equal(t, uint16(tls.VersionTLS12), config.Redis.TLS.MinimumVersion.Value)
- assert.Equal(t, uint16(0), config.Redis.TLS.MaximumVersion.Value)
- assert.Equal(t, "redis.local", config.Redis.TLS.ServerName)
+ assert.Equal(t, uint16(tls.VersionTLS12), config.Session.Redis.TLS.MinimumVersion.Value)
+ assert.Equal(t, uint16(0), config.Session.Redis.TLS.MaximumVersion.Value)
+ assert.Equal(t, "redis.local", config.Session.Redis.TLS.ServerName)
}
func TestShouldRaiseErrorOnBadRedisTLSOptionsSSL30(t *testing.T) {
validator := schema.NewStructValidator()
config := newDefaultSessionConfig()
- config.Redis = &schema.SessionRedis{
+ config.Session.Redis = &schema.SessionRedis{
Host: "redis.local",
Port: 6379,
TLS: &schema.TLS{
@@ -619,7 +642,7 @@ func TestShouldRaiseErrorOnBadRedisTLSOptionsMinVerGreaterThanMax(t *testing.T)
validator := schema.NewStructValidator()
config := newDefaultSessionConfig()
- config.Redis = &schema.SessionRedis{
+ config.Session.Redis = &schema.SessionRedis{
Host: "redis.local",
Port: 6379,
TLS: &schema.TLS{
@@ -639,38 +662,111 @@ func TestShouldRaiseErrorOnBadRedisTLSOptionsMinVerGreaterThanMax(t *testing.T)
func TestShouldRaiseErrorWhenHaveDuplicatedDomainName(t *testing.T) {
validator := schema.NewStructValidator()
config := newDefaultSessionConfig()
- config.Domain = "" //nolint:staticcheck
- config.Cookies = append(config.Cookies, schema.SessionCookie{
+ config.Session.Domain = "" //nolint:staticcheck
+ config.Session.Cookies = append(config.Session.Cookies, schema.SessionCookie{
Domain: exampleDotCom,
AutheliaURL: MustParseURL("https://login.example.com"),
})
- config.Cookies = append(config.Cookies, schema.SessionCookie{
+ config.Session.Cookies = append(config.Session.Cookies, schema.SessionCookie{
Domain: exampleDotCom,
AutheliaURL: MustParseURL("https://login.example.com"),
})
ValidateSession(&config, validator)
assert.False(t, validator.HasWarnings())
- assert.Len(t, validator.Errors(), 1)
- assert.EqualError(t, validator.Errors()[0], fmt.Sprintf(errFmtSessionDomainDuplicate, sessionDomainDescriptor(1, schema.SessionCookie{Domain: exampleDotCom})))
+ require.Len(t, validator.Errors(), 1)
+ assert.EqualError(t, validator.Errors()[0], "session: domain config #2 (domain 'example.com'): option 'domain' is a duplicate value for another configured session domain")
+}
+
+func TestShouldRaiseErrorWhenHaveNonAbsAutheliaURL(t *testing.T) {
+ validator := schema.NewStructValidator()
+ config := newDefaultSessionConfig()
+ config.Session.Domain = "" //nolint:staticcheck
+ config.Session.Cookies = []schema.SessionCookie{
+ {
+ Domain: exampleDotCom,
+ AutheliaURL: MustParseURL("login.example.com"),
+ },
+ }
+
+ ValidateSession(&config, validator)
+ assert.False(t, validator.HasWarnings())
+ require.Len(t, validator.Errors(), 2)
+ assert.EqualError(t, validator.Errors()[0], "session: domain config #1 (domain 'example.com'): option 'authelia_url' is not absolute with a value of 'login.example.com'")
+ assert.EqualError(t, validator.Errors()[1], "session: domain config #1 (domain 'example.com'): option 'authelia_url' does not share a cookie scope with domain 'example.com' with a value of 'login.example.com'")
+}
+
+func TestShouldRaiseErrorWhenHaveNonAbsDefaultRedirectionURL(t *testing.T) {
+ validator := schema.NewStructValidator()
+ config := newDefaultSessionConfig()
+ config.Session.Domain = "" //nolint:staticcheck
+ config.Session.Cookies = []schema.SessionCookie{
+ {
+ Domain: exampleDotCom,
+ AutheliaURL: MustParseURL("https://login.example.com"),
+ DefaultRedirectionURL: MustParseURL("home.example.com"),
+ },
+ }
+
+ ValidateSession(&config, validator)
+ assert.False(t, validator.HasWarnings())
+ require.Len(t, validator.Errors(), 2)
+ assert.EqualError(t, validator.Errors()[0], "session: domain config #1 (domain 'example.com'): option 'default_redirection_url' is not absolute with a value of 'home.example.com'")
+ assert.EqualError(t, validator.Errors()[1], "session: domain config #1 (domain 'example.com'): option 'default_redirection_url' does not share a cookie scope with domain 'example.com' with a value of 'home.example.com'")
+}
+
+func TestShouldRaiseErrorWhenHaveNonSecureDefaultRedirectionURL(t *testing.T) {
+ validator := schema.NewStructValidator()
+ config := newDefaultSessionConfig()
+ config.Session.Domain = "" //nolint:staticcheck
+ config.Session.Cookies = []schema.SessionCookie{
+ {
+ Domain: exampleDotCom,
+ AutheliaURL: MustParseURL("https://login.example.com"),
+ DefaultRedirectionURL: MustParseURL("http://home.example.com"),
+ },
+ }
+
+ ValidateSession(&config, validator)
+ assert.False(t, validator.HasWarnings())
+ require.Len(t, validator.Errors(), 1)
+ assert.EqualError(t, validator.Errors()[0], "session: domain config #1 (domain 'example.com'): option 'default_redirection_url' does not have a secure scheme with a value of 'http://home.example.com'")
+}
+
+func TestShouldRaiseErrorWhenHaveDefaultRedirectionURLEqualAutheliaURL(t *testing.T) {
+ validator := schema.NewStructValidator()
+ config := newDefaultSessionConfig()
+ config.Session.Domain = "" //nolint:staticcheck
+ config.Session.Cookies = []schema.SessionCookie{
+ {
+ Domain: exampleDotCom,
+ AutheliaURL: MustParseURL("https://login.example.com"),
+ DefaultRedirectionURL: MustParseURL("https://login.example.com"),
+ },
+ }
+
+ ValidateSession(&config, validator)
+ assert.False(t, validator.HasWarnings())
+ require.Len(t, validator.Errors(), 1)
+ assert.EqualError(t, validator.Errors()[0], "session: domain config #1 (domain 'example.com'): option 'default_redirection_url' with value 'https://login.example.com' is effectively equal to option 'authelia_url' with value 'https://login.example.com' which is not permitted")
}
func TestShouldRaiseErrorWhenSubdomainConflicts(t *testing.T) {
validator := schema.NewStructValidator()
config := newDefaultSessionConfig()
- config.Domain = "" //nolint:staticcheck
- config.Cookies = append(config.Cookies, schema.SessionCookie{
+ config.Session.Domain = "" //nolint:staticcheck
+ config.Session.Cookies = append(config.Session.Cookies, schema.SessionCookie{
Domain: exampleDotCom,
AutheliaURL: MustParseURL("https://login.example.com"),
})
- config.Cookies = append(config.Cookies, schema.SessionCookie{
+ config.Session.Cookies = append(config.Session.Cookies, schema.SessionCookie{
Domain: "internal.example.com",
AutheliaURL: MustParseURL("https://login.internal.example.com"),
})
ValidateSession(&config, validator)
assert.False(t, validator.HasWarnings())
- assert.Len(t, validator.Errors(), 1)
+ require.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "session: domain config #2 (domain 'internal.example.com'): option 'domain' shares the same cookie domain scope as another configured session domain")
}
@@ -699,14 +795,18 @@ func TestShouldRaiseErrorWhenDomainIsInvalid(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
validator := schema.NewStructValidator()
config := newDefaultSessionConfig()
- config.Domain = "" //nolint:staticcheck
+ config.Session.Domain = "" //nolint:staticcheck
- config.Cookies = []schema.SessionCookie{
+ config.Session.Cookies = []schema.SessionCookie{
{
Domain: tc.have,
},
}
+ if tc.have != "" {
+ config.Session.Cookies[0].AutheliaURL = &url.URL{Scheme: "https", Host: "auth." + tc.have}
+ }
+
ValidateSession(&config, validator)
require.Len(t, validator.Warnings(), len(tc.warnings))
@@ -737,8 +837,8 @@ func TestShouldRaiseErrorWhenPortalURLIsInvalid(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
validator := schema.NewStructValidator()
config := newDefaultSessionConfig()
- config.Domain = "" //nolint:staticcheck
- config.Cookies = []schema.SessionCookie{
+ config.Session.Domain = "" //nolint:staticcheck
+ config.Session.Cookies = []schema.SessionCookie{
{
SessionCookieCommon: schema.SessionCookieCommon{
Name: "authelia_session",
@@ -762,7 +862,7 @@ func TestShouldRaiseErrorWhenPortalURLIsInvalid(t *testing.T) {
func TestShouldRaiseErrorWhenSameSiteSetIncorrectly(t *testing.T) {
validator := schema.NewStructValidator()
config := newDefaultSessionConfig()
- config.SameSite = "NOne"
+ config.Session.SameSite = "NOne"
ValidateSession(&config, validator)
@@ -776,7 +876,7 @@ func TestShouldRaiseErrorWhenSameSiteSetIncorrectly(t *testing.T) {
func TestShouldNotRaiseErrorWhenSameSiteSetCorrectly(t *testing.T) {
validator := schema.NewStructValidator()
- var config schema.Session
+ var config schema.Configuration
validOptions := []string{"none", "lax", "strict"}
@@ -784,7 +884,7 @@ func TestShouldNotRaiseErrorWhenSameSiteSetCorrectly(t *testing.T) {
validator.Clear()
config = newDefaultSessionConfig()
- config.SameSite = opt
+ config.Session.SameSite = opt
ValidateSession(&config, validator)
@@ -796,19 +896,19 @@ func TestShouldNotRaiseErrorWhenSameSiteSetCorrectly(t *testing.T) {
func TestShouldSetDefaultWhenNegativeAndNotOverrideDisabledRememberMe(t *testing.T) {
validator := schema.NewStructValidator()
config := newDefaultSessionConfig()
- config.Inactivity = -1
- config.Expiration = -1
- config.RememberMe = schema.RememberMeDisabled
+ config.Session.Inactivity = -1
+ config.Session.Expiration = -1
+ config.Session.RememberMe = schema.RememberMeDisabled
ValidateSession(&config, validator)
assert.Len(t, validator.Warnings(), 0)
assert.Len(t, validator.Errors(), 0)
- assert.Equal(t, schema.DefaultSessionConfiguration.Inactivity, config.Inactivity)
- assert.Equal(t, schema.DefaultSessionConfiguration.Expiration, config.Expiration)
- assert.Equal(t, schema.RememberMeDisabled, config.RememberMe)
- assert.True(t, config.DisableRememberMe)
+ assert.Equal(t, schema.DefaultSessionConfiguration.Inactivity, config.Session.Inactivity)
+ assert.Equal(t, schema.DefaultSessionConfiguration.Expiration, config.Session.Expiration)
+ assert.Equal(t, schema.RememberMeDisabled, config.Session.RememberMe)
+ assert.True(t, config.Session.DisableRememberMe)
}
func TestShouldSetDefaultRememberMeDuration(t *testing.T) {
@@ -820,34 +920,35 @@ func TestShouldSetDefaultRememberMeDuration(t *testing.T) {
assert.Len(t, validator.Warnings(), 0)
assert.Len(t, validator.Errors(), 0)
- assert.Equal(t, config.RememberMe, schema.DefaultSessionConfiguration.RememberMe)
+ assert.Equal(t, config.Session.RememberMe, schema.DefaultSessionConfiguration.RememberMe)
}
func TestShouldNotAllowLegacyAndModernCookiesConfig(t *testing.T) {
validator := schema.NewStructValidator()
config := newDefaultSessionConfig()
- config.Cookies = append(config.Cookies, schema.SessionCookie{
+ config.Session.Cookies = append(config.Session.Cookies, schema.SessionCookie{
SessionCookieCommon: schema.SessionCookieCommon{
- Name: config.Name,
- SameSite: config.SameSite,
- Expiration: config.Expiration,
- Inactivity: config.Inactivity,
- RememberMe: config.RememberMe,
+ Name: config.Session.Name,
+ SameSite: config.Session.SameSite,
+ Expiration: config.Session.Expiration,
+ Inactivity: config.Session.Inactivity,
+ RememberMe: config.Session.RememberMe,
},
- Domain: config.Domain, //nolint:staticcheck
+ Domain: config.Session.Domain, //nolint:staticcheck
})
ValidateSession(&config, validator)
assert.Len(t, validator.Warnings(), 0)
- require.Len(t, validator.Errors(), 1)
+ require.Len(t, validator.Errors(), 2)
assert.EqualError(t, validator.Errors()[0], "session: option 'domain' and option 'cookies' can't be specified at the same time")
+ assert.EqualError(t, validator.Errors()[1], "session: domain config #1 (domain 'example.com'): option 'authelia_url' is required")
}
func MustParseURL(uri string) *url.URL {
- u, err := url.ParseRequestURI(uri)
+ u, err := url.Parse(uri)
if err != nil {
panic(err)
diff --git a/internal/handlers/const_test.go b/internal/handlers/const_test.go
index 142775f51..1ce5f8a1e 100644
--- a/internal/handlers/const_test.go
+++ b/internal/handlers/const_test.go
@@ -1,6 +1,7 @@
package handlers
import (
+ "net/url"
"time"
"github.com/valyala/fasthttp"
@@ -28,8 +29,19 @@ const (
)
const (
- testInactivity = time.Second * 10
- testRedirectionURL = "http://redirection.local"
- testUsername = "john"
- exampleDotCom = "example.com"
+ testInactivity = time.Second * 10
+ testRedirectionURLString = "https://www.example.com"
+ testUsername = "john"
+ exampleDotCom = "example.com"
+)
+
+var (
+ testRedirectionURL = func() *url.URL {
+ u, err := url.ParseRequestURI(testRedirectionURLString)
+ if err != nil {
+ panic(err)
+ }
+
+ return u
+ }()
)
diff --git a/internal/handlers/handler_firstfactor_test.go b/internal/handlers/handler_firstfactor_test.go
index ff27fa1b4..ec192fcd6 100644
--- a/internal/handlers/handler_firstfactor_test.go
+++ b/internal/handlers/handler_firstfactor_test.go
@@ -2,6 +2,7 @@ package handlers
import (
"fmt"
+ "net/url"
"testing"
"github.com/golang/mock/gomock"
@@ -315,7 +316,7 @@ type FirstFactorRedirectionSuite struct {
func (s *FirstFactorRedirectionSuite) SetupTest() {
s.mock = mocks.NewMockAutheliaCtx(s.T())
- s.mock.Ctx.Configuration.DefaultRedirectionURL = "https://default.local"
+ s.mock.Ctx.Configuration.DefaultRedirectionURL = &url.URL{Scheme: "https", Host: "default.local"}
s.mock.Ctx.Configuration.AccessControl.DefaultPolicy = testBypass
s.mock.Ctx.Configuration.AccessControl.Rules = []schema.AccessControlRule{
{
@@ -368,7 +369,7 @@ func (s *FirstFactorRedirectionSuite) TestShouldRedirectToDefaultURLWhenNoTarget
FirstFactorPOST(nil)(s.mock.Ctx)
// Respond with 200.
- s.mock.Assert200OK(s.T(), redirectResponse{Redirect: "https://default.local"})
+ s.mock.Assert200OK(s.T(), &redirectResponse{Redirect: "https://www.example.com"})
}
// When:
@@ -392,7 +393,7 @@ func (s *FirstFactorRedirectionSuite) TestShouldRedirectToDefaultURLWhenURLIsUns
FirstFactorPOST(nil)(s.mock.Ctx)
// Respond with 200.
- s.mock.Assert200OK(s.T(), redirectResponse{Redirect: "https://default.local"})
+ s.mock.Assert200OK(s.T(), &redirectResponse{Redirect: "https://www.example.com"})
}
// When:
diff --git a/internal/handlers/handler_sign_duo_test.go b/internal/handlers/handler_sign_duo_test.go
index 6602958d5..344cd8eec 100644
--- a/internal/handlers/handler_sign_duo_test.go
+++ b/internal/handlers/handler_sign_duo_test.go
@@ -532,7 +532,7 @@ func (s *SecondFactorDuoPostSuite) TestShouldRedirectUserToDefaultURL() {
DuoPOST(duoMock)(s.mock.Ctx)
s.mock.Assert200OK(s.T(), redirectResponse{
- Redirect: testRedirectionURL,
+ Redirect: testRedirectionURLString,
})
}
@@ -578,7 +578,7 @@ func (s *SecondFactorDuoPostSuite) TestShouldNotReturnRedirectURL() {
s.mock.Ctx.Request.SetBody(bodyBytes)
DuoPOST(duoMock)(s.mock.Ctx)
- s.mock.Assert200OK(s.T(), nil)
+ s.mock.Assert200OK(s.T(), &redirectResponse{Redirect: "https://www.example.com"})
}
func (s *SecondFactorDuoPostSuite) TestShouldRedirectUserToSafeTargetURL() {
diff --git a/internal/handlers/handler_sign_totp_test.go b/internal/handlers/handler_sign_totp_test.go
index f062b6c95..97c466a06 100644
--- a/internal/handlers/handler_sign_totp_test.go
+++ b/internal/handlers/handler_sign_totp_test.go
@@ -68,7 +68,7 @@ func (s *HandlerSignTOTPSuite) TestShouldRedirectUserToDefaultURL() {
TimeBasedOneTimePasswordPOST(s.mock.Ctx)
s.mock.Assert200OK(s.T(), redirectResponse{
- Redirect: testRedirectionURL,
+ Redirect: testRedirectionURLString,
})
}
@@ -139,7 +139,7 @@ func (s *HandlerSignTOTPSuite) TestShouldNotReturnRedirectURL() {
s.mock.Ctx.Request.SetBody(bodyBytes)
TimeBasedOneTimePasswordPOST(s.mock.Ctx)
- s.mock.Assert200OK(s.T(), nil)
+ s.mock.Assert200OK(s.T(), &redirectResponse{Redirect: "https://www.example.com"})
}
func (s *HandlerSignTOTPSuite) TestShouldRedirectUserToSafeTargetURL() {
@@ -260,7 +260,7 @@ func (s *HandlerSignTOTPSuite) TestShouldRegenerateSessionForPreventingSessionFi
res := r.FindAllStringSubmatch(string(s.mock.Ctx.Response.Header.PeekCookie("authelia_session")), -1)
TimeBasedOneTimePasswordPOST(s.mock.Ctx)
- s.mock.Assert200OK(s.T(), nil)
+ s.mock.Assert200OK(s.T(), &redirectResponse{Redirect: "https://www.example.com"})
s.NotEqual(
res[0][1],
diff --git a/internal/handlers/handler_state.go b/internal/handlers/handler_state.go
index c537d9b65..d77988051 100644
--- a/internal/handlers/handler_state.go
+++ b/internal/handlers/handler_state.go
@@ -21,9 +21,12 @@ func StateGET(ctx *middlewares.AutheliaCtx) {
}
stateResponse := StateResponse{
- Username: userSession.Username,
- AuthenticationLevel: userSession.AuthenticationLevel,
- DefaultRedirectionURL: ctx.Configuration.DefaultRedirectionURL,
+ Username: userSession.Username,
+ AuthenticationLevel: userSession.AuthenticationLevel,
+ }
+
+ if uri := ctx.GetDefaultRedirectionURL(); uri != nil {
+ stateResponse.DefaultRedirectionURL = uri.String()
}
if err = ctx.SetJSONBody(stateResponse); err != nil {
diff --git a/internal/handlers/handler_state_test.go b/internal/handlers/handler_state_test.go
index 793668374..4897ef75d 100644
--- a/internal/handlers/handler_state_test.go
+++ b/internal/handlers/handler_state_test.go
@@ -45,7 +45,7 @@ func (s *StateGetSuite) TestShouldReturnUsernameFromSession() {
Status: "OK",
Data: StateResponse{
Username: "username",
- DefaultRedirectionURL: "",
+ DefaultRedirectionURL: "https://www.example.com",
AuthenticationLevel: authentication.NotAuthenticated,
},
}
@@ -77,7 +77,7 @@ func (s *StateGetSuite) TestShouldReturnAuthenticationLevelFromSession() {
Status: "OK",
Data: StateResponse{
Username: "",
- DefaultRedirectionURL: "",
+ DefaultRedirectionURL: "https://www.example.com",
AuthenticationLevel: authentication.OneFactor,
},
}
diff --git a/internal/handlers/response.go b/internal/handlers/response.go
index 406b0cbc9..0092fcd56 100644
--- a/internal/handlers/response.go
+++ b/internal/handlers/response.go
@@ -21,8 +21,10 @@ func Handle1FAResponse(ctx *middlewares.AutheliaCtx, targetURI, requestMethod st
var err error
if len(targetURI) == 0 {
- if !ctx.Providers.Authorizer.IsSecondFactorEnabled() && ctx.Configuration.DefaultRedirectionURL != "" {
- if err = ctx.SetJSONBody(redirectResponse{Redirect: ctx.Configuration.DefaultRedirectionURL}); err != nil {
+ defaultRedirectionURL := ctx.GetDefaultRedirectionURL()
+
+ if !ctx.Providers.Authorizer.IsSecondFactorEnabled() && defaultRedirectionURL != nil {
+ if err = ctx.SetJSONBody(redirectResponse{Redirect: defaultRedirectionURL.String()}); err != nil {
ctx.Logger.Errorf("Unable to set default redirection URL in body: %s", err)
}
} else {
@@ -60,8 +62,10 @@ func Handle1FAResponse(ctx *middlewares.AutheliaCtx, targetURI, requestMethod st
if !ctx.IsSafeRedirectionTargetURI(targetURL) {
ctx.Logger.Debugf("Redirection URL %s is not safe", targetURI)
- if !ctx.Providers.Authorizer.IsSecondFactorEnabled() && ctx.Configuration.DefaultRedirectionURL != "" {
- if err = ctx.SetJSONBody(redirectResponse{Redirect: ctx.Configuration.DefaultRedirectionURL}); err != nil {
+ defaultRedirectionURL := ctx.GetDefaultRedirectionURL()
+
+ if !ctx.Providers.Authorizer.IsSecondFactorEnabled() && defaultRedirectionURL != nil {
+ if err = ctx.SetJSONBody(redirectResponse{Redirect: defaultRedirectionURL.String()}); err != nil {
ctx.Logger.Errorf("Unable to set default redirection URL in body: %s", err)
}
@@ -85,13 +89,15 @@ func Handle2FAResponse(ctx *middlewares.AutheliaCtx, targetURI string) {
var err error
if len(targetURI) == 0 {
- if len(ctx.Configuration.DefaultRedirectionURL) == 0 {
+ defaultRedirectionURL := ctx.GetDefaultRedirectionURL()
+
+ if defaultRedirectionURL == nil {
ctx.ReplyOK()
return
}
- if err = ctx.SetJSONBody(redirectResponse{Redirect: ctx.Configuration.DefaultRedirectionURL}); err != nil {
+ if err = ctx.SetJSONBody(redirectResponse{Redirect: defaultRedirectionURL.String()}); err != nil {
ctx.Logger.Errorf("Unable to set default redirection URL in body: %s", err)
}
diff --git a/internal/middlewares/authelia_context.go b/internal/middlewares/authelia_context.go
index c985d9333..57b5feb13 100644
--- a/internal/middlewares/authelia_context.go
+++ b/internal/middlewares/authelia_context.go
@@ -396,6 +396,15 @@ func (ctx *AutheliaCtx) DestroySession() error {
return provider.DestroySession(ctx.RequestCtx)
}
+// GetDefaultRedirectionURL retrieves the default redirection URL for the request.
+func (ctx *AutheliaCtx) GetDefaultRedirectionURL() *url.URL {
+ if provider, err := ctx.GetSessionProvider(); err == nil {
+ return provider.Config.DefaultRedirectionURL
+ }
+
+ return ctx.Configuration.DefaultRedirectionURL
+}
+
// ReplyOK is a helper method to reply ok.
func (ctx *AutheliaCtx) ReplyOK() {
ctx.SetContentTypeApplicationJSON()
diff --git a/internal/middlewares/authelia_context_test.go b/internal/middlewares/authelia_context_test.go
index 4092b24cd..3292f181d 100644
--- a/internal/middlewares/authelia_context_test.go
+++ b/internal/middlewares/authelia_context_test.go
@@ -546,3 +546,23 @@ func TestAutheliaCtx_GetTargetURICookieDomain(t *testing.T) {
})
}
}
+
+func TestAutheliaCtx_GetDefaultRedirectionURL(t *testing.T) {
+ mock := mocks.NewMockAutheliaCtx(t)
+ defer mock.Close()
+
+ mock.Ctx.Request.Header.Set("X-Original-URL", "https://auth.example4.com/consent")
+
+ assert.Equal(t, &url.URL{Scheme: "https", Host: "fallback.example.com"}, mock.Ctx.GetDefaultRedirectionURL())
+
+ mock.Ctx.Request.Header.Set("X-Original-URL", "https://auth.example.com/consent")
+
+ assert.Equal(t, &url.URL{Scheme: "https", Host: "www.example.com"}, mock.Ctx.GetDefaultRedirectionURL())
+
+ mock2 := mocks.NewMockAutheliaCtx(t)
+ defer mock2.Close()
+
+ mock2.Ctx.Request.Header.Set("X-Original-URL", "https://auth.example2.com/consent")
+
+ assert.Equal(t, &url.URL{Scheme: "https", Host: "www.example2.com"}, mock2.Ctx.GetDefaultRedirectionURL())
+}
diff --git a/internal/mocks/authelia_ctx.go b/internal/mocks/authelia_ctx.go
index 3de8f2f52..1ededfa1d 100644
--- a/internal/mocks/authelia_ctx.go
+++ b/internal/mocks/authelia_ctx.go
@@ -3,6 +3,7 @@ package mocks
import (
"encoding/json"
"fmt"
+ "net/url"
"testing"
"time"
@@ -50,7 +51,10 @@ func NewMockAutheliaCtx(t *testing.T) *MockAutheliaCtx {
datetime, _ := time.Parse("2006-Jan-02", "2013-Feb-03")
mockAuthelia.Clock.Set(datetime)
- config := schema.Configuration{}
+ config := schema.Configuration{
+ DefaultRedirectionURL: &url.URL{Scheme: "https", Host: "fallback.example.com"},
+ }
+
config.Session.Cookies = []schema.SessionCookie{
{
SessionCookieCommon: schema.SessionCookieCommon{
@@ -58,7 +62,8 @@ func NewMockAutheliaCtx(t *testing.T) *MockAutheliaCtx {
RememberMe: schema.DefaultSessionConfiguration.RememberMe,
Expiration: schema.DefaultSessionConfiguration.Expiration,
},
- Domain: "example.com",
+ Domain: "example.com",
+ DefaultRedirectionURL: &url.URL{Scheme: "https", Host: "www.example.com"},
},
{
SessionCookieCommon: schema.SessionCookieCommon{
@@ -66,7 +71,8 @@ func NewMockAutheliaCtx(t *testing.T) *MockAutheliaCtx {
RememberMe: schema.DefaultSessionConfiguration.RememberMe,
Expiration: schema.DefaultSessionConfiguration.Expiration,
},
- Domain: "example2.com",
+ Domain: "example2.com",
+ DefaultRedirectionURL: &url.URL{Scheme: "https", Host: "www.example2.com"},
},
}
diff --git a/internal/suites/ActiveDirectory/configuration.yml b/internal/suites/ActiveDirectory/configuration.yml
index cf9c504ad..0ad97c5b3 100644
--- a/internal/suites/ActiveDirectory/configuration.yml
+++ b/internal/suites/ActiveDirectory/configuration.yml
@@ -5,7 +5,6 @@
theme: grey
jwt_secret: very_important_secret
-default_redirection_url: https://home.example.com:8080/
server:
address: 'tcp://:9091'
@@ -37,6 +36,7 @@ session:
cookies:
- domain: 'example.com'
authelia_url: 'https://login.example.com:8080'
+ default_redirection_url: https://home.example.com:8080/
storage:
encryption_key: a_not_so_secure_encryption_key
diff --git a/internal/suites/Docker/configuration.yml b/internal/suites/Docker/configuration.yml
index 15dacd9f8..51d3c62ec 100644
--- a/internal/suites/Docker/configuration.yml
+++ b/internal/suites/Docker/configuration.yml
@@ -4,7 +4,6 @@
###############################################################
jwt_secret: very_important_secret
-default_redirection_url: https://home.example.com:8080/
server:
address: 'tcp://:9091'
@@ -27,6 +26,7 @@ session:
cookies:
- domain: 'example.com'
authelia_url: 'https://login.example.com:8080'
+ default_redirection_url: https://home.example.com:8080/
storage:
encryption_key: a_not_so_secure_encryption_key
diff --git a/internal/suites/DuoPush/configuration.yml b/internal/suites/DuoPush/configuration.yml
index 3235ce73c..3945772e9 100644
--- a/internal/suites/DuoPush/configuration.yml
+++ b/internal/suites/DuoPush/configuration.yml
@@ -4,7 +4,6 @@
###############################################################
jwt_secret: very_important_secret
-default_redirection_url: https://home.example.com:8080/
server:
address: 'tcp://:9091'
@@ -27,6 +26,7 @@ session:
cookies:
- domain: 'example.com'
authelia_url: 'https://login.example.com:8080'
+ default_redirection_url: https://home.example.com:8080/
# Configuration of the storage backend used to store data and secrets. i.e. totp data
storage:
diff --git a/internal/suites/LDAP/configuration.yml b/internal/suites/LDAP/configuration.yml
index ceb0115d3..2784c618b 100644
--- a/internal/suites/LDAP/configuration.yml
+++ b/internal/suites/LDAP/configuration.yml
@@ -5,7 +5,6 @@
theme: dark
jwt_secret: very_important_secret
-default_redirection_url: https://home.example.com:8080/
server:
address: 'tcp://:9091'
@@ -44,6 +43,7 @@ session:
cookies:
- domain: 'example.com'
authelia_url: 'https://login.example.com:8080'
+ default_redirection_url: https://home.example.com:8080/
storage:
encryption_key: a_not_so_secure_encryption_key
diff --git a/internal/suites/MariaDB/configuration.yml b/internal/suites/MariaDB/configuration.yml
index 858040965..27c05eda4 100644
--- a/internal/suites/MariaDB/configuration.yml
+++ b/internal/suites/MariaDB/configuration.yml
@@ -4,7 +4,6 @@
###############################################################
jwt_secret: very_important_secret
-default_redirection_url: https://home.example.com:8080/
server:
address: 'tcp://:9091'
@@ -27,6 +26,7 @@ session:
cookies:
- domain: 'example.com'
authelia_url: 'https://login.example.com:8080'
+ default_redirection_url: https://home.example.com:8080/
# Configuration of the storage backend used to store data and secrets. i.e. totp data
storage:
diff --git a/internal/suites/MySQL/configuration.yml b/internal/suites/MySQL/configuration.yml
index 062677e9d..a5c12f551 100644
--- a/internal/suites/MySQL/configuration.yml
+++ b/internal/suites/MySQL/configuration.yml
@@ -12,8 +12,6 @@ server:
log:
level: debug
-default_redirection_url: https://home.example.com:8080/
-
jwt_secret: very_important_secret
authentication_backend:
@@ -28,6 +26,7 @@ session:
cookies:
- domain: 'example.com'
authelia_url: 'https://login.example.com:8080'
+ default_redirection_url: https://home.example.com:8080/
# Configuration of the storage backend used to store data and secrets. i.e. totp data
storage:
diff --git a/internal/suites/OneFactorOnly/configuration.yml b/internal/suites/OneFactorOnly/configuration.yml
index 21f6cc5f0..b7a5ad5d4 100644
--- a/internal/suites/OneFactorOnly/configuration.yml
+++ b/internal/suites/OneFactorOnly/configuration.yml
@@ -4,7 +4,6 @@
###############################################################
jwt_secret: unsecure_secret
-default_redirection_url: https://home.example.com:8080/
server:
address: 'tcp://:9091'
@@ -27,6 +26,7 @@ session:
cookies:
- domain: 'example.com'
authelia_url: 'https://login.example.com:8080'
+ default_redirection_url: https://home.example.com:8080/
storage:
encryption_key: a_not_so_secure_encryption_key
diff --git a/internal/suites/Postgres/configuration.yml b/internal/suites/Postgres/configuration.yml
index 05d952bbb..03e296f25 100644
--- a/internal/suites/Postgres/configuration.yml
+++ b/internal/suites/Postgres/configuration.yml
@@ -4,7 +4,6 @@
###############################################################
jwt_secret: very_important_secret
-default_redirection_url: https://home.example.com:8080/
server:
address: 'tcp://:9091'
@@ -27,6 +26,7 @@ session:
cookies:
- domain: 'example.com'
authelia_url: 'https://login.example.com:8080'
+ default_redirection_url: https://home.example.com:8080/
# Configuration of the storage backend used to store data and secrets. i.e. totp data
storage:
diff --git a/internal/suites/ShortTimeouts/configuration.yml b/internal/suites/ShortTimeouts/configuration.yml
index e330dca6c..4aacc72e6 100644
--- a/internal/suites/ShortTimeouts/configuration.yml
+++ b/internal/suites/ShortTimeouts/configuration.yml
@@ -4,7 +4,6 @@
###############################################################
jwt_secret: unsecure_secret
-default_redirection_url: https://home.example.com:8080/
server:
address: 'tcp://:9091'
@@ -25,6 +24,7 @@ session:
- name: 'authelia_sessin'
domain: 'example.com'
authelia_url: 'https://login.example.com:8080'
+ default_redirection_url: https://home.example.com:8080/
inactivity: 5
expiration: 8
remember_me: 1y
diff --git a/internal/suites/example/kube/authelia/configs/configuration.yml b/internal/suites/example/kube/authelia/configs/configuration.yml
index d02eba4c5..2b822978f 100644
--- a/internal/suites/example/kube/authelia/configs/configuration.yml
+++ b/internal/suites/example/kube/authelia/configs/configuration.yml
@@ -3,8 +3,6 @@
# Authelia configuration #
###############################################################
-default_redirection_url: https://home.example.com:8080
-
server:
address: 'tcp://:443'
tls:
@@ -54,6 +52,7 @@ session:
cookies:
- domain: 'example.com'
authelia_url: 'https://login.example.com:8080'
+ default_redirection_url: https://home.example.com:8080
redis:
host: redis-service
diff --git a/internal/utils/url.go b/internal/utils/url.go
index 037591527..04e737400 100644
--- a/internal/utils/url.go
+++ b/internal/utils/url.go
@@ -69,3 +69,38 @@ func HasDomainSuffix(domain, domainSuffix string) bool {
return false
}
+
+// EqualURLs returns true if the two *url.URL values are effectively equal taking into consideration web normalization.
+func EqualURLs(first, second *url.URL) bool {
+ if first == nil && second == nil {
+ return true
+ } else if first == nil || second == nil {
+ return false
+ }
+
+ if !strings.EqualFold(first.Scheme, second.Scheme) {
+ return false
+ }
+
+ if !strings.EqualFold(first.Host, second.Host) {
+ return false
+ }
+
+ if first.Path != second.Path {
+ return false
+ }
+
+ if first.RawQuery != second.RawQuery {
+ return false
+ }
+
+ if first.Fragment != second.Fragment {
+ return false
+ }
+
+ if first.RawFragment != second.RawFragment {
+ return false
+ }
+
+ return true
+}
diff --git a/internal/utils/url_test.go b/internal/utils/url_test.go
index dc8ab1508..f6ddd6a2d 100644
--- a/internal/utils/url_test.go
+++ b/internal/utils/url_test.go
@@ -64,3 +64,26 @@ func TestHasDomainSuffix(t *testing.T) {
assert.False(t, HasDomainSuffix("abc", ""))
assert.False(t, HasDomainSuffix("", ""))
}
+
+func TestEqualURLs(t *testing.T) {
+ assert.False(t, EqualURLs(MustParseURL(url.Parse("https://google.com/abc#frag")), MustParseURL(url.Parse("https://google.com/abc"))))
+ assert.False(t, EqualURLs(&url.URL{Scheme: "https", Host: "example.com", RawFragment: "example"}, &url.URL{Scheme: "https", Host: "example.com"}))
+
+ assert.True(t, EqualURLs(MustParseURL(url.Parse("https://google.com")), MustParseURL(url.Parse("https://google.com"))))
+ assert.True(t, EqualURLs(MustParseURL(url.Parse("https://google.com")), MustParseURL(url.Parse("https://Google.com"))))
+ assert.True(t, EqualURLs(MustParseURL(url.Parse("https://google.com/abc")), MustParseURL(url.Parse("https://Google.com/abc"))))
+ assert.False(t, EqualURLs(MustParseURL(url.Parse("https://google.com/abc")), MustParseURL(url.Parse("https://Google.com/ABC"))))
+ assert.False(t, EqualURLs(MustParseURL(url.Parse("https://google.com/abc?abc=1")), MustParseURL(url.Parse("https://Google.com/abc"))))
+ assert.False(t, EqualURLs(MustParseURL(url.Parse("https://google2.com/abc")), MustParseURL(url.Parse("https://Google.com/abc"))))
+ assert.False(t, EqualURLs(MustParseURL(url.Parse("http://google.com/abc")), MustParseURL(url.Parse("https://Google.com/abc"))))
+ assert.True(t, EqualURLs(nil, nil))
+ assert.False(t, EqualURLs(nil, MustParseURL(url.Parse("http://google.com/abc"))))
+}
+
+func MustParseURL(uri *url.URL, err error) *url.URL {
+ if err != nil {
+ panic(err)
+ }
+
+ return uri
+}