summaryrefslogtreecommitdiff
path: root/internal/configuration
diff options
context:
space:
mode:
Diffstat (limited to 'internal/configuration')
-rw-r--r--internal/configuration/config.template.yml40
-rw-r--r--internal/configuration/deprecation.go20
-rw-r--r--internal/configuration/helpers_test.go11
-rw-r--r--internal/configuration/schema/const.go28
-rw-r--r--internal/configuration/schema/identity_providers.go5
-rw-r--r--internal/configuration/schema/keys.go1
-rw-r--r--internal/configuration/schema/server.go42
-rw-r--r--internal/configuration/schema/types.go2
-rw-r--r--internal/configuration/validator/access_control.go2
-rw-r--r--internal/configuration/validator/const.go56
-rw-r--r--internal/configuration/validator/identity_providers.go158
-rw-r--r--internal/configuration/validator/identity_providers_test.go388
-rw-r--r--internal/configuration/validator/server.go53
-rw-r--r--internal/configuration/validator/server_test.go81
-rw-r--r--internal/configuration/validator/util.go2
15 files changed, 709 insertions, 180 deletions
diff --git a/internal/configuration/config.template.yml b/internal/configuration/config.template.yml
index 62bf2b15e..d759a9ae5 100644
--- a/internal/configuration/config.template.yml
+++ b/internal/configuration/config.template.yml
@@ -1292,46 +1292,6 @@ notifier:
# 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
# -----END CERTIFICATE-----
- ## The issuer_private_key is used to sign the JWT forged by OpenID Connect. This is in addition to the
- ## issuer_private_keys option. Assumed to use the RS256 algorithm, and must not be specified if any of the
- ## keys in issuer_private_keys also has the algorithm RS256 or are an RSA key without an algorithm.
- ## Issuer Private Key can also be set using a secret: https://www.authelia.com/c/secrets
- # issuer_private_key: |
- # -----BEGIN RSA PRIVATE KEY-----
- # MIIBPAIBAAJBAK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZF
- # p7aTcToHMf00z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAQJBAJdpB0+RQ9ZFwy9Uk38P
- # 5zZpUB8cL8ZFeEFluQeVbt0vyNa+cPLvDLouY87onduXtMz5AKIatLaTOjuG2thh
- # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
- # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
- # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
- # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE=
- # -----END RSA PRIVATE KEY-----
-
- ## Optional matching certificate chain in PEM DER form that matches the issuer_private_key. All certificates within
- ## the chain must be valid and current, and from top to bottom each certificate must be signed by the next
- ## certificate in the chain if provided.
- # issuer_certificate_chain: |
- # -----BEGIN CERTIFICATE-----
- # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
- # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
- # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
- # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
- # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
- # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
- # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
- # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
- # -----END CERTIFICATE-----
- # -----BEGIN CERTIFICATE-----
- # MIIBWzCCAQWgAwIBAgIQYAKsXhJOXKfyySlmpKicTzANBgkqhkiG9w0BAQsFADAT
- # MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMzA0MjEwMDA3NDRaFw0yNDA0MjAwMDA3
- # NDRaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
- # AK2i7RlJEYo/Xa6mQmv9zmT0XUj3DcEhRJGPVw2qMyadUFxNg/ZFp7aTcToHMf00
- # z6T3b7mwdBkCFQOL3Kb7WRcCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
- # JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADQQB8
- # Of2iM7fPadmtChCMna8lYWH+lEplj6BxOJlRuGRawxszLwi78bnq0sCR33LU6xMx
- # 1oAPwIHNaJJwC4z6oG9E_DO_NOT_USE=
- # -----END CERTIFICATE-----
-
## Enables additional debug messages.
# enable_client_debug_messages: false
diff --git a/internal/configuration/deprecation.go b/internal/configuration/deprecation.go
index 420acd194..ad48456ba 100644
--- a/internal/configuration/deprecation.go
+++ b/internal/configuration/deprecation.go
@@ -358,6 +358,26 @@ var deprecations = map[string]Deprecation{
MapFunc: nil,
ErrFunc: nil,
},
+ "identity_providers.oidc.issuer_private_key": {
+ Version: model.SemanticVersion{Major: 4, Minor: 38},
+ Key: "identity_providers.oidc.issuer_private_key",
+ NewKey: "identity_providers.oidc.issuer_private_keys",
+ AutoMap: false,
+ MapFunc: nil,
+ ErrFunc: func(d Deprecation, keysFinal map[string]any, value any, val *schema.StructValidator) {
+ val.PushWarning(fmt.Errorf("configuration key '%s' is deprecated in %s and should be configured using the new configuration key '%s': this has been automatically mapped for you but you will need to adjust your configuration (see https://www.authelia.com/c/oidc) to remove this message", d.Key, d.Version, d.NewKey))
+ },
+ },
+ "identity_providers.oidc.issuer_certificate_chain": {
+ Version: model.SemanticVersion{Major: 4, Minor: 38},
+ Key: "identity_providers.oidc.issuer_certificate_chain",
+ NewKey: "identity_providers.oidc.issuer_private_keys",
+ AutoMap: false,
+ MapFunc: nil,
+ ErrFunc: func(d Deprecation, keysFinal map[string]any, value any, val *schema.StructValidator) {
+ val.PushWarning(fmt.Errorf("configuration key '%s' is deprecated in %s and should be configured using the new configuration key '%s': this has been automatically mapped for you but you will need to adjust your configuration (see https://www.authelia.com/c/oidc) to remove this message", d.Key, d.Version, d.NewKey))
+ },
+ },
"jwt_secret": {
Version: model.SemanticVersion{Major: 4, Minor: 38},
Key: "jwt_secret",
diff --git a/internal/configuration/helpers_test.go b/internal/configuration/helpers_test.go
index fcb7e343d..678b8535c 100644
--- a/internal/configuration/helpers_test.go
+++ b/internal/configuration/helpers_test.go
@@ -48,11 +48,12 @@ func TestGetEnvConfigMaps(t *testing.T) {
assert.True(t, ok)
assert.Equal(t, "mysecret.password", key)
- assert.Len(t, ignoredKeys, 4)
- assert.Contains(t, ignoredKeys, DefaultEnvPrefix+"MYOTHER_CONFIGKEY_FILE")
- assert.Contains(t, ignoredKeys, DefaultEnvPrefix+"MYSECRET_PASSWORD_FILE")
- assert.Contains(t, ignoredKeys, DefaultEnvPrefix+"MYSECRET_USER_PASSWORD_FILE")
- assert.Contains(t, ignoredKeys, DefaultEnvPrefix+"JWT_SECRET_FILE")
+ assert.Len(t, ignoredKeys, 6)
+ assert.Contains(t, ignoredKeys, DefaultEnvPrefix+MYOTHER_CONFIGKEY_FILE)
+ assert.Contains(t, ignoredKeys, DefaultEnvPrefix+MYSECRET_PASSWORD_FILE)
+ assert.Contains(t, ignoredKeys, DefaultEnvPrefix+MYSECRET_USER_PASSWORD_FILE)
+ assert.Contains(t, ignoredKeys, DefaultEnvPrefix+"IDENTITY_PROVIDERS_OIDC_ISSUER_PRIVATE_KEY_FILE")
+ assert.Contains(t, ignoredKeys, DefaultEnvPrefix+"IDENTITY_PROVIDERS_OIDC_ISSUER_CERTIFICATE_CHAIN_FILE")
}
func TestGetSecretConfigMapMockInput(t *testing.T) {
diff --git a/internal/configuration/schema/const.go b/internal/configuration/schema/const.go
index 52c92d591..d5de64444 100644
--- a/internal/configuration/schema/const.go
+++ b/internal/configuration/schema/const.go
@@ -125,8 +125,32 @@ const (
)
const (
- blockCERTIFICATE = "CERTIFICATE"
- blockRSAPRIVATEKEY = "RSA PRIVATE KEY"
+ blockCERTIFICATE = "CERTIFICATE"
+)
+
+// Authorization Schemes.
+const (
+ SchemeBasic = "basic"
+ SchemeBearer = "bearer"
+)
+
+// Authz values.
+const (
+ AuthzEndpointNameLegacy = "legacy"
+ AuthzEndpointNameAuthRequest = "auth-request"
+ AuthzEndpointNameExtAuthz = "ext-authz"
+ AuthzEndpointNameForwardAuth = "forward-auth"
+
+ AuthzImplementationLegacy = "Legacy"
+ AuthzImplementationAuthRequest = "AuthRequest"
+ AuthzImplementationExtAuthz = "ExtAuthz"
+ AuthzImplementationForwardAuth = "ForwardAuth"
+
+ AuthzStrategyHeaderCookieSession = "CookieSession"
+ AuthzStrategyHeaderAuthorization = "HeaderAuthorization"
+ AuthzStrategyHeaderProxyAuthorization = "HeaderProxyAuthorization"
+ AuthzStrategyHeaderAuthRequestProxyAuthorization = "HeaderAuthRequestProxyAuthorization"
+ AuthzStrategyHeaderLegacy = "HeaderLegacy"
)
const (
diff --git a/internal/configuration/schema/identity_providers.go b/internal/configuration/schema/identity_providers.go
index e755ba45b..2d1f6fcad 100644
--- a/internal/configuration/schema/identity_providers.go
+++ b/internal/configuration/schema/identity_providers.go
@@ -16,8 +16,8 @@ type IdentityProvidersOpenIDConnect struct {
HMACSecret string `koanf:"hmac_secret" json:"hmac_secret" jsonschema:"title=HMAC Secret" jsonschema_description:"The HMAC Secret used to sign Access Tokens."`
IssuerPrivateKeys []JWK `koanf:"issuer_private_keys" json:"issuer_private_keys" jsonschema:"title=Issuer Private Keys" jsonschema_description:"The Private Keys used to sign ID Tokens."`
- IssuerCertificateChain X509CertificateChain `koanf:"issuer_certificate_chain" json:"issuer_certificate_chain" jsonschema:"title=Issuer Certificate Chain" jsonschema_description:"The Issuer Certificate Chain with an RSA Public Key used to sign ID Tokens."`
- IssuerPrivateKey *rsa.PrivateKey `koanf:"issuer_private_key" json:"issuer_private_key" jsonschema:"title=Issuer Private Key" jsonschema_description:"The Issuer Private Key with an RSA Private Key used to sign ID Tokens."`
+ IssuerCertificateChain X509CertificateChain `koanf:"issuer_certificate_chain" json:"issuer_certificate_chain" jsonschema:"title=Issuer Certificate Chain,deprecated" jsonschema_description:"The Issuer Certificate Chain with an RSA Public Key used to sign ID Tokens."`
+ IssuerPrivateKey *rsa.PrivateKey `koanf:"issuer_private_key" json:"issuer_private_key" jsonschema:"title=Issuer Private Key,deprecated" jsonschema_description:"The Issuer Private Key with an RSA Private Key used to sign ID Tokens."`
EnableClientDebugMessages bool `koanf:"enable_client_debug_messages" json:"enable_client_debug_messages" jsonschema:"default=false,title=Enable Client Debug Messages" jsonschema_description:"Enables additional debug messages for clients."`
MinimumParameterEntropy int `koanf:"minimum_parameter_entropy" json:"minimum_parameter_entropy" jsonschema:"default=8,minimum=-1,title=Minimum Parameter Entropy" jsonschema_description:"The minimum entropy of the nonce parameter."`
@@ -61,6 +61,7 @@ type IdentityProvidersOpenIDConnectDiscovery struct {
ResponseObjectSigningAlgs []string
RequestObjectSigningAlgs []string
JWTResponseAccessTokens bool
+ BearerAuthorization bool
}
type IdentityProvidersOpenIDConnectLifespans struct {
diff --git a/internal/configuration/schema/keys.go b/internal/configuration/schema/keys.go
index de96acdb3..52a5e5a4a 100644
--- a/internal/configuration/schema/keys.go
+++ b/internal/configuration/schema/keys.go
@@ -318,6 +318,7 @@ var Keys = []string{
"server.endpoints.authz.*.implementation",
"server.endpoints.authz.*.authn_strategies",
"server.endpoints.authz.*.authn_strategies[].name",
+ "server.endpoints.authz.*.authn_strategies[].schemes",
"server.buffers.read",
"server.buffers.write",
"server.timeouts.read",
diff --git a/internal/configuration/schema/server.go b/internal/configuration/schema/server.go
index bc9069c43..2a5e4f299 100644
--- a/internal/configuration/schema/server.go
+++ b/internal/configuration/schema/server.go
@@ -45,7 +45,8 @@ type ServerEndpointsAuthz struct {
// ServerEndpointsAuthzAuthnStrategy is the Authz endpoints configuration for the HTTP server.
type ServerEndpointsAuthzAuthnStrategy struct {
- Name string `koanf:"name" json:"name" jsonschema:"enum=HeaderAuthorization,enum=HeaderProxyAuthorization,enum=HeaderAuthRequestProxyAuthorization,enum=HeaderLegacy,enum=CookieSession,title=Name" jsonschema_description:"The name of the Authorization strategy to use."`
+ Name string `koanf:"name" json:"name" jsonschema:"enum=HeaderAuthorization,enum=HeaderProxyAuthorization,enum=HeaderAuthRequestProxyAuthorization,enum=HeaderLegacy,enum=CookieSession,title=Name" jsonschema_description:"The name of the Authorization strategy to use."`
+ Schemes []string `koanf:"schemes" json:"schemes" jsonschema:"enum=basic,enum=bearer,default=basic,title=Authorization Schemes" jsonschema_description:"The name of the authorization schemes to allow with the header strategies."`
}
// ServerTLS represents the configuration of the http servers TLS options.
@@ -74,39 +75,50 @@ var DefaultServerConfiguration = Server{
},
Endpoints: ServerEndpoints{
Authz: map[string]ServerEndpointsAuthz{
- "legacy": {
- Implementation: "Legacy",
+ AuthzEndpointNameLegacy: {
+ Implementation: AuthzImplementationLegacy,
+ AuthnStrategies: []ServerEndpointsAuthzAuthnStrategy{
+ {
+ Name: AuthzStrategyHeaderLegacy,
+ },
+ {
+ Name: AuthzStrategyHeaderCookieSession,
+ },
+ },
},
- "auth-request": {
- Implementation: "AuthRequest",
+ AuthzEndpointNameAuthRequest: {
+ Implementation: AuthzImplementationAuthRequest,
AuthnStrategies: []ServerEndpointsAuthzAuthnStrategy{
{
- Name: "HeaderAuthRequestProxyAuthorization",
+ Name: AuthzStrategyHeaderAuthorization,
+ Schemes: []string{SchemeBasic},
},
{
- Name: "CookieSession",
+ Name: AuthzStrategyHeaderCookieSession,
},
},
},
- "forward-auth": {
- Implementation: "ForwardAuth",
+ AuthzEndpointNameExtAuthz: {
+ Implementation: AuthzImplementationExtAuthz,
AuthnStrategies: []ServerEndpointsAuthzAuthnStrategy{
{
- Name: "HeaderProxyAuthorization",
+ Name: AuthzStrategyHeaderAuthorization,
+ Schemes: []string{SchemeBasic},
},
{
- Name: "CookieSession",
+ Name: AuthzStrategyHeaderCookieSession,
},
},
},
- "ext-authz": {
- Implementation: "ExtAuthz",
+ AuthzEndpointNameForwardAuth: {
+ Implementation: AuthzImplementationForwardAuth,
AuthnStrategies: []ServerEndpointsAuthzAuthnStrategy{
{
- Name: "HeaderProxyAuthorization",
+ Name: AuthzStrategyHeaderAuthorization,
+ Schemes: []string{SchemeBasic},
},
{
- Name: "CookieSession",
+ Name: AuthzStrategyHeaderCookieSession,
},
},
},
diff --git a/internal/configuration/schema/types.go b/internal/configuration/schema/types.go
index 7f0497c51..a7705db1f 100644
--- a/internal/configuration/schema/types.go
+++ b/internal/configuration/schema/types.go
@@ -604,7 +604,7 @@ var jsonschemaACLNetwork = jsonschema.Schema{
var jsonschemaACLSubject = jsonschema.Schema{
Type: jsonschema.TypeString,
- Pattern: "^(user|group):.+$",
+ Pattern: "^(user|group|oauth2:client:):.+$",
}
var jsonschemaACLMethod = jsonschema.Schema{
diff --git a/internal/configuration/validator/access_control.go b/internal/configuration/validator/access_control.go
index 9b92eff80..9b7db2bea 100644
--- a/internal/configuration/validator/access_control.go
+++ b/internal/configuration/validator/access_control.go
@@ -18,7 +18,7 @@ func IsPolicyValid(policy string) (isValid bool) {
// IsSubjectValid check if a subject is valid.
func IsSubjectValid(subject string) (isValid bool) {
- return subject == "" || strings.HasPrefix(subject, "user:") || strings.HasPrefix(subject, "group:")
+ return subject == "" || strings.HasPrefix(subject, "user:") || strings.HasPrefix(subject, "group:") || strings.HasPrefix(subject, "oauth2:client:")
}
// IsNetworkGroupValid check if a network group is valid.
diff --git a/internal/configuration/validator/const.go b/internal/configuration/validator/const.go
index a2c636ece..d212ab9ce 100644
--- a/internal/configuration/validator/const.go
+++ b/internal/configuration/validator/const.go
@@ -159,7 +159,7 @@ const (
const (
errFmtOIDCProviderNoClientsConfigured = "identity_providers: oidc: option 'clients' must have one or " +
"more clients configured"
- errFmtOIDCProviderNoPrivateKey = "identity_providers: oidc: option `issuer_private_keys` or 'issuer_private_key' is required"
+ errFmtOIDCProviderNoPrivateKey = "identity_providers: oidc: option `issuer_private_keys` is required"
errFmtOIDCProviderEnforcePKCEInvalidValue = "identity_providers: oidc: option 'enforce_pkce' must be 'never', " +
"'public_clients_only' or 'always', but it's configured as '%s'"
errFmtOIDCProviderInsecureParameterEntropy = "identity_providers: oidc: option 'minimum_parameter_entropy' is "
@@ -196,6 +196,10 @@ const (
errFmtOIDCClientsWithEmptyID = "identity_providers: oidc: clients: option 'id' is required but was absent on the clients in positions %s"
errFmtOIDCClientsDeprecated = "identity_providers: oidc: clients: warnings for clients above indicate deprecated functionality and it's strongly suggested these issues are checked and fixed if they're legitimate issues or reported if they are not as in a future version these warnings will become errors"
+ errFmtMustOnlyHaveValues = "'%s' must only have the values %s "
+ errFmtMustBeConfiguredAs = "'%s' must be configured as %s "
+ errFmtOIDCClientOption = "identity_providers: oidc: clients: client '%s': option "
+ errFmtOIDCWhenScope = "when configured with scope '%s'"
errFmtOIDCClientInvalidSecretIs = errFmtOIDCClientOption + "'secret' is "
errFmtOIDCClientInvalidSecret = errFmtOIDCClientInvalidSecretIs + "required"
errFmtOIDCClientInvalidSecretPlainText = errFmtOIDCClientInvalidSecretIs + "plaintext but for clients not using the 'token_endpoint_auth_method' of 'client_secret_jwt' it should be a hashed value as plaintext values are deprecated with the exception of 'client_secret_jwt' and will be removed when oidc becomes stable"
@@ -204,7 +208,9 @@ const (
"required to be empty when option 'public' is true"
errFmtOIDCClientPublicInvalidSecretClientAuthMethod = errFmtOIDCClientInvalidSecretIs +
"required to be empty when option 'token_endpoint_auth_method' is configured as '%s'"
- errFmtOIDCClientOption = "identity_providers: oidc: clients: client '%s': option "
+ errFmtOIDCClientIDTooLong = errFmtOIDCClientOption + "'id' must not be more than 100 characters but it has %d characters"
+ errFmtOIDCClientIDInvalidCharacters = errFmtOIDCClientOption + "'id' must only contain RFC3986 unreserved characters"
+
errFmtOIDCClientRedirectURIHas = errFmtOIDCClientOption + "'redirect_uris' has "
errFmtOIDCClientRedirectURICantBeParsed = errFmtOIDCClientRedirectURIHas +
"an invalid value: redirect uri '%s' could not be parsed: %v"
@@ -215,10 +221,19 @@ const (
"an invalid value: redirect uri '%s' must have a scheme but it's absent"
errFmtOIDCClientInvalidConsentMode = "identity_providers: oidc: clients: client '%s': consent: option 'mode' must be one of " +
"%s but it's configured as '%s'"
- errFmtOIDCClientInvalidEntries = errFmtOIDCClientOption + "'%s' must only have the values " +
- "%s but the values %s are present"
+ errFmtOIDCClientInvalidEntries = errFmtOIDCClientOption + errFmtMustOnlyHaveValues +
+ "but the values %s are present"
+ errFmtOIDCClientUnknownScopeEntries = errFmtOIDCClientOption + "'%s' only expects the values " +
+ "%s but the unknown values %s are present and should generally only be used if a particular client requires a scope outside of our standard scopes"
+ errFmtOIDCClientInvalidEntriesScope = errFmtOIDCClientOption + errFmtMustOnlyHaveValues +
+ errFmtOIDCWhenScope + " but the values %s are present"
+ errFmtOIDCClientEmptyEntriesScope = errFmtOIDCClientOption + errFmtMustOnlyHaveValues +
+ errFmtOIDCWhenScope + " but it's not configured"
+ errFmtOIDCClientOptionRequiredScope = errFmtOIDCClientOption + "'%s' must be configured " + errFmtOIDCWhenScope + " but it's absent"
+ errFmtOIDCClientOptionMustScope = errFmtOIDCClientOption + errFmtMustBeConfiguredAs + errFmtOIDCWhenScope + " but it's configured as '%s'"
+ errFmtOIDCClientOptionMustScopeClientType = errFmtOIDCClientOption + errFmtMustBeConfiguredAs + errFmtOIDCWhenScope + " and the '%s' client type but it's configured as '%s'"
errFmtOIDCClientInvalidEntriesClientCredentials = errFmtOIDCClientOption + "'scopes' has the values " +
- "%s however when exclusively utilizing the 'client_credentials' value for the 'grant_types' the values %s are not allowed"
+ "%s however when utilizing the 'client_credentials' value for the 'grant_types' the values %s are not allowed"
errFmtOIDCClientInvalidEntryDuplicates = errFmtOIDCClientOption + "'%s' must have unique values but the values %s are duplicated"
errFmtOIDCClientInvalidValue = errFmtOIDCClientOption +
"'%s' must be one of %s but it's configured as '%s'"
@@ -367,11 +382,14 @@ const (
errFmtServerPathNotEndForwardSlash = "server: option 'address' must not and with a forward slash but it's configured as '%s'"
errFmtServerPathAlphaNum = "server: option 'path' must only contain alpha numeric characters"
- errFmtServerEndpointsAuthzImplementation = "server: endpoints: authz: %s: option 'implementation' must be one of %s but it's configured as '%s'"
- errFmtServerEndpointsAuthzStrategy = "server: endpoints: authz: %s: authn_strategies: option 'name' must be one of %s but it's configured as '%s'"
- errFmtServerEndpointsAuthzStrategyDuplicate = "server: endpoints: authz: %s: authn_strategies: duplicate strategy name detected with name '%s'"
- errFmtServerEndpointsAuthzPrefixDuplicate = "server: endpoints: authz: %s: endpoint starts with the same prefix as the '%s' endpoint with the '%s' implementation which accepts prefixes as part of its implementation"
- errFmtServerEndpointsAuthzInvalidName = "server: endpoints: authz: %s: contains invalid characters"
+ errFmtServerEndpointsAuthzImplementation = "server: endpoints: authz: %s: option 'implementation' must be one of %s but it's configured as '%s'"
+ errFmtServerEndpointsAuthzStrategy = "server: endpoints: authz: %s: authn_strategies: option 'name' must be one of %s but it's configured as '%s'"
+ errFmtServerEndpointsAuthzSchemes = "server: endpoints: authz: %s: authn_strategies: strategy #%d (%s): option 'schemes' must only include the values %s but has '%s'"
+ errFmtServerEndpointsAuthzSchemesInvalidForStrategy = "server: endpoints: authz: %s: authn_strategies: strategy #%d (%s): option 'schemes' is not valid for the strategy"
+ errFmtServerEndpointsAuthzStrategyNoName = "server: endpoints: authz: %s: authn_strategies: strategy #%d: option 'name' must be configured"
+ errFmtServerEndpointsAuthzStrategyDuplicate = "server: endpoints: authz: %s: authn_strategies: duplicate strategy name detected with name '%s'"
+ errFmtServerEndpointsAuthzPrefixDuplicate = "server: endpoints: authz: %s: endpoint starts with the same prefix as the '%s' endpoint with the '%s' implementation which accepts prefixes as part of its implementation"
+ errFmtServerEndpointsAuthzInvalidName = "server: endpoints: authz: %s: contains invalid characters"
errFmtServerEndpointsAuthzLegacyInvalidImplementation = "server: endpoints: authz: %s: option 'implementation' is invalid: the endpoint with the name 'legacy' must use the 'Legacy' implementation"
)
@@ -421,9 +439,7 @@ const (
)
const (
- legacy = "legacy"
- authzImplementationLegacy = "Legacy"
- authzImplementationExtAuthz = "ExtAuthz"
+ legacy = "legacy"
)
const (
@@ -431,8 +447,10 @@ const (
)
var (
- validAuthzImplementations = []string{"AuthRequest", "ForwardAuth", authzImplementationExtAuthz, authzImplementationLegacy}
- validAuthzAuthnStrategies = []string{"CookieSession", "HeaderAuthorization", "HeaderProxyAuthorization", "HeaderAuthRequestProxyAuthorization", "HeaderLegacy"}
+ validAuthzImplementations = []string{schema.AuthzImplementationAuthRequest, schema.AuthzImplementationForwardAuth, schema.AuthzImplementationExtAuthz, schema.AuthzImplementationLegacy}
+ validAuthzAuthnStrategies = []string{schema.AuthzStrategyHeaderCookieSession, schema.AuthzStrategyHeaderAuthorization, schema.AuthzStrategyHeaderProxyAuthorization, schema.AuthzStrategyHeaderAuthRequestProxyAuthorization, schema.AuthzStrategyHeaderLegacy}
+ validAuthzAuthnHeaderStrategies = []string{schema.AuthzStrategyHeaderAuthorization, schema.AuthzStrategyHeaderProxyAuthorization, schema.AuthzStrategyHeaderAuthRequestProxyAuthorization}
+ validAuthzAuthnStrategySchemes = []string{schema.SchemeBasic, schema.SchemeBearer}
)
var (
@@ -514,7 +532,7 @@ var (
var (
validOIDCCORSEndpoints = []string{oidc.EndpointAuthorization, oidc.EndpointPushedAuthorizationRequest, oidc.EndpointToken, oidc.EndpointIntrospection, oidc.EndpointRevocation, oidc.EndpointUserinfo}
- validOIDCClientScopes = []string{oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeProfile, oidc.ScopeGroups, oidc.ScopeOfflineAccess}
+ validOIDCClientScopes = []string{oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeProfile, oidc.ScopeGroups, oidc.ScopeOfflineAccess, oidc.ScopeOffline, oidc.ScopeAutheliaBearerAuthz}
validOIDCClientConsentModes = []string{auto, oidc.ClientConsentModeImplicit.String(), oidc.ClientConsentModeExplicit.String(), oidc.ClientConsentModePreConfigured.String()}
validOIDCClientResponseModes = []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment, oidc.ResponseModeJWT, oidc.ResponseModeFormPostJWT, oidc.ResponseModeQueryJWT, oidc.ResponseModeFragmentJWT}
validOIDCClientResponseTypes = []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowIDToken, oidc.ResponseTypeImplicitFlowToken, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeHybridFlowIDToken, oidc.ResponseTypeHybridFlowToken, oidc.ResponseTypeHybridFlowBoth}
@@ -527,6 +545,11 @@ var (
validOIDCClientTokenEndpointAuthMethodsConfidential = []string{oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic, oidc.ClientAuthMethodPrivateKeyJWT}
validOIDCClientTokenEndpointAuthSigAlgsClientSecretJWT = []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512}
validOIDCIssuerJWKSigningAlgs = []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgRSAPSSUsingSHA256, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgRSAUsingSHA384, oidc.SigningAlgRSAPSSUsingSHA384, oidc.SigningAlgECDSAUsingP384AndSHA384, oidc.SigningAlgRSAUsingSHA512, oidc.SigningAlgRSAPSSUsingSHA512, oidc.SigningAlgECDSAUsingP521AndSHA512}
+
+ validOIDCClientScopesBearerAuthz = []string{oidc.ScopeOfflineAccess, oidc.ScopeOffline, oidc.ScopeAutheliaBearerAuthz}
+ validOIDCClientResponseModesBearerAuthz = []string{oidc.ResponseModeFormPost, oidc.ResponseModeFormPostJWT}
+ validOIDCClientResponseTypesBearerAuthz = []string{oidc.ResponseTypeAuthorizationCodeFlow}
+ validOIDCClientGrantTypesBearerAuthz = []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeRefreshToken, oidc.GrantTypeClientCredentials}
)
var (
@@ -534,6 +557,7 @@ var (
reDomainCharacters = regexp.MustCompile(`^[a-z0-9-]+(\.[a-z0-9-]+)+[a-z0-9]$`)
reAuthzEndpointName = regexp.MustCompile(`^[a-zA-Z](([a-zA-Z0-9/._-]*)([a-zA-Z]))?$`)
reOpenIDConnectKID = regexp.MustCompile(`^([a-zA-Z0-9](([a-zA-Z0-9._~-]*)([a-zA-Z0-9]))?)?$`)
+ reRFC3986Unreserved = regexp.MustCompile(`^[a-zA-Z0-9._~-]+$`)
)
var replacedKeys = map[string]string{
diff --git a/internal/configuration/validator/identity_providers.go b/internal/configuration/validator/identity_providers.go
index 7b86d1b86..c28bbce8d 100644
--- a/internal/configuration/validator/identity_providers.go
+++ b/internal/configuration/validator/identity_providers.go
@@ -7,7 +7,6 @@ import (
"net/url"
"sort"
"strconv"
- "strings"
"github.com/ory/fosite"
@@ -120,6 +119,8 @@ func validateOIDCLifespans(config *schema.IdentityProvidersOpenIDConnect, _ *sch
func validateOIDCIssuer(config *schema.IdentityProvidersOpenIDConnect, validator *schema.StructValidator) {
switch {
+ case len(config.IssuerPrivateKeys) != 0 && (config.IssuerPrivateKey != nil || config.IssuerCertificateChain.HasCertificates()):
+ validator.Push(fmt.Errorf("identity_providers: oidc: option `issuer_private_keys` must not be configured at the same time as 'issuer_private_key' or 'issuer_certificate_chain'"))
case config.IssuerPrivateKey != nil:
validateOIDCIssuerPrivateKey(config)
@@ -347,19 +348,26 @@ func validateOIDCClients(config *schema.IdentityProvidersOpenIDConnect, validato
errDeprecatedFunc := func() { errDeprecated = true }
for c, client := range config.Clients {
- if client.ID == "" {
+ n := len(client.ID)
+
+ switch {
+ case n == 0:
blankClientIDs = append(blankClientIDs, "#"+strconv.Itoa(c+1))
- } else {
+ case n > 100:
+ validator.Push(fmt.Errorf(errFmtOIDCClientIDTooLong, client.ID, n))
+ case !reRFC3986Unreserved.MatchString(client.ID):
+ validator.Push(fmt.Errorf(errFmtOIDCClientIDInvalidCharacters, client.ID))
+ default:
if client.Description == "" {
config.Clients[c].Description = client.ID
}
- if id := strings.ToLower(client.ID); utils.IsStringInSlice(id, clientIDs) {
- if !utils.IsStringInSlice(id, duplicateClientIDs) {
- duplicateClientIDs = append(duplicateClientIDs, id)
+ if utils.IsStringInSlice(client.ID, clientIDs) {
+ if !utils.IsStringInSlice(client.ID, duplicateClientIDs) {
+ duplicateClientIDs = append(duplicateClientIDs, client.ID)
}
} else {
- clientIDs = append(clientIDs, id)
+ clientIDs = append(clientIDs, client.ID)
}
}
@@ -380,7 +388,15 @@ func validateOIDCClients(config *schema.IdentityProvidersOpenIDConnect, validato
}
func validateOIDCClient(c int, config *schema.IdentityProvidersOpenIDConnect, validator *schema.StructValidator, errDeprecatedFunc func()) {
+ ccg := utils.IsStringInSlice(oidc.GrantTypeClientCredentials, config.Clients[c].GrantTypes)
+
switch {
+ case ccg:
+ if config.Clients[c].AuthorizationPolicy == "" {
+ config.Clients[c].AuthorizationPolicy = policyOneFactor
+ } else if config.Clients[c].AuthorizationPolicy != policyOneFactor {
+ validator.Push(fmt.Errorf(errFmtOIDCClientInvalidValue, config.Clients[c].ID, "authorization_policy", strJoinOr([]string{policyOneFactor}), config.Clients[c].AuthorizationPolicy))
+ }
case config.Clients[c].AuthorizationPolicy == "":
config.Clients[c].AuthorizationPolicy = schema.DefaultOpenIDConnectClientConfiguration.AuthorizationPolicy
case utils.IsStringInSlice(config.Clients[c].AuthorizationPolicy, config.Discovery.AuthorizationPolicies):
@@ -416,12 +432,14 @@ func validateOIDCClient(c int, config *schema.IdentityProvidersOpenIDConnect, va
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidValue, config.Clients[c].ID, attrOIDCRequestedAudienceMode, strJoinOr([]string{oidc.ClientRequestedAudienceModeExplicit.String(), oidc.ClientRequestedAudienceModeImplicit.String()}), config.Clients[c].RequestedAudienceMode))
}
- validateOIDCClientConsentMode(c, config, validator)
+ setDefaults := validateOIDCClientScopesSpecialBearerAuthz(c, config, ccg, validator)
- validateOIDCClientScopes(c, config, validator, errDeprecatedFunc)
- validateOIDCClientResponseTypes(c, config, validator, errDeprecatedFunc)
- validateOIDCClientResponseModes(c, config, validator, errDeprecatedFunc)
- validateOIDCClientGrantTypes(c, config, validator, errDeprecatedFunc)
+ validateOIDCClientConsentMode(c, config, validator, setDefaults)
+
+ validateOIDCClientScopes(c, config, validator, ccg, errDeprecatedFunc)
+ validateOIDCClientResponseTypes(c, config, validator, setDefaults, errDeprecatedFunc)
+ validateOIDCClientResponseModes(c, config, validator, setDefaults, errDeprecatedFunc)
+ validateOIDCClientGrantTypes(c, config, validator, setDefaults, errDeprecatedFunc)
validateOIDCClientRedirectURIs(c, config, validator, errDeprecatedFunc)
validateOIDDClientSigningAlgs(c, config, validator)
@@ -569,9 +587,13 @@ func validateOIDCClientSectorIdentifier(c int, config *schema.IdentityProvidersO
}
}
-func validateOIDCClientConsentMode(c int, config *schema.IdentityProvidersOpenIDConnect, validator *schema.StructValidator) {
+func validateOIDCClientConsentMode(c int, config *schema.IdentityProvidersOpenIDConnect, validator *schema.StructValidator, setDefaults bool) {
switch {
case utils.IsStringInSlice(config.Clients[c].ConsentMode, []string{"", auto}):
+ if !setDefaults {
+ break
+ }
+
if config.Clients[c].ConsentPreConfiguredDuration != nil {
config.Clients[c].ConsentMode = oidc.ClientConsentModePreConfigured.String()
} else {
@@ -588,8 +610,8 @@ func validateOIDCClientConsentMode(c int, config *schema.IdentityProvidersOpenID
}
}
-func validateOIDCClientScopes(c int, config *schema.IdentityProvidersOpenIDConnect, validator *schema.StructValidator, errDeprecatedFunc func()) {
- if len(config.Clients[c].Scopes) == 0 {
+func validateOIDCClientScopes(c int, config *schema.IdentityProvidersOpenIDConnect, validator *schema.StructValidator, ccg bool, errDeprecatedFunc func()) {
+ if len(config.Clients[c].Scopes) == 0 && !ccg {
config.Clients[c].Scopes = schema.DefaultOpenIDConnectClientConfiguration.Scopes
}
@@ -601,16 +623,10 @@ func validateOIDCClientScopes(c int, config *schema.IdentityProvidersOpenIDConne
validator.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidEntryDuplicates, config.Clients[c].ID, attrOIDCScopes, strJoinAnd(duplicates)))
}
- if utils.IsStringInSlice(oidc.GrantTypeClientCredentials, config.Clients[c].GrantTypes) {
+ if ccg {
validateOIDCClientScopesClientCredentialsGrant(c, config, validator)
- } else {
- if !utils.IsStringInSlice(oidc.ScopeOpenID, config.Clients[c].Scopes) {
- config.Clients[c].Scopes = append([]string{oidc.ScopeOpenID}, config.Clients[c].Scopes...)
- }
-
- if len(invalid) != 0 {
- validator.Push(fmt.Errorf(errFmtOIDCClientInvalidEntries, config.Clients[c].ID, attrOIDCScopes, strJoinOr(validOIDCClientScopes), strJoinAnd(invalid)))
- }
+ } else if len(invalid) != 0 {
+ validator.PushWarning(fmt.Errorf(errFmtOIDCClientUnknownScopeEntries, config.Clients[c].ID, attrOIDCScopes, strJoinOr(validOIDCClientScopes), strJoinAnd(invalid)))
}
if utils.IsStringSliceContainsAny([]string{oidc.ScopeOfflineAccess, oidc.ScopeOffline}, config.Clients[c].Scopes) &&
@@ -625,11 +641,81 @@ func validateOIDCClientScopes(c int, config *schema.IdentityProvidersOpenIDConne
}
}
-func validateOIDCClientScopesClientCredentialsGrant(c int, config *schema.IdentityProvidersOpenIDConnect, validator *schema.StructValidator) {
- if len(config.Clients[c].GrantTypes) != 1 {
- return
+//nolint:gocyclo
+func validateOIDCClientScopesSpecialBearerAuthz(c int, config *schema.IdentityProvidersOpenIDConnect, ccg bool, validator *schema.StructValidator) bool {
+ if !utils.IsStringInSlice(oidc.ScopeAutheliaBearerAuthz, config.Clients[c].Scopes) {
+ return true
+ }
+
+ if !config.Discovery.BearerAuthorization {
+ config.Discovery.BearerAuthorization = true
+ }
+
+ if !utils.IsStringSliceContainsAll(config.Clients[c].Scopes, validOIDCClientScopesBearerAuthz) {
+ validator.Push(fmt.Errorf(errFmtOIDCClientInvalidEntriesScope, config.Clients[c].ID, attrOIDCScopes, strJoinAnd(validOIDCClientScopesBearerAuthz), oidc.ScopeAutheliaBearerAuthz, strJoinAnd(config.Clients[c].Scopes)))
}
+ if len(config.Clients[c].GrantTypes) == 0 {
+ validator.Push(fmt.Errorf(errFmtOIDCClientEmptyEntriesScope, config.Clients[c].ID, attrOIDCGrantTypes, strJoinAnd(validOIDCClientGrantTypesBearerAuthz), oidc.ScopeAutheliaBearerAuthz))
+ } else {
+ invalid, _ := validateList(config.Clients[c].GrantTypes, validOIDCClientGrantTypesBearerAuthz, false)
+
+ if len(invalid) != 0 {
+ validator.Push(fmt.Errorf(errFmtOIDCClientInvalidEntriesScope, config.Clients[c].ID, attrOIDCGrantTypes, strJoinAnd(validOIDCClientGrantTypesBearerAuthz), oidc.ScopeAutheliaBearerAuthz, strJoinAnd(invalid)))
+ }
+ }
+
+ if len(config.Clients[c].Audience) == 0 {
+ validator.Push(fmt.Errorf(errFmtOIDCClientOptionRequiredScope, config.Clients[c].ID, "audience", oidc.ScopeAutheliaBearerAuthz))
+ }
+
+ if !ccg {
+ if !config.Clients[c].EnforcePAR {
+ validator.Push(fmt.Errorf(errFmtOIDCClientOptionMustScope, config.Clients[c].ID, "enforce_par", "'true'", oidc.ScopeAutheliaBearerAuthz, "false"))
+ }
+
+ if !config.Clients[c].EnforcePKCE {
+ validator.Push(fmt.Errorf(errFmtOIDCClientOptionMustScope, config.Clients[c].ID, "enforce_pkce", "'true'", oidc.ScopeAutheliaBearerAuthz, "false"))
+ } else if config.Clients[c].PKCEChallengeMethod != oidc.PKCEChallengeMethodSHA256 {
+ validator.Push(fmt.Errorf(errFmtOIDCClientOptionMustScope, config.Clients[c].ID, attrOIDCPKCEChallengeMethod, "'"+oidc.PKCEChallengeMethodSHA256+"'", oidc.ScopeAutheliaBearerAuthz, config.Clients[c].PKCEChallengeMethod))
+ }
+
+ if config.Clients[c].ConsentMode != oidc.ClientConsentModeExplicit.String() {
+ validator.Push(fmt.Errorf(errFmtOIDCClientOptionMustScope, config.Clients[c].ID, "consent_mode", "'"+oidc.ClientConsentModeExplicit.String()+"'", oidc.ScopeAutheliaBearerAuthz, config.Clients[c].ConsentMode))
+ }
+
+ if len(config.Clients[c].ResponseTypes) == 0 {
+ validator.Push(fmt.Errorf(errFmtOIDCClientEmptyEntriesScope, config.Clients[c].ID, attrOIDCResponseTypes, strJoinAnd(validOIDCClientResponseTypesBearerAuthz), oidc.ScopeAutheliaBearerAuthz))
+ } else if !utils.IsStringSliceContainsAll(config.Clients[c].ResponseTypes, validOIDCClientResponseTypesBearerAuthz) ||
+ !utils.IsStringSliceContainsAny(config.Clients[c].ResponseTypes, validOIDCClientResponseTypesBearerAuthz) {
+ validator.Push(fmt.Errorf(errFmtOIDCClientInvalidEntriesScope, config.Clients[c].ID, attrOIDCResponseTypes, strJoinAnd(validOIDCClientResponseTypesBearerAuthz), oidc.ScopeAutheliaBearerAuthz, strJoinAnd(config.Clients[c].ResponseTypes)))
+ }
+
+ if len(config.Clients[c].ResponseModes) == 0 {
+ validator.Push(fmt.Errorf(errFmtOIDCClientEmptyEntriesScope, config.Clients[c].ID, attrOIDCResponseModes, strJoinAnd(validOIDCClientResponseModesBearerAuthz), oidc.ScopeAutheliaBearerAuthz))
+ } else if !utils.IsStringSliceContainsAll(config.Clients[c].ResponseModes, validOIDCClientResponseModesBearerAuthz) ||
+ !utils.IsStringSliceContainsAny(config.Clients[c].ResponseModes, validOIDCClientResponseModesBearerAuthz) {
+ validator.Push(fmt.Errorf(errFmtOIDCClientInvalidEntriesScope, config.Clients[c].ID, attrOIDCResponseModes, strJoinAnd(validOIDCClientResponseModesBearerAuthz), oidc.ScopeAutheliaBearerAuthz, strJoinAnd(config.Clients[c].ResponseModes)))
+ }
+ }
+
+ if config.Clients[c].Public {
+ if config.Clients[c].TokenEndpointAuthMethod != oidc.ClientAuthMethodNone {
+ validator.Push(fmt.Errorf(errFmtOIDCClientOptionMustScopeClientType, config.Clients[c].ID, attrOIDCTokenAuthMethod, "'"+oidc.ClientAuthMethodNone+"'", oidc.ScopeAutheliaBearerAuthz, "public", config.Clients[c].TokenEndpointAuthMethod))
+ }
+ } else {
+ switch config.Clients[c].TokenEndpointAuthMethod {
+ case oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretJWT, oidc.ClientAuthMethodPrivateKeyJWT:
+ break
+ default:
+ validator.Push(fmt.Errorf(errFmtOIDCClientOptionMustScopeClientType, config.Clients[c].ID, attrOIDCTokenAuthMethod, strJoinOr([]string{oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretJWT, oidc.ClientAuthMethodPrivateKeyJWT}), oidc.ScopeAutheliaBearerAuthz, "confidential", config.Clients[c].TokenEndpointAuthMethod))
+ }
+ }
+
+ return false
+}
+
+func validateOIDCClientScopesClientCredentialsGrant(c int, config *schema.IdentityProvidersOpenIDConnect, validator *schema.StructValidator) {
invalid := validateListNotAllowed(config.Clients[c].Scopes, []string{oidc.ScopeOpenID, oidc.ScopeOffline, oidc.ScopeOfflineAccess})
if len(invalid) > 0 {
@@ -637,8 +723,12 @@ func validateOIDCClientScopesClientCredentialsGrant(c int, config *schema.Identi
}
}
-func validateOIDCClientResponseTypes(c int, config *schema.IdentityProvidersOpenIDConnect, validator *schema.StructValidator, errDeprecatedFunc func()) {
+func validateOIDCClientResponseTypes(c int, config *schema.IdentityProvidersOpenIDConnect, validator *schema.StructValidator, setDefaults bool, errDeprecatedFunc func()) {
if len(config.Clients[c].ResponseTypes) == 0 {
+ if !setDefaults {
+ return
+ }
+
config.Clients[c].ResponseTypes = schema.DefaultOpenIDConnectClientConfiguration.ResponseTypes
}
@@ -655,8 +745,12 @@ func validateOIDCClientResponseTypes(c int, config *schema.IdentityProvidersOpen
}
}
-func validateOIDCClientResponseModes(c int, config *schema.IdentityProvidersOpenIDConnect, validator *schema.StructValidator, errDeprecatedFunc func()) {
+func validateOIDCClientResponseModes(c int, config *schema.IdentityProvidersOpenIDConnect, validator *schema.StructValidator, setDefaults bool, errDeprecatedFunc func()) {
if len(config.Clients[c].ResponseModes) == 0 {
+ if !setDefaults {
+ return
+ }
+
config.Clients[c].ResponseModes = schema.DefaultOpenIDConnectClientConfiguration.ResponseModes
for _, responseType := range config.Clients[c].ResponseTypes {
@@ -687,8 +781,12 @@ func validateOIDCClientResponseModes(c int, config *schema.IdentityProvidersOpen
}
}
-func validateOIDCClientGrantTypes(c int, config *schema.IdentityProvidersOpenIDConnect, validator *schema.StructValidator, errDeprecatedFunc func()) {
+func validateOIDCClientGrantTypes(c int, config *schema.IdentityProvidersOpenIDConnect, validator *schema.StructValidator, setDefaults bool, errDeprecatedFunc func()) {
if len(config.Clients[c].GrantTypes) == 0 {
+ if !setDefaults {
+ return
+ }
+
validateOIDCClientGrantTypesSetDefaults(c, config)
}
diff --git a/internal/configuration/validator/identity_providers_test.go b/internal/configuration/validator/identity_providers_test.go
index b3e276488..a5af2a01d 100644
--- a/internal/configuration/validator/identity_providers_test.go
+++ b/internal/configuration/validator/identity_providers_test.go
@@ -34,7 +34,31 @@ func TestShouldRaiseErrorWhenInvalidOIDCServerConfiguration(t *testing.T) {
require.Len(t, validator.Errors(), 2)
- assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: option `issuer_private_keys` or 'issuer_private_key' is required")
+ assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: option `issuer_private_keys` is required")
+ assert.EqualError(t, validator.Errors()[1], "identity_providers: oidc: option 'clients' must have one or more clients configured")
+}
+
+func TestShouldRaiseErrorWhenInvalidOIDCServerConfigurationBothKeyTypesSpecified(t *testing.T) {
+ validator := schema.NewStructValidator()
+ config := &schema.IdentityProviders{
+ OIDC: &schema.IdentityProvidersOpenIDConnect{
+ HMACSecret: "abc",
+ IssuerPrivateKey: keyRSA2048,
+ IssuerPrivateKeys: []schema.JWK{
+ {
+ Use: "sig",
+ Algorithm: "RS256",
+ Key: keyRSA4096,
+ },
+ },
+ },
+ }
+
+ ValidateIdentityProviders(config, validator)
+
+ require.Len(t, validator.Errors(), 2)
+
+ assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: option `issuer_private_keys` must not be configured at the same time as 'issuer_private_key' or 'issuer_certificate_chain'")
assert.EqualError(t, validator.Errors()[1], "identity_providers: oidc: option 'clients' must have one or more clients configured")
}
@@ -188,6 +212,30 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
},
},
{
+ name: "BadIDTooLong",
+ clients: []schema.IdentityProvidersOpenIDConnectClient{
+ {
+ ID: "abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123",
+ Secret: tOpenIDConnectPlainTextClientSecret,
+ },
+ },
+ errors: []string{
+ "identity_providers: oidc: clients: client 'abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123': option 'id' must not be more than 100 characters but it has 108 characters",
+ },
+ },
+ {
+ name: "BadIDInvalidCharacters",
+ clients: []schema.IdentityProvidersOpenIDConnectClient{
+ {
+ ID: "@!#!@$!@#*()!&@%*(!^@#*()!&@^%!(_@#&",
+ Secret: tOpenIDConnectPlainTextClientSecret,
+ },
+ },
+ errors: []string{
+ "identity_providers: oidc: clients: client '@!#!@$!@#*()!&@%*(!^@#*()!&@^%!(_@#&': option 'id' must only contain RFC3986 unreserved characters",
+ },
+ },
+ {
name: "InvalidPolicy",
clients: []schema.IdentityProvidersOpenIDConnectClient{
{
@@ -204,6 +252,25 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
},
},
{
+ name: "InvalidPolicyCCG",
+ clients: []schema.IdentityProvidersOpenIDConnectClient{
+ {
+ ID: "client-1",
+ Secret: tOpenIDConnectPlainTextClientSecret,
+ AuthorizationPolicy: "a-policy",
+ RedirectURIs: []string{
+ "https://google.com",
+ },
+ GrantTypes: []string{
+ oidc.GrantTypeClientCredentials,
+ },
+ },
+ },
+ errors: []string{
+ "identity_providers: oidc: clients: client 'client-1': option 'authorization_policy' must be one of 'one_factor' but it's configured as 'a-policy'",
+ },
+ },
+ {
name: "ClientIDDuplicated",
clients: []schema.IdentityProvidersOpenIDConnectClient{
{
@@ -453,32 +520,6 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
}
}
-func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadScopes(t *testing.T) {
- validator := schema.NewStructValidator()
- config := &schema.IdentityProviders{
- OIDC: &schema.IdentityProvidersOpenIDConnect{
- HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
- IssuerPrivateKey: keyRSA2048,
- Clients: []schema.IdentityProvidersOpenIDConnectClient{
- {
- ID: "good_id",
- Secret: tOpenIDConnectPlainTextClientSecret,
- AuthorizationPolicy: "two_factor",
- Scopes: []string{"openid", "bad_scope"},
- RedirectURIs: []string{
- "https://google.com/callback",
- },
- },
- },
- },
- }
-
- ValidateIdentityProviders(config, validator)
-
- require.Len(t, validator.Errors(), 1)
- assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: clients: client 'good_id': option 'scopes' must only have the values 'openid', 'email', 'profile', 'groups', or 'offline_access' but the values 'bad_scope' are present")
-}
-
func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadGrantTypes(t *testing.T) {
validator := schema.NewStructValidator()
config := &schema.IdentityProviders{
@@ -858,7 +899,7 @@ func TestValidateOIDCClients(t *testing.T) {
nil,
},
{
- "ShouldIncludeMinimalScope",
+ "ShouldNotIncludeOldMinimalScope",
nil,
nil,
tcv{
@@ -868,7 +909,7 @@ func TestValidateOIDCClients(t *testing.T) {
nil,
},
tcv{
- []string{oidc.ScopeOpenID, oidc.ScopeEmail},
+ []string{oidc.ScopeEmail},
[]string{oidc.ResponseTypeAuthorizationCodeFlow},
[]string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
[]string{oidc.GrantTypeAuthorizationCode},
@@ -1031,7 +1072,7 @@ func TestValidateOIDCClients(t *testing.T) {
nil,
},
{
- "ShouldRaiseErrorOnInvalidScopes",
+ "ShouldRaiseWarningOnInvalidScopes",
nil,
nil,
tcv{
@@ -1046,10 +1087,10 @@ func TestValidateOIDCClients(t *testing.T) {
[]string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
[]string{oidc.GrantTypeAuthorizationCode},
},
- nil,
[]string{
- "identity_providers: oidc: clients: client 'test': option 'scopes' must only have the values 'openid', 'email', 'profile', 'groups', or 'offline_access' but the values 'group' are present",
+ "identity_providers: oidc: clients: client 'test': option 'scopes' only expects the values 'openid', 'email', 'profile', 'groups', 'offline_access', 'offline', or 'authelia.bearer.authz' but the unknown values 'group' are present and should generally only be used if a particular client requires a scope outside of our standard scopes",
},
+ nil,
},
{
"ShouldRaiseErrorOnMissingAuthorizationCodeFlowResponseTypeWithRefreshTokenValues",
@@ -1290,33 +1331,8 @@ func TestValidateOIDCClients(t *testing.T) {
"identity_providers: oidc: clients: client 'test': option 'scopes' should only have the values 'offline_access' or 'offline' if the client is also configured with a 'response_type' such as 'code', 'code id_token', 'code token', or 'code id_token token' which respond with authorization codes",
},
[]string{
- "identity_providers: oidc: clients: client 'test': option 'scopes' has the values 'openid', 'offline', and 'offline_access' however when exclusively utilizing the 'client_credentials' value for the 'grant_types' the values 'openid', 'offline', or 'offline_access' are not allowed",
- },
- },
- {
- "ShouldNotRestrictRefreshOpenIDScopesWithMultipleGrantTypesAndAllowCustomClientCredentials",
- func(have *schema.IdentityProvidersOpenIDConnect) {
- have.Clients[0].Public = false
- have.Clients[0].Scopes = []string{oidc.ScopeOpenID, oidc.ScopeOffline, oidc.ScopeOfflineAccess, "custom"}
- },
- nil,
- tcv{
- nil,
- nil,
- nil,
- []string{oidc.GrantTypeClientCredentials, oidc.GrantTypeImplicit},
- },
- tcv{
- []string{oidc.ScopeOpenID, oidc.ScopeOffline, oidc.ScopeOfflineAccess, "custom"},
- []string{oidc.ResponseTypeAuthorizationCodeFlow},
- []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
- []string{oidc.GrantTypeClientCredentials, oidc.GrantTypeImplicit},
- },
- []string{
- "identity_providers: oidc: clients: client 'test': option 'scopes' should only have the values 'offline_access' or 'offline' if the client is also configured with a 'response_type' such as 'code', 'code id_token', 'code token', or 'code id_token token' which respond with authorization codes",
- "identity_providers: oidc: clients: client 'test': option 'grant_types' should only have grant type values which are valid with the configured 'response_types' for the client but 'implicit' expects a response type for either the implicit or hybrid flow such as 'id_token', 'token', 'id_token token', 'code id_token', 'code token', or 'code id_token token' but the response types are 'code'",
+ "identity_providers: oidc: clients: client 'test': option 'scopes' has the values 'openid', 'offline', and 'offline_access' however when utilizing the 'client_credentials' value for the 'grant_types' the values 'openid', 'offline', or 'offline_access' are not allowed",
},
- nil,
},
{
"ShouldRaiseErrorOnGrantTypeRefreshTokenWithoutScopeOfflineAccess",
@@ -2062,6 +2078,262 @@ func TestValidateOIDCClients(t *testing.T) {
},
},
{
+ "ShouldHandleBearerErrorsMisconfiguredPublicClientType",
+ func(have *schema.IdentityProvidersOpenIDConnect) {
+ have.Clients[0] = schema.IdentityProvidersOpenIDConnectClient{
+ ID: "abc",
+ Secret: nil,
+ Public: true,
+ RedirectURIs: []string{"http://localhost"},
+ Audience: nil,
+ Scopes: []string{oidc.ScopeAutheliaBearerAuthz, oidc.ScopeOpenID},
+ GrantTypes: []string{oidc.GrantTypeImplicit},
+ ResponseTypes: []string{oidc.ResponseTypeImplicitFlowBoth},
+ ResponseModes: []string{oidc.ResponseModeQuery},
+ AuthorizationPolicy: "",
+ RequestedAudienceMode: "",
+ ConsentMode: oidc.ClientConsentModeImplicit.String(),
+ EnforcePAR: false,
+ EnforcePKCE: false,
+ PKCEChallengeMethod: "",
+ TokenEndpointAuthMethod: "",
+ }
+ },
+ nil,
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeAutheliaBearerAuthz, oidc.ScopeOpenID},
+ []string{oidc.ResponseTypeImplicitFlowBoth},
+ []string{oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeImplicit},
+ },
+ nil,
+ []string{
+ "identity_providers: oidc: clients: client 'abc': option 'scopes' must only have the values 'offline_access', 'offline', and 'authelia.bearer.authz' when configured with scope 'authelia.bearer.authz' but the values 'authelia.bearer.authz' and 'openid' are present",
+ "identity_providers: oidc: clients: client 'abc': option 'grant_types' must only have the values 'authorization_code', 'refresh_token', and 'client_credentials' when configured with scope 'authelia.bearer.authz' but the values 'implicit' are present",
+ "identity_providers: oidc: clients: client 'abc': option 'audience' must be configured when configured with scope 'authelia.bearer.authz' but it's absent",
+ "identity_providers: oidc: clients: client 'abc': option 'enforce_par' must be configured as 'true' when configured with scope 'authelia.bearer.authz' but it's configured as 'false'",
+ "identity_providers: oidc: clients: client 'abc': option 'enforce_pkce' must be configured as 'true' when configured with scope 'authelia.bearer.authz' but it's configured as 'false'",
+ "identity_providers: oidc: clients: client 'abc': option 'consent_mode' must be configured as 'explicit' when configured with scope 'authelia.bearer.authz' but it's configured as 'implicit'",
+ "identity_providers: oidc: clients: client 'abc': option 'response_types' must only have the values 'code' when configured with scope 'authelia.bearer.authz' but the values 'id_token token' are present",
+ "identity_providers: oidc: clients: client 'abc': option 'response_modes' must only have the values 'form_post' and 'form_post.jwt' when configured with scope 'authelia.bearer.authz' but the values 'query' are present",
+ "identity_providers: oidc: clients: client 'abc': option 'token_endpoint_auth_method' must be configured as 'none' when configured with scope 'authelia.bearer.authz' and the 'public' client type but it's configured as ''",
+ },
+ },
+ {
+ "ShouldHandleBearerErrorsMisconfiguredConfidentialClientType",
+ func(have *schema.IdentityProvidersOpenIDConnect) {
+ have.Clients[0] = schema.IdentityProvidersOpenIDConnectClient{
+ ID: "abc",
+ Secret: tOpenIDConnectPBKDF2ClientSecret,
+ Public: false,
+ RedirectURIs: []string{"http://localhost"},
+ Audience: nil,
+ Scopes: []string{oidc.ScopeAutheliaBearerAuthz, oidc.ScopeOpenID},
+ GrantTypes: []string{oidc.GrantTypeImplicit},
+ ResponseTypes: []string{oidc.ResponseTypeImplicitFlowBoth},
+ ResponseModes: []string{oidc.ResponseModeQuery},
+ AuthorizationPolicy: "",
+ RequestedAudienceMode: "",
+ ConsentMode: oidc.ClientConsentModeImplicit.String(),
+ EnforcePAR: false,
+ EnforcePKCE: true,
+ PKCEChallengeMethod: "",
+ TokenEndpointAuthMethod: "",
+ }
+ },
+ nil,
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeAutheliaBearerAuthz, oidc.ScopeOpenID},
+ []string{oidc.ResponseTypeImplicitFlowBoth},
+ []string{oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeImplicit},
+ },
+ nil,
+ []string{
+ "identity_providers: oidc: clients: client 'abc': option 'scopes' must only have the values 'offline_access', 'offline', and 'authelia.bearer.authz' when configured with scope 'authelia.bearer.authz' but the values 'authelia.bearer.authz' and 'openid' are present",
+ "identity_providers: oidc: clients: client 'abc': option 'grant_types' must only have the values 'authorization_code', 'refresh_token', and 'client_credentials' when configured with scope 'authelia.bearer.authz' but the values 'implicit' are present",
+ "identity_providers: oidc: clients: client 'abc': option 'audience' must be configured when configured with scope 'authelia.bearer.authz' but it's absent",
+ "identity_providers: oidc: clients: client 'abc': option 'enforce_par' must be configured as 'true' when configured with scope 'authelia.bearer.authz' but it's configured as 'false'",
+ "identity_providers: oidc: clients: client 'abc': option 'pkce_challenge_method' must be configured as 'S256' when configured with scope 'authelia.bearer.authz' but it's configured as ''",
+ "identity_providers: oidc: clients: client 'abc': option 'consent_mode' must be configured as 'explicit' when configured with scope 'authelia.bearer.authz' but it's configured as 'implicit'",
+ "identity_providers: oidc: clients: client 'abc': option 'response_types' must only have the values 'code' when configured with scope 'authelia.bearer.authz' but the values 'id_token token' are present",
+ "identity_providers: oidc: clients: client 'abc': option 'response_modes' must only have the values 'form_post' and 'form_post.jwt' when configured with scope 'authelia.bearer.authz' but the values 'query' are present",
+ "identity_providers: oidc: clients: client 'abc': option 'token_endpoint_auth_method' must be configured as 'client_secret_post', 'client_secret_jwt', or 'private_key_jwt' when configured with scope 'authelia.bearer.authz' and the 'confidential' client type but it's configured as ''",
+ },
+ },
+ {
+ "ShouldHandleBearerErrorsMisconfiguredConfidentialClientTypeClientCredentials",
+ func(have *schema.IdentityProvidersOpenIDConnect) {
+ have.Clients[0] = schema.IdentityProvidersOpenIDConnectClient{
+ ID: "abc",
+ Secret: tOpenIDConnectPBKDF2ClientSecret,
+ Public: false,
+ RedirectURIs: []string{"http://localhost"},
+ Audience: nil,
+ Scopes: []string{oidc.ScopeAutheliaBearerAuthz, oidc.ScopeOpenID},
+ GrantTypes: []string{oidc.GrantTypeClientCredentials},
+ ResponseTypes: nil,
+ ResponseModes: nil,
+ AuthorizationPolicy: "",
+ RequestedAudienceMode: "",
+ ConsentMode: oidc.ClientConsentModeImplicit.String(),
+ EnforcePAR: false,
+ EnforcePKCE: true,
+ PKCEChallengeMethod: "",
+ TokenEndpointAuthMethod: "",
+ }
+ },
+ nil,
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeAutheliaBearerAuthz, oidc.ScopeOpenID},
+ []string(nil),
+ []string(nil),
+ []string{oidc.GrantTypeClientCredentials},
+ },
+ nil,
+ []string{
+ "identity_providers: oidc: clients: client 'abc': option 'scopes' must only have the values 'offline_access', 'offline', and 'authelia.bearer.authz' when configured with scope 'authelia.bearer.authz' but the values 'authelia.bearer.authz' and 'openid' are present",
+ "identity_providers: oidc: clients: client 'abc': option 'audience' must be configured when configured with scope 'authelia.bearer.authz' but it's absent",
+ "identity_providers: oidc: clients: client 'abc': option 'token_endpoint_auth_method' must be configured as 'client_secret_post', 'client_secret_jwt', or 'private_key_jwt' when configured with scope 'authelia.bearer.authz' and the 'confidential' client type but it's configured as ''",
+ "identity_providers: oidc: clients: client 'abc': option 'scopes' has the values 'authelia.bearer.authz' and 'openid' however when utilizing the 'client_credentials' value for the 'grant_types' the values 'openid' are not allowed",
+ },
+ },
+ {
+ "ShouldHandleBearerErrorsNotExplicit",
+ func(have *schema.IdentityProvidersOpenIDConnect) {
+ have.Clients[0] = schema.IdentityProvidersOpenIDConnectClient{
+ ID: "abc",
+ Secret: nil,
+ Public: true,
+ RedirectURIs: []string{"http://localhost"},
+ Audience: nil,
+ Scopes: []string{oidc.ScopeAutheliaBearerAuthz},
+ EnforcePAR: false,
+ EnforcePKCE: false,
+ PKCEChallengeMethod: "",
+ TokenEndpointAuthMethod: "",
+ }
+ },
+ nil,
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeAutheliaBearerAuthz},
+ nil,
+ nil,
+ nil,
+ },
+ nil,
+ []string{
+ "identity_providers: oidc: clients: client 'abc': option 'grant_types' must only have the values 'authorization_code', 'refresh_token', and 'client_credentials' when configured with scope 'authelia.bearer.authz' but it's not configured",
+ "identity_providers: oidc: clients: client 'abc': option 'audience' must be configured when configured with scope 'authelia.bearer.authz' but it's absent",
+ "identity_providers: oidc: clients: client 'abc': option 'enforce_par' must be configured as 'true' when configured with scope 'authelia.bearer.authz' but it's configured as 'false'",
+ "identity_providers: oidc: clients: client 'abc': option 'enforce_pkce' must be configured as 'true' when configured with scope 'authelia.bearer.authz' but it's configured as 'false'",
+ "identity_providers: oidc: clients: client 'abc': option 'consent_mode' must be configured as 'explicit' when configured with scope 'authelia.bearer.authz' but it's configured as ''",
+ "identity_providers: oidc: clients: client 'abc': option 'response_types' must only have the values 'code' when configured with scope 'authelia.bearer.authz' but it's not configured",
+ "identity_providers: oidc: clients: client 'abc': option 'response_modes' must only have the values 'form_post' and 'form_post.jwt' when configured with scope 'authelia.bearer.authz' but it's not configured",
+ "identity_providers: oidc: clients: client 'abc': option 'token_endpoint_auth_method' must be configured as 'none' when configured with scope 'authelia.bearer.authz' and the 'public' client type but it's configured as ''",
+ },
+ },
+ {
+ "ShouldHandleBearerValidConfidentialClientType",
+ func(have *schema.IdentityProvidersOpenIDConnect) {
+ have.Clients[0] = schema.IdentityProvidersOpenIDConnectClient{
+ ID: "abc",
+ Secret: tOpenIDConnectPBKDF2ClientSecret,
+ Public: false,
+ RedirectURIs: []string{"http://localhost"},
+ Audience: []string{"https://app.example.com"},
+ Scopes: []string{oidc.ScopeAutheliaBearerAuthz, oidc.ScopeOfflineAccess},
+ GrantTypes: []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeRefreshToken},
+ ResponseTypes: []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ ResponseModes: []string{oidc.ResponseModeFormPost, oidc.ResponseModeFormPostJWT},
+ AuthorizationPolicy: "",
+ RequestedAudienceMode: "",
+ ConsentMode: oidc.ClientConsentModeExplicit.String(),
+ EnforcePAR: true,
+ EnforcePKCE: true,
+ PKCEChallengeMethod: oidc.PKCEChallengeMethodSHA256,
+ TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretPost,
+ }
+ },
+ nil,
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeAutheliaBearerAuthz, oidc.ScopeOfflineAccess},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeFormPostJWT},
+ []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeRefreshToken},
+ },
+ nil,
+ nil,
+ },
+ {
+ "ShouldHandleBearerValidPublicClientType",
+ func(have *schema.IdentityProvidersOpenIDConnect) {
+ have.Clients[0] = schema.IdentityProvidersOpenIDConnectClient{
+ ID: "abc",
+ Secret: nil,
+ Public: true,
+ RedirectURIs: []string{"http://localhost"},
+ Audience: []string{"https://app.example.com"},
+ Scopes: []string{oidc.ScopeAutheliaBearerAuthz, oidc.ScopeOfflineAccess},
+ GrantTypes: []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeRefreshToken},
+ ResponseTypes: []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ ResponseModes: []string{oidc.ResponseModeFormPost},
+ AuthorizationPolicy: "",
+ RequestedAudienceMode: "",
+ ConsentMode: oidc.ClientConsentModeExplicit.String(),
+ EnforcePAR: true,
+ EnforcePKCE: true,
+ PKCEChallengeMethod: oidc.PKCEChallengeMethodSHA256,
+ TokenEndpointAuthMethod: oidc.ClientAuthMethodNone,
+ }
+ },
+ nil,
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeAutheliaBearerAuthz, oidc.ScopeOfflineAccess},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost},
+ []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeRefreshToken},
+ },
+ nil,
+ nil,
+ },
+ {
"ShouldSetDefaultConsentMode",
nil,
func(t *testing.T, have *schema.IdentityProvidersOpenIDConnect) {
diff --git a/internal/configuration/validator/server.go b/internal/configuration/validator/server.go
index 90c4ab744..89b96d6ab 100644
--- a/internal/configuration/validator/server.go
+++ b/internal/configuration/validator/server.go
@@ -174,7 +174,7 @@ func ValidateServerEndpoints(config *schema.Configuration, validator *schema.Str
}
switch oEndpoint.Implementation {
- case authzImplementationLegacy, authzImplementationExtAuthz:
+ case schema.AuthzImplementationLegacy, schema.AuthzImplementationExtAuthz:
if strings.HasPrefix(name, oName+"/") {
validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzPrefixDuplicate, name, oName, oEndpoint.Implementation))
}
@@ -183,17 +183,17 @@ func ValidateServerEndpoints(config *schema.Configuration, validator *schema.Str
}
}
- validateServerEndpointsAuthzStrategies(name, endpoint.AuthnStrategies, validator)
+ validateServerEndpointsAuthzStrategies(name, endpoint.Implementation, endpoint.AuthnStrategies, validator)
}
}
func validateServerEndpointsAuthzEndpoint(config *schema.Configuration, name string, endpoint schema.ServerEndpointsAuthz, validator *schema.StructValidator) {
if name == legacy {
switch endpoint.Implementation {
- case authzImplementationLegacy:
+ case schema.AuthzImplementationLegacy:
break
case "":
- endpoint.Implementation = authzImplementationLegacy
+ endpoint.Implementation = schema.AuthzImplementationLegacy
config.Server.Endpoints.Authz[name] = endpoint
default:
@@ -212,18 +212,55 @@ func validateServerEndpointsAuthzEndpoint(config *schema.Configuration, name str
}
}
-func validateServerEndpointsAuthzStrategies(name string, strategies []schema.ServerEndpointsAuthzAuthnStrategy, validator *schema.StructValidator) {
+//nolint:gocyclo
+func validateServerEndpointsAuthzStrategies(name, implementation string, strategies []schema.ServerEndpointsAuthzAuthnStrategy, validator *schema.StructValidator) {
+ var defaults []schema.ServerEndpointsAuthzAuthnStrategy
+
+ switch implementation {
+ case schema.AuthzImplementationLegacy:
+ defaults = schema.DefaultServerConfiguration.Endpoints.Authz[schema.AuthzEndpointNameLegacy].AuthnStrategies
+ case schema.AuthzImplementationAuthRequest:
+ defaults = schema.DefaultServerConfiguration.Endpoints.Authz[schema.AuthzEndpointNameAuthRequest].AuthnStrategies
+ case schema.AuthzImplementationExtAuthz:
+ defaults = schema.DefaultServerConfiguration.Endpoints.Authz[schema.AuthzEndpointNameExtAuthz].AuthnStrategies
+ case schema.AuthzImplementationForwardAuth:
+ defaults = schema.DefaultServerConfiguration.Endpoints.Authz[schema.AuthzEndpointNameForwardAuth].AuthnStrategies
+ }
+
+ if len(strategies) == 0 {
+ copy(strategies, defaults)
+
+ return
+ }
+
names := make([]string, len(strategies))
- for _, strategy := range strategies {
- if utils.IsStringInSlice(strategy.Name, names) {
+ for i, strategy := range strategies {
+ if strategy.Name != "" && utils.IsStringInSlice(strategy.Name, names) {
validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzStrategyDuplicate, name, strategy.Name))
}
names = append(names, strategy.Name)
- if !utils.IsStringInSlice(strategy.Name, validAuthzAuthnStrategies) {
+ switch {
+ case strategy.Name == "":
+ validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzStrategyNoName, name, i+1))
+ case !utils.IsStringInSlice(strategy.Name, validAuthzAuthnStrategies):
validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzStrategy, name, strJoinOr(validAuthzAuthnStrategies), strategy.Name))
+ default:
+ if utils.IsStringInSlice(strategy.Name, validAuthzAuthnHeaderStrategies) {
+ if len(strategy.Schemes) == 0 {
+ strategies[i].Schemes = defaults[0].Schemes
+ } else {
+ for _, scheme := range strategy.Schemes {
+ if !utils.IsStringInSliceFold(scheme, validAuthzAuthnStrategySchemes) {
+ validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzSchemes, name, i+1, strategy.Name, strJoinOr(validAuthzAuthnStrategySchemes), scheme))
+ }
+ }
+ }
+ } else if len(strategy.Schemes) != 0 {
+ validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzSchemesInvalidForStrategy, name, i+1, strategy.Name))
+ }
}
}
}
diff --git a/internal/configuration/validator/server_test.go b/internal/configuration/validator/server_test.go
index c425bb420..231efe9c2 100644
--- a/internal/configuration/validator/server_test.go
+++ b/internal/configuration/validator/server_test.go
@@ -489,6 +489,38 @@ func TestServerAuthzEndpointErrors(t *testing.T) {
[]string{"server: endpoints: authz: example: authn_strategies: duplicate strategy name detected with name 'CookieSession'"},
},
{
+ "ShouldErrorOnSchemesForInvalidStrategy",
+ map[string]schema.ServerEndpointsAuthz{
+ "example": {Implementation: "ForwardAuth", AuthnStrategies: []schema.ServerEndpointsAuthzAuthnStrategy{{Name: "CookieSession", Schemes: []string{"basic"}}}},
+ },
+ []string{"server: endpoints: authz: example: authn_strategies: strategy #1 (CookieSession): option 'schemes' is not valid for the strategy"},
+ },
+ {
+ "ShouldNotErrorOnSchemeCase",
+ map[string]schema.ServerEndpointsAuthz{
+ "example": {Implementation: "ForwardAuth", AuthnStrategies: []schema.ServerEndpointsAuthzAuthnStrategy{{Name: "HeaderAuthorization", Schemes: []string{"basIc"}}}},
+ },
+ nil,
+ },
+ {
+ "ShouldErrorOnInvalidStrategySchemesAndUnnamedStrategy",
+ map[string]schema.ServerEndpointsAuthz{
+ "example": {Implementation: "ForwardAuth", AuthnStrategies: []schema.ServerEndpointsAuthzAuthnStrategy{{Name: "HeaderAuthorization", Schemes: []string{"basic", "bearer", "abc"}}}},
+ },
+ []string{
+ "server: endpoints: authz: example: authn_strategies: strategy #1 (HeaderAuthorization): option 'schemes' must only include the values 'basic' or 'bearer' but has 'abc'",
+ },
+ },
+ {
+ "ShouldErrorOnUnnamedStrategy",
+ map[string]schema.ServerEndpointsAuthz{
+ "example": {Implementation: "ForwardAuth", AuthnStrategies: []schema.ServerEndpointsAuthzAuthnStrategy{{Name: "", Schemes: []string{"basic", "bearer", "abc"}}}},
+ },
+ []string{
+ "server: endpoints: authz: example: authn_strategies: strategy #1: option 'name' must be configured",
+ },
+ },
+ {
"ShouldErrorOnInvalidChars",
map[string]schema.ServerEndpointsAuthz{
"/abc": {Implementation: "ForwardAuth"},
@@ -567,6 +599,53 @@ func TestServerAuthzEndpointErrors(t *testing.T) {
}
}
+func TestServerAuthzEndpointDefaults(t *testing.T) {
+ testCases := []struct {
+ name string
+ have map[string]schema.ServerEndpointsAuthz
+ expected map[string]schema.ServerEndpointsAuthz
+ }{
+ {
+ "ShouldSetDefaultSchemes",
+ map[string]schema.ServerEndpointsAuthz{
+ "example": {Implementation: "ForwardAuth", AuthnStrategies: []schema.ServerEndpointsAuthzAuthnStrategy{
+ {
+ Name: "HeaderAuthorization",
+ Schemes: []string{},
+ },
+ }},
+ },
+ map[string]schema.ServerEndpointsAuthz{
+ "example": {Implementation: "ForwardAuth", AuthnStrategies: []schema.ServerEndpointsAuthzAuthnStrategy{
+ {
+ Name: "HeaderAuthorization",
+ Schemes: []string{"basic"},
+ },
+ }},
+ },
+ },
+ }
+
+ validator := schema.NewStructValidator()
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ validator.Clear()
+
+ config := newDefaultConfig()
+
+ config.Server.Endpoints.Authz = tc.have
+
+ ValidateServerEndpoints(&config, validator)
+
+ assert.Len(t, validator.Warnings(), 0)
+ assert.Len(t, validator.Errors(), 0)
+
+ assert.Equal(t, tc.expected, config.Server.Endpoints.Authz)
+ })
+ }
+}
+
func TestServerAuthzEndpointLegacyAsImplementationLegacyWhenBlank(t *testing.T) {
have := map[string]schema.ServerEndpointsAuthz{
"legacy": {},
@@ -583,7 +662,7 @@ func TestServerAuthzEndpointLegacyAsImplementationLegacyWhenBlank(t *testing.T)
assert.Len(t, validator.Warnings(), 0)
assert.Len(t, validator.Errors(), 0)
- assert.Equal(t, authzImplementationLegacy, config.Server.Endpoints.Authz[legacy].Implementation)
+ assert.Equal(t, schema.AuthzImplementationLegacy, config.Server.Endpoints.Authz[legacy].Implementation)
}
func TestValidateTLSPathStatInvalidArgument(t *testing.T) {
diff --git a/internal/configuration/validator/util.go b/internal/configuration/validator/util.go
index 67403dd90..0d6955570 100644
--- a/internal/configuration/validator/util.go
+++ b/internal/configuration/validator/util.go
@@ -92,7 +92,7 @@ func validateListNotAllowed(values, filter []string) (invalid []string) {
return invalid
}
-func validateList(values, valid []string, chkDuplicate bool) (invalid, duplicates []string) { //nolint:unparam
+func validateList(values, valid []string, chkDuplicate bool) (invalid, duplicates []string) {
chkValid := len(valid) != 0
for i, value := range values {