summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--config.template.yml15
-rw-r--r--docs/content/en/configuration/identity-providers/open-id-connect.md54
-rw-r--r--docs/content/en/integration/kubernetes/istio.md2
-rw-r--r--docs/content/en/integration/openid-connect/introduction.md113
-rw-r--r--docs/content/en/integration/proxies/envoy.md2
-rw-r--r--docs/content/en/integration/proxies/support.md2
-rw-r--r--internal/configuration/config.template.yml15
-rw-r--r--internal/configuration/schema/identity_providers.go5
-rw-r--r--internal/configuration/schema/keys.go1
-rw-r--r--internal/configuration/validator/access_control.go25
-rw-r--r--internal/configuration/validator/access_control_test.go47
-rw-r--r--internal/configuration/validator/authentication.go12
-rw-r--r--internal/configuration/validator/authentication_test.go20
-rw-r--r--internal/configuration/validator/configuration.go4
-rw-r--r--internal/configuration/validator/configuration_test.go8
-rw-r--r--internal/configuration/validator/const.go156
-rw-r--r--internal/configuration/validator/duo_test.go11
-rw-r--r--internal/configuration/validator/identity_providers.go380
-rw-r--r--internal/configuration/validator/identity_providers_test.go1377
-rw-r--r--internal/configuration/validator/keys.go2
-rw-r--r--internal/configuration/validator/keys_test.go14
-rw-r--r--internal/configuration/validator/log.go3
-rw-r--r--internal/configuration/validator/log_test.go2
-rw-r--r--internal/configuration/validator/notifier_test.go28
-rw-r--r--internal/configuration/validator/ntp_test.go2
-rw-r--r--internal/configuration/validator/password_policy_test.go2
-rw-r--r--internal/configuration/validator/server.go6
-rw-r--r--internal/configuration/validator/server_test.go12
-rw-r--r--internal/configuration/validator/session.go6
-rw-r--r--internal/configuration/validator/session_test.go28
-rw-r--r--internal/configuration/validator/storage.go3
-rw-r--r--internal/configuration/validator/storage_test.go2
-rw-r--r--internal/configuration/validator/telemetry_test.go2
-rw-r--r--internal/configuration/validator/theme.go3
-rw-r--r--internal/configuration/validator/theme_test.go2
-rw-r--r--internal/configuration/validator/totp.go2
-rw-r--r--internal/configuration/validator/totp_test.go12
-rw-r--r--internal/configuration/validator/util.go94
-rw-r--r--internal/configuration/validator/webauthn.go5
-rw-r--r--internal/configuration/validator/webauthn_test.go4
-rw-r--r--internal/handlers/handler_oidc_authorization.go6
-rw-r--r--internal/handlers/handler_oidc_authorization_consent.go30
-rw-r--r--internal/handlers/handler_oidc_authorization_consent_explicit.go16
-rw-r--r--internal/handlers/handler_oidc_authorization_consent_implicit.go26
-rw-r--r--internal/handlers/handler_oidc_authorization_consent_pre_configured.go42
-rw-r--r--internal/handlers/handler_oidc_consent.go12
-rw-r--r--internal/handlers/handler_oidc_userinfo.go6
-rw-r--r--internal/handlers/response.go8
-rw-r--r--internal/handlers/types.go2
-rw-r--r--internal/oidc/client.go272
-rw-r--r--internal/oidc/client_test.go221
-rw-r--r--internal/oidc/config.go6
-rw-r--r--internal/oidc/const.go10
-rw-r--r--internal/oidc/const_test.go12
-rw-r--r--internal/oidc/discovery.go237
-rw-r--r--internal/oidc/discovery_test.go24
-rw-r--r--internal/oidc/provider.go15
-rw-r--r--internal/oidc/provider_test.go52
-rw-r--r--internal/oidc/store.go6
-rw-r--r--internal/oidc/store_test.go63
-rw-r--r--internal/oidc/types.go347
-rw-r--r--internal/oidc/types_test.go4
-rw-r--r--internal/utils/strings.go4
-rw-r--r--internal/utils/strings_test.go2
64 files changed, 2971 insertions, 935 deletions
diff --git a/config.template.yml b/config.template.yml
index 1058fecc1..077237358 100644
--- a/config.template.yml
+++ b/config.template.yml
@@ -1480,12 +1480,6 @@ notifier:
# - email
# - profile
- ## Grant Types configures which grants this client can obtain.
- ## It's not recommended to define this unless you know what you're doing.
- # grant_types:
- # - refresh_token
- # - authorization_code
-
## Response Types configures which responses this client can be sent.
## It's not recommended to define this unless you know what you're doing.
# response_types:
@@ -1495,7 +1489,14 @@ notifier:
# response_modes:
# - form_post
# - query
- # - fragment
+
+ ## Grant Types configures which grants this client can obtain.
+ ## It's not recommended to define this unless you know what you're doing.
+ # grant_types:
+ # - authorization_code
+
+ ## The permitted client authentication method for the Token Endpoint for this client.
+ # token_endpoint_auth_method: client_secret_basic
## The policy to require for this client; one_factor or two_factor.
# authorization_policy: two_factor
diff --git a/docs/content/en/configuration/identity-providers/open-id-connect.md b/docs/content/en/configuration/identity-providers/open-id-connect.md
index 89d35e63a..40b3bd695 100644
--- a/docs/content/en/configuration/identity-providers/open-id-connect.md
+++ b/docs/content/en/configuration/identity-providers/open-id-connect.md
@@ -451,38 +451,47 @@ A list of scopes to allow this client to consume. See
documentation for the application you are trying to configure [OpenID Connect 1.0] for will likely have a list of scopes
or claims required which can be matched with the above guide.
-#### grant_types
-
-{{< confkey type="list(string)" default="refresh_token, authorization_code" required="no" >}}
-
-*__Important Note:__ It is recommended that this isn't configured at this time unless you know what you're doing.*
-
-The list of grant types this client is permitted to use in order to obtain access to the relevant tokens.
-
-See the [Grant Types](../../integration/openid-connect/introduction.md#grant-types) section of the
-[OpenID Connect 1.0 Integration Guide](../../integration/openid-connect/introduction.md#grant-types) for more information.
-
#### response_types
{{< confkey type="list(string)" default="code" required="no" >}}
-*__Important Note:__ It is recommended that this isn't configured at this time unless you know what you're doing.*
+*__Security Note:__ It is recommended that only the `code` response type (i.e. the default) is used. The other response
+types are not as secure as this response type.*
-A list of response types this client supports.
+A list of response types this client supports. If a response type not in this list is requested by a client then an
+error will be returned to the client. The response type indicates the types of values that are returned to the client.
See the [Response Types](../../integration/openid-connect/introduction.md#response-types) section of the
[OpenID Connect 1.0 Integration Guide](../../integration/openid-connect/introduction.md#response-types) for more information.
#### response_modes
-{{< confkey type="list(string)" default="form_post, query, fragment" required="no" >}}
+{{< confkey type="list(string)" default="form_post, query" required="no" >}}
*__Important Note:__ It is recommended that this isn't configured at this time unless you know what you're doing.*
-A list of response modes this client supports.
+A list of response modes this client supports. If a response mode not in this list is requested by a client then an
+error will be returned to the client. The response mode controls how the response type is returned to the client.
See the [Response Modes](../../integration/openid-connect/introduction.md#response-modes) section of the
-[OpenID Connect 1.0 Integration Guide](../../integration/openid-connect/introduction.md#response-modes) for more information.
+[OpenID Connect 1.0 Integration Guide](../../integration/openid-connect/introduction.md#response-modes) for more
+information.
+
+The default values are based on the [response_types](#responsetypes) values. When the [response_types](#responsetypes)
+values include the `code` type then the `query` response mode will be included. When any other type is included the
+`fragment` response mode will be included. It's important to note at this time we do not support the `none` response
+type, but when it is supported it will include the `query` response mode.
+
+#### grant_types
+
+{{< confkey type="list(string)" default="authorization_code" required="no" >}}
+
+*__Important Note:__ It is recommended that this isn't configured at this time unless you know what you're doing.*
+
+The list of grant types this client is permitted to use in order to obtain access to the relevant tokens.
+
+See the [Grant Types](../../integration/openid-connect/introduction.md#grant-types) section of the
+[OpenID Connect 1.0 Integration Guide](../../integration/openid-connect/introduction.md#grant-types) for more information.
#### authorization_policy
@@ -522,6 +531,18 @@ The algorithm used to sign the userinfo endpoint responses. This can either be `
See the [integration guide](../../integration/openid-connect/introduction.md#user-information-signing-algorithm) for
more information.
+#### token_endpoint_auth_method
+
+{{< confkey type="string" default="" required="no" >}}
+
+The registered client authentication mechanism used by this client for the [Token Endpoint]. If no method is defined
+the confidential client type will accept any supported method. The public client type defaults to `none` as this
+is required by the specification. This may be required as a breaking change in future versions.
+Supported values are `client_secret_basic`, `client_secret_post`, and `none`.
+
+See the [integration guide](../../integration/openid-connect/introduction.md#client-authentication-method) for
+more information.
+
#### consent_mode
{{< confkey type="string" default="auto" required="no" >}}
@@ -565,6 +586,7 @@ To integrate Authelia's [OpenID Connect 1.0] implementation with a relying party
[token lifespan]: https://docs.apigee.com/api-platform/antipatterns/oauth-long-expiration
[OpenID Connect 1.0]: https://openid.net/connect/
+[Token Endpoint]: https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
[JWT]: https://datatracker.ietf.org/doc/html/rfc7519
[RFC6234]: https://datatracker.ietf.org/doc/html/rfc6234
[RFC4648]: https://datatracker.ietf.org/doc/html/rfc4648
diff --git a/docs/content/en/integration/kubernetes/istio.md b/docs/content/en/integration/kubernetes/istio.md
index c1c4927e3..60360c6b0 100644
--- a/docs/content/en/integration/kubernetes/istio.md
+++ b/docs/content/en/integration/kubernetes/istio.md
@@ -13,7 +13,7 @@ toc: true
---
Istio uses [Envoy](../proxies/envoy.md) as an Ingress. This means it has a relatively comprehensive integration option.
-Istio is supported with Authelia v4.37.0 and higher via [Envoy]'s [external authorization] filter.
+Istio is supported with Authelia v4.37.0 and higher via the [Envoy] proxy [external authorization] filter.
[external authorization]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_authz/v3/ext_authz.proto.html#extensions-filters-http-ext-authz-v3-extauthz
diff --git a/docs/content/en/integration/openid-connect/introduction.md b/docs/content/en/integration/openid-connect/introduction.md
index 6ab7ba46c..37cca969d 100644
--- a/docs/content/en/integration/openid-connect/introduction.md
+++ b/docs/content/en/integration/openid-connect/introduction.md
@@ -21,8 +21,15 @@ documentation for some [OpenID Connect 1.0] Relying Party implementations.
See the [configuration documentation](../../configuration/identity-providers/open-id-connect.md) for information on how
to configure the Authelia [OpenID Connect 1.0] Provider.
+This page is intended as an integration reference point for any implementers who wish to integrate an
+[OpenID Connect 1.0] Relying Party (client application) either as a developer or user of the third party Reyling Party.
+
## Scope Definitions
+The following scope definitions describe each scope supported and the associated effects including the individual claims
+returned by granting this scope. By default we do not issue any claims which reveal the users identity which allows
+administrators semi-granular control over which claims the client is entitled to.
+
### openid
This is the default scope for [OpenID Connect 1.0]. This field is forced on every client by the configuration validation
@@ -54,9 +61,16 @@ This scope is a special scope designed to allow applications to obtain a [Refres
an application on behalf of a user. A [Refresh Token] is a special [Access Token] that allows refreshing previously
issued token credentials, effectively it allows the relying party to obtain new tokens periodically.
+As per [OpenID Connect 1.0] Section 11 [Offline Access] can only be granted during the [Authorization Code Flow] or a
+[Hybrid Flow]. The [Refresh Token] will only ever be returned at the [Token Endpoint] when the client is exchanging
+their [OAuth 2.0 Authorization Code].
+
Generally unless an application supports this and actively requests this scope they should not be granted this scope via
the client configuration.
+It is also important to note that we treat a [Refresh Token] as single use and reissue a new [Refresh Token] during the
+refresh flow.
+
### groups
This scope includes the groups the authentication backend reports the user is a member of in the [Claims] of the
@@ -92,43 +106,21 @@ This scope includes the profile information the authentication backend reports a
The following section describes advanced parameters which can be used in various endpoints as well as their related
configuration options.
-### Grant Types
-
-The following describes the various [OAuth 2.0] and [OpenID Connect 1.0] grant types and their support level. The value
-field is both the required value for the `grant_type` parameter in the authorization request and the `grant_types`
-configuration option.
-
-| Grant Type | Supported | Value | Notes |
-|:-----------------------------------------------:|:---------:|:----------------------------------------------:|:-------------------------------------------------------------------:|
-| [OAuth 2.0 Authorization Code] | Yes | `authorization_code` | |
-| [OAuth 2.0 Resource Owner Password Credentials] | No | `password` | This Grant Type has been deprecated and should not normally be used |
-| [OAuth 2.0 Client Credentials] | Yes | `client_credentials` | |
-| [OAuth 2.0 Implicit] | Yes | `implicit` | This Grant Type has been deprecated and should not normally be used |
-| [OAuth 2.0 Refresh Token] | Yes | `refresh_token` | |
-| [OAuth 2.0 Device Code] | No | `urn:ietf:params:oauth:grant-type:device_code` | |
-|
-
-[OAuth 2.0 Authorization Code]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.1
-[OAuth 2.0 Implicit]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.2
-[OAuth 2.0 Resource Owner Password Credentials]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.3
-[OAuth 2.0 Client Credentials]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.4
-[OAuth 2.0 Refresh Token]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.5
-[OAuth 2.0 Device Code]: https://datatracker.ietf.org/doc/html/rfc8628#section-3.4
-
### Response Types
The following describes the supported response types. See the [OAuth 2.0 Multiple Response Type Encoding Practices] for
-more technical information.
-
-| Flow Type | Values |
-|:-------------------------:|:---------------------:|
-| [Authorization Code Flow] | `code` |
-| [Implicit Flow] | `token id_token` |
-| [Implicit Flow] | `id_token` |
-| [Implicit Flow] | `token` |
-| [Hybrid Flow] | `code token` |
-| [Hybrid Flow] | `code id_token` |
-| [Hybrid Flow] | `code token id_token` |
+more technical information. The default response modes column indicates which response modes are allowed by default on
+clients configured with this flow type value. If more than a single response type is configured
+
+| Flow Type | Value | Default [Response Modes](#response-modes) Values |
+|:-------------------------:|:---------------------:|:------------------------------------------------:|
+| [Authorization Code Flow] | `code` | `form_post`, `query` |
+| [Implicit Flow] | `id_token token` | `form_post`, `fragment` |
+| [Implicit Flow] | `id_token` | `form_post`, `fragment` |
+| [Implicit Flow] | `token` | `form_post`, `fragment` |
+| [Hybrid Flow] | `code token` | `form_post`, `fragment` |
+| [Hybrid Flow] | `code id_token` | `form_post`, `fragment` |
+| [Hybrid Flow] | `code id_token token` | `form_post`, `fragment` |
[Authorization Code Flow]: https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth
[Implicit Flow]: https://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth
@@ -139,16 +131,60 @@ more technical information.
### Response Modes
The following describes the supported response modes. See the [OAuth 2.0 Multiple Response Type Encoding Practices] for
-more technical information.
+more technical information. The default response modes of a client is based on the [Response Types](#response-types)
+configuration.
| Name | Value |
|:---------------------:|:-----------:|
+| [OAuth 2.0 Form Post] | `form_post` |
| Query String | `query` |
| Fragment | `fragment` |
-| [OAuth 2.0 Form Post] | `form_post` |
[OAuth 2.0 Form Post]: https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html
+### Grant Types
+
+The following describes the various [OAuth 2.0] and [OpenID Connect 1.0] grant types and their support level. The value
+field is both the required value for the `grant_type` parameter in the authorization request and the `grant_types`
+configuration option.
+
+| Grant Type | Supported | Value | Notes |
+|:-----------------------------------------------:|:---------:|:----------------------------------------------:|:-----------------------------------------------------------------------------------------------:|
+| [OAuth 2.0 Authorization Code] | Yes | `authorization_code` | |
+| [OAuth 2.0 Resource Owner Password Credentials] | No | `password` | This Grant Type has been deprecated and should not normally be used |
+| [OAuth 2.0 Client Credentials] | No | `client_credentials` | |
+| [OAuth 2.0 Implicit] | Yes | `implicit` | This Grant Type has been deprecated and should not normally be used |
+| [OAuth 2.0 Refresh Token] | Yes | `refresh_token` | This Grant Type should genreally only be used for clients which have the `offline_access` scope |
+| [OAuth 2.0 Device Code] | No | `urn:ietf:params:oauth:grant-type:device_code` | |
+|
+
+[OAuth 2.0 Authorization Code]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.1
+[OAuth 2.0 Implicit]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.2
+[OAuth 2.0 Resource Owner Password Credentials]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.3
+[OAuth 2.0 Client Credentials]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.4
+[OAuth 2.0 Refresh Token]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.5
+[OAuth 2.0 Device Code]: https://datatracker.ietf.org/doc/html/rfc8628#section-3.4
+
+### Client Authentication Method
+
+The following describes the supported client authentication methods. See the [OpenID Connect 1.0 Client Authentication]
+specification and the [OAuth 2.0 - Client Types] specification for more information.
+
+| Description | Value / Name | Supported Client Types | Default for Client Type | Assertion Type |
+|:------------------------------------:|:-----------------------------:|:----------------------:|:-----------------------:|:--------------------------------------------------------:|
+| Secret via HTTP Basic Auth Scheme | `client_secret_basic` | `confidential` | N/A | N/A |
+| Secret via HTTP POST Body | `client_secret_post` | `confidential` | N/A | N/A |
+| JWT (signed by secret) | `client_secret_jwt` | Not Supported | N/A | `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` |
+| JWT (signed by private key) | `private_key_jwt` | Not Supported | N/A | `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` |
+| [OAuth 2.0 Mutual-TLS] | `tls_client_auth` | Not Supported | N/A | N/A |
+| [OAuth 2.0 Mutual-TLS] (Self Signed) | `self_signed_tls_client_auth` | Not Supported | N/A | N/A |
+| No Authentication | `none` | `public` | `public` | N/A |
+
+
+[OpenID Connect 1.0 Client Authentication]: https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication
+[OAuth 2.0 Mutual-TLS]: https://datatracker.ietf.org/doc/html/rfc8705
+[OAuth 2.0 - Client Types]: https://datatracker.ietf.org/doc/html/rfc8705#section-2.1
+
## Authentication Method References
Authelia currently supports adding the `amr` [Claim] to the [ID Token] utilizing the [RFC8176] Authentication Method
@@ -289,10 +325,13 @@ The advantages of this approach are as follows:
[JSON Web Key Set]: https://datatracker.ietf.org/doc/html/rfc7517#section-5
+[Offline Access]: https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
+
[Authorization]: https://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint
-[Pushed Authorization Requests]: https://datatracker.ietf.org/doc/html/rfc9126
[Token]: https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
[UserInfo]: https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
+
+[Pushed Authorization Requests]: https://datatracker.ietf.org/doc/html/rfc9126
[Introspection]: https://datatracker.ietf.org/doc/html/rfc7662
[Revocation]: https://datatracker.ietf.org/doc/html/rfc7009
[Proof Key Code Exchange]: https://www.rfc-editor.org/rfc/rfc7636.html
diff --git a/docs/content/en/integration/proxies/envoy.md b/docs/content/en/integration/proxies/envoy.md
index 0d45b7cea..af37d3cc8 100644
--- a/docs/content/en/integration/proxies/envoy.md
+++ b/docs/content/en/integration/proxies/envoy.md
@@ -87,7 +87,7 @@ Below you will find commented examples of the following configuration:
### Example
-Support for [Envoy] is possible with Authelia v4.37.0 and higher via [Envoy]'s [external authorization] filter.
+Support for [Envoy] is possible with Authelia v4.37.0 and higher via the [Envoy] proxy [external authorization] filter.
[external authorization]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_authz/v3/ext_authz.proto.html#extensions-filters-http-ext-authz-v3-extauthz
diff --git a/docs/content/en/integration/proxies/support.md b/docs/content/en/integration/proxies/support.md
index 6364fde3a..9b33f8de4 100644
--- a/docs/content/en/integration/proxies/support.md
+++ b/docs/content/en/integration/proxies/support.md
@@ -92,7 +92,7 @@ available in [Kubernetes]. You would likely have to build your own [HAProxy] ima
### Envoy
-[Envoy] is supported with Authelia v4.37.0 and higher via [Envoy]'s [external authorization] filter.
+[Envoy] is supported with Authelia v4.37.0 and higher via the [Envoy] proxy [external authorization] filter.
[external authorization]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_authz/v3/ext_authz.proto.html#extensions-filters-http-ext-authz-v3-extauthz
diff --git a/internal/configuration/config.template.yml b/internal/configuration/config.template.yml
index 1058fecc1..077237358 100644
--- a/internal/configuration/config.template.yml
+++ b/internal/configuration/config.template.yml
@@ -1480,12 +1480,6 @@ notifier:
# - email
# - profile
- ## Grant Types configures which grants this client can obtain.
- ## It's not recommended to define this unless you know what you're doing.
- # grant_types:
- # - refresh_token
- # - authorization_code
-
## Response Types configures which responses this client can be sent.
## It's not recommended to define this unless you know what you're doing.
# response_types:
@@ -1495,7 +1489,14 @@ notifier:
# response_modes:
# - form_post
# - query
- # - fragment
+
+ ## Grant Types configures which grants this client can obtain.
+ ## It's not recommended to define this unless you know what you're doing.
+ # grant_types:
+ # - authorization_code
+
+ ## The permitted client authentication method for the Token Endpoint for this client.
+ # token_endpoint_auth_method: client_secret_basic
## The policy to require for this client; one_factor or two_factor.
# authorization_policy: two_factor
diff --git a/internal/configuration/schema/identity_providers.go b/internal/configuration/schema/identity_providers.go
index 57376dc87..d253a4d07 100644
--- a/internal/configuration/schema/identity_providers.go
+++ b/internal/configuration/schema/identity_providers.go
@@ -64,6 +64,8 @@ type OpenIDConnectClientConfiguration struct {
ResponseTypes []string `koanf:"response_types"`
ResponseModes []string `koanf:"response_modes"`
+ TokenEndpointAuthMethod string `koanf:"token_endpoint_auth_method"`
+
Policy string `koanf:"authorization_policy"`
EnforcePAR bool `koanf:"enforce_par"`
@@ -91,9 +93,8 @@ var defaultOIDCClientConsentPreConfiguredDuration = time.Hour * 24 * 7
var DefaultOpenIDConnectClientConfiguration = OpenIDConnectClientConfiguration{
Policy: "two_factor",
Scopes: []string{"openid", "groups", "profile", "email"},
- GrantTypes: []string{"refresh_token", "authorization_code"},
ResponseTypes: []string{"code"},
- ResponseModes: []string{"form_post", "query", "fragment"},
+ ResponseModes: []string{"form_post"},
UserinfoSigningAlgorithm: "none",
ConsentMode: "auto",
diff --git a/internal/configuration/schema/keys.go b/internal/configuration/schema/keys.go
index 526913918..85923eb1a 100644
--- a/internal/configuration/schema/keys.go
+++ b/internal/configuration/schema/keys.go
@@ -45,6 +45,7 @@ var Keys = []string{
"identity_providers.oidc.clients[].grant_types",
"identity_providers.oidc.clients[].response_types",
"identity_providers.oidc.clients[].response_modes",
+ "identity_providers.oidc.clients[].token_endpoint_auth_method",
"identity_providers.oidc.clients[].authorization_policy",
"identity_providers.oidc.clients[].enforce_par",
"identity_providers.oidc.clients[].enforce_pkce",
diff --git a/internal/configuration/validator/access_control.go b/internal/configuration/validator/access_control.go
index 994d7559c..93f1efa4c 100644
--- a/internal/configuration/validator/access_control.go
+++ b/internal/configuration/validator/access_control.go
@@ -59,7 +59,7 @@ func ValidateAccessControl(config *schema.Configuration, validator *schema.Struc
}
if !IsPolicyValid(config.AccessControl.DefaultPolicy) {
- validator.Push(fmt.Errorf(errFmtAccessControlDefaultPolicyValue, strings.Join(validACLRulePolicies, "', '"), config.AccessControl.DefaultPolicy))
+ validator.Push(fmt.Errorf(errFmtAccessControlDefaultPolicyValue, strJoinOr(validACLRulePolicies), config.AccessControl.DefaultPolicy))
}
if config.AccessControl.Networks != nil {
@@ -92,8 +92,13 @@ func ValidateRules(config *schema.Configuration, validator *schema.StructValidat
validateDomains(rulePosition, rule, validator)
- if !IsPolicyValid(rule.Policy) {
- validator.Push(fmt.Errorf(errFmtAccessControlRuleInvalidPolicy, ruleDescriptor(rulePosition, rule), rule.Policy))
+ switch rule.Policy {
+ case "":
+ validator.Push(fmt.Errorf(errFmtAccessControlRuleNoPolicy, ruleDescriptor(rulePosition, rule)))
+ default:
+ if !IsPolicyValid(rule.Policy) {
+ validator.Push(fmt.Errorf(errFmtAccessControlRuleInvalidPolicy, ruleDescriptor(rulePosition, rule), strJoinOr(validACLRulePolicies), rule.Policy))
+ }
}
validateNetworks(rulePosition, rule, config.AccessControl, validator)
@@ -156,10 +161,14 @@ func validateSubjects(rulePosition int, rule schema.ACLRule, validator *schema.S
}
func validateMethods(rulePosition int, rule schema.ACLRule, validator *schema.StructValidator) {
- for _, method := range rule.Methods {
- if !utils.IsStringInSliceFold(method, validACLHTTPMethodVerbs) {
- validator.Push(fmt.Errorf(errFmtAccessControlRuleMethodInvalid, ruleDescriptor(rulePosition, rule), method, strings.Join(validACLHTTPMethodVerbs, "', '")))
- }
+ invalid, duplicates := validateList(rule.Methods, validACLHTTPMethodVerbs, true)
+
+ if len(invalid) != 0 {
+ validator.Push(fmt.Errorf(errFmtAccessControlRuleInvalidEntries, ruleDescriptor(rulePosition, rule), "methods", strJoinOr(validACLHTTPMethodVerbs), strJoinAnd(invalid)))
+ }
+
+ if len(duplicates) != 0 {
+ validator.Push(fmt.Errorf(errFmtAccessControlRuleInvalidDuplicates, ruleDescriptor(rulePosition, rule), "methods", strJoinAnd(duplicates)))
}
}
@@ -177,7 +186,7 @@ func validateQuery(i int, rule schema.ACLRule, config *schema.Configuration, val
}
}
} else if !utils.IsStringInSliceFold(config.AccessControl.Rules[i].Query[j][k].Operator, validACLRuleOperators) {
- validator.Push(fmt.Errorf(errFmtAccessControlRuleQueryInvalid, ruleDescriptor(i+1, rule), config.AccessControl.Rules[i].Query[j][k].Operator, strings.Join(validACLRuleOperators, "', '")))
+ validator.Push(fmt.Errorf(errFmtAccessControlRuleQueryInvalid, ruleDescriptor(i+1, rule), strJoinOr(validACLRuleOperators), config.AccessControl.Rules[i].Query[j][k].Operator))
}
if config.AccessControl.Rules[i].Query[j][k].Key == "" {
diff --git a/internal/configuration/validator/access_control_test.go b/internal/configuration/validator/access_control_test.go
index 0671455a1..1543959e0 100644
--- a/internal/configuration/validator/access_control_test.go
+++ b/internal/configuration/validator/access_control_test.go
@@ -58,7 +58,7 @@ func (suite *AccessControl) TestShouldValidateEitherDomainsOrDomainsRegex() {
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1)
- assert.EqualError(suite.T(), suite.validator.Errors()[0], "access control: rule #3: rule is invalid: must have the option 'domain' or 'domain_regex' configured")
+ assert.EqualError(suite.T(), suite.validator.Errors()[0], "access control: rule #3: option 'domain' or 'domain_regex' must be present but are both absent")
}
func (suite *AccessControl) TestShouldRaiseErrorInvalidDefaultPolicy() {
@@ -69,7 +69,7 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidDefaultPolicy() {
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1)
- suite.Assert().EqualError(suite.validator.Errors()[0], "access control: option 'default_policy' must be one of 'bypass', 'one_factor', 'two_factor', 'deny' but it is configured as 'invalid'")
+ suite.Assert().EqualError(suite.validator.Errors()[0], "access control: option 'default_policy' must be one of 'bypass', 'one_factor', 'two_factor', or 'deny' but it's configured as 'invalid'")
}
func (suite *AccessControl) TestShouldRaiseErrorInvalidNetworkGroupNetwork() {
@@ -141,10 +141,10 @@ func (suite *AccessControl) TestShouldRaiseErrorsWithEmptyRules() {
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 4)
- suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1: rule is invalid: must have the option 'domain' or 'domain_regex' configured")
- suite.Assert().EqualError(suite.validator.Errors()[1], "access control: rule #1: rule 'policy' option '' is invalid: must be one of 'deny', 'two_factor', 'one_factor' or 'bypass'")
- suite.Assert().EqualError(suite.validator.Errors()[2], "access control: rule #2: rule is invalid: must have the option 'domain' or 'domain_regex' configured")
- suite.Assert().EqualError(suite.validator.Errors()[3], "access control: rule #2: rule 'policy' option 'wrong' is invalid: must be one of 'deny', 'two_factor', 'one_factor' or 'bypass'")
+ suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1: option 'domain' or 'domain_regex' must be present but are both absent")
+ suite.Assert().EqualError(suite.validator.Errors()[1], "access control: rule #1: option 'policy' must be present but it's absent")
+ suite.Assert().EqualError(suite.validator.Errors()[2], "access control: rule #2: option 'domain' or 'domain_regex' must be present but are both absent")
+ suite.Assert().EqualError(suite.validator.Errors()[3], "access control: rule #2: option 'policy' must be one of 'bypass', 'one_factor', 'two_factor', or 'deny' but it's configured as 'wrong'")
}
func (suite *AccessControl) TestShouldRaiseErrorInvalidPolicy() {
@@ -160,7 +160,7 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidPolicy() {
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1)
- suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): rule 'policy' option 'invalid' is invalid: must be one of 'deny', 'two_factor', 'one_factor' or 'bypass'")
+ suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): option 'policy' must be one of 'bypass', 'one_factor', 'two_factor', or 'deny' but it's configured as 'invalid'")
}
func (suite *AccessControl) TestShouldRaiseErrorInvalidNetwork() {
@@ -194,7 +194,24 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidMethod() {
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1)
- suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): 'methods' option 'HOP' is invalid: must be one of 'GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'TRACE', 'CONNECT', 'OPTIONS', 'COPY', 'LOCK', 'MKCOL', 'MOVE', 'PROPFIND', 'PROPPATCH', 'UNLOCK'")
+ suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): option 'methods' must only have the values 'GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'TRACE', 'CONNECT', 'OPTIONS', 'COPY', 'LOCK', 'MKCOL', 'MOVE', 'PROPFIND', 'PROPPATCH', or 'UNLOCK' but the values 'HOP' are present")
+}
+
+func (suite *AccessControl) TestShouldRaiseErrorDuplicateMethod() {
+ suite.config.AccessControl.Rules = []schema.ACLRule{
+ {
+ Domains: []string{"public.example.com"},
+ Policy: "bypass",
+ Methods: []string{"GET", "GET"},
+ },
+ }
+
+ ValidateRules(suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 1)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): option 'methods' must have unique values but the values 'GET' are duplicated")
}
func (suite *AccessControl) TestShouldRaiseErrorInvalidSubject() {
@@ -367,13 +384,13 @@ func (suite *AccessControl) TestShouldErrorOnInvalidRulesQuery() {
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 7)
- suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): 'query' option 'value' is invalid: must have a value when the operator is 'equal'")
- suite.Assert().EqualError(suite.validator.Errors()[1], "access control: rule #2 (domain 'public.example.com'): 'query' option 'key' is invalid: must have a value")
- suite.Assert().EqualError(suite.validator.Errors()[2], "access control: rule #5 (domain 'public.example.com'): 'query' option 'key' is invalid: must have a value")
- suite.Assert().EqualError(suite.validator.Errors()[3], "access control: rule #6 (domain 'public.example.com'): 'query' option 'operator' with value 'not' is invalid: must be one of 'present', 'absent', 'equal', 'not equal', 'pattern', 'not pattern'")
- suite.Assert().EqualError(suite.validator.Errors()[4], "access control: rule #7 (domain 'public.example.com'): 'query' option 'value' is invalid: error parsing regexp: missing closing ): `(bad pattern`")
- suite.Assert().EqualError(suite.validator.Errors()[5], "access control: rule #8 (domain 'public.example.com'): 'query' option 'value' is invalid: must not have a value when the operator is 'present'")
- suite.Assert().EqualError(suite.validator.Errors()[6], "access control: rule #9 (domain 'public.example.com'): 'query' option 'value' is invalid: expected type was string but got int")
+ suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): query: option 'value' must be present when the option 'operator' is 'equal' but it's absent")
+ suite.Assert().EqualError(suite.validator.Errors()[1], "access control: rule #2 (domain 'public.example.com'): query: option 'key' is required but it's absent")
+ suite.Assert().EqualError(suite.validator.Errors()[2], "access control: rule #5 (domain 'public.example.com'): query: option 'key' is required but it's absent")
+ suite.Assert().EqualError(suite.validator.Errors()[3], "access control: rule #6 (domain 'public.example.com'): query: option 'operator' must be one of 'present', 'absent', 'equal', 'not equal', 'pattern', or 'not pattern' but it's configured as 'not'")
+ suite.Assert().EqualError(suite.validator.Errors()[4], "access control: rule #7 (domain 'public.example.com'): query: option 'value' is invalid: error parsing regexp: missing closing ): `(bad pattern`")
+ suite.Assert().EqualError(suite.validator.Errors()[5], "access control: rule #8 (domain 'public.example.com'): query: option 'value' must not be present when the option 'operator' is 'present' but it's present")
+ suite.Assert().EqualError(suite.validator.Errors()[6], "access control: rule #9 (domain 'public.example.com'): query: option 'value' is invalid: expected type was string but got int")
}
func TestAccessControl(t *testing.T) {
diff --git a/internal/configuration/validator/authentication.go b/internal/configuration/validator/authentication.go
index bcd64fabf..fb209f179 100644
--- a/internal/configuration/validator/authentication.go
+++ b/internal/configuration/validator/authentication.go
@@ -71,7 +71,7 @@ func ValidatePasswordConfiguration(config *schema.Password, validator *schema.St
case utils.IsStringInSlice(config.Algorithm, validHashAlgorithms):
break
default:
- validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordUnknownAlg, config.Algorithm, strings.Join(validHashAlgorithms, "', '")))
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordUnknownAlg, strJoinOr(validHashAlgorithms), config.Algorithm))
}
validateFileAuthenticationBackendPasswordConfigArgon2(config, validator)
@@ -89,7 +89,7 @@ func validateFileAuthenticationBackendPasswordConfigArgon2(config *schema.Passwo
case utils.IsStringInSlice(config.Argon2.Variant, validArgon2Variants):
break
default:
- validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashArgon2, config.Argon2.Variant, strings.Join(validArgon2Variants, "', '")))
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashArgon2, strJoinOr(validArgon2Variants), config.Argon2.Variant))
}
switch {
@@ -147,7 +147,7 @@ func validateFileAuthenticationBackendPasswordConfigSHA2Crypt(config *schema.Pas
case utils.IsStringInSlice(config.SHA2Crypt.Variant, validSHA2CryptVariants):
break
default:
- validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashSHA2Crypt, config.SHA2Crypt.Variant, strings.Join(validSHA2CryptVariants, "', '")))
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashSHA2Crypt, strJoinOr(validSHA2CryptVariants), config.SHA2Crypt.Variant))
}
switch {
@@ -176,7 +176,7 @@ func validateFileAuthenticationBackendPasswordConfigPBKDF2(config *schema.Passwo
case utils.IsStringInSlice(config.PBKDF2.Variant, validPBKDF2Variants):
break
default:
- validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashPBKDF2, config.PBKDF2.Variant, strings.Join(validPBKDF2Variants, "', '")))
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashPBKDF2, strJoinOr(validPBKDF2Variants), config.PBKDF2.Variant))
}
switch {
@@ -205,7 +205,7 @@ func validateFileAuthenticationBackendPasswordConfigBCrypt(config *schema.Passwo
case utils.IsStringInSlice(config.BCrypt.Variant, validBCryptVariants):
break
default:
- validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashBCrypt, config.BCrypt.Variant, strings.Join(validBCryptVariants, "', '")))
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashBCrypt, strJoinOr(validBCryptVariants), config.BCrypt.Variant))
}
switch {
@@ -369,7 +369,7 @@ func validateLDAPAuthenticationBackendImplementation(config *schema.Authenticati
case schema.LDAPImplementationGLAuth:
implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth
default:
- validator.Push(fmt.Errorf(errFmtLDAPAuthBackendImplementation, config.LDAP.Implementation, strings.Join(validLDAPImplementations, "', '")))
+ validator.Push(fmt.Errorf(errFmtLDAPAuthBackendImplementation, strJoinOr(validLDAPImplementations), config.LDAP.Implementation))
}
tlsconfig := &schema.TLSConfig{}
diff --git a/internal/configuration/validator/authentication_test.go b/internal/configuration/validator/authentication_test.go
index cc540f064..9abec6e1a 100644
--- a/internal/configuration/validator/authentication_test.go
+++ b/internal/configuration/validator/authentication_test.go
@@ -256,7 +256,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidArgon2
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1)
- suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: argon2: option 'variant' is configured as 'invalid' but must be one of the following values: 'argon2id', 'id', 'argon2i', 'i', 'argon2d', 'd'")
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: argon2: option 'variant' must be one of 'argon2id', 'id', 'argon2i', 'i', 'argon2d', or 'd' but it's configured as 'invalid'")
}
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidSHA2CryptVariant() {
@@ -270,7 +270,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidSHA2Cr
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1)
- suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: sha2crypt: option 'variant' is configured as 'invalid' but must be one of the following values: 'sha256', 'sha512'")
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: sha2crypt: option 'variant' must be one of 'sha256' or 'sha512' but it's configured as 'invalid'")
}
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidSHA2CryptSaltLength() {
@@ -298,7 +298,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidPBKDF2
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1)
- suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: pbkdf2: option 'variant' is configured as 'invalid' but must be one of the following values: 'sha1', 'sha224', 'sha256', 'sha384', 'sha512'")
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: pbkdf2: option 'variant' must be one of 'sha1', 'sha224', 'sha256', 'sha384', or 'sha512' but it's configured as 'invalid'")
}
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidBCryptVariant() {
@@ -312,7 +312,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidBCrypt
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1)
- suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: bcrypt: option 'variant' is configured as 'invalid' but must be one of the following values: 'standard', 'sha256'")
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: bcrypt: option 'variant' must be one of 'standard' or 'sha256' but it's configured as 'invalid'")
}
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenSHA2CryptOptionsTooLow() {
@@ -497,7 +497,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenBadAlgorith
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1)
- suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: option 'algorithm' is configured as 'bogus' but must be one of the following values: 'sha2crypt', 'pbkdf2', 'scrypt', 'bcrypt', 'argon2'")
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: option 'algorithm' must be one of 'sha2crypt', 'pbkdf2', 'scrypt', 'bcrypt', or 'argon2' but it's configured as 'bogus'")
}
func (suite *FileBasedAuthenticationBackend) TestShouldSetDefaultValues() {
@@ -609,7 +609,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenImplementat
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1)
- suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'implementation' is configured as 'masd' but must be one of the following values: 'custom', 'activedirectory', 'rfc2307bis', 'freeipa', 'lldap', 'glauth'")
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'implementation' must be one of 'custom', 'activedirectory', 'rfc2307bis', 'freeipa', 'lldap', or 'glauth' but it's configured as 'masd'")
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenURLNotProvided() {
@@ -755,7 +755,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorOnBadFilterPlac
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'users_filter' has an invalid placeholder: '{0}' has been removed, please use '{input}' instead")
suite.Assert().EqualError(suite.validator.Errors()[1], "authentication_backend: ldap: option 'groups_filter' has an invalid placeholder: '{0}' has been removed, please use '{input}' instead")
suite.Assert().EqualError(suite.validator.Errors()[2], "authentication_backend: ldap: option 'groups_filter' has an invalid placeholder: '{1}' has been removed, please use '{username}' instead")
- suite.Assert().EqualError(suite.validator.Errors()[3], "authentication_backend: ldap: option 'users_filter' must contain the placeholder '{input}' but it is required")
+ suite.Assert().EqualError(suite.validator.Errors()[3], "authentication_backend: ldap: option 'users_filter' must contain the placeholder '{input}' but it's absent")
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultGroupNameAttribute() {
@@ -823,7 +823,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseWhenUsersFilterDoesN
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1)
- suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'users_filter' must contain the placeholder '{username_attribute}' but it is required")
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'users_filter' must contain the placeholder '{username_attribute}' but it's absent")
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldHelpDetectNoInputPlaceholder() {
@@ -834,7 +834,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldHelpDetectNoInputPlacehol
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1)
- suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'users_filter' must contain the placeholder '{input}' but it is required")
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'users_filter' must contain the placeholder '{input}' but it's absent")
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultTLSMinimumVersion() {
@@ -986,7 +986,7 @@ func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldRaiseErrorOnIn
validateLDAPAuthenticationBackendURL(suite.config.LDAP, suite.validator)
suite.Require().Len(suite.validator.Errors(), 1)
- suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'url' must have either the 'ldap' or 'ldaps' scheme but it is configured as 'http'")
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'url' must have either the 'ldap' or 'ldaps' scheme but it's configured as 'http'")
}
func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldRaiseErrorOnInvalidURLWithBadCharacters() {
diff --git a/internal/configuration/validator/configuration.go b/internal/configuration/validator/configuration.go
index 13045b86a..874e0809b 100644
--- a/internal/configuration/validator/configuration.go
+++ b/internal/configuration/validator/configuration.go
@@ -78,7 +78,7 @@ func validateDefault2FAMethod(config *schema.Configuration, validator *schema.St
}
if !utils.IsStringInSlice(config.Default2FAMethod, validDefault2FAMethods) {
- validator.Push(fmt.Errorf(errFmtInvalidDefault2FAMethod, config.Default2FAMethod, strings.Join(validDefault2FAMethods, "', '")))
+ validator.Push(fmt.Errorf(errFmtInvalidDefault2FAMethod, strJoinOr(validDefault2FAMethods), config.Default2FAMethod))
return
}
@@ -98,6 +98,6 @@ func validateDefault2FAMethod(config *schema.Configuration, validator *schema.St
}
if !utils.IsStringInSlice(config.Default2FAMethod, enabledMethods) {
- validator.Push(fmt.Errorf(errFmtInvalidDefault2FAMethodDisabled, config.Default2FAMethod, strings.Join(enabledMethods, "', '")))
+ validator.Push(fmt.Errorf(errFmtInvalidDefault2FAMethodDisabled, strJoinOr(enabledMethods), config.Default2FAMethod))
}
}
diff --git a/internal/configuration/validator/configuration_test.go b/internal/configuration/validator/configuration_test.go
index 77e35c3b7..7fee1355e 100644
--- a/internal/configuration/validator/configuration_test.go
+++ b/internal/configuration/validator/configuration_test.go
@@ -221,7 +221,7 @@ func TestValidateDefault2FAMethod(t *testing.T) {
TOTP: schema.TOTPConfiguration{Disable: true},
},
expectedErrs: []string{
- "option 'default_2fa_method' is configured as 'totp' but must be one of the following enabled method values: 'webauthn', 'mobile_push'",
+ "option 'default_2fa_method' must be one of the enabled options 'webauthn' or 'mobile_push' but it's configured as 'totp'",
},
},
{
@@ -236,7 +236,7 @@ func TestValidateDefault2FAMethod(t *testing.T) {
Webauthn: schema.WebauthnConfiguration{Disable: true},
},
expectedErrs: []string{
- "option 'default_2fa_method' is configured as 'webauthn' but must be one of the following enabled method values: 'totp', 'mobile_push'",
+ "option 'default_2fa_method' must be one of the enabled options 'totp' or 'mobile_push' but it's configured as 'webauthn'",
},
},
{
@@ -246,7 +246,7 @@ func TestValidateDefault2FAMethod(t *testing.T) {
DuoAPI: schema.DuoAPIConfiguration{Disable: true},
},
expectedErrs: []string{
- "option 'default_2fa_method' is configured as 'mobile_push' but must be one of the following enabled method values: 'totp', 'webauthn'",
+ "option 'default_2fa_method' must be one of the enabled options 'totp' or 'webauthn' but it's configured as 'mobile_push'",
},
},
{
@@ -255,7 +255,7 @@ func TestValidateDefault2FAMethod(t *testing.T) {
Default2FAMethod: "duo",
},
expectedErrs: []string{
- "option 'default_2fa_method' is configured as 'duo' but must be one of the following values: 'totp', 'webauthn', 'mobile_push'",
+ "option 'default_2fa_method' must be one of 'totp', 'webauthn', or 'mobile_push' but it's configured as 'duo'",
},
},
}
diff --git a/internal/configuration/validator/const.go b/internal/configuration/validator/const.go
index 44ac2622b..5f697f7e8 100644
--- a/internal/configuration/validator/const.go
+++ b/internal/configuration/validator/const.go
@@ -67,7 +67,7 @@ const (
)
const (
- errSuffixMustBeOneOf = "is configured as '%s' but must be one of the following values: '%s'"
+ errSuffixMustBeOneOf = "must be one of %s but it's configured as '%s'"
)
// Authentication Backend Error constants.
@@ -105,19 +105,19 @@ const (
errFmtLDAPAuthBackendURLNotParsable = "authentication_backend: ldap: option " +
"'url' could not be parsed: %w"
errFmtLDAPAuthBackendURLInvalidScheme = "authentication_backend: ldap: option " +
- "'url' must have either the 'ldap' or 'ldaps' scheme but it is configured as '%s'"
+ "'url' must have either the 'ldap' or 'ldaps' scheme but it's configured as '%s'"
errFmtLDAPAuthBackendFilterEnclosingParenthesis = "authentication_backend: ldap: option " +
"'%s' must contain enclosing parenthesis: '%s' should probably be '(%s)'"
errFmtLDAPAuthBackendFilterMissingPlaceholder = "authentication_backend: ldap: option " +
- "'%s' must contain the placeholder '{%s}' but it is required"
+ "'%s' must contain the placeholder '{%s}' but it's absent"
)
// TOTP Error constants.
const (
- errFmtTOTPInvalidAlgorithm = "totp: option 'algorithm' must be one of '%s' but it is configured as '%s'"
- errFmtTOTPInvalidPeriod = "totp: option 'period' option must be 15 or more but it is configured as '%d'"
- errFmtTOTPInvalidDigits = "totp: option 'digits' must be 6 or 8 but it is configured as '%d'"
- errFmtTOTPInvalidSecretSize = "totp: option 'secret_size' must be %d or higher but it is configured as '%d'" //nolint:gosec
+ errFmtTOTPInvalidAlgorithm = "totp: option 'algorithm' must be one of %s but it's configured as '%s'"
+ errFmtTOTPInvalidPeriod = "totp: option 'period' option must be 15 or more but it's configured as '%d'"
+ errFmtTOTPInvalidDigits = "totp: option 'digits' must be 6 or 8 but it's configured as '%d'"
+ errFmtTOTPInvalidSecretSize = "totp: option 'secret_size' must be %d or higher but it's configured as '%d'" //nolint:gosec
)
// Storage Error constants.
@@ -128,14 +128,14 @@ const (
errFmtStorageUserPassMustBeProvided = "storage: %s: option 'username' and 'password' are required" //nolint:gosec
errFmtStorageOptionMustBeProvided = "storage: %s: option '%s' is required"
errFmtStorageTLSConfigInvalid = "storage: %s: tls: %w"
- errFmtStoragePostgreSQLInvalidSSLMode = "storage: postgres: ssl: option 'mode' must be one of '%s' but it is configured as '%s'"
+ errFmtStoragePostgreSQLInvalidSSLMode = "storage: postgres: ssl: option 'mode' must be one of %s but it's configured as '%s'"
errFmtStoragePostgreSQLInvalidSSLAndTLSConfig = "storage: postgres: can't define both 'tls' and 'ssl' configuration options"
warnFmtStoragePostgreSQLInvalidSSLDeprecated = "storage: postgres: ssl: the ssl configuration options are deprecated and we recommend the tls options instead"
)
// Telemetry Error constants.
const (
- errFmtTelemetryMetricsScheme = "telemetry: metrics: option 'address' must have a scheme 'tcp://' but it is configured as '%s'"
+ errFmtTelemetryMetricsScheme = "telemetry: metrics: option 'address' must have a scheme 'tcp://' but it's configured as '%s'"
)
// OpenID Error constants.
@@ -148,17 +148,16 @@ const (
errFmtOIDCCertificateMismatch = "identity_providers: oidc: option 'issuer_private_key' does not appear to be the private key the certificate provided by option 'issuer_certificate_chain'"
errFmtOIDCCertificateChain = "identity_providers: oidc: option 'issuer_certificate_chain' produced an error during validation of the chain: %w"
errFmtOIDCEnforcePKCEInvalidValue = "identity_providers: oidc: option 'enforce_pkce' must be 'never', " +
- "'public_clients_only' or 'always', but it is configured as '%s'"
+ "'public_clients_only' or 'always', but it's configured as '%s'"
errFmtOIDCCORSInvalidOrigin = "identity_providers: oidc: cors: option 'allowed_origins' contains an invalid value '%s' as it has a %s: origins must only be scheme, hostname, and an optional port"
errFmtOIDCCORSInvalidOriginWildcard = "identity_providers: oidc: cors: option 'allowed_origins' contains the wildcard origin '*' with more than one origin but the wildcard origin must be defined by itself"
errFmtOIDCCORSInvalidOriginWildcardWithClients = "identity_providers: oidc: cors: option 'allowed_origins' contains the wildcard origin '*' cannot be specified with option 'allowed_origins_from_client_redirect_uris' enabled"
- errFmtOIDCCORSInvalidEndpoint = "identity_providers: oidc: cors: option 'endpoints' contains an invalid value '%s': must be one of '%s'"
+ errFmtOIDCCORSInvalidEndpoint = "identity_providers: oidc: cors: option 'endpoints' contains an invalid value '%s': must be one of %s"
- errFmtOIDCClientsDuplicateID = "identity_providers: oidc: one or more clients have the same id but all client" +
- "id's must be unique"
- errFmtOIDCClientsWithEmptyID = "identity_providers: oidc: one or more clients have been configured with " +
- "an empty id"
+ errFmtOIDCClientsDuplicateID = "identity_providers: oidc: clients: option 'id' must be unique for every client but one or more clients share the following 'id' values %s"
+ 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"
errFmtOIDCClientInvalidSecret = "identity_providers: oidc: client '%s': option 'secret' is required"
errFmtOIDCClientInvalidSecretPlainText = "identity_providers: oidc: client '%s': option 'secret' is plaintext but it should be a hashed value as plaintext values are deprecated and will be removed when oidc becomes stable"
@@ -170,36 +169,43 @@ const (
"redirect uri '%s' when option 'public' is false but this is invalid as this uri is not valid " +
"for the openid connect confidential client type"
errFmtOIDCClientRedirectURIAbsolute = "identity_providers: oidc: client '%s': option 'redirect_uris' has an " +
- "invalid value: redirect uri '%s' must have the scheme but it is absent"
- errFmtOIDCClientInvalidPolicy = "identity_providers: oidc: client '%s': option 'policy' must be 'one_factor' " +
- "or 'two_factor' but it is configured as '%s'"
- errFmtOIDCClientInvalidPKCEChallengeMethod = "identity_providers: oidc: client '%s': option 'pkce_challenge_method' must be 'plain' " +
- "or 'S256' but it is configured as '%s'"
+ "invalid value: redirect uri '%s' must have a scheme but it's absent"
errFmtOIDCClientInvalidConsentMode = "identity_providers: oidc: client '%s': consent: option 'mode' must be one of " +
- "'%s' but it is configured as '%s'"
- errFmtOIDCClientInvalidEntry = "identity_providers: oidc: client '%s': option '%s' must only have the values " +
- "'%s' but one option is configured as '%s'"
- errFmtOIDCClientInvalidUserinfoAlgorithm = "identity_providers: oidc: client '%s': option " +
- "'userinfo_signing_algorithm' must be one of '%s' but it is configured as '%s'"
+ "%s but it's configured as '%s'"
+ errFmtOIDCClientInvalidEntries = "identity_providers: oidc: client '%s': option '%s' must only have the values " +
+ "%s but the values %s are present"
+ errFmtOIDCClientInvalidEntryDuplicates = "identity_providers: oidc: client '%s': option '%s' must have unique values but the values %s are duplicated"
+ errFmtOIDCClientInvalidValue = "identity_providers: oidc: client '%s': option " +
+ "'%s' must be one of %s but it's configured as '%s'"
+ errFmtOIDCClientInvalidTokenEndpointAuthMethod = "identity_providers: oidc: client '%s': option " +
+ "'token_endpoint_auth_method' must be one of %s when configured as the confidential client type unless it only includes implicit flow response types such as %s but it's configured as '%s'"
+ errFmtOIDCClientInvalidTokenEndpointAuthMethodPublic = "identity_providers: oidc: client '%s': option " +
+ "'token_endpoint_auth_method' must be 'none' when configured as the public client type but it's configured as '%s'"
errFmtOIDCClientInvalidSectorIdentifier = "identity_providers: oidc: client '%s': option " +
"'sector_identifier' with value '%s': must be a URL with only the host component for example '%s' but it has a %s with the value '%s'"
errFmtOIDCClientInvalidSectorIdentifierWithoutValue = "identity_providers: oidc: client '%s': option " +
"'sector_identifier' with value '%s': must be a URL with only the host component for example '%s' but it has a %s"
errFmtOIDCClientInvalidSectorIdentifierHost = "identity_providers: oidc: client '%s': option " +
"'sector_identifier' with value '%s': must be a URL with only the host component but appears to be invalid"
+ errFmtOIDCClientInvalidGrantTypeMatch = "identity_providers: oidc: client '%s': option " +
+ "'grant_types' should only have grant type values which are valid with the configured 'response_types' for the client but '%s' expects a response type %s such as %s but the response types are %s"
+ errFmtOIDCClientInvalidGrantTypeRefresh = "identity_providers: oidc: client '%s': option " +
+ "'grant_types' should only have the 'refresh_token' value if the client is also configured with the 'offline_access' scope"
+ errFmtOIDCClientInvalidRefreshTokenOptionWithoutCodeResponseType = "identity_providers: oidc: client '%s': option " +
+ "'%s' should only have the values %s if the client is also configured with a 'response_type' such as %s which respond with authorization codes"
errFmtOIDCServerInsecureParameterEntropy = "openid connect provider: SECURITY ISSUE - minimum parameter entropy is " +
"configured to an unsafe value, it should be above 8 but it's configured to %d"
)
// Webauthn Error constants.
const (
- errFmtWebauthnConveyancePreference = "webauthn: option 'attestation_conveyance_preference' must be one of '%s' but it is configured as '%s'"
- errFmtWebauthnUserVerification = "webauthn: option 'user_verification' must be one of 'discouraged', 'preferred', 'required' but it is configured as '%s'"
+ errFmtWebauthnConveyancePreference = "webauthn: option 'attestation_conveyance_preference' must be one of %s but it's configured as '%s'"
+ errFmtWebauthnUserVerification = "webauthn: option 'user_verification' must be one of %s but it's configured as '%s'"
)
// Access Control error constants.
const (
- errFmtAccessControlDefaultPolicyValue = "access control: option 'default_policy' must be one of '%s' but it is " +
+ errFmtAccessControlDefaultPolicyValue = "access control: option 'default_policy' must be one of %s but it's " +
"configured as '%s'"
errFmtAccessControlDefaultPolicyWithoutRules = "access control: 'default_policy' option '%s' is invalid: when " +
"no rules are specified it must be 'two_factor' or 'one_factor'"
@@ -207,10 +213,9 @@ const (
"network '%s' is not a valid IP or CIDR notation"
errFmtAccessControlWarnNoRulesDefaultPolicy = "access control: no rules have been specified so the " +
"'default_policy' of '%s' is going to be applied to all requests"
- errFmtAccessControlRuleNoDomains = "access control: rule %s: rule is invalid: must have the option " +
- "'domain' or 'domain_regex' configured"
- errFmtAccessControlRuleInvalidPolicy = "access control: rule %s: rule 'policy' option '%s' " +
- "is invalid: must be one of 'deny', 'two_factor', 'one_factor' or 'bypass'"
+ errFmtAccessControlRuleNoDomains = "access control: rule %s: option 'domain' or 'domain_regex' must be present but are both absent"
+ errFmtAccessControlRuleNoPolicy = "access control: rule %s: option 'policy' must be present but it's absent"
+ errFmtAccessControlRuleInvalidPolicy = "access control: rule %s: option 'policy' must be one of %s but it's configured as '%s'"
errAccessControlRuleBypassPolicyInvalidWithSubjects = "access control: rule %s: 'policy' option 'bypass' is " +
"not supported when 'subject' option is configured: see " +
"https://www.authelia.com/c/acl#bypass"
@@ -221,39 +226,35 @@ const (
"valid Group Name, IP, or CIDR notation"
errFmtAccessControlRuleSubjectInvalid = "access control: rule %s: 'subject' option '%s' is " +
"invalid: must start with 'user:' or 'group:'"
- errFmtAccessControlRuleMethodInvalid = "access control: rule %s: 'methods' option '%s' is " +
- "invalid: must be one of '%s'"
- errFmtAccessControlRuleQueryInvalid = "access control: rule %s: 'query' option 'operator' with value '%s' is " +
- "invalid: must be one of '%s'"
- errFmtAccessControlRuleQueryInvalidNoValue = "access control: rule %s: 'query' option '%s' is " +
- "invalid: must have a value"
- errFmtAccessControlRuleQueryInvalidNoValueOperator = "access control: rule %s: 'query' option '%s' is " +
- "invalid: must have a value when the operator is '%s'"
- errFmtAccessControlRuleQueryInvalidValue = "access control: rule %s: 'query' option '%s' is " +
- "invalid: must not have a value when the operator is '%s'"
- errFmtAccessControlRuleQueryInvalidValueParse = "access control: rule %s: 'query' option '%s' is " +
+ errFmtAccessControlRuleInvalidEntries = "access control: rule %s: option '%s' must only have the values %s but the values %s are present"
+ errFmtAccessControlRuleInvalidDuplicates = "access control: rule %s: option '%s' must have unique values but the values %s are duplicated"
+ errFmtAccessControlRuleQueryInvalid = "access control: rule %s: query: option 'operator' must be one of %s but it's configured as '%s'"
+ errFmtAccessControlRuleQueryInvalidNoValue = "access control: rule %s: query: option '%s' is required but it's absent"
+ errFmtAccessControlRuleQueryInvalidNoValueOperator = "access control: rule %s: query: option '%s' must be present when the option 'operator' is '%s' but it's absent"
+ errFmtAccessControlRuleQueryInvalidValue = "access control: rule %s: query: option '%s' must not be present when the option 'operator' is '%s' but it's present"
+ errFmtAccessControlRuleQueryInvalidValueParse = "access control: rule %s: query: option '%s' is " +
"invalid: %w"
- errFmtAccessControlRuleQueryInvalidValueType = "access control: rule %s: 'query' option 'value' is " +
+ errFmtAccessControlRuleQueryInvalidValueType = "access control: rule %s: query: option 'value' is " +
"invalid: expected type was string but got %T"
)
// Theme Error constants.
const (
- errFmtThemeName = "option 'theme' must be one of '%s' but it is configured as '%s'"
+ errFmtThemeName = "option 'theme' must be one of %s but it's configured as '%s'"
)
// NTP Error constants.
const (
- errFmtNTPVersion = "ntp: option 'version' must be either 3 or 4 but it is configured as '%d'"
+ errFmtNTPVersion = "ntp: option 'version' must be either 3 or 4 but it's configured as '%d'"
)
// Session error constants.
const (
errFmtSessionOptionRequired = "session: option '%s' is required"
errFmtSessionLegacyAndWarning = "session: option 'domain' and option 'cookies' can't be specified at the same time"
- errFmtSessionSameSite = "session: option 'same_site' must be one of '%s' but is configured as '%s'"
+ errFmtSessionSameSite = "session: option 'same_site' must be one of %s but it's configured as '%s'"
errFmtSessionSecretRequired = "session: option 'secret' is required when using the '%s' provider"
- errFmtSessionRedisPortRange = "session: redis: option 'port' must be between 1 and 65535 but is configured as '%d'"
+ errFmtSessionRedisPortRange = "session: redis: option 'port' must be between 1 and 65535 but it's configured as '%d'"
errFmtSessionRedisHostRequired = "session: redis: option 'host' is required"
errFmtSessionRedisHostOrNodesRequired = "session: redis: option 'host' or the 'high_availability' option 'nodes' is required"
errFmtSessionRedisTLSConfigInvalid = "session: redis: tls: %w"
@@ -261,8 +262,8 @@ 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 is configured as '%s'"
- errFmtSessionDomainSameSite = "session: domain config %s: option 'same_site' must be one of '%s' but is configured as '%s'"
+ 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"
@@ -291,8 +292,8 @@ const (
errFmtServerPathNoForwardSlashes = "server: option 'path' must not contain any forward slashes"
errFmtServerPathAlphaNum = "server: option 'path' must only contain alpha numeric characters"
- errFmtServerEndpointsAuthzImplementation = "server: endpoints: authz: %s: option 'implementation' must be one of '%s' but is configured as '%s'"
- errFmtServerEndpointsAuthzStrategy = "server: endpoints: authz: %s: authn_strategies: option 'name' must be one of '%s' but is configured as '%s'"
+ 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"
@@ -302,7 +303,7 @@ const (
const (
errPasswordPolicyMultipleDefined = "password_policy: only a single password policy mechanism can be specified"
- errFmtPasswordPolicyStandardMinLengthNotGreaterThanZero = "password_policy: standard: option 'min_length' must be greater than 0 but is configured as %d"
+ errFmtPasswordPolicyStandardMinLengthNotGreaterThanZero = "password_policy: standard: option 'min_length' must be greater than 0 but it's configured as %d"
errFmtPasswordPolicyZXCVBNMinScoreInvalid = "password_policy: zxcvbn: option 'min_score' is invalid: must be between 1 and 4 but it's configured as %d"
)
@@ -312,19 +313,17 @@ const (
)
const (
- errFmtDuoMissingOption = "duo_api: option '%s' is required when duo is enabled but it is missing"
+ errFmtDuoMissingOption = "duo_api: option '%s' is required when duo is enabled but it's absent"
)
// Error constants.
const (
- errFmtInvalidDefault2FAMethod = "option 'default_2fa_method' is configured as '%s' but must be one of " +
- "the following values: '%s'"
- errFmtInvalidDefault2FAMethodDisabled = "option 'default_2fa_method' is configured as '%s' " +
- "but must be one of the following enabled method values: '%s'"
+ errFmtInvalidDefault2FAMethod = "option 'default_2fa_method' must be one of %s but it's configured as '%s'"
+ errFmtInvalidDefault2FAMethodDisabled = "option 'default_2fa_method' must be one of the enabled options %s but it's configured as '%s'"
errFmtReplacedConfigurationKey = "invalid configuration key '%s' was replaced by '%s'"
- errFmtLoggingLevelInvalid = "log: option 'level' must be one of '%s' but it is configured as '%s'"
+ errFmtLoggingLevelInvalid = "log: option 'level' must be one of %s but it's configured as '%s'"
errFileHashing = "config key incorrect: authentication_backend.file.hashing should be authentication_backend.file.password"
errFilePHashing = "config key incorrect: authentication_backend.file.password_hashing should be authentication_backend.file.password"
@@ -357,6 +356,10 @@ const (
authzImplementationExtAuthz = "ExtAuthz"
)
+const (
+ auto = "auto"
+)
+
var (
validAuthzImplementations = []string{"AuthRequest", "ForwardAuth", authzImplementationExtAuthz, authzImplementationLegacy}
validAuthzAuthnStrategies = []string{"CookieSession", "HeaderAuthorization", "HeaderProxyAuthorization", "HeaderAuthRequestProxyAuthorization", "HeaderLegacy"}
@@ -372,7 +375,7 @@ var (
var (
validStoragePostgreSQLSSLModes = []string{"disable", "require", "verify-ca", "verify-full"}
- validThemeNames = []string{"light", "dark", "grey", "auto"}
+ validThemeNames = []string{"light", "dark", "grey", auto}
validSessionSameSiteValues = []string{"none", "lax", "strict"}
validLogLevels = []string{"trace", "debug", "info", "warn", "error"}
validWebauthnConveyancePreferences = []string{string(protocol.PreferNoAttestation), string(protocol.PreferIndirectAttestation), string(protocol.PreferDirectAttestation)}
@@ -389,19 +392,38 @@ var (
var validDefault2FAMethods = []string{"totp", "webauthn", "mobile_push"}
+const (
+ attrOIDCScopes = "scopes"
+ attrOIDCResponseTypes = "response_types"
+ attrOIDCResponseModes = "response_modes"
+ attrOIDCGrantTypes = "grant_types"
+ attrOIDCRedirectURIs = "redirect_uris"
+ attrOIDCTokenAuthMethod = "token_endpoint_auth_method"
+ attrOIDCUsrSigAlg = "userinfo_signing_algorithm"
+ attrOIDCPKCEChallengeMethod = "pkce_challenge_method"
+)
+
var (
- validOIDCScopes = []string{oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeProfile, oidc.ScopeGroups, oidc.ScopeOfflineAccess}
- validOIDCGrantTypes = []string{oidc.GrantTypeImplicit, oidc.GrantTypeRefreshToken, oidc.GrantTypeAuthorizationCode, oidc.GrantTypePassword, oidc.GrantTypeClientCredentials}
- validOIDCResponseModes = []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment}
- validOIDCUserinfoAlgorithms = []string{oidc.SigningAlgorithmNone, oidc.SigningAlgorithmRSAWithSHA256}
- validOIDCCORSEndpoints = []string{oidc.EndpointAuthorization, oidc.EndpointPushedAuthorizationRequest, oidc.EndpointToken, oidc.EndpointIntrospection, oidc.EndpointRevocation, oidc.EndpointUserinfo}
- validOIDCClientConsentModes = []string{"auto", oidc.ClientConsentModeImplicit.String(), oidc.ClientConsentModeExplicit.String(), oidc.ClientConsentModePreConfigured.String()}
+ 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}
+ validOIDCClientUserinfoAlgorithms = []string{oidc.SigningAlgorithmNone, oidc.SigningAlgorithmRSAWithSHA256}
+ validOIDCClientConsentModes = []string{auto, oidc.ClientConsentModeImplicit.String(), oidc.ClientConsentModeExplicit.String(), oidc.ClientConsentModePreConfigured.String()}
+ validOIDCClientResponseModes = []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment}
+ validOIDCClientResponseTypes = []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowIDToken, oidc.ResponseTypeImplicitFlowToken, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeHybridFlowIDToken, oidc.ResponseTypeHybridFlowToken, oidc.ResponseTypeHybridFlowBoth}
+ validOIDCClientResponseTypesImplicitFlow = []string{oidc.ResponseTypeImplicitFlowIDToken, oidc.ResponseTypeImplicitFlowToken, oidc.ResponseTypeImplicitFlowBoth}
+ validOIDCClientResponseTypesHybridFlow = []string{oidc.ResponseTypeHybridFlowIDToken, oidc.ResponseTypeHybridFlowToken, oidc.ResponseTypeHybridFlowBoth}
+ validOIDCClientResponseTypesRefreshToken = []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeHybridFlowIDToken, oidc.ResponseTypeHybridFlowToken, oidc.ResponseTypeHybridFlowBoth}
+ validOIDCClientGrantTypes = []string{oidc.GrantTypeImplicit, oidc.GrantTypeRefreshToken, oidc.GrantTypeAuthorizationCode}
+
+ validOIDCClientTokenEndpointAuthMethods = []string{oidc.ClientAuthMethodNone, oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic}
+ validOIDCClientTokenEndpointAuthMethodsConfidential = []string{oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic}
)
var (
reKeyReplacer = regexp.MustCompile(`\[\d+]`)
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]))?$`)
+ reAuthzEndpointName = regexp.MustCompile(`^[a-zA-Z](([a-zA-Z0-9/._-]*)([a-zA-Z]))?$`)
)
var replacedKeys = map[string]string{
diff --git a/internal/configuration/validator/duo_test.go b/internal/configuration/validator/duo_test.go
index ef4856b56..21cdbf6b1 100644
--- a/internal/configuration/validator/duo_test.go
+++ b/internal/configuration/validator/duo_test.go
@@ -23,6 +23,11 @@ func TestValidateDuo(t *testing.T) {
expected: schema.DuoAPIConfiguration{Disable: true},
},
{
+ desc: "ShouldDisableDuoConfigured",
+ have: &schema.Configuration{DuoAPI: schema.DuoAPIConfiguration{Disable: true, Hostname: "example.com"}},
+ expected: schema.DuoAPIConfiguration{Disable: true, Hostname: "example.com"},
+ },
+ {
desc: "ShouldNotDisableDuo",
have: &schema.Configuration{DuoAPI: schema.DuoAPIConfiguration{
Hostname: "test",
@@ -46,7 +51,7 @@ func TestValidateDuo(t *testing.T) {
IntegrationKey: "test",
},
errs: []string{
- "duo_api: option 'secret_key' is required when duo is enabled but it is missing",
+ "duo_api: option 'secret_key' is required when duo is enabled but it's absent",
},
},
{
@@ -60,7 +65,7 @@ func TestValidateDuo(t *testing.T) {
SecretKey: "test",
},
errs: []string{
- "duo_api: option 'integration_key' is required when duo is enabled but it is missing",
+ "duo_api: option 'integration_key' is required when duo is enabled but it's absent",
},
},
{
@@ -74,7 +79,7 @@ func TestValidateDuo(t *testing.T) {
SecretKey: "test",
},
errs: []string{
- "duo_api: option 'hostname' is required when duo is enabled but it is missing",
+ "duo_api: option 'hostname' is required when duo is enabled but it's absent",
},
},
}
diff --git a/internal/configuration/validator/identity_providers.go b/internal/configuration/validator/identity_providers.go
index b9ea7e9b9..cb61e31db 100644
--- a/internal/configuration/validator/identity_providers.go
+++ b/internal/configuration/validator/identity_providers.go
@@ -3,6 +3,7 @@ package validator
import (
"fmt"
"net/url"
+ "strconv"
"strings"
"time"
@@ -125,10 +126,10 @@ func validateOIDCOptionsCORSAllowedOriginsFromClientRedirectURIs(config *schema.
continue
}
- origin := utils.OriginFromURL(*uri)
+ origin := utils.OriginFromURL(uri)
- if !utils.IsURLInSlice(origin, config.CORS.AllowedOrigins) {
- config.CORS.AllowedOrigins = append(config.CORS.AllowedOrigins, origin)
+ if !utils.IsURLInSlice(*origin, config.CORS.AllowedOrigins) {
+ config.CORS.AllowedOrigins = append(config.CORS.AllowedOrigins, *origin)
}
}
}
@@ -137,113 +138,135 @@ func validateOIDCOptionsCORSAllowedOriginsFromClientRedirectURIs(config *schema.
func validateOIDCOptionsCORSEndpoints(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
for _, endpoint := range config.CORS.Endpoints {
if !utils.IsStringInSlice(endpoint, validOIDCCORSEndpoints) {
- val.Push(fmt.Errorf(errFmtOIDCCORSInvalidEndpoint, endpoint, strings.Join(validOIDCCORSEndpoints, "', '")))
+ val.Push(fmt.Errorf(errFmtOIDCCORSInvalidEndpoint, endpoint, strJoinOr(validOIDCCORSEndpoints)))
}
}
}
func validateOIDCClients(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
- invalidID, duplicateIDs := false, false
+ var (
+ errDeprecated bool
- var ids []string
+ clientIDs, duplicateClientIDs, blankClientIDs []string
+ )
+
+ errDeprecatedFunc := func() { errDeprecated = true }
for c, client := range config.Clients {
if client.ID == "" {
- invalidID = true
+ blankClientIDs = append(blankClientIDs, "#"+strconv.Itoa(c+1))
} else {
if client.Description == "" {
config.Clients[c].Description = client.ID
}
- if utils.IsStringInSliceFold(client.ID, ids) {
- duplicateIDs = true
+ if id := strings.ToLower(client.ID); utils.IsStringInSlice(id, clientIDs) {
+ if !utils.IsStringInSlice(id, duplicateClientIDs) {
+ duplicateClientIDs = append(duplicateClientIDs, id)
+ }
+ } else {
+ clientIDs = append(clientIDs, id)
}
- ids = append(ids, client.ID)
}
- if client.Public {
- if client.Secret != nil {
- val.Push(fmt.Errorf(errFmtOIDCClientPublicInvalidSecret, client.ID))
- }
- } else {
- if client.Secret == nil {
- val.Push(fmt.Errorf(errFmtOIDCClientInvalidSecret, client.ID))
- } else if client.Secret.IsPlainText() {
- val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidSecretPlainText, client.ID))
- }
- }
+ validateOIDCClient(c, config, val, errDeprecatedFunc)
+ }
- if client.Policy == "" {
- config.Clients[c].Policy = schema.DefaultOpenIDConnectClientConfiguration.Policy
- } else if client.Policy != policyOneFactor && client.Policy != policyTwoFactor {
- val.Push(fmt.Errorf(errFmtOIDCClientInvalidPolicy, client.ID, client.Policy))
- }
+ if errDeprecated {
+ val.PushWarning(fmt.Errorf(errFmtOIDCClientsDeprecated))
+ }
- switch client.PKCEChallengeMethod {
- case "", "plain", "S256":
- break
- default:
- val.Push(fmt.Errorf(errFmtOIDCClientInvalidPKCEChallengeMethod, client.ID, client.PKCEChallengeMethod))
- }
+ if len(blankClientIDs) != 0 {
+ val.Push(fmt.Errorf(errFmtOIDCClientsWithEmptyID, buildJoinedString(", ", "or", "", blankClientIDs)))
+ }
- validateOIDCClientConsentMode(c, config, val)
- validateOIDCClientSectorIdentifier(client, val)
- validateOIDCClientScopes(c, config, val)
- validateOIDCClientGrantTypes(c, config, val)
- validateOIDCClientResponseTypes(c, config, val)
- validateOIDCClientResponseModes(c, config, val)
- validateOIDDClientUserinfoAlgorithm(c, config, val)
- validateOIDCClientRedirectURIs(client, val)
+ if len(duplicateClientIDs) != 0 {
+ val.Push(fmt.Errorf(errFmtOIDCClientsDuplicateID, strJoinOr(duplicateClientIDs)))
}
+}
- if invalidID {
- val.Push(fmt.Errorf(errFmtOIDCClientsWithEmptyID))
+func validateOIDCClient(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator, errDeprecatedFunc func()) {
+ if config.Clients[c].Public {
+ if config.Clients[c].Secret != nil {
+ val.Push(fmt.Errorf(errFmtOIDCClientPublicInvalidSecret, config.Clients[c].ID))
+ }
+ } else {
+ if config.Clients[c].Secret == nil {
+ val.Push(fmt.Errorf(errFmtOIDCClientInvalidSecret, config.Clients[c].ID))
+ } else if config.Clients[c].Secret.IsPlainText() {
+ val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidSecretPlainText, config.Clients[c].ID))
+ }
}
- if duplicateIDs {
- val.Push(fmt.Errorf(errFmtOIDCClientsDuplicateID))
+ switch config.Clients[c].Policy {
+ case "":
+ config.Clients[c].Policy = schema.DefaultOpenIDConnectClientConfiguration.Policy
+ case policyOneFactor, policyTwoFactor:
+ break
+ default:
+ val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue, config.Clients[c].ID, "policy", strJoinOr([]string{policyOneFactor, policyTwoFactor}), config.Clients[c].Policy))
+ }
+
+ switch config.Clients[c].PKCEChallengeMethod {
+ case "", oidc.PKCEChallengeMethodPlain, oidc.PKCEChallengeMethodSHA256:
+ break
+ default:
+ val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue, config.Clients[c].ID, attrOIDCPKCEChallengeMethod, strJoinOr([]string{oidc.PKCEChallengeMethodPlain, oidc.PKCEChallengeMethodSHA256}), config.Clients[c].PKCEChallengeMethod))
}
+
+ validateOIDCClientConsentMode(c, config, val)
+
+ validateOIDCClientScopes(c, config, val, errDeprecatedFunc)
+ validateOIDCClientResponseTypes(c, config, val, errDeprecatedFunc)
+ validateOIDCClientResponseModes(c, config, val, errDeprecatedFunc)
+ validateOIDCClientGrantTypes(c, config, val, errDeprecatedFunc)
+ validateOIDCClientRedirectURIs(c, config, val, errDeprecatedFunc)
+
+ validateOIDCClientTokenEndpointAuthMethod(c, config, val)
+ validateOIDDClientUserinfoAlgorithm(c, config, val)
+
+ validateOIDCClientSectorIdentifier(c, config, val)
}
-func validateOIDCClientSectorIdentifier(client schema.OpenIDConnectClientConfiguration, val *schema.StructValidator) {
- if client.SectorIdentifier.String() != "" {
- if utils.IsURLHostComponent(client.SectorIdentifier) || utils.IsURLHostComponentWithPort(client.SectorIdentifier) {
+func validateOIDCClientSectorIdentifier(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
+ if config.Clients[c].SectorIdentifier.String() != "" {
+ if utils.IsURLHostComponent(config.Clients[c].SectorIdentifier) || utils.IsURLHostComponentWithPort(config.Clients[c].SectorIdentifier) {
return
}
- if client.SectorIdentifier.Scheme != "" {
- val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "scheme", client.SectorIdentifier.Scheme))
+ if config.Clients[c].SectorIdentifier.Scheme != "" {
+ val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, config.Clients[c].ID, config.Clients[c].SectorIdentifier.String(), config.Clients[c].SectorIdentifier.Host, "scheme", config.Clients[c].SectorIdentifier.Scheme))
- if client.SectorIdentifier.Path != "" {
- val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "path", client.SectorIdentifier.Path))
+ if config.Clients[c].SectorIdentifier.Path != "" {
+ val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, config.Clients[c].ID, config.Clients[c].SectorIdentifier.String(), config.Clients[c].SectorIdentifier.Host, "path", config.Clients[c].SectorIdentifier.Path))
}
- if client.SectorIdentifier.RawQuery != "" {
- val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "query", client.SectorIdentifier.RawQuery))
+ if config.Clients[c].SectorIdentifier.RawQuery != "" {
+ val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, config.Clients[c].ID, config.Clients[c].SectorIdentifier.String(), config.Clients[c].SectorIdentifier.Host, "query", config.Clients[c].SectorIdentifier.RawQuery))
}
- if client.SectorIdentifier.Fragment != "" {
- val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "fragment", client.SectorIdentifier.Fragment))
+ if config.Clients[c].SectorIdentifier.Fragment != "" {
+ val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, config.Clients[c].ID, config.Clients[c].SectorIdentifier.String(), config.Clients[c].SectorIdentifier.Host, "fragment", config.Clients[c].SectorIdentifier.Fragment))
}
- if client.SectorIdentifier.User != nil {
- if client.SectorIdentifier.User.Username() != "" {
- val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "username", client.SectorIdentifier.User.Username()))
+ if config.Clients[c].SectorIdentifier.User != nil {
+ if config.Clients[c].SectorIdentifier.User.Username() != "" {
+ val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, config.Clients[c].ID, config.Clients[c].SectorIdentifier.String(), config.Clients[c].SectorIdentifier.Host, "username", config.Clients[c].SectorIdentifier.User.Username()))
}
- if _, set := client.SectorIdentifier.User.Password(); set {
- val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifierWithoutValue, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "password"))
+ if _, set := config.Clients[c].SectorIdentifier.User.Password(); set {
+ val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifierWithoutValue, config.Clients[c].ID, config.Clients[c].SectorIdentifier.String(), config.Clients[c].SectorIdentifier.Host, "password"))
}
}
- } else if client.SectorIdentifier.Host == "" {
- val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifierHost, client.ID, client.SectorIdentifier.String()))
+ } else if config.Clients[c].SectorIdentifier.Host == "" {
+ val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifierHost, config.Clients[c].ID, config.Clients[c].SectorIdentifier.String()))
}
}
}
func validateOIDCClientConsentMode(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
switch {
- case utils.IsStringInSlice(config.Clients[c].ConsentMode, []string{"", "auto"}):
+ case utils.IsStringInSlice(config.Clients[c].ConsentMode, []string{"", auto}):
if config.Clients[c].ConsentPreConfiguredDuration != nil {
config.Clients[c].ConsentMode = oidc.ClientConsentModePreConfigured.String()
} else {
@@ -252,7 +275,7 @@ func validateOIDCClientConsentMode(c int, config *schema.OpenIDConnectConfigurat
case utils.IsStringInSlice(config.Clients[c].ConsentMode, validOIDCClientConsentModes):
break
default:
- val.Push(fmt.Errorf(errFmtOIDCClientInvalidConsentMode, config.Clients[c].ID, strings.Join(append(validOIDCClientConsentModes, "auto"), "', '"), config.Clients[c].ConsentMode))
+ val.Push(fmt.Errorf(errFmtOIDCClientInvalidConsentMode, config.Clients[c].ID, strJoinOr(append(validOIDCClientConsentModes, auto)), config.Clients[c].ConsentMode))
}
if config.Clients[c].ConsentMode == oidc.ClientConsentModePreConfigured.String() && config.Clients[c].ConsentPreConfiguredDuration == nil {
@@ -260,92 +283,233 @@ func validateOIDCClientConsentMode(c int, config *schema.OpenIDConnectConfigurat
}
}
-func validateOIDCClientScopes(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
+func validateOIDCClientScopes(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator, errDeprecatedFunc func()) {
if len(config.Clients[c].Scopes) == 0 {
config.Clients[c].Scopes = schema.DefaultOpenIDConnectClientConfiguration.Scopes
- return
}
if !utils.IsStringInSlice(oidc.ScopeOpenID, config.Clients[c].Scopes) {
- config.Clients[c].Scopes = append(config.Clients[c].Scopes, oidc.ScopeOpenID)
+ config.Clients[c].Scopes = append([]string{oidc.ScopeOpenID}, config.Clients[c].Scopes...)
}
- for _, scope := range config.Clients[c].Scopes {
- if !utils.IsStringInSlice(scope, validOIDCScopes) {
- val.Push(fmt.Errorf(
- errFmtOIDCClientInvalidEntry,
- config.Clients[c].ID, "scopes", strings.Join(validOIDCScopes, "', '"), scope))
- }
+ invalid, duplicates := validateList(config.Clients[c].Scopes, validOIDCClientScopes, true)
+
+ if len(invalid) != 0 {
+ val.Push(fmt.Errorf(errFmtOIDCClientInvalidEntries, config.Clients[c].ID, attrOIDCScopes, strJoinOr(validOIDCClientScopes), strJoinAnd(invalid)))
}
-}
-func validateOIDCClientGrantTypes(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
- if len(config.Clients[c].GrantTypes) == 0 {
- config.Clients[c].GrantTypes = schema.DefaultOpenIDConnectClientConfiguration.GrantTypes
- return
+ if len(duplicates) != 0 {
+ errDeprecatedFunc()
+
+ val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidEntryDuplicates, config.Clients[c].ID, attrOIDCScopes, strJoinAnd(duplicates)))
}
- for _, grantType := range config.Clients[c].GrantTypes {
- if !utils.IsStringInSlice(grantType, validOIDCGrantTypes) {
- val.Push(fmt.Errorf(
- errFmtOIDCClientInvalidEntry,
- config.Clients[c].ID, "grant_types", strings.Join(validOIDCGrantTypes, "', '"), grantType))
- }
+ if utils.IsStringSliceContainsAny([]string{oidc.ScopeOfflineAccess, oidc.ScopeOffline}, config.Clients[c].Scopes) &&
+ !utils.IsStringSliceContainsAny(validOIDCClientResponseTypesRefreshToken, config.Clients[c].ResponseTypes) {
+ errDeprecatedFunc()
+
+ val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidRefreshTokenOptionWithoutCodeResponseType,
+ config.Clients[c].ID, attrOIDCScopes,
+ strJoinOr([]string{oidc.ScopeOfflineAccess, oidc.ScopeOffline}),
+ strJoinOr(validOIDCClientResponseTypesRefreshToken)),
+ )
}
}
-func validateOIDCClientResponseTypes(c int, config *schema.OpenIDConnectConfiguration, _ *schema.StructValidator) {
+func validateOIDCClientResponseTypes(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator, errDeprecatedFunc func()) {
if len(config.Clients[c].ResponseTypes) == 0 {
config.Clients[c].ResponseTypes = schema.DefaultOpenIDConnectClientConfiguration.ResponseTypes
- return
+ }
+
+ invalid, duplicates := validateList(config.Clients[c].ResponseTypes, validOIDCClientResponseTypes, true)
+
+ if len(invalid) != 0 {
+ val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidEntries, config.Clients[c].ID, attrOIDCResponseTypes, strJoinOr(validOIDCClientResponseTypes), strJoinAnd(invalid)))
+ }
+
+ if len(duplicates) != 0 {
+ errDeprecatedFunc()
+
+ val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidEntryDuplicates, config.Clients[c].ID, attrOIDCResponseTypes, strJoinAnd(duplicates)))
}
}
-func validateOIDCClientResponseModes(c int, config *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) {
+func validateOIDCClientResponseModes(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator, errDeprecatedFunc func()) {
if len(config.Clients[c].ResponseModes) == 0 {
config.Clients[c].ResponseModes = schema.DefaultOpenIDConnectClientConfiguration.ResponseModes
- return
+
+ for _, responseType := range config.Clients[c].ResponseTypes {
+ switch responseType {
+ case oidc.ResponseTypeAuthorizationCodeFlow:
+ if !utils.IsStringInSlice(oidc.ResponseModeQuery, config.Clients[c].ResponseModes) {
+ config.Clients[c].ResponseModes = append(config.Clients[c].ResponseModes, oidc.ResponseModeQuery)
+ }
+ case oidc.ResponseTypeImplicitFlowIDToken, oidc.ResponseTypeImplicitFlowToken, oidc.ResponseTypeImplicitFlowBoth,
+ oidc.ResponseTypeHybridFlowIDToken, oidc.ResponseTypeHybridFlowToken, oidc.ResponseTypeHybridFlowBoth:
+ if !utils.IsStringInSlice(oidc.ResponseModeFragment, config.Clients[c].ResponseModes) {
+ config.Clients[c].ResponseModes = append(config.Clients[c].ResponseModes, oidc.ResponseModeFragment)
+ }
+ }
+ }
+ }
+
+ invalid, duplicates := validateList(config.Clients[c].ResponseModes, validOIDCClientResponseModes, true)
+
+ if len(invalid) != 0 {
+ val.Push(fmt.Errorf(errFmtOIDCClientInvalidEntries, config.Clients[c].ID, attrOIDCResponseModes, strJoinOr(validOIDCClientResponseModes), strJoinAnd(invalid)))
+ }
+
+ if len(duplicates) != 0 {
+ errDeprecatedFunc()
+
+ val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidEntryDuplicates, config.Clients[c].ID, attrOIDCResponseModes, strJoinAnd(duplicates)))
+ }
+}
+
+func validateOIDCClientGrantTypes(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator, errDeprecatedFunc func()) {
+ if len(config.Clients[c].GrantTypes) == 0 {
+ validateOIDCClientGrantTypesSetDefaults(c, config)
+ }
+
+ validateOIDCClientGrantTypesCheckRelated(c, config, val, errDeprecatedFunc)
+
+ invalid, duplicates := validateList(config.Clients[c].GrantTypes, validOIDCClientGrantTypes, true)
+
+ if len(invalid) != 0 {
+ val.Push(fmt.Errorf(errFmtOIDCClientInvalidEntries, config.Clients[c].ID, attrOIDCGrantTypes, strJoinOr(validOIDCClientGrantTypes), strJoinAnd(invalid)))
}
- for _, responseMode := range config.Clients[c].ResponseModes {
- if !utils.IsStringInSlice(responseMode, validOIDCResponseModes) {
- validator.Push(fmt.Errorf(
- errFmtOIDCClientInvalidEntry,
- config.Clients[c].ID, "response_modes", strings.Join(validOIDCResponseModes, "', '"), responseMode))
+ if len(duplicates) != 0 {
+ errDeprecatedFunc()
+
+ val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidEntryDuplicates, config.Clients[c].ID, attrOIDCGrantTypes, strJoinAnd(duplicates)))
+ }
+}
+
+func validateOIDCClientGrantTypesSetDefaults(c int, config *schema.OpenIDConnectConfiguration) {
+ for _, responseType := range config.Clients[c].ResponseTypes {
+ switch responseType {
+ case oidc.ResponseTypeAuthorizationCodeFlow:
+ if !utils.IsStringInSlice(oidc.GrantTypeAuthorizationCode, config.Clients[c].GrantTypes) {
+ config.Clients[c].GrantTypes = append(config.Clients[c].GrantTypes, oidc.GrantTypeAuthorizationCode)
+ }
+ case oidc.ResponseTypeImplicitFlowIDToken, oidc.ResponseTypeImplicitFlowToken, oidc.ResponseTypeImplicitFlowBoth:
+ if !utils.IsStringInSlice(oidc.GrantTypeImplicit, config.Clients[c].GrantTypes) {
+ config.Clients[c].GrantTypes = append(config.Clients[c].GrantTypes, oidc.GrantTypeImplicit)
+ }
+ case oidc.ResponseTypeHybridFlowIDToken, oidc.ResponseTypeHybridFlowToken, oidc.ResponseTypeHybridFlowBoth:
+ if !utils.IsStringInSlice(oidc.GrantTypeAuthorizationCode, config.Clients[c].GrantTypes) {
+ config.Clients[c].GrantTypes = append(config.Clients[c].GrantTypes, oidc.GrantTypeAuthorizationCode)
+ }
+
+ if !utils.IsStringInSlice(oidc.GrantTypeImplicit, config.Clients[c].GrantTypes) {
+ config.Clients[c].GrantTypes = append(config.Clients[c].GrantTypes, oidc.GrantTypeImplicit)
+ }
}
}
}
-func validateOIDDClientUserinfoAlgorithm(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
- if config.Clients[c].UserinfoSigningAlgorithm == "" {
- config.Clients[c].UserinfoSigningAlgorithm = schema.DefaultOpenIDConnectClientConfiguration.UserinfoSigningAlgorithm
- } else if !utils.IsStringInSlice(config.Clients[c].UserinfoSigningAlgorithm, validOIDCUserinfoAlgorithms) {
- val.Push(fmt.Errorf(errFmtOIDCClientInvalidUserinfoAlgorithm,
- config.Clients[c].ID, strings.Join(validOIDCUserinfoAlgorithms, ", "), config.Clients[c].UserinfoSigningAlgorithm))
+func validateOIDCClientGrantTypesCheckRelated(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator, errDeprecatedFunc func()) {
+ for _, grantType := range config.Clients[c].GrantTypes {
+ switch grantType {
+ case oidc.GrantTypeImplicit:
+ if !utils.IsStringSliceContainsAny(validOIDCClientResponseTypesImplicitFlow, config.Clients[c].ResponseTypes) && !utils.IsStringSliceContainsAny(validOIDCClientResponseTypesHybridFlow, config.Clients[c].ResponseTypes) {
+ errDeprecatedFunc()
+
+ val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidGrantTypeMatch, config.Clients[c].ID, grantType, "for either the implicit or hybrid flow", strJoinOr(append(append([]string{}, validOIDCClientResponseTypesImplicitFlow...), validOIDCClientResponseTypesHybridFlow...)), strJoinAnd(config.Clients[c].ResponseTypes)))
+ }
+ case oidc.GrantTypeAuthorizationCode:
+ if !utils.IsStringInSlice(oidc.ResponseTypeAuthorizationCodeFlow, config.Clients[c].ResponseTypes) && !utils.IsStringSliceContainsAny(validOIDCClientResponseTypesHybridFlow, config.Clients[c].ResponseTypes) {
+ errDeprecatedFunc()
+
+ val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidGrantTypeMatch, config.Clients[c].ID, grantType, "for either the authorization code or hybrid flow", strJoinOr(append([]string{oidc.ResponseTypeAuthorizationCodeFlow}, validOIDCClientResponseTypesHybridFlow...)), strJoinAnd(config.Clients[c].ResponseTypes)))
+ }
+ case oidc.GrantTypeRefreshToken:
+ if !utils.IsStringSliceContainsAny([]string{oidc.ScopeOfflineAccess, oidc.ScopeOffline}, config.Clients[c].Scopes) {
+ errDeprecatedFunc()
+
+ val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidGrantTypeRefresh, config.Clients[c].ID))
+ }
+
+ if !utils.IsStringSliceContainsAny(validOIDCClientResponseTypesRefreshToken, config.Clients[c].ResponseTypes) {
+ errDeprecatedFunc()
+
+ val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidRefreshTokenOptionWithoutCodeResponseType,
+ config.Clients[c].ID, attrOIDCGrantTypes,
+ strJoinOr([]string{oidc.GrantTypeRefreshToken}),
+ strJoinOr(validOIDCClientResponseTypesRefreshToken)),
+ )
+ }
+ }
}
}
-func validateOIDCClientRedirectURIs(client schema.OpenIDConnectClientConfiguration, val *schema.StructValidator) {
- for _, redirectURI := range client.RedirectURIs {
+func validateOIDCClientRedirectURIs(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator, errDeprecatedFunc func()) {
+ var (
+ parsedRedirectURI *url.URL
+ err error
+ )
+
+ for _, redirectURI := range config.Clients[c].RedirectURIs {
if redirectURI == oauth2InstalledApp {
- if client.Public {
+ if config.Clients[c].Public {
continue
}
- val.Push(fmt.Errorf(errFmtOIDCClientRedirectURIPublic, client.ID, oauth2InstalledApp))
+ val.Push(fmt.Errorf(errFmtOIDCClientRedirectURIPublic, config.Clients[c].ID, oauth2InstalledApp))
continue
}
- parsedURL, err := url.Parse(redirectURI)
- if err != nil {
- val.Push(fmt.Errorf(errFmtOIDCClientRedirectURICantBeParsed, client.ID, redirectURI, err))
+ if parsedRedirectURI, err = url.Parse(redirectURI); err != nil {
+ val.Push(fmt.Errorf(errFmtOIDCClientRedirectURICantBeParsed, config.Clients[c].ID, redirectURI, err))
continue
}
- if !parsedURL.IsAbs() || (!client.Public && parsedURL.Scheme == "") {
- val.Push(fmt.Errorf(errFmtOIDCClientRedirectURIAbsolute, client.ID, redirectURI))
+ if !parsedRedirectURI.IsAbs() || (!config.Clients[c].Public && parsedRedirectURI.Scheme == "") {
+ val.Push(fmt.Errorf(errFmtOIDCClientRedirectURIAbsolute, config.Clients[c].ID, redirectURI))
return
}
}
+
+ _, duplicates := validateList(config.Clients[c].RedirectURIs, nil, true)
+
+ if len(duplicates) != 0 {
+ errDeprecatedFunc()
+
+ val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidEntryDuplicates, config.Clients[c].ID, attrOIDCRedirectURIs, strJoinAnd(duplicates)))
+ }
+}
+
+func validateOIDCClientTokenEndpointAuthMethod(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
+ implcit := len(config.Clients[c].ResponseTypes) != 0 && utils.IsStringSliceContainsAll(config.Clients[c].ResponseTypes, validOIDCClientResponseTypesImplicitFlow)
+
+ if config.Clients[c].TokenEndpointAuthMethod == "" && (config.Clients[c].Public || implcit) {
+ config.Clients[c].TokenEndpointAuthMethod = oidc.ClientAuthMethodNone
+ }
+
+ switch {
+ case config.Clients[c].TokenEndpointAuthMethod == "":
+ break
+ case !utils.IsStringInSlice(config.Clients[c].TokenEndpointAuthMethod, validOIDCClientTokenEndpointAuthMethods):
+ val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue,
+ config.Clients[c].ID, attrOIDCTokenAuthMethod, strJoinOr(validOIDCClientTokenEndpointAuthMethods), config.Clients[c].TokenEndpointAuthMethod))
+ case config.Clients[c].TokenEndpointAuthMethod == oidc.ClientAuthMethodNone && !config.Clients[c].Public && !implcit:
+ val.Push(fmt.Errorf(errFmtOIDCClientInvalidTokenEndpointAuthMethod,
+ config.Clients[c].ID, strJoinOr(validOIDCClientTokenEndpointAuthMethodsConfidential), strJoinAnd(validOIDCClientResponseTypesImplicitFlow), config.Clients[c].TokenEndpointAuthMethod))
+ case config.Clients[c].TokenEndpointAuthMethod != oidc.ClientAuthMethodNone && config.Clients[c].Public:
+ val.Push(fmt.Errorf(errFmtOIDCClientInvalidTokenEndpointAuthMethodPublic,
+ config.Clients[c].ID, config.Clients[c].TokenEndpointAuthMethod))
+ }
+}
+
+func validateOIDDClientUserinfoAlgorithm(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
+ if config.Clients[c].UserinfoSigningAlgorithm == "" {
+ config.Clients[c].UserinfoSigningAlgorithm = schema.DefaultOpenIDConnectClientConfiguration.UserinfoSigningAlgorithm
+ }
+
+ if !utils.IsStringInSlice(config.Clients[c].UserinfoSigningAlgorithm, validOIDCClientUserinfoAlgorithms) {
+ val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue,
+ config.Clients[c].ID, attrOIDCUsrSigAlg, strJoinOr(validOIDCClientUserinfoAlgorithms), config.Clients[c].UserinfoSigningAlgorithm))
+ }
}
diff --git a/internal/configuration/validator/identity_providers_test.go b/internal/configuration/validator/identity_providers_test.go
index 8957c61f8..bdb11f3d0 100644
--- a/internal/configuration/validator/identity_providers_test.go
+++ b/internal/configuration/validator/identity_providers_test.go
@@ -7,7 +7,6 @@ import (
"errors"
"fmt"
"net/url"
- "strings"
"testing"
"time"
@@ -31,8 +30,8 @@ func TestShouldRaiseErrorWhenInvalidOIDCServerConfiguration(t *testing.T) {
require.Len(t, validator.Errors(), 2)
- assert.EqualError(t, validator.Errors()[0], errFmtOIDCNoPrivateKey)
- assert.EqualError(t, validator.Errors()[1], errFmtOIDCNoClientsConfigured)
+ assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: option 'issuer_private_key' is required")
+ assert.EqualError(t, validator.Errors()[1], "identity_providers: oidc: option 'clients' must have one or more clients configured")
}
func TestShouldNotRaiseErrorWhenCORSEndpointsValid(t *testing.T) {
@@ -80,7 +79,7 @@ func TestShouldRaiseErrorWhenCORSEndpointsNotValid(t *testing.T) {
require.Len(t, validator.Errors(), 1)
- assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: cors: option 'endpoints' contains an invalid value 'invalid_endpoint': must be one of 'authorization', 'pushed-authorization-request', 'token', 'introspection', 'revocation', 'userinfo'")
+ assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: cors: option 'endpoints' contains an invalid value 'invalid_endpoint': must be one of 'authorization', 'pushed-authorization-request', 'token', 'introspection', 'revocation', or 'userinfo'")
}
func TestShouldRaiseErrorWhenOIDCPKCEEnforceValueInvalid(t *testing.T) {
@@ -97,8 +96,8 @@ func TestShouldRaiseErrorWhenOIDCPKCEEnforceValueInvalid(t *testing.T) {
require.Len(t, validator.Errors(), 2)
- assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: option 'enforce_pkce' must be 'never', 'public_clients_only' or 'always', but it is configured as 'invalid'")
- assert.EqualError(t, validator.Errors()[1], errFmtOIDCNoClientsConfigured)
+ assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: option 'enforce_pkce' must be 'never', 'public_clients_only' or 'always', but it's configured as 'invalid'")
+ assert.EqualError(t, validator.Errors()[1], "identity_providers: oidc: option 'clients' must have one or more clients configured")
}
func TestShouldRaiseErrorWhenOIDCCORSOriginsHasInvalidValues(t *testing.T) {
@@ -150,7 +149,7 @@ func TestShouldRaiseErrorWhenOIDCServerNoClients(t *testing.T) {
require.Len(t, validator.Errors(), 1)
- assert.EqualError(t, validator.Errors()[0], errFmtOIDCNoClientsConfigured)
+ assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: option 'clients' must have one or more clients configured")
}
func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
@@ -180,7 +179,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
},
Errors: []string{
"identity_providers: oidc: client '': option 'secret' is required",
- "identity_providers: oidc: one or more clients have been configured with an empty id",
+ "identity_providers: oidc: clients: option 'id' is required but was absent on the clients in positions #1",
},
},
{
@@ -195,7 +194,9 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
},
},
},
- Errors: []string{"identity_providers: oidc: client 'client-1': option 'policy' must be 'one_factor' or 'two_factor' but it is configured as 'a-policy'"},
+ Errors: []string{
+ "identity_providers: oidc: client 'client-1': option 'policy' must be one of 'one_factor' or 'two_factor' but it's configured as 'a-policy'",
+ },
},
{
Name: "ClientIDDuplicated",
@@ -213,7 +214,9 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
RedirectURIs: []string{},
},
},
- Errors: []string{errFmtOIDCClientsDuplicateID},
+ Errors: []string{
+ "identity_providers: oidc: clients: option 'id' must be unique for every client but one or more clients share the following 'id' values 'client-x'",
+ },
},
{
Name: "RedirectURIInvalid",
@@ -228,7 +231,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
},
},
Errors: []string{
- fmt.Sprintf(errFmtOIDCClientRedirectURICantBeParsed, "client-check-uri-parse", "http://abc@%two", errors.New("parse \"http://abc@%two\": invalid URL escape \"%tw\"")),
+ "identity_providers: oidc: client 'client-check-uri-parse': option 'redirect_uris' has an invalid value: redirect uri 'http://abc@%two' could not be parsed: parse \"http://abc@%two\": invalid URL escape \"%tw\"",
},
},
{
@@ -244,7 +247,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
},
},
Errors: []string{
- fmt.Sprintf(errFmtOIDCClientRedirectURIAbsolute, "client-check-uri-abs", "google.com"),
+ "identity_providers: oidc: client 'client-check-uri-abs': option 'redirect_uris' has an invalid value: redirect uri 'google.com' must have a scheme but it's absent",
},
},
{
@@ -289,12 +292,12 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
},
},
Errors: []string{
- fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", exampleDotCom, "scheme", "https"),
- fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", exampleDotCom, "path", "/path"),
- fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", exampleDotCom, "query", "query=abc"),
- fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", exampleDotCom, "fragment", "fragment"),
- fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", exampleDotCom, "username", "user"),
- fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifierWithoutValue, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", exampleDotCom, "password"),
+ "identity_providers: oidc: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a scheme with the value 'https'",
+ "identity_providers: oidc: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a path with the value '/path'",
+ "identity_providers: oidc: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a query with the value 'query=abc'",
+ "identity_providers: oidc: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a fragment with the value 'fragment'",
+ "identity_providers: oidc: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a username with the value 'user'",
+ "identity_providers: oidc: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a password",
},
},
{
@@ -311,7 +314,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
},
},
Errors: []string{
- fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifierHost, "client-invalid-sector", "example.com/path?query=abc#fragment"),
+ "identity_providers: oidc: client 'client-invalid-sector': option 'sector_identifier' with value 'example.com/path?query=abc#fragment': must be a URL with only the host component but appears to be invalid",
},
},
{
@@ -328,7 +331,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
},
},
Errors: []string{
- fmt.Sprintf(errFmtOIDCClientInvalidConsentMode, "client-bad-consent-mode", strings.Join(append(validOIDCClientConsentModes, "auto"), "', '"), "cap"),
+ "identity_providers: oidc: client 'client-bad-consent-mode': consent: option 'mode' must be one of 'auto', 'implicit', 'explicit', 'pre-configured', or 'auto' but it's configured as 'cap'",
},
},
{
@@ -345,7 +348,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
},
},
Errors: []string{
- fmt.Sprintf(errFmtOIDCClientInvalidPKCEChallengeMethod, "client-bad-pkce-mode", "abc"),
+ "identity_providers: oidc: client 'client-bad-pkce-mode': option 'pkce_challenge_method' must be one of 'plain' or 'S256' but it's configured as 'abc'",
},
},
{
@@ -362,7 +365,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
},
},
Errors: []string{
- fmt.Sprintf(errFmtOIDCClientInvalidPKCEChallengeMethod, "client-bad-pkce-mode-s256", "s256"),
+ "identity_providers: oidc: client 'client-bad-pkce-mode-s256': option 'pkce_challenge_method' must be one of 'plain' or 'S256' but it's configured as 's256'",
},
},
}
@@ -415,7 +418,7 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadScopes(t *testing.T) {
ValidateIdentityProviders(config, validator)
require.Len(t, validator.Errors(), 1)
- assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'scopes' must only have the values 'openid', 'email', 'profile', 'groups', 'offline_access' but one option is configured as 'bad_scope'")
+ assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: 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) {
@@ -441,7 +444,7 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadGrantTypes(t *testing.T)
ValidateIdentityProviders(config, validator)
require.Len(t, validator.Errors(), 1)
- assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'grant_types' must only have the values 'implicit', 'refresh_token', 'authorization_code', 'password', 'client_credentials' but one option is configured as 'bad_grant_type'")
+ assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'grant_types' must only have the values 'implicit', 'refresh_token', or 'authorization_code' but the values 'bad_grant_type' are present")
}
func TestShouldNotErrorOnCertificateValid(t *testing.T) {
@@ -577,7 +580,7 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadResponseModes(t *testing
ValidateIdentityProviders(config, validator)
require.Len(t, validator.Errors(), 1)
- assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'response_modes' must only have the values 'form_post', 'query', 'fragment' but one option is configured as 'bad_responsemode'")
+ assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'response_modes' must only have the values 'form_post', 'query', or 'fragment' but the values 'bad_responsemode' are present")
}
func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadUserinfoAlg(t *testing.T) {
@@ -603,7 +606,7 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadUserinfoAlg(t *testing.T
ValidateIdentityProviders(config, validator)
require.Len(t, validator.Errors(), 1)
- assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'userinfo_signing_algorithm' must be one of 'none, RS256' but it is configured as 'rs256'")
+ assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'userinfo_signing_algorithm' must be one of 'none' or 'RS256' but it's configured as 'rs256'")
}
func TestValidateIdentityProvidersShouldRaiseWarningOnSecurityIssue(t *testing.T) {
@@ -668,8 +671,8 @@ func TestValidateIdentityProvidersShouldRaiseErrorsOnInvalidClientTypes(t *testi
require.Len(t, validator.Errors(), 2)
assert.Len(t, validator.Warnings(), 0)
- assert.EqualError(t, validator.Errors()[0], fmt.Sprintf(errFmtOIDCClientPublicInvalidSecret, "client-with-invalid-secret"))
- assert.EqualError(t, validator.Errors()[1], fmt.Sprintf(errFmtOIDCClientRedirectURIPublic, "client-with-bad-redirect-uri", oauth2InstalledApp))
+ assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'client-with-invalid-secret': option 'secret' is required to be empty when option 'public' is true")
+ assert.EqualError(t, validator.Errors()[1], "identity_providers: oidc: client 'client-with-bad-redirect-uri': option 'redirect_uris' has the redirect uri 'urn:ietf:wg:oauth:2.0:oob' when option 'public' is false but this is invalid as this uri is not valid for the openid connect confidential client type")
}
func TestValidateIdentityProvidersShouldNotRaiseErrorsOnValidClientOptions(t *testing.T) {
@@ -758,175 +761,28 @@ func TestValidateIdentityProvidersShouldRaiseWarningOnPlainTextClients(t *testin
assert.EqualError(t, validator.Warnings()[0], "identity_providers: oidc: client 'client-with-invalid-secret_standard': option 'secret' is plaintext but it should be a hashed value as plaintext values are deprecated and will be removed when oidc becomes stable")
}
-func TestValidateIdentityProvidersShouldSetDefaultValues(t *testing.T) {
- timeDay := time.Hour * 24
-
- validator := schema.NewStructValidator()
- config := &schema.IdentityProvidersConfiguration{
- OIDC: &schema.OpenIDConnectConfiguration{
- HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
- IssuerPrivateKey: MustParseRSAPrivateKey(testKey1),
- Clients: []schema.OpenIDConnectClientConfiguration{
- {
- ID: "a-client",
- Secret: MustDecodeSecret(goodOpenIDConnectClientSecret),
- RedirectURIs: []string{
- "https://google.com",
- },
- ConsentPreConfiguredDuration: &timeDay,
- },
- {
- ID: "b-client",
- Description: "Normal Description",
- Secret: MustDecodeSecret(goodOpenIDConnectClientSecret),
- Policy: policyOneFactor,
- UserinfoSigningAlgorithm: "RS256",
- RedirectURIs: []string{
- "https://google.com",
- },
- Scopes: []string{
- "groups",
- },
- GrantTypes: []string{
- "refresh_token",
- },
- ResponseTypes: []string{
- "token",
- "code",
- },
- ResponseModes: []string{
- "form_post",
- "fragment",
- },
- },
- {
- ID: "c-client",
- Secret: MustDecodeSecret(goodOpenIDConnectClientSecret),
- RedirectURIs: []string{
- "https://google.com",
- },
- ConsentMode: "implicit",
- },
- {
- ID: "d-client",
- Secret: MustDecodeSecret(goodOpenIDConnectClientSecret),
- RedirectURIs: []string{
- "https://google.com",
- },
- ConsentMode: "explicit",
- },
- {
- ID: "e-client",
- Secret: MustDecodeSecret(goodOpenIDConnectClientSecret),
- RedirectURIs: []string{
- "https://google.com",
- },
- ConsentMode: "pre-configured",
- },
- },
- },
- }
-
- ValidateIdentityProviders(config, validator)
-
- assert.Len(t, validator.Warnings(), 0)
- assert.Len(t, validator.Errors(), 0)
-
- // Assert Clients[0] Policy is set to the default, and the default doesn't override Clients[1]'s Policy.
- assert.Equal(t, policyTwoFactor, config.OIDC.Clients[0].Policy)
- assert.Equal(t, policyOneFactor, config.OIDC.Clients[1].Policy)
-
- assert.Equal(t, "none", config.OIDC.Clients[0].UserinfoSigningAlgorithm)
- assert.Equal(t, "RS256", config.OIDC.Clients[1].UserinfoSigningAlgorithm)
-
- // Assert Clients[0] Description is set to the Clients[0] ID, and Clients[1]'s Description is not overridden.
- assert.Equal(t, config.OIDC.Clients[0].ID, config.OIDC.Clients[0].Description)
- assert.Equal(t, "Normal Description", config.OIDC.Clients[1].Description)
-
- // Assert Clients[0] ends up configured with the default Scopes.
- require.Len(t, config.OIDC.Clients[0].Scopes, 4)
- assert.Equal(t, "openid", config.OIDC.Clients[0].Scopes[0])
- assert.Equal(t, "groups", config.OIDC.Clients[0].Scopes[1])
- assert.Equal(t, "profile", config.OIDC.Clients[0].Scopes[2])
- assert.Equal(t, "email", config.OIDC.Clients[0].Scopes[3])
-
- // Assert Clients[1] ends up configured with the configured Scopes and the openid Scope.
- require.Len(t, config.OIDC.Clients[1].Scopes, 2)
- assert.Equal(t, "groups", config.OIDC.Clients[1].Scopes[0])
- assert.Equal(t, "openid", config.OIDC.Clients[1].Scopes[1])
-
- // Assert Clients[0] ends up configured with the correct consent mode.
- require.NotNil(t, config.OIDC.Clients[0].ConsentPreConfiguredDuration)
- assert.Equal(t, time.Hour*24, *config.OIDC.Clients[0].ConsentPreConfiguredDuration)
- assert.Equal(t, "pre-configured", config.OIDC.Clients[0].ConsentMode)
-
- // Assert Clients[1] ends up configured with the correct consent mode.
- assert.Nil(t, config.OIDC.Clients[1].ConsentPreConfiguredDuration)
- assert.Equal(t, "explicit", config.OIDC.Clients[1].ConsentMode)
-
- // Assert Clients[0] ends up configured with the default GrantTypes.
- require.Len(t, config.OIDC.Clients[0].GrantTypes, 2)
- assert.Equal(t, "refresh_token", config.OIDC.Clients[0].GrantTypes[0])
- assert.Equal(t, "authorization_code", config.OIDC.Clients[0].GrantTypes[1])
-
- // Assert Clients[1] ends up configured with only the configured GrantTypes.
- require.Len(t, config.OIDC.Clients[1].GrantTypes, 1)
- assert.Equal(t, "refresh_token", config.OIDC.Clients[1].GrantTypes[0])
-
- // Assert Clients[0] ends up configured with the default ResponseTypes.
- require.Len(t, config.OIDC.Clients[0].ResponseTypes, 1)
- assert.Equal(t, "code", config.OIDC.Clients[0].ResponseTypes[0])
-
- // Assert Clients[1] ends up configured only with the configured ResponseTypes.
- require.Len(t, config.OIDC.Clients[1].ResponseTypes, 2)
- assert.Equal(t, "token", config.OIDC.Clients[1].ResponseTypes[0])
- assert.Equal(t, "code", config.OIDC.Clients[1].ResponseTypes[1])
-
- // Assert Clients[0] ends up configured with the default ResponseModes.
- require.Len(t, config.OIDC.Clients[0].ResponseModes, 3)
- assert.Equal(t, "form_post", config.OIDC.Clients[0].ResponseModes[0])
- assert.Equal(t, "query", config.OIDC.Clients[0].ResponseModes[1])
- assert.Equal(t, "fragment", config.OIDC.Clients[0].ResponseModes[2])
-
- // Assert Clients[1] ends up configured only with the configured ResponseModes.
- require.Len(t, config.OIDC.Clients[1].ResponseModes, 2)
- assert.Equal(t, "form_post", config.OIDC.Clients[1].ResponseModes[0])
- assert.Equal(t, "fragment", config.OIDC.Clients[1].ResponseModes[1])
-
- assert.Equal(t, false, config.OIDC.EnableClientDebugMessages)
- assert.Equal(t, time.Hour, config.OIDC.AccessTokenLifespan)
- assert.Equal(t, time.Minute, config.OIDC.AuthorizeCodeLifespan)
- assert.Equal(t, time.Hour, config.OIDC.IDTokenLifespan)
- assert.Equal(t, time.Minute*90, config.OIDC.RefreshTokenLifespan)
-
- assert.Equal(t, "implicit", config.OIDC.Clients[2].ConsentMode)
- assert.Nil(t, config.OIDC.Clients[2].ConsentPreConfiguredDuration)
-
- assert.Equal(t, "explicit", config.OIDC.Clients[3].ConsentMode)
- assert.Nil(t, config.OIDC.Clients[3].ConsentPreConfiguredDuration)
-
- assert.Equal(t, "pre-configured", config.OIDC.Clients[4].ConsentMode)
- assert.Equal(t, schema.DefaultOpenIDConnectClientConfiguration.ConsentPreConfiguredDuration, config.OIDC.Clients[4].ConsentPreConfiguredDuration)
-}
-
// All valid schemes are supported as defined in https://datatracker.ietf.org/doc/html/rfc8252#section-7.1
func TestValidateOIDCClientRedirectURIsSupportingPrivateUseURISchemes(t *testing.T) {
- conf := schema.OpenIDConnectClientConfiguration{
- ID: "owncloud",
- RedirectURIs: []string{
- "https://www.mywebsite.com",
- "http://www.mywebsite.com",
- "oc://ios.owncloud.com",
- // example given in the RFC https://datatracker.ietf.org/doc/html/rfc8252#section-7.1
- "com.example.app:/oauth2redirect/example-provider",
- oauth2InstalledApp,
+ have := &schema.OpenIDConnectConfiguration{
+ Clients: []schema.OpenIDConnectClientConfiguration{
+ {
+ ID: "owncloud",
+ RedirectURIs: []string{
+ "https://www.mywebsite.com",
+ "http://www.mywebsite.com",
+ "oc://ios.owncloud.com",
+ // example given in the RFC https://datatracker.ietf.org/doc/html/rfc8252#section-7.1
+ "com.example.app:/oauth2redirect/example-provider",
+ oauth2InstalledApp,
+ },
+ },
},
}
t.Run("public", func(t *testing.T) {
validator := schema.NewStructValidator()
- conf.Public = true
- validateOIDCClientRedirectURIs(conf, validator)
+ have.Clients[0].Public = true
+ validateOIDCClientRedirectURIs(0, have, validator, nil)
assert.Len(t, validator.Warnings(), 0)
assert.Len(t, validator.Errors(), 0)
@@ -934,8 +790,8 @@ func TestValidateOIDCClientRedirectURIsSupportingPrivateUseURISchemes(t *testing
t.Run("not public", func(t *testing.T) {
validator := schema.NewStructValidator()
- conf.Public = false
- validateOIDCClientRedirectURIs(conf, validator)
+ have.Clients[0].Public = false
+ validateOIDCClientRedirectURIs(0, have, validator, nil)
assert.Len(t, validator.Warnings(), 0)
assert.Len(t, validator.Errors(), 1)
@@ -945,6 +801,1143 @@ func TestValidateOIDCClientRedirectURIsSupportingPrivateUseURISchemes(t *testing
})
}
+func TestValidateOIDCClients(t *testing.T) {
+ type tcv struct {
+ Scopes []string
+ ResponseTypes []string
+ ResponseModes []string
+ GrantTypes []string
+ }
+
+ testCasses := []struct {
+ name string
+ setup func(have *schema.OpenIDConnectConfiguration)
+ validate func(t *testing.T, have *schema.OpenIDConnectConfiguration)
+ have tcv
+ expected tcv
+ serrs []string // Soft errors which will be warnings before GA.
+ errs []string
+ }{
+ {
+ "ShouldSetDefaultResponseTypeAndResponseModes",
+ nil,
+ nil,
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ nil,
+ nil,
+ },
+ {
+ "ShouldIncludeMinimalScope",
+ nil,
+ nil,
+ tcv{
+ []string{oidc.ScopeEmail},
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ nil,
+ nil,
+ },
+ {
+ "ShouldSetDefaultResponseModesFlowAuthorizeCode",
+ nil,
+ nil,
+ tcv{
+ nil,
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ nil,
+ nil,
+ },
+ {
+ "ShouldSetDefaultResponseModesFlowImplicit",
+ nil,
+ nil,
+ tcv{
+ nil,
+ []string{oidc.ResponseTypeImplicitFlowBoth},
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeImplicitFlowBoth},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeFragment},
+ []string{oidc.GrantTypeImplicit},
+ },
+ nil,
+ nil,
+ },
+ {
+ "ShouldSetDefaultResponseModesFlowHybrid",
+ nil,
+ nil,
+ tcv{
+ nil,
+ []string{oidc.ResponseTypeHybridFlowBoth},
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeHybridFlowBoth},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeFragment},
+ []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeImplicit},
+ },
+ nil,
+ nil,
+ },
+ {
+ "ShouldSetDefaultResponseModesFlowMixedAuthorizeCodeHybrid",
+ nil,
+ nil,
+ tcv{
+ nil,
+ []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeHybridFlowBoth},
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeHybridFlowBoth},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment},
+ []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeImplicit},
+ },
+ nil,
+ nil,
+ },
+ {
+ "ShouldSetDefaultResponseModesFlowMixedAuthorizeCodeImplicit",
+ nil,
+ nil,
+ tcv{
+ nil,
+ []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowBoth},
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowBoth},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment},
+ []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeImplicit},
+ },
+ nil,
+ nil,
+ },
+ {
+ "ShouldSetDefaultResponseModesFlowMixedAll",
+ nil,
+ nil,
+ tcv{
+ nil,
+ []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeHybridFlowBoth},
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeHybridFlowBoth},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment},
+ []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeImplicit},
+ },
+ nil,
+ nil,
+ },
+ {
+ "ShouldNotOverrideValues",
+ nil,
+ nil,
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeHybridFlowBoth},
+ []string{oidc.ResponseModeFormPost},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeHybridFlowBoth},
+ []string{oidc.ResponseModeFormPost},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ nil,
+ nil,
+ },
+ {
+ "ShouldRaiseErrorOnDuplicateScopes",
+ nil,
+ nil,
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeOpenID},
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeOpenID},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ []string{
+ "identity_providers: oidc: client 'test': option 'scopes' must have unique values but the values 'openid' are duplicated",
+ },
+ nil,
+ },
+ {
+ "ShouldRaiseErrorOnInvalidScopes",
+ nil,
+ nil,
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeProfile, "group"},
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeProfile, "group"},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ nil,
+ []string{
+ "identity_providers: oidc: client 'test': option 'scopes' must only have the values 'openid', 'email', 'profile', 'groups', or 'offline_access' but the values 'group' are present",
+ },
+ },
+ {
+ "ShouldRaiseErrorOnMissingAuthorizationCodeFlowResponseTypeWithRefreshTokenValues",
+ nil,
+ nil,
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeOfflineAccess},
+ []string{oidc.ResponseTypeImplicitFlowBoth},
+ nil,
+ []string{oidc.GrantTypeImplicit, oidc.GrantTypeRefreshToken},
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeOfflineAccess},
+ []string{oidc.ResponseTypeImplicitFlowBoth},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeFragment},
+ []string{oidc.GrantTypeImplicit, oidc.GrantTypeRefreshToken},
+ },
+ []string{
+ "identity_providers: oidc: 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: client 'test': option 'grant_types' should only have the values 'refresh_token' 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",
+ },
+ nil,
+ },
+ {
+ "ShouldRaiseErrorOnDuplicateResponseTypes",
+ nil,
+ nil,
+ tcv{
+ nil,
+ []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeAuthorizationCodeFlow},
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment},
+ []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeImplicit},
+ },
+ []string{
+ "identity_providers: oidc: client 'test': option 'response_types' must have unique values but the values 'code' are duplicated",
+ },
+ nil,
+ },
+ {
+ "ShouldRaiseErrorOnInvalidResponseTypesOrder",
+ nil,
+ nil,
+ tcv{
+ nil,
+ []string{oidc.ResponseTypeImplicitFlowBoth, "token id_token"},
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeImplicitFlowBoth, "token id_token"},
+ []string{"form_post", "fragment"},
+ []string{"implicit"},
+ },
+ []string{
+ "identity_providers: oidc: client 'test': option 'response_types' must only have the values 'code', 'id_token', 'token', 'id_token token', 'code id_token', 'code token', or 'code id_token token' but the values 'token id_token' are present",
+ },
+ nil,
+ },
+ {
+ "ShouldRaiseErrorOnInvalidResponseTypes",
+ nil,
+ nil,
+ tcv{
+ nil,
+ []string{"not_valid"},
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{"not_valid"},
+ []string{oidc.ResponseModeFormPost},
+ nil,
+ },
+ []string{
+ "identity_providers: oidc: client 'test': option 'response_types' must only have the values 'code', 'id_token', 'token', 'id_token token', 'code id_token', 'code token', or 'code id_token token' but the values 'not_valid' are present",
+ },
+ nil,
+ },
+ {
+ "ShouldRaiseErrorOnInvalidResponseModes",
+ nil,
+ nil,
+ tcv{
+ nil,
+ nil,
+ []string{"not_valid"},
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{"not_valid"},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ nil,
+ []string{
+ "identity_providers: oidc: client 'test': option 'response_modes' must only have the values 'form_post', 'query', or 'fragment' but the values 'not_valid' are present",
+ },
+ },
+ {
+ "ShouldRaiseErrorOnDuplicateResponseModes",
+ nil,
+ nil,
+ tcv{
+ nil,
+ nil,
+ []string{oidc.ResponseModeQuery, oidc.ResponseModeQuery},
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeQuery, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ []string{
+ "identity_providers: oidc: client 'test': option 'response_modes' must have unique values but the values 'query' are duplicated",
+ },
+ nil,
+ },
+ {
+ "ShouldRaiseErrorOnInvalidGrantTypes",
+ nil,
+ nil,
+ tcv{
+ nil,
+ nil,
+ nil,
+ []string{"invalid"},
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{"invalid"},
+ },
+ nil,
+ []string{
+ "identity_providers: oidc: client 'test': option 'grant_types' must only have the values 'implicit', 'refresh_token', or 'authorization_code' but the values 'invalid' are present",
+ },
+ },
+ {
+ "ShouldRaiseErrorOnDuplicateGrantTypes",
+ nil,
+ nil,
+ tcv{
+ nil,
+ nil,
+ nil,
+ []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeAuthorizationCode},
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeAuthorizationCode},
+ },
+ []string{
+ "identity_providers: oidc: client 'test': option 'grant_types' must have unique values but the values 'authorization_code' are duplicated",
+ },
+ nil,
+ },
+ {
+ "ShouldRaiseErrorOnGrantTypeRefreshTokenWithoutScopeOfflineAccess",
+ nil,
+ nil,
+ tcv{
+ nil,
+ nil,
+ nil,
+ []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeRefreshToken},
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeRefreshToken},
+ },
+ []string{
+ "identity_providers: oidc: client 'test': option 'grant_types' should only have the 'refresh_token' value if the client is also configured with the 'offline_access' scope",
+ },
+ nil,
+ },
+ {
+ "ShouldRaiseErrorOnGrantTypeAuthorizationCodeWithoutAuthorizationCodeOrHybridFlow",
+ nil,
+ nil,
+ tcv{
+ nil,
+ []string{oidc.ResponseTypeImplicitFlowBoth},
+ nil,
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeImplicitFlowBoth},
+ []string{"form_post", "fragment"},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ []string{
+ "identity_providers: oidc: client 'test': option 'grant_types' should only have grant type values which are valid with the configured 'response_types' for the client but 'authorization_code' expects a response type for either the authorization code or hybrid flow such as 'code', 'code id_token', 'code token', or 'code id_token token' but the response types are 'id_token token'",
+ },
+ nil,
+ },
+ {
+ "ShouldRaiseErrorOnGrantTypeImplicitWithoutImplicitOrHybridFlow",
+ nil,
+ nil,
+ tcv{
+ nil,
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ nil,
+ []string{oidc.GrantTypeImplicit},
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeImplicit},
+ },
+ []string{
+ "identity_providers: oidc: 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'",
+ },
+ nil,
+ },
+ {
+ "ShouldValidateCorrectRedirectURIsConfidentialClientType",
+ func(have *schema.OpenIDConnectConfiguration) {
+ have.Clients[0].RedirectURIs = []string{
+ "https://google.com",
+ }
+ },
+ func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
+ assert.Equal(t, []string{"https://google.com"}, have.Clients[0].RedirectURIs)
+ },
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ nil,
+ nil,
+ },
+ {
+ "ShouldValidateCorrectRedirectURIsPublicClientType",
+ func(have *schema.OpenIDConnectConfiguration) {
+ have.Clients[0].Public = true
+ have.Clients[0].Secret = nil
+ have.Clients[0].RedirectURIs = []string{
+ oauth2InstalledApp,
+ }
+ },
+ func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
+ assert.Equal(t, []string{oauth2InstalledApp}, have.Clients[0].RedirectURIs)
+ },
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ nil,
+ nil,
+ },
+ {
+ "ShouldRaiseErrorOnInvalidRedirectURIsPublicOnly",
+ func(have *schema.OpenIDConnectConfiguration) {
+ have.Clients[0].RedirectURIs = []string{
+ "urn:ietf:wg:oauth:2.0:oob",
+ }
+ },
+ func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
+ assert.Equal(t, []string{oauth2InstalledApp}, have.Clients[0].RedirectURIs)
+ },
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ nil,
+ []string{
+ "identity_providers: oidc: client 'test': option 'redirect_uris' has the redirect uri 'urn:ietf:wg:oauth:2.0:oob' when option 'public' is false but this is invalid as this uri is not valid for the openid connect confidential client type",
+ },
+ },
+ {
+ "ShouldRaiseErrorOnInvalidRedirectURIsMalformedURI",
+ func(have *schema.OpenIDConnectConfiguration) {
+ have.Clients[0].RedirectURIs = []string{
+ "http://abc@%two",
+ }
+ },
+ func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
+ assert.Equal(t, []string{"http://abc@%two"}, have.Clients[0].RedirectURIs)
+ },
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ nil,
+ []string{
+ "identity_providers: oidc: client 'test': option 'redirect_uris' has an invalid value: redirect uri 'http://abc@%two' could not be parsed: parse \"http://abc@%two\": invalid URL escape \"%tw\"",
+ },
+ },
+ {
+ "ShouldRaiseErrorOnInvalidRedirectURIsNotAbsolute",
+ func(have *schema.OpenIDConnectConfiguration) {
+ have.Clients[0].RedirectURIs = []string{
+ "google.com",
+ }
+ },
+ func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
+ assert.Equal(t, []string{"google.com"}, have.Clients[0].RedirectURIs)
+ },
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ nil,
+ []string{
+ "identity_providers: oidc: client 'test': option 'redirect_uris' has an invalid value: redirect uri 'google.com' must have a scheme but it's absent",
+ },
+ },
+ {
+ "ShouldRaiseErrorOnDuplicateRedirectURI",
+ func(have *schema.OpenIDConnectConfiguration) {
+ have.Clients[0].RedirectURIs = []string{
+ "https://google.com",
+ "https://google.com",
+ }
+ },
+ func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
+ assert.Equal(t, []string{"https://google.com", "https://google.com"}, have.Clients[0].RedirectURIs)
+ },
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ []string{
+ "identity_providers: oidc: client 'test': option 'redirect_uris' must have unique values but the values 'https://google.com' are duplicated",
+ },
+ nil,
+ },
+ {
+ "ShouldNotSetDefaultTokenEndpointClientAuthMethodConfidentialClientType",
+ nil,
+ func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
+ assert.Equal(t, "", have.Clients[0].TokenEndpointAuthMethod)
+ },
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ nil,
+ nil,
+ },
+ {
+ "ShouldSetDefaultTokenEndpointClientAuthMethodPublicClientType",
+ func(have *schema.OpenIDConnectConfiguration) {
+ have.Clients[0].Public = true
+ have.Clients[0].Secret = nil
+ },
+ func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
+ assert.Equal(t, oidc.ClientAuthMethodNone, have.Clients[0].TokenEndpointAuthMethod)
+ },
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ nil,
+ nil,
+ },
+ {
+ "ShouldSetDefaultTokenEndpointClientAuthMethodConfidentialClientTypeImplicitFlow",
+ nil,
+ func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
+ assert.Equal(t, oidc.ClientAuthMethodNone, have.Clients[0].TokenEndpointAuthMethod)
+ },
+ tcv{
+ nil,
+ []string{oidc.ResponseTypeImplicitFlowIDToken, oidc.ResponseTypeImplicitFlowToken, oidc.ResponseTypeImplicitFlowBoth},
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeImplicitFlowIDToken, oidc.ResponseTypeImplicitFlowToken, oidc.ResponseTypeImplicitFlowBoth},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeFragment},
+ []string{oidc.GrantTypeImplicit},
+ },
+ nil,
+ nil,
+ },
+ {
+ "ShouldNotOverrideValidClientAuthMethod",
+ func(have *schema.OpenIDConnectConfiguration) {
+ have.Clients[0].TokenEndpointAuthMethod = oidc.ClientAuthMethodClientSecretPost
+ },
+ func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
+ assert.Equal(t, oidc.ClientAuthMethodClientSecretPost, have.Clients[0].TokenEndpointAuthMethod)
+ },
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ nil,
+ nil,
+ },
+ {
+ "ShouldRaiseErrorOnInvalidClientAuthMethod",
+ func(have *schema.OpenIDConnectConfiguration) {
+ have.Clients[0].TokenEndpointAuthMethod = "client_credentials"
+ },
+ func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
+ assert.Equal(t, "client_credentials", have.Clients[0].TokenEndpointAuthMethod)
+ },
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ nil,
+ []string{
+ "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be one of 'none', 'client_secret_post', or 'client_secret_basic' but it's configured as 'client_credentials'",
+ },
+ },
+ {
+ "ShouldRaiseErrorOnInvalidClientAuthMethodForPublicClientType",
+ func(have *schema.OpenIDConnectConfiguration) {
+ have.Clients[0].TokenEndpointAuthMethod = oidc.ClientAuthMethodClientSecretBasic
+ have.Clients[0].Public = true
+ have.Clients[0].Secret = nil
+ },
+ func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
+ assert.Equal(t, oidc.ClientAuthMethodClientSecretBasic, have.Clients[0].TokenEndpointAuthMethod)
+ },
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ nil,
+ []string{
+ "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be 'none' when configured as the public client type but it's configured as 'client_secret_basic'",
+ },
+ },
+ {
+ "ShouldRaiseErrorOnInvalidClientAuthMethodForConfidentialClientTypeAuthorizationCodeFlow",
+ func(have *schema.OpenIDConnectConfiguration) {
+ have.Clients[0].TokenEndpointAuthMethod = oidc.ClientAuthMethodNone
+ },
+ func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
+ assert.Equal(t, oidc.ClientAuthMethodNone, have.Clients[0].TokenEndpointAuthMethod)
+ },
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ nil,
+ []string{
+ "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be one of 'client_secret_post' or 'client_secret_basic' when configured as the confidential client type unless it only includes implicit flow response types such as 'id_token', 'token', and 'id_token token' but it's configured as 'none'",
+ },
+ },
+ {
+ "ShouldRaiseErrorOnInvalidClientAuthMethodForConfidentialClientTypeHybridFlow",
+ func(have *schema.OpenIDConnectConfiguration) {
+ have.Clients[0].TokenEndpointAuthMethod = oidc.ClientAuthMethodNone
+ },
+ func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
+ assert.Equal(t, oidc.ClientAuthMethodNone, have.Clients[0].TokenEndpointAuthMethod)
+ },
+ tcv{
+ nil,
+ []string{oidc.ResponseTypeHybridFlowToken},
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeHybridFlowToken},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeFragment},
+ []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeImplicit},
+ },
+ nil,
+ []string{
+ "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be one of 'client_secret_post' or 'client_secret_basic' when configured as the confidential client type unless it only includes implicit flow response types such as 'id_token', 'token', and 'id_token token' but it's configured as 'none'",
+ },
+ },
+ {
+ "ShouldSetDefaultUserInfoAlg",
+ nil,
+ func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
+ assert.Equal(t, oidc.SigningAlgorithmNone, have.Clients[0].UserinfoSigningAlgorithm)
+ },
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ nil,
+ nil,
+ },
+ {
+ "ShouldNotOverrideUserInfoAlg",
+ func(have *schema.OpenIDConnectConfiguration) {
+ have.Clients[0].UserinfoSigningAlgorithm = oidc.SigningAlgorithmRSAWithSHA256
+ },
+ func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
+ assert.Equal(t, oidc.SigningAlgorithmRSAWithSHA256, have.Clients[0].UserinfoSigningAlgorithm)
+ },
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ nil,
+ nil,
+ },
+ {
+ "ShouldRaiseErrorOnInvalidUserInfoAlg",
+ func(have *schema.OpenIDConnectConfiguration) {
+ have.Clients[0].UserinfoSigningAlgorithm = "rs256"
+ },
+ func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
+ assert.Equal(t, "rs256", have.Clients[0].UserinfoSigningAlgorithm)
+ },
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ nil,
+ []string{
+ "identity_providers: oidc: client 'test': option 'userinfo_signing_algorithm' must be one of 'none' or 'RS256' but it's configured as 'rs256'",
+ },
+ },
+ {
+ "ShouldSetDefaultConsentMode",
+ nil,
+ func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
+ assert.Equal(t, "explicit", have.Clients[0].ConsentMode)
+ },
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ nil,
+ nil,
+ },
+ {
+ "ShouldSetDefaultConsentModeAuto",
+ func(have *schema.OpenIDConnectConfiguration) {
+ have.Clients[0].ConsentMode = auto
+ },
+ func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
+ assert.Equal(t, "explicit", have.Clients[0].ConsentMode)
+ },
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ nil,
+ nil,
+ },
+ {
+ "ShouldSetDefaultConsentModePreConfigured",
+ func(have *schema.OpenIDConnectConfiguration) {
+ d := time.Minute
+
+ have.Clients[0].ConsentMode = ""
+ have.Clients[0].ConsentPreConfiguredDuration = &d
+ },
+ func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
+ assert.Equal(t, "pre-configured", have.Clients[0].ConsentMode)
+ },
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ nil,
+ nil,
+ },
+ {
+ "ShouldSetDefaultConsentModeAutoPreConfigured",
+ func(have *schema.OpenIDConnectConfiguration) {
+ d := time.Minute
+
+ have.Clients[0].ConsentMode = auto
+ have.Clients[0].ConsentPreConfiguredDuration = &d
+ },
+ func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
+ assert.Equal(t, "pre-configured", have.Clients[0].ConsentMode)
+ },
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ nil,
+ nil,
+ },
+ {
+ "ShouldNotOverrideConsentMode",
+ func(have *schema.OpenIDConnectConfiguration) {
+ have.Clients[0].ConsentMode = "implicit"
+ },
+ func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
+ assert.Equal(t, "implicit", have.Clients[0].ConsentMode)
+ },
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ nil,
+ nil,
+ },
+ {
+ "ShouldSentConsentPreConfiguredDefaultDuration",
+ func(have *schema.OpenIDConnectConfiguration) {
+ have.Clients[0].ConsentMode = "pre-configured"
+ },
+ func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
+ assert.Equal(t, "pre-configured", have.Clients[0].ConsentMode)
+ assert.Equal(t, schema.DefaultOpenIDConnectClientConfiguration.ConsentPreConfiguredDuration, have.Clients[0].ConsentPreConfiguredDuration)
+ },
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeAuthorizationCode},
+ },
+ nil,
+ nil,
+ },
+ }
+
+ errDeprecatedFunc := func() {}
+
+ for _, tc := range testCasses {
+ t.Run(tc.name, func(t *testing.T) {
+ have := &schema.OpenIDConnectConfiguration{
+ Clients: []schema.OpenIDConnectClientConfiguration{
+ {
+ ID: "test",
+ Secret: MustDecodeSecret("$pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng"),
+ Scopes: tc.have.Scopes,
+ ResponseModes: tc.have.ResponseModes,
+ ResponseTypes: tc.have.ResponseTypes,
+ GrantTypes: tc.have.GrantTypes,
+ },
+ },
+ }
+
+ if tc.setup != nil {
+ tc.setup(have)
+ }
+
+ val := schema.NewStructValidator()
+
+ validateOIDCClient(0, have, val, errDeprecatedFunc)
+
+ t.Run("General", func(t *testing.T) {
+ assert.Equal(t, tc.expected.Scopes, have.Clients[0].Scopes)
+ assert.Equal(t, tc.expected.ResponseTypes, have.Clients[0].ResponseTypes)
+ assert.Equal(t, tc.expected.ResponseModes, have.Clients[0].ResponseModes)
+ assert.Equal(t, tc.expected.GrantTypes, have.Clients[0].GrantTypes)
+
+ if tc.validate != nil {
+ tc.validate(t, have)
+ }
+ })
+
+ t.Run("Warnings", func(t *testing.T) {
+ require.Len(t, val.Warnings(), len(tc.serrs))
+ for i, err := range tc.serrs {
+ assert.EqualError(t, val.Warnings()[i], err)
+ }
+ })
+
+ t.Run("Errors", func(t *testing.T) {
+ require.Len(t, val.Errors(), len(tc.errs))
+ for i, err := range tc.errs {
+ assert.EqualError(t, val.Errors()[i], err)
+ }
+ })
+ })
+ }
+}
+
+func TestValidateOIDCClientTokenEndpointAuthMethod(t *testing.T) {
+ testCasses := []struct {
+ name string
+ have string
+ public bool
+ expected string
+ errs []string
+ }{
+ {"ShouldSetDefaultValueConfidential", "", false, "", nil},
+ {"ShouldSetDefaultValuePublic", "", true, oidc.ClientAuthMethodNone, nil},
+ {"ShouldErrorOnInvalidValue", "abc", false, "abc",
+ []string{
+ "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be one of 'none', 'client_secret_post', or 'client_secret_basic' but it's configured as 'abc'",
+ },
+ },
+ {"ShouldErrorOnInvalidValueForPublicClient", "client_secret_post", true, "client_secret_post",
+ []string{
+ "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be 'none' when configured as the public client type but it's configured as 'client_secret_post'",
+ },
+ },
+ {"ShouldErrorOnInvalidValueForConfidentialClient", "none", false, "none",
+ []string{
+ "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be one of 'client_secret_post' or 'client_secret_basic' when configured as the confidential client type unless it only includes implicit flow response types such as 'id_token', 'token', and 'id_token token' but it's configured as 'none'",
+ },
+ },
+ }
+
+ for _, tc := range testCasses {
+ t.Run(tc.name, func(t *testing.T) {
+ have := &schema.OpenIDConnectConfiguration{
+ Clients: []schema.OpenIDConnectClientConfiguration{
+ {
+ ID: "test",
+ Public: tc.public,
+ TokenEndpointAuthMethod: tc.have,
+ },
+ },
+ }
+
+ val := schema.NewStructValidator()
+
+ validateOIDCClientTokenEndpointAuthMethod(0, have, val)
+
+ assert.Equal(t, tc.expected, have.Clients[0].TokenEndpointAuthMethod)
+ assert.Len(t, val.Warnings(), 0)
+ require.Len(t, val.Errors(), len(tc.errs))
+
+ if tc.errs != nil {
+ for i, err := range tc.errs {
+ assert.EqualError(t, val.Errors()[i], err)
+ }
+ }
+ })
+ }
+}
+
func MustDecodeSecret(value string) *schema.PasswordDigest {
if secret, err := schema.DecodePasswordDigest(value); err != nil {
panic(err)
diff --git a/internal/configuration/validator/keys.go b/internal/configuration/validator/keys.go
index 67b9d964d..863d2030e 100644
--- a/internal/configuration/validator/keys.go
+++ b/internal/configuration/validator/keys.go
@@ -100,7 +100,7 @@ func NewKeyMapPattern(key string) (pattern *regexp.Regexp, err error) {
}
if i < n {
- buf.WriteString("\\.[a-z0-9]([a-z0-9-_]+)?[a-z0-9]")
+ buf.WriteString("\\.[a-z0-9](([a-z0-9-_]+)?[a-z0-9])?")
}
}
diff --git a/internal/configuration/validator/keys_test.go b/internal/configuration/validator/keys_test.go
index 989b4b815..a5123ef8c 100644
--- a/internal/configuration/validator/keys_test.go
+++ b/internal/configuration/validator/keys_test.go
@@ -101,6 +101,20 @@ func TestSpecificErrorKeys(t *testing.T) {
assert.EqualError(t, errs[4], specificErrorKeys["authentication_backend.file.hashing.algorithm"])
}
+func TestPatternKeys(t *testing.T) {
+ configKeys := []string{
+ "server.endpoints.authz.xx.implementation",
+ "server.endpoints.authz.x.implementation",
+ }
+
+ val := schema.NewStructValidator()
+ ValidateKeys(configKeys, "AUTHELIA_", val)
+
+ errs := val.Errors()
+
+ require.Len(t, errs, 0)
+}
+
func TestReplacedErrors(t *testing.T) {
configKeys := []string{
"authentication_backend.ldap.skip_verify",
diff --git a/internal/configuration/validator/log.go b/internal/configuration/validator/log.go
index 5c7a0761b..7b8c7f6ea 100644
--- a/internal/configuration/validator/log.go
+++ b/internal/configuration/validator/log.go
@@ -2,7 +2,6 @@ package validator
import (
"fmt"
- "strings"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/utils"
@@ -19,6 +18,6 @@ func ValidateLog(config *schema.Configuration, validator *schema.StructValidator
}
if !utils.IsStringInSlice(config.Log.Level, validLogLevels) {
- validator.Push(fmt.Errorf(errFmtLoggingLevelInvalid, strings.Join(validLogLevels, "', '"), config.Log.Level))
+ validator.Push(fmt.Errorf(errFmtLoggingLevelInvalid, strJoinOr(validLogLevels), config.Log.Level))
}
}
diff --git a/internal/configuration/validator/log_test.go b/internal/configuration/validator/log_test.go
index 56cff19de..cf3de2736 100644
--- a/internal/configuration/validator/log_test.go
+++ b/internal/configuration/validator/log_test.go
@@ -40,5 +40,5 @@ func TestShouldRaiseErrorOnInvalidLoggingLevel(t *testing.T) {
assert.Len(t, validator.Warnings(), 0)
require.Len(t, validator.Errors(), 1)
- assert.EqualError(t, validator.Errors()[0], "log: option 'level' must be one of 'trace', 'debug', 'info', 'warn', 'error' but it is configured as 'TRACE'")
+ assert.EqualError(t, validator.Errors()[0], "log: option 'level' must be one of 'trace', 'debug', 'info', 'warn', or 'error' but it's configured as 'TRACE'")
}
diff --git a/internal/configuration/validator/notifier_test.go b/internal/configuration/validator/notifier_test.go
index c41776a29..17819993f 100644
--- a/internal/configuration/validator/notifier_test.go
+++ b/internal/configuration/validator/notifier_test.go
@@ -4,8 +4,10 @@ import (
"crypto/tls"
"fmt"
"net/mail"
+ "path/filepath"
"testing"
+ "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"github.com/authelia/authelia/v4/internal/configuration/schema"
@@ -187,6 +189,32 @@ func (suite *NotifierSuite) TestSMTPShouldEnsureSenderIsProvided() {
suite.Assert().EqualError(suite.validator.Errors()[0], fmt.Sprintf(errFmtNotifierSMTPNotConfigured, "sender"))
}
+func (suite *NotifierSuite) TestTemplatesEmptyDir() {
+ dir := suite.T().TempDir()
+
+ suite.config.TemplatePath = dir
+
+ ValidateNotifier(&suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Assert().Len(suite.validator.Errors(), 0)
+}
+
+func (suite *NotifierSuite) TestTemplatesEmptyDirNoExist() {
+ dir := suite.T().TempDir()
+
+ p := filepath.Join(dir, "notexist")
+
+ suite.config.TemplatePath = p
+
+ ValidateNotifier(&suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Assert().Len(suite.validator.Errors(), 1)
+
+ assert.EqualError(suite.T(), suite.validator.Errors()[0], fmt.Sprintf("notifier: option 'template_path' refers to location '%s' which does not exist", p))
+}
+
/*
File Tests.
*/
diff --git a/internal/configuration/validator/ntp_test.go b/internal/configuration/validator/ntp_test.go
index 9780245f9..0bae5b830 100644
--- a/internal/configuration/validator/ntp_test.go
+++ b/internal/configuration/validator/ntp_test.go
@@ -49,5 +49,5 @@ func TestShouldRaiseErrorOnInvalidNTPVersion(t *testing.T) {
require.Len(t, validator.Errors(), 1)
- assert.EqualError(t, validator.Errors()[0], "ntp: option 'version' must be either 3 or 4 but it is configured as '1'")
+ assert.EqualError(t, validator.Errors()[0], "ntp: option 'version' must be either 3 or 4 but it's configured as '1'")
}
diff --git a/internal/configuration/validator/password_policy_test.go b/internal/configuration/validator/password_policy_test.go
index 3d27f08d6..5ce417c01 100644
--- a/internal/configuration/validator/password_policy_test.go
+++ b/internal/configuration/validator/password_policy_test.go
@@ -39,7 +39,7 @@ func TestValidatePasswordPolicy(t *testing.T) {
},
expectedErrs: []string{
"password_policy: only a single password policy mechanism can be specified",
- "password_policy: standard: option 'min_length' must be greater than 0 but is configured as -1",
+ "password_policy: standard: option 'min_length' must be greater than 0 but it's configured as -1",
},
},
{
diff --git a/internal/configuration/validator/server.go b/internal/configuration/validator/server.go
index 66a12d150..c38850634 100644
--- a/internal/configuration/validator/server.go
+++ b/internal/configuration/validator/server.go
@@ -155,13 +155,13 @@ func validateServerEndpointsAuthzEndpoint(config *schema.Configuration, name str
config.Server.Endpoints.Authz[name] = endpoint
default:
if !utils.IsStringInSlice(endpoint.Implementation, validAuthzImplementations) {
- validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzImplementation, name, strings.Join(validAuthzImplementations, "', '"), endpoint.Implementation))
+ validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzImplementation, name, strJoinOr(validAuthzImplementations), endpoint.Implementation))
} else {
validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzLegacyInvalidImplementation, name))
}
}
} else if !utils.IsStringInSlice(endpoint.Implementation, validAuthzImplementations) {
- validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzImplementation, name, strings.Join(validAuthzImplementations, "', '"), endpoint.Implementation))
+ validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzImplementation, name, strJoinOr(validAuthzImplementations), endpoint.Implementation))
}
if !reAuthzEndpointName.MatchString(name) {
@@ -180,7 +180,7 @@ func validateServerEndpointsAuthzStrategies(name string, strategies []schema.Ser
names = append(names, strategy.Name)
if !utils.IsStringInSlice(strategy.Name, validAuthzAuthnStrategies) {
- validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzStrategy, name, strings.Join(validAuthzAuthnStrategies, "', '"), strategy.Name))
+ validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzStrategy, name, strJoinOr(validAuthzAuthnStrategies), strategy.Name))
}
}
}
diff --git a/internal/configuration/validator/server_test.go b/internal/configuration/validator/server_test.go
index c70e7124e..cf330d393 100644
--- a/internal/configuration/validator/server_test.go
+++ b/internal/configuration/validator/server_test.go
@@ -314,14 +314,18 @@ func TestServerAuthzEndpointErrors(t *testing.T) {
map[string]schema.ServerAuthzEndpoint{
"example": {Implementation: "zero"},
},
- []string{"server: endpoints: authz: example: option 'implementation' must be one of 'AuthRequest', 'ForwardAuth', 'ExtAuthz', 'Legacy' but is configured as 'zero'"},
+ []string{
+ "server: endpoints: authz: example: option 'implementation' must be one of 'AuthRequest', 'ForwardAuth', 'ExtAuthz', or 'Legacy' but it's configured as 'zero'",
+ },
},
{
"ShouldErrorOnInvalidEndpointImplementationLegacy",
map[string]schema.ServerAuthzEndpoint{
"legacy": {Implementation: "zero"},
},
- []string{"server: endpoints: authz: legacy: option 'implementation' must be one of 'AuthRequest', 'ForwardAuth', 'ExtAuthz', 'Legacy' but is configured as 'zero'"},
+ []string{
+ "server: endpoints: authz: legacy: option 'implementation' must be one of 'AuthRequest', 'ForwardAuth', 'ExtAuthz', or 'Legacy' but it's configured as 'zero'",
+ },
},
{
"ShouldErrorOnInvalidEndpointLegacyImplementation",
@@ -335,7 +339,9 @@ func TestServerAuthzEndpointErrors(t *testing.T) {
map[string]schema.ServerAuthzEndpoint{
"example": {Implementation: "ExtAuthz", AuthnStrategies: []schema.ServerAuthzEndpointAuthnStrategy{{Name: "bad-name"}}},
},
- []string{"server: endpoints: authz: example: authn_strategies: option 'name' must be one of 'CookieSession', 'HeaderAuthorization', 'HeaderProxyAuthorization', 'HeaderAuthRequestProxyAuthorization', 'HeaderLegacy' but is configured as 'bad-name'"},
+ []string{
+ "server: endpoints: authz: example: authn_strategies: option 'name' must be one of 'CookieSession', 'HeaderAuthorization', 'HeaderProxyAuthorization', 'HeaderAuthRequestProxyAuthorization', or 'HeaderLegacy' but it's configured as 'bad-name'",
+ },
},
{
"ShouldErrorOnDuplicateName",
diff --git a/internal/configuration/validator/session.go b/internal/configuration/validator/session.go
index f63d24ded..1de078ef8 100644
--- a/internal/configuration/validator/session.go
+++ b/internal/configuration/validator/session.go
@@ -45,7 +45,7 @@ func validateSession(config *schema.SessionConfiguration, validator *schema.Stru
if config.SameSite == "" {
config.SameSite = schema.DefaultSessionConfiguration.SameSite
} else if !utils.IsStringInSlice(config.SameSite, validSessionSameSiteValues) {
- validator.Push(fmt.Errorf(errFmtSessionSameSite, strings.Join(validSessionSameSiteValues, "', '"), config.SameSite))
+ validator.Push(fmt.Errorf(errFmtSessionSameSite, strJoinOr(validSessionSameSiteValues), config.SameSite))
}
cookies := len(config.Cookies)
@@ -73,7 +73,7 @@ func validateSession(config *schema.SessionConfiguration, validator *schema.Stru
func validateSessionCookieDomains(config *schema.SessionConfiguration, validator *schema.StructValidator) {
if len(config.Cookies) == 0 {
- validator.Push(fmt.Errorf(errFmtSessionOptionRequired, "domain"))
+ validator.Push(fmt.Errorf(errFmtSessionOptionRequired, "cookies"))
}
domains := make([]string, 0)
@@ -182,7 +182,7 @@ func validateSessionSameSite(i int, config *schema.SessionConfiguration, validat
config.Cookies[i].SameSite = schema.DefaultSessionConfiguration.SameSite
}
} else if !utils.IsStringInSlice(config.Cookies[i].SameSite, validSessionSameSiteValues) {
- validator.Push(fmt.Errorf(errFmtSessionDomainSameSite, sessionDomainDescriptor(i, config.Cookies[i]), strings.Join(validSessionSameSiteValues, "', '"), config.Cookies[i].SameSite))
+ validator.Push(fmt.Errorf(errFmtSessionDomainSameSite, sessionDomainDescriptor(i, config.Cookies[i]), strJoinOr(validSessionSameSiteValues), config.Cookies[i].SameSite))
}
}
diff --git a/internal/configuration/validator/session_test.go b/internal/configuration/validator/session_test.go
index f8db62b5b..1f18eaea4 100644
--- a/internal/configuration/validator/session_test.go
+++ b/internal/configuration/validator/session_test.go
@@ -95,7 +95,7 @@ func TestShouldSetDefaultSessionDomainsValues(t *testing.T) {
},
},
[]string{
- "session: option 'same_site' must be one of 'none', 'lax', 'strict' but is configured as 'BAD VALUE'",
+ "session: option 'same_site' must be one of 'none', 'lax', or 'strict' but it's configured as 'BAD VALUE'",
},
},
{
@@ -140,6 +140,24 @@ func TestShouldSetDefaultSessionDomainsValues(t *testing.T) {
},
nil,
},
+ {
+ "ShouldErrorOnEmptyConfig",
+ schema.SessionConfiguration{
+ SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
+ Name: "", SameSite: "", Domain: "",
+ },
+ Cookies: []schema.SessionCookieConfiguration{},
+ },
+ schema.SessionConfiguration{
+ SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
+ Name: "authelia_session", SameSite: "lax", Expiration: time.Hour, Inactivity: time.Minute * 5, RememberMe: time.Hour * 24 * 30,
+ },
+ Cookies: []schema.SessionCookieConfiguration{},
+ },
+ []string{
+ "session: option 'cookies' is required",
+ },
+ },
}
validator := schema.NewStructValidator()
@@ -302,7 +320,7 @@ func TestShouldRaiseErrorWhenRedisHasHostnameButNoPort(t *testing.T) {
assert.False(t, validator.HasWarnings())
assert.Len(t, validator.Errors(), 1)
- assert.EqualError(t, validator.Errors()[0], "session: redis: option 'port' must be between 1 and 65535 but is configured as '0'")
+ assert.EqualError(t, validator.Errors()[0], "session: redis: option 'port' must be between 1 and 65535 but it's configured as '0'")
}
func TestShouldRaiseOneErrorWhenRedisHighAvailabilityHasNodesWithNoHost(t *testing.T) {
@@ -646,7 +664,7 @@ func TestShouldRaiseErrorWhenDomainIsInvalid(t *testing.T) {
{"ShouldRaiseErrorOnPublicDomainDuckDNS", "duckdns.org", nil, []string{"session: domain config #1 (domain 'duckdns.org'): option 'domain' is not a valid cookie domain: the domain is part of the special public suffix list"}},
{"ShouldNotRaiseErrorOnSuffixOfPublicDomainDuckDNS", "example.duckdns.org", nil, nil},
{"ShouldRaiseWarningOnDomainWithLeadingDot", ".example.com", []string{"session: domain config #1 (domain '.example.com'): 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"}, nil},
- {"ShouldRaiseErrorOnDomainWithLeadingStarDot", "*.example.com", nil, []string{"session: domain config #1 (domain '*.example.com'): option 'domain' must be the domain you wish to protect not a wildcard domain but it is configured as '*.example.com'"}},
+ {"ShouldRaiseErrorOnDomainWithLeadingStarDot", "*.example.com", nil, []string{"session: domain config #1 (domain '*.example.com'): option 'domain' must be the domain you wish to protect not a wildcard domain but it's configured as '*.example.com'"}},
{"ShouldRaiseErrorOnDomainNotSet", "", nil, []string{"session: domain config #1 (domain ''): option 'domain' is required"}},
}
@@ -726,8 +744,8 @@ func TestShouldRaiseErrorWhenSameSiteSetIncorrectly(t *testing.T) {
assert.False(t, validator.HasWarnings())
require.Len(t, validator.Errors(), 2)
- assert.EqualError(t, validator.Errors()[0], "session: option 'same_site' must be one of 'none', 'lax', 'strict' but is configured as 'NOne'")
- assert.EqualError(t, validator.Errors()[1], "session: domain config #1 (domain 'example.com'): option 'same_site' must be one of 'none', 'lax', 'strict' but is configured as 'NOne'")
+ assert.EqualError(t, validator.Errors()[0], "session: option 'same_site' must be one of 'none', 'lax', or 'strict' but it's configured as 'NOne'")
+ assert.EqualError(t, validator.Errors()[1], "session: domain config #1 (domain 'example.com'): option 'same_site' must be one of 'none', 'lax', or 'strict' but it's configured as 'NOne'")
}
func TestShouldNotRaiseErrorWhenSameSiteSetCorrectly(t *testing.T) {
diff --git a/internal/configuration/validator/storage.go b/internal/configuration/validator/storage.go
index 7172383a7..035ac62e8 100644
--- a/internal/configuration/validator/storage.go
+++ b/internal/configuration/validator/storage.go
@@ -3,7 +3,6 @@ package validator
import (
"errors"
"fmt"
- "strings"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/utils"
@@ -92,7 +91,7 @@ func validatePostgreSQLConfiguration(config *schema.PostgreSQLStorageConfigurati
case config.SSL.Mode == "":
config.SSL.Mode = schema.DefaultPostgreSQLStorageConfiguration.SSL.Mode
case !utils.IsStringInSlice(config.SSL.Mode, validStoragePostgreSQLSSLModes):
- validator.Push(fmt.Errorf(errFmtStoragePostgreSQLInvalidSSLMode, strings.Join(validStoragePostgreSQLSSLModes, "', '"), config.SSL.Mode))
+ validator.Push(fmt.Errorf(errFmtStoragePostgreSQLInvalidSSLMode, strJoinOr(validStoragePostgreSQLSSLModes), config.SSL.Mode))
}
}
}
diff --git a/internal/configuration/validator/storage_test.go b/internal/configuration/validator/storage_test.go
index 8ac7d9dbb..f69bffab1 100644
--- a/internal/configuration/validator/storage_test.go
+++ b/internal/configuration/validator/storage_test.go
@@ -360,7 +360,7 @@ func (suite *StorageSuite) TestShouldValidatePostgresSSLModeMustBeValid() {
suite.Assert().Len(suite.validator.Warnings(), 1)
suite.Require().Len(suite.validator.Errors(), 1)
- suite.Assert().EqualError(suite.validator.Errors()[0], "storage: postgres: ssl: option 'mode' must be one of 'disable', 'require', 'verify-ca', 'verify-full' but it is configured as 'unknown'")
+ suite.Assert().EqualError(suite.validator.Errors()[0], "storage: postgres: ssl: option 'mode' must be one of 'disable', 'require', 'verify-ca', or 'verify-full' but it's configured as 'unknown'")
}
func (suite *StorageSuite) TestShouldRaiseErrorOnNoEncryptionKey() {
diff --git a/internal/configuration/validator/telemetry_test.go b/internal/configuration/validator/telemetry_test.go
index aa3e59683..1643a5c40 100644
--- a/internal/configuration/validator/telemetry_test.go
+++ b/internal/configuration/validator/telemetry_test.go
@@ -58,7 +58,7 @@ func TestValidateTelemetry(t *testing.T) {
&schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("udp://0.0.0.0")}}},
&schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("udp://0.0.0.0:9959")}}},
nil,
- []string{"telemetry: metrics: option 'address' must have a scheme 'tcp://' but it is configured as 'udp'"},
+ []string{"telemetry: metrics: option 'address' must have a scheme 'tcp://' but it's configured as 'udp'"},
},
}
diff --git a/internal/configuration/validator/theme.go b/internal/configuration/validator/theme.go
index f6c1a68d7..ccb8b7f52 100644
--- a/internal/configuration/validator/theme.go
+++ b/internal/configuration/validator/theme.go
@@ -2,7 +2,6 @@ package validator
import (
"fmt"
- "strings"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/utils"
@@ -15,6 +14,6 @@ func ValidateTheme(config *schema.Configuration, validator *schema.StructValidat
}
if !utils.IsStringInSlice(config.Theme, validThemeNames) {
- validator.Push(fmt.Errorf(errFmtThemeName, strings.Join(validThemeNames, "', '"), config.Theme))
+ validator.Push(fmt.Errorf(errFmtThemeName, strJoinOr(validThemeNames), config.Theme))
}
}
diff --git a/internal/configuration/validator/theme_test.go b/internal/configuration/validator/theme_test.go
index abe796611..b1c8b89bc 100644
--- a/internal/configuration/validator/theme_test.go
+++ b/internal/configuration/validator/theme_test.go
@@ -36,7 +36,7 @@ func (suite *Theme) TestShouldRaiseErrorWhenInvalidThemeProvided() {
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1)
- suite.Assert().EqualError(suite.validator.Errors()[0], "option 'theme' must be one of 'light', 'dark', 'grey', 'auto' but it is configured as 'invalid'")
+ suite.Assert().EqualError(suite.validator.Errors()[0], "option 'theme' must be one of 'light', 'dark', 'grey', or 'auto' but it's configured as 'invalid'")
}
func TestThemes(t *testing.T) {
diff --git a/internal/configuration/validator/totp.go b/internal/configuration/validator/totp.go
index 0a379a067..01a763f88 100644
--- a/internal/configuration/validator/totp.go
+++ b/internal/configuration/validator/totp.go
@@ -24,7 +24,7 @@ func ValidateTOTP(config *schema.Configuration, validator *schema.StructValidato
config.TOTP.Algorithm = strings.ToUpper(config.TOTP.Algorithm)
if !utils.IsStringInSlice(config.TOTP.Algorithm, schema.TOTPPossibleAlgorithms) {
- validator.Push(fmt.Errorf(errFmtTOTPInvalidAlgorithm, strings.Join(schema.TOTPPossibleAlgorithms, "', '"), config.TOTP.Algorithm))
+ validator.Push(fmt.Errorf(errFmtTOTPInvalidAlgorithm, strJoinOr(schema.TOTPPossibleAlgorithms), config.TOTP.Algorithm))
}
}
diff --git a/internal/configuration/validator/totp_test.go b/internal/configuration/validator/totp_test.go
index 956f074c4..b94f7f00b 100644
--- a/internal/configuration/validator/totp_test.go
+++ b/internal/configuration/validator/totp_test.go
@@ -56,7 +56,9 @@ func TestValidateTOTP(t *testing.T) {
Skew: schema.DefaultTOTPConfiguration.Skew,
Issuer: "abc",
},
- errs: []string{"totp: option 'algorithm' must be one of 'SHA1', 'SHA256', 'SHA512' but it is configured as 'SHA3'"},
+ errs: []string{
+ "totp: option 'algorithm' must be one of 'SHA1', 'SHA256', or 'SHA512' but it's configured as 'SHA3'",
+ },
},
{
desc: "ShouldRaiseErrorWhenInvalidTOTPValue",
@@ -69,10 +71,10 @@ func TestValidateTOTP(t *testing.T) {
Issuer: "abc",
},
errs: []string{
- "totp: option 'algorithm' must be one of 'SHA1', 'SHA256', 'SHA512' but it is configured as 'SHA3'",
- "totp: option 'period' option must be 15 or more but it is configured as '5'",
- "totp: option 'digits' must be 6 or 8 but it is configured as '20'",
- "totp: option 'secret_size' must be 20 or higher but it is configured as '10'",
+ "totp: option 'algorithm' must be one of 'SHA1', 'SHA256', or 'SHA512' but it's configured as 'SHA3'",
+ "totp: option 'period' option must be 15 or more but it's configured as '5'",
+ "totp: option 'digits' must be 6 or 8 but it's configured as '20'",
+ "totp: option 'secret_size' must be 20 or higher but it's configured as '10'",
},
},
}
diff --git a/internal/configuration/validator/util.go b/internal/configuration/validator/util.go
index b68bfee7a..59b9411b1 100644
--- a/internal/configuration/validator/util.go
+++ b/internal/configuration/validator/util.go
@@ -4,6 +4,8 @@ import (
"strings"
"golang.org/x/net/publicsuffix"
+
+ "github.com/authelia/authelia/v4/internal/utils"
)
func isCookieDomainAPublicSuffix(domain string) (valid bool) {
@@ -13,3 +15,95 @@ func isCookieDomainAPublicSuffix(domain string) (valid bool) {
return len(strings.TrimLeft(domain, ".")) == len(suffix)
}
+
+func strJoinOr(items []string) string {
+ return strJoinComma("or", items)
+}
+
+func strJoinAnd(items []string) string {
+ return strJoinComma("and", items)
+}
+
+func strJoinComma(word string, items []string) string {
+ if word == "" {
+ return buildJoinedString(",", "", "'", items)
+ }
+
+ return buildJoinedString(",", word, "'", items)
+}
+
+func buildJoinedString(sep, sepFinal, quote string, items []string) string {
+ n := len(items)
+
+ if n == 0 {
+ return ""
+ }
+
+ b := &strings.Builder{}
+
+ for i := 0; i < n; i++ {
+ if quote != "" {
+ b.WriteString(quote)
+ }
+
+ b.WriteString(items[i])
+
+ if quote != "" {
+ b.WriteString(quote)
+ }
+
+ if i == (n - 1) {
+ continue
+ }
+
+ if sep != "" {
+ if sepFinal == "" || n != 2 {
+ b.WriteString(sep)
+ }
+
+ b.WriteString(" ")
+ }
+
+ if sepFinal != "" && i == (n-2) {
+ b.WriteString(strings.Trim(sepFinal, " "))
+ b.WriteString(" ")
+ }
+ }
+
+ return b.String()
+}
+
+func validateList(values, valid []string, chkDuplicate bool) (invalid, duplicates []string) { //nolint:unparam
+ chkValid := len(valid) != 0
+
+ for i, value := range values {
+ if chkValid {
+ if !utils.IsStringInSlice(value, valid) {
+ invalid = append(invalid, value)
+
+ // Skip checking duplicates for invalid values.
+ continue
+ }
+ }
+
+ if chkDuplicate {
+ for j, valueAlt := range values {
+ if i == j {
+ continue
+ }
+
+ if value != valueAlt {
+ continue
+ }
+
+ if utils.IsStringInSlice(value, duplicates) {
+ continue
+ }
+
+ duplicates = append(duplicates, value)
+ }
+ }
+ }
+
+ return
+}
diff --git a/internal/configuration/validator/webauthn.go b/internal/configuration/validator/webauthn.go
index 47aaa2704..406ccc7e6 100644
--- a/internal/configuration/validator/webauthn.go
+++ b/internal/configuration/validator/webauthn.go
@@ -2,7 +2,6 @@ package validator
import (
"fmt"
- "strings"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/utils"
@@ -22,13 +21,13 @@ func ValidateWebauthn(config *schema.Configuration, validator *schema.StructVali
case config.Webauthn.ConveyancePreference == "":
config.Webauthn.ConveyancePreference = schema.DefaultWebauthnConfiguration.ConveyancePreference
case !utils.IsStringInSlice(string(config.Webauthn.ConveyancePreference), validWebauthnConveyancePreferences):
- validator.Push(fmt.Errorf(errFmtWebauthnConveyancePreference, strings.Join(validWebauthnConveyancePreferences, "', '"), config.Webauthn.ConveyancePreference))
+ validator.Push(fmt.Errorf(errFmtWebauthnConveyancePreference, strJoinOr(validWebauthnConveyancePreferences), config.Webauthn.ConveyancePreference))
}
switch {
case config.Webauthn.UserVerification == "":
config.Webauthn.UserVerification = schema.DefaultWebauthnConfiguration.UserVerification
case !utils.IsStringInSlice(string(config.Webauthn.UserVerification), validWebauthnUserVerificationRequirement):
- validator.Push(fmt.Errorf(errFmtWebauthnUserVerification, config.Webauthn.UserVerification))
+ validator.Push(fmt.Errorf(errFmtWebauthnUserVerification, strJoinOr(validWebauthnConveyancePreferences), config.Webauthn.UserVerification))
}
}
diff --git a/internal/configuration/validator/webauthn_test.go b/internal/configuration/validator/webauthn_test.go
index bfa746a32..9aaaa0aac 100644
--- a/internal/configuration/validator/webauthn_test.go
+++ b/internal/configuration/validator/webauthn_test.go
@@ -93,6 +93,6 @@ func TestWebauthnShouldRaiseErrorsOnInvalidOptions(t *testing.T) {
require.Len(t, validator.Errors(), 2)
- assert.EqualError(t, validator.Errors()[0], "webauthn: option 'attestation_conveyance_preference' must be one of 'none', 'indirect', 'direct' but it is configured as 'no'")
- assert.EqualError(t, validator.Errors()[1], "webauthn: option 'user_verification' must be one of 'discouraged', 'preferred', 'required' but it is configured as 'yes'")
+ assert.EqualError(t, validator.Errors()[0], "webauthn: option 'attestation_conveyance_preference' must be one of 'none', 'indirect', or 'direct' but it's configured as 'no'")
+ assert.EqualError(t, validator.Errors()[1], "webauthn: option 'user_verification' must be one of 'none', 'indirect', or 'direct' but it's configured as 'yes'")
}
diff --git a/internal/handlers/handler_oidc_authorization.go b/internal/handlers/handler_oidc_authorization.go
index 5cb193920..9924c5ec1 100644
--- a/internal/handlers/handler_oidc_authorization.go
+++ b/internal/handlers/handler_oidc_authorization.go
@@ -21,7 +21,7 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr
var (
requester fosite.AuthorizeRequester
responder fosite.AuthorizeResponder
- client *oidc.Client
+ client oidc.Client
authTime time.Time
issuer *url.URL
err error
@@ -117,7 +117,7 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr
extraClaims := oidcGrantRequests(requester, consent, &userSession)
- if authTime, err = userSession.AuthenticatedTime(client.Policy); err != nil {
+ if authTime, err = userSession.AuthenticatedTime(client.GetAuthorizationPolicy()); err != nil {
ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' could not be processed: error occurred checking authentication time: %+v", requester.GetID(), client.GetID(), err)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, fosite.ErrServerError.WithHint("Could not obtain the authentication time."))
@@ -178,7 +178,7 @@ func OpenIDConnectPushedAuthorizationRequest(ctx *middlewares.AutheliaCtx, rw ht
return
}
- var client *oidc.Client
+ var client oidc.Client
clientID := requester.GetClient().GetID()
diff --git a/internal/handlers/handler_oidc_authorization_consent.go b/internal/handlers/handler_oidc_authorization_consent.go
index fef764701..1a9c400f8 100644
--- a/internal/handlers/handler_oidc_authorization_consent.go
+++ b/internal/handlers/handler_oidc_authorization_consent.go
@@ -18,7 +18,7 @@ import (
"github.com/authelia/authelia/v4/internal/utils"
)
-func handleOIDCAuthorizationConsent(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client,
+func handleOIDCAuthorizationConsent(ctx *middlewares.AutheliaCtx, issuer *url.URL, client oidc.Client,
userSession session.UserSession,
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
var (
@@ -33,14 +33,14 @@ func handleOIDCAuthorizationConsent(ctx *middlewares.AutheliaCtx, issuer *url.UR
handler = handleOIDCAuthorizationConsentNotAuthenticated
case client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel):
if subject, err = ctx.Providers.OpenIDConnect.GetSubject(ctx, client.GetSectorIdentifier(), userSession.Username); err != nil {
- ctx.Logger.Errorf(logFmtErrConsentCantGetSubject, requester.GetID(), client.GetID(), client.Consent, userSession.Username, client.GetSectorIdentifier(), err)
+ ctx.Logger.Errorf(logFmtErrConsentCantGetSubject, requester.GetID(), client.GetID(), client.GetConsentPolicy(), userSession.Username, client.GetSectorIdentifier(), err)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrSubjectCouldNotLookup)
return nil, true
}
- switch client.Consent.Mode {
+ switch client.GetConsentPolicy().Mode {
case oidc.ClientConsentModeExplicit:
handler = handleOIDCAuthorizationConsentModeExplicit
case oidc.ClientConsentModeImplicit:
@@ -56,7 +56,7 @@ func handleOIDCAuthorizationConsent(ctx *middlewares.AutheliaCtx, issuer *url.UR
}
default:
if subject, err = ctx.Providers.OpenIDConnect.GetSubject(ctx, client.GetSectorIdentifier(), userSession.Username); err != nil {
- ctx.Logger.Errorf(logFmtErrConsentCantGetSubject, requester.GetID(), client.GetID(), client.Consent, userSession.Username, client.GetSectorIdentifier(), err)
+ ctx.Logger.Errorf(logFmtErrConsentCantGetSubject, requester.GetID(), client.GetID(), client.GetConsentPolicy(), userSession.Username, client.GetSectorIdentifier(), err)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrSubjectCouldNotLookup)
@@ -69,7 +69,7 @@ func handleOIDCAuthorizationConsent(ctx *middlewares.AutheliaCtx, issuer *url.UR
return handler(ctx, issuer, client, userSession, subject, rw, r, requester)
}
-func handleOIDCAuthorizationConsentNotAuthenticated(_ *middlewares.AutheliaCtx, issuer *url.URL, _ *oidc.Client,
+func handleOIDCAuthorizationConsentNotAuthenticated(_ *middlewares.AutheliaCtx, issuer *url.URL, _ oidc.Client,
_ session.UserSession, _ uuid.UUID,
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
redirectionURL := handleOIDCAuthorizationConsentGetRedirectionURL(issuer, nil, requester)
@@ -79,17 +79,17 @@ func handleOIDCAuthorizationConsentNotAuthenticated(_ *middlewares.AutheliaCtx,
return nil, true
}
-func handleOIDCAuthorizationConsentGenerate(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client,
+func handleOIDCAuthorizationConsentGenerate(ctx *middlewares.AutheliaCtx, issuer *url.URL, client oidc.Client,
userSession session.UserSession, subject uuid.UUID,
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
var (
err error
)
- ctx.Logger.Debugf(logFmtDbgConsentGenerate, requester.GetID(), client.GetID(), client.Consent)
+ ctx.Logger.Debugf(logFmtDbgConsentGenerate, requester.GetID(), client.GetID(), client.GetConsentPolicy())
if len(ctx.QueryArgs().PeekBytes(qryArgConsentID)) != 0 {
- ctx.Logger.Errorf(logFmtErrConsentGenerateError, requester.GetID(), client.GetID(), client.Consent, "generating", errors.New("consent id value was present when it should be absent"))
+ ctx.Logger.Errorf(logFmtErrConsentGenerateError, requester.GetID(), client.GetID(), client.GetConsentPolicy(), "generating", errors.New("consent id value was present when it should be absent"))
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotGenerate)
@@ -97,7 +97,7 @@ func handleOIDCAuthorizationConsentGenerate(ctx *middlewares.AutheliaCtx, issuer
}
if consent, err = model.NewOAuth2ConsentSession(subject, requester); err != nil {
- ctx.Logger.Errorf(logFmtErrConsentGenerateError, requester.GetID(), client.GetID(), client.Consent, "generating", err)
+ ctx.Logger.Errorf(logFmtErrConsentGenerateError, requester.GetID(), client.GetID(), client.GetConsentPolicy(), "generating", err)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotGenerate)
@@ -105,7 +105,7 @@ func handleOIDCAuthorizationConsentGenerate(ctx *middlewares.AutheliaCtx, issuer
}
if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSession(ctx, *consent); err != nil {
- ctx.Logger.Errorf(logFmtErrConsentGenerateError, requester.GetID(), client.GetID(), client.Consent, "saving", err)
+ ctx.Logger.Errorf(logFmtErrConsentGenerateError, requester.GetID(), client.GetID(), client.GetConsentPolicy(), "saving", err)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave)
@@ -117,7 +117,7 @@ func handleOIDCAuthorizationConsentGenerate(ctx *middlewares.AutheliaCtx, issuer
return consent, true
}
-func handleOIDCAuthorizationConsentRedirect(ctx *middlewares.AutheliaCtx, issuer *url.URL, consent *model.OAuth2ConsentSession, client *oidc.Client,
+func handleOIDCAuthorizationConsentRedirect(ctx *middlewares.AutheliaCtx, issuer *url.URL, consent *model.OAuth2ConsentSession, client oidc.Client,
userSession session.UserSession, rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) {
var location *url.URL
@@ -130,14 +130,14 @@ func handleOIDCAuthorizationConsentRedirect(ctx *middlewares.AutheliaCtx, issuer
location.RawQuery = query.Encode()
- ctx.Logger.Debugf(logFmtDbgConsentAuthenticationSufficiency, requester.GetID(), client.GetID(), client.Consent, userSession.AuthenticationLevel.String(), "sufficient", client.Policy)
+ ctx.Logger.Debugf(logFmtDbgConsentAuthenticationSufficiency, requester.GetID(), client.GetID(), client.GetConsentPolicy(), userSession.AuthenticationLevel.String(), "sufficient", client.GetAuthorizationPolicy())
} else {
location = handleOIDCAuthorizationConsentGetRedirectionURL(issuer, consent, requester)
- ctx.Logger.Debugf(logFmtDbgConsentAuthenticationSufficiency, requester.GetID(), client.GetID(), client.Consent, userSession.AuthenticationLevel.String(), "insufficient", client.Policy)
+ ctx.Logger.Debugf(logFmtDbgConsentAuthenticationSufficiency, requester.GetID(), client.GetID(), client.GetConsentPolicy(), userSession.AuthenticationLevel.String(), "insufficient", client.GetAuthorizationPolicy())
}
- ctx.Logger.Debugf(logFmtDbgConsentRedirect, requester.GetID(), client.GetID(), client.Consent, location)
+ ctx.Logger.Debugf(logFmtDbgConsentRedirect, requester.GetID(), client.GetID(), client.GetConsentPolicy(), location)
http.Redirect(rw, r, location.String(), http.StatusFound)
}
@@ -170,7 +170,7 @@ func handleOIDCAuthorizationConsentGetRedirectionURL(issuer *url.URL, consent *m
return redirectURL
}
-func verifyOIDCUserAuthorizedForConsent(ctx *middlewares.AutheliaCtx, client *oidc.Client, userSession session.UserSession, consent *model.OAuth2ConsentSession, subject uuid.UUID) (err error) {
+func verifyOIDCUserAuthorizedForConsent(ctx *middlewares.AutheliaCtx, client oidc.Client, userSession session.UserSession, consent *model.OAuth2ConsentSession, subject uuid.UUID) (err error) {
var sid uint32
if client == nil {
diff --git a/internal/handlers/handler_oidc_authorization_consent_explicit.go b/internal/handlers/handler_oidc_authorization_consent_explicit.go
index 9388c74d8..901806c9c 100644
--- a/internal/handlers/handler_oidc_authorization_consent_explicit.go
+++ b/internal/handlers/handler_oidc_authorization_consent_explicit.go
@@ -13,7 +13,7 @@ import (
"github.com/authelia/authelia/v4/internal/session"
)
-func handleOIDCAuthorizationConsentModeExplicit(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client,
+func handleOIDCAuthorizationConsentModeExplicit(ctx *middlewares.AutheliaCtx, issuer *url.URL, client oidc.Client,
userSession session.UserSession, subject uuid.UUID,
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
var (
@@ -28,7 +28,7 @@ func handleOIDCAuthorizationConsentModeExplicit(ctx *middlewares.AutheliaCtx, is
return handleOIDCAuthorizationConsentGenerate(ctx, issuer, client, userSession, subject, rw, r, requester)
default:
if consentID, err = uuid.ParseBytes(bytesConsentID); err != nil {
- ctx.Logger.Errorf(logFmtErrConsentParseChallengeID, requester.GetID(), client.GetID(), client.Consent, bytesConsentID, err)
+ ctx.Logger.Errorf(logFmtErrConsentParseChallengeID, requester.GetID(), client.GetID(), client.GetConsentPolicy(), bytesConsentID, err)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentMalformedChallengeID)
@@ -39,7 +39,7 @@ func handleOIDCAuthorizationConsentModeExplicit(ctx *middlewares.AutheliaCtx, is
}
}
-func handleOIDCAuthorizationConsentModeExplicitWithID(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client,
+func handleOIDCAuthorizationConsentModeExplicitWithID(ctx *middlewares.AutheliaCtx, issuer *url.URL, client oidc.Client,
userSession session.UserSession, subject uuid.UUID, consentID uuid.UUID,
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
var (
@@ -47,7 +47,7 @@ func handleOIDCAuthorizationConsentModeExplicitWithID(ctx *middlewares.AutheliaC
)
if consentID.ID() == 0 {
- ctx.Logger.Errorf(logFmtErrConsentZeroID, requester.GetID(), client.GetID(), client.Consent)
+ ctx.Logger.Errorf(logFmtErrConsentZeroID, requester.GetID(), client.GetID(), client.GetConsentPolicy())
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup)
@@ -55,7 +55,7 @@ func handleOIDCAuthorizationConsentModeExplicitWithID(ctx *middlewares.AutheliaC
}
if consent, err = ctx.Providers.StorageProvider.LoadOAuth2ConsentSessionByChallengeID(ctx, consentID); err != nil {
- ctx.Logger.Errorf(logFmtErrConsentLookupLoadingSession, requester.GetID(), client.GetID(), client.Consent, consentID, err)
+ ctx.Logger.Errorf(logFmtErrConsentLookupLoadingSession, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consentID, err)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup)
@@ -63,7 +63,7 @@ func handleOIDCAuthorizationConsentModeExplicitWithID(ctx *middlewares.AutheliaC
}
if subject.ID() != consent.Subject.UUID.ID() {
- ctx.Logger.Errorf(logFmtErrConsentSessionSubjectNotAuthorized, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, userSession.Username, subject, consent.Subject.UUID)
+ ctx.Logger.Errorf(logFmtErrConsentSessionSubjectNotAuthorized, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, userSession.Username, subject, consent.Subject.UUID)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup)
@@ -71,7 +71,7 @@ func handleOIDCAuthorizationConsentModeExplicitWithID(ctx *middlewares.AutheliaC
}
if !consent.CanGrant() {
- ctx.Logger.Errorf(logFmtErrConsentCantGrant, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, "explicit")
+ ctx.Logger.Errorf(logFmtErrConsentCantGrant, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, "explicit")
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotPerform)
@@ -80,7 +80,7 @@ func handleOIDCAuthorizationConsentModeExplicitWithID(ctx *middlewares.AutheliaC
if !consent.IsAuthorized() {
if consent.Responded() {
- ctx.Logger.Errorf(logFmtErrConsentCantGrantRejected, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID)
+ ctx.Logger.Errorf(logFmtErrConsentCantGrantRejected, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, fosite.ErrAccessDenied)
diff --git a/internal/handlers/handler_oidc_authorization_consent_implicit.go b/internal/handlers/handler_oidc_authorization_consent_implicit.go
index 5ec5b3ad1..b0f38c0b0 100644
--- a/internal/handlers/handler_oidc_authorization_consent_implicit.go
+++ b/internal/handlers/handler_oidc_authorization_consent_implicit.go
@@ -13,7 +13,7 @@ import (
"github.com/authelia/authelia/v4/internal/session"
)
-func handleOIDCAuthorizationConsentModeImplicit(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client,
+func handleOIDCAuthorizationConsentModeImplicit(ctx *middlewares.AutheliaCtx, issuer *url.URL, client oidc.Client,
userSession session.UserSession, subject uuid.UUID,
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
var (
@@ -26,7 +26,7 @@ func handleOIDCAuthorizationConsentModeImplicit(ctx *middlewares.AutheliaCtx, is
return handleOIDCAuthorizationConsentModeImplicitWithoutID(ctx, issuer, client, userSession, subject, rw, r, requester)
default:
if consentID, err = uuid.ParseBytes(bytesConsentID); err != nil {
- ctx.Logger.Errorf(logFmtErrConsentParseChallengeID, requester.GetID(), client.GetID(), client.Consent, bytesConsentID, err)
+ ctx.Logger.Errorf(logFmtErrConsentParseChallengeID, requester.GetID(), client.GetID(), client.GetConsentPolicy(), bytesConsentID, err)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentMalformedChallengeID)
@@ -37,7 +37,7 @@ func handleOIDCAuthorizationConsentModeImplicit(ctx *middlewares.AutheliaCtx, is
}
}
-func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaCtx, _ *url.URL, client *oidc.Client,
+func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaCtx, _ *url.URL, client oidc.Client,
userSession session.UserSession, subject uuid.UUID, consentID uuid.UUID,
rw http.ResponseWriter, _ *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
var (
@@ -45,7 +45,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaC
)
if consentID.ID() == 0 {
- ctx.Logger.Errorf(logFmtErrConsentZeroID, requester.GetID(), client.GetID(), client.Consent)
+ ctx.Logger.Errorf(logFmtErrConsentZeroID, requester.GetID(), client.GetID(), client.GetConsentPolicy())
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup)
@@ -53,7 +53,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaC
}
if consent, err = ctx.Providers.StorageProvider.LoadOAuth2ConsentSessionByChallengeID(ctx, consentID); err != nil {
- ctx.Logger.Errorf(logFmtErrConsentLookupLoadingSession, requester.GetID(), client.GetID(), client.Consent, consentID, err)
+ ctx.Logger.Errorf(logFmtErrConsentLookupLoadingSession, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consentID, err)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup)
@@ -61,7 +61,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaC
}
if subject.ID() != consent.Subject.UUID.ID() {
- ctx.Logger.Errorf(logFmtErrConsentSessionSubjectNotAuthorized, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, userSession.Username, subject, consent.Subject.UUID)
+ ctx.Logger.Errorf(logFmtErrConsentSessionSubjectNotAuthorized, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, userSession.Username, subject, consent.Subject.UUID)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup)
@@ -69,7 +69,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaC
}
if !consent.CanGrant() {
- ctx.Logger.Errorf(logFmtErrConsentCantGrant, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, "implicit")
+ ctx.Logger.Errorf(logFmtErrConsentCantGrant, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, "implicit")
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotPerform)
@@ -79,7 +79,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaC
consent.Grant()
if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSessionResponse(ctx, *consent, false); err != nil {
- ctx.Logger.Errorf(logFmtErrConsentSaveSessionResponse, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err)
+ ctx.Logger.Errorf(logFmtErrConsentSaveSessionResponse, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, err)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave)
@@ -89,7 +89,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaC
return consent, false
}
-func handleOIDCAuthorizationConsentModeImplicitWithoutID(ctx *middlewares.AutheliaCtx, _ *url.URL, client *oidc.Client,
+func handleOIDCAuthorizationConsentModeImplicitWithoutID(ctx *middlewares.AutheliaCtx, _ *url.URL, client oidc.Client,
_ session.UserSession, subject uuid.UUID,
rw http.ResponseWriter, _ *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
var (
@@ -97,7 +97,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithoutID(ctx *middlewares.Authel
)
if consent, err = model.NewOAuth2ConsentSession(subject, requester); err != nil {
- ctx.Logger.Errorf(logFmtErrConsentGenerate, requester.GetID(), client.GetID(), client.Consent, err)
+ ctx.Logger.Errorf(logFmtErrConsentGenerate, requester.GetID(), client.GetID(), client.GetConsentPolicy(), err)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotGenerate)
@@ -105,7 +105,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithoutID(ctx *middlewares.Authel
}
if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSession(ctx, *consent); err != nil {
- ctx.Logger.Errorf(logFmtErrConsentSaveSession, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err)
+ ctx.Logger.Errorf(logFmtErrConsentSaveSession, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, err)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave)
@@ -113,7 +113,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithoutID(ctx *middlewares.Authel
}
if consent, err = ctx.Providers.StorageProvider.LoadOAuth2ConsentSessionByChallengeID(ctx, consent.ChallengeID); err != nil {
- ctx.Logger.Errorf(logFmtErrConsentSaveSession, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err)
+ ctx.Logger.Errorf(logFmtErrConsentSaveSession, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, err)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave)
@@ -123,7 +123,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithoutID(ctx *middlewares.Authel
consent.Grant()
if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSessionResponse(ctx, *consent, false); err != nil {
- ctx.Logger.Errorf(logFmtErrConsentSaveSessionResponse, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err)
+ ctx.Logger.Errorf(logFmtErrConsentSaveSessionResponse, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, err)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave)
diff --git a/internal/handlers/handler_oidc_authorization_consent_pre_configured.go b/internal/handlers/handler_oidc_authorization_consent_pre_configured.go
index 0b8b26229..9f9e708f8 100644
--- a/internal/handlers/handler_oidc_authorization_consent_pre_configured.go
+++ b/internal/handlers/handler_oidc_authorization_consent_pre_configured.go
@@ -17,7 +17,7 @@ import (
"github.com/authelia/authelia/v4/internal/storage"
)
-func handleOIDCAuthorizationConsentModePreConfigured(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client,
+func handleOIDCAuthorizationConsentModePreConfigured(ctx *middlewares.AutheliaCtx, issuer *url.URL, client oidc.Client,
userSession session.UserSession, subject uuid.UUID,
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
var (
@@ -32,7 +32,7 @@ func handleOIDCAuthorizationConsentModePreConfigured(ctx *middlewares.AutheliaCt
return handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx, issuer, client, userSession, subject, rw, r, requester)
default:
if consentID, err = uuid.ParseBytes(bytesConsentID); err != nil {
- ctx.Logger.Errorf(logFmtErrConsentParseChallengeID, requester.GetID(), client.GetID(), client.Consent, bytesConsentID, err)
+ ctx.Logger.Errorf(logFmtErrConsentParseChallengeID, requester.GetID(), client.GetID(), client.GetConsentPolicy(), bytesConsentID, err)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentMalformedChallengeID)
@@ -43,7 +43,7 @@ func handleOIDCAuthorizationConsentModePreConfigured(ctx *middlewares.AutheliaCt
}
}
-func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client,
+func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.AutheliaCtx, issuer *url.URL, client oidc.Client,
userSession session.UserSession, subject uuid.UUID, consentID uuid.UUID,
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
var (
@@ -52,7 +52,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.Auth
)
if consentID.ID() == 0 {
- ctx.Logger.Errorf(logFmtErrConsentZeroID, requester.GetID(), client.GetID(), client.Consent)
+ ctx.Logger.Errorf(logFmtErrConsentZeroID, requester.GetID(), client.GetID(), client.GetConsentPolicy())
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup)
@@ -60,7 +60,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.Auth
}
if consent, err = ctx.Providers.StorageProvider.LoadOAuth2ConsentSessionByChallengeID(ctx, consentID); err != nil {
- ctx.Logger.Errorf(logFmtErrConsentLookupLoadingSession, requester.GetID(), client.GetID(), client.Consent, consentID, err)
+ ctx.Logger.Errorf(logFmtErrConsentLookupLoadingSession, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consentID, err)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup)
@@ -68,7 +68,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.Auth
}
if subject.ID() != consent.Subject.UUID.ID() {
- ctx.Logger.Errorf(logFmtErrConsentSessionSubjectNotAuthorized, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, userSession.Username, subject, consent.Subject.UUID)
+ ctx.Logger.Errorf(logFmtErrConsentSessionSubjectNotAuthorized, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, userSession.Username, subject, consent.Subject.UUID)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup)
@@ -76,7 +76,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.Auth
}
if !consent.CanGrant() {
- ctx.Logger.Errorf(logFmtErrConsentCantGrantPreConf, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID)
+ ctx.Logger.Errorf(logFmtErrConsentCantGrantPreConf, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotPerform)
@@ -84,7 +84,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.Auth
}
if config, err = handleOIDCAuthorizationConsentModePreConfiguredGetPreConfig(ctx, client, subject, requester); err != nil {
- ctx.Logger.Errorf(logFmtErrConsentPreConfLookup, requester.GetID(), client.GetID(), client.Consent, err)
+ ctx.Logger.Errorf(logFmtErrConsentPreConfLookup, requester.GetID(), client.GetID(), client.GetConsentPolicy(), err)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup)
@@ -97,7 +97,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.Auth
consent.PreConfiguration = sql.NullInt64{Int64: config.ID, Valid: true}
if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSessionResponse(ctx, *consent, false); err != nil {
- ctx.Logger.Errorf(logFmtErrConsentSaveSessionResponse, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err)
+ ctx.Logger.Errorf(logFmtErrConsentSaveSessionResponse, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, err)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave)
@@ -109,7 +109,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.Auth
if !consent.IsAuthorized() {
if consent.Responded() {
- ctx.Logger.Errorf(logFmtErrConsentCantGrantRejected, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID)
+ ctx.Logger.Errorf(logFmtErrConsentCantGrantRejected, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, fosite.ErrAccessDenied)
@@ -124,7 +124,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.Auth
return consent, false
}
-func handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client,
+func handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx *middlewares.AutheliaCtx, issuer *url.URL, client oidc.Client,
userSession session.UserSession, subject uuid.UUID,
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
var (
@@ -133,7 +133,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx *middlewares.A
)
if config, err = handleOIDCAuthorizationConsentModePreConfiguredGetPreConfig(ctx, client, subject, requester); err != nil {
- ctx.Logger.Errorf(logFmtErrConsentPreConfLookup, requester.GetID(), client.GetID(), client.Consent, err)
+ ctx.Logger.Errorf(logFmtErrConsentPreConfLookup, requester.GetID(), client.GetID(), client.GetConsentPolicy(), err)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup)
@@ -145,7 +145,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx *middlewares.A
}
if consent, err = model.NewOAuth2ConsentSession(subject, requester); err != nil {
- ctx.Logger.Errorf(logFmtErrConsentGenerate, requester.GetID(), client.GetID(), client.Consent, err)
+ ctx.Logger.Errorf(logFmtErrConsentGenerate, requester.GetID(), client.GetID(), client.GetConsentPolicy(), err)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotGenerate)
@@ -153,7 +153,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx *middlewares.A
}
if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSession(ctx, *consent); err != nil {
- ctx.Logger.Errorf(logFmtErrConsentSaveSession, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err)
+ ctx.Logger.Errorf(logFmtErrConsentSaveSession, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, err)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave)
@@ -161,7 +161,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx *middlewares.A
}
if consent, err = ctx.Providers.StorageProvider.LoadOAuth2ConsentSessionByChallengeID(ctx, consent.ChallengeID); err != nil {
- ctx.Logger.Errorf(logFmtErrConsentSaveSession, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err)
+ ctx.Logger.Errorf(logFmtErrConsentSaveSession, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, err)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave)
@@ -173,7 +173,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx *middlewares.A
consent.PreConfiguration = sql.NullInt64{Int64: config.ID, Valid: true}
if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSessionResponse(ctx, *consent, false); err != nil {
- ctx.Logger.Errorf(logFmtErrConsentSaveSessionResponse, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err)
+ ctx.Logger.Errorf(logFmtErrConsentSaveSessionResponse, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, err)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave)
@@ -183,12 +183,12 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx *middlewares.A
return consent, false
}
-func handleOIDCAuthorizationConsentModePreConfiguredGetPreConfig(ctx *middlewares.AutheliaCtx, client *oidc.Client, subject uuid.UUID, requester fosite.Requester) (config *model.OAuth2ConsentPreConfig, err error) {
+func handleOIDCAuthorizationConsentModePreConfiguredGetPreConfig(ctx *middlewares.AutheliaCtx, client oidc.Client, subject uuid.UUID, requester fosite.Requester) (config *model.OAuth2ConsentPreConfig, err error) {
var (
rows *storage.ConsentPreConfigRows
)
- ctx.Logger.Debugf(logFmtDbgConsentPreConfTryingLookup, requester.GetID(), client.GetID(), client.Consent, client.GetID(), subject, strings.Join(requester.GetRequestedScopes(), " "))
+ ctx.Logger.Debugf(logFmtDbgConsentPreConfTryingLookup, requester.GetID(), client.GetID(), client.GetConsentPolicy(), client.GetID(), subject, strings.Join(requester.GetRequestedScopes(), " "))
if rows, err = ctx.Providers.StorageProvider.LoadOAuth2ConsentPreConfigurations(ctx, client.GetID(), subject); err != nil {
return nil, fmt.Errorf("error loading rows: %w", err)
@@ -196,7 +196,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredGetPreConfig(ctx *middleware
defer func() {
if err := rows.Close(); err != nil {
- ctx.Logger.Errorf(logFmtErrConsentPreConfRowsClose, requester.GetID(), client.GetID(), client.Consent, err)
+ ctx.Logger.Errorf(logFmtErrConsentPreConfRowsClose, requester.GetID(), client.GetID(), client.GetConsentPolicy(), err)
}
}()
@@ -208,13 +208,13 @@ func handleOIDCAuthorizationConsentModePreConfiguredGetPreConfig(ctx *middleware
}
if config.HasExactGrants(scopes, audience) && config.CanConsent() {
- ctx.Logger.Debugf(logFmtDbgConsentPreConfSuccessfulLookup, requester.GetID(), client.GetID(), client.Consent, client.GetID(), subject, strings.Join(requester.GetRequestedScopes(), " "), config.ID)
+ ctx.Logger.Debugf(logFmtDbgConsentPreConfSuccessfulLookup, requester.GetID(), client.GetID(), client.GetConsentPolicy(), client.GetID(), subject, strings.Join(requester.GetRequestedScopes(), " "), config.ID)
return config, nil
}
}
- ctx.Logger.Debugf(logFmtDbgConsentPreConfUnsuccessfulLookup, requester.GetID(), client.GetID(), client.Consent, client.GetID(), subject, strings.Join(requester.GetRequestedScopes(), " "))
+ ctx.Logger.Debugf(logFmtDbgConsentPreConfUnsuccessfulLookup, requester.GetID(), client.GetID(), client.GetConsentPolicy(), client.GetID(), subject, strings.Join(requester.GetRequestedScopes(), " "))
return nil, nil
}
diff --git a/internal/handlers/handler_oidc_consent.go b/internal/handlers/handler_oidc_consent.go
index bea6e9067..cc97a2889 100644
--- a/internal/handlers/handler_oidc_consent.go
+++ b/internal/handlers/handler_oidc_consent.go
@@ -32,7 +32,7 @@ func OpenIDConnectConsentGET(ctx *middlewares.AutheliaCtx) {
var (
consent *model.OAuth2ConsentSession
- client *oidc.Client
+ client oidc.Client
handled bool
)
@@ -70,7 +70,7 @@ func OpenIDConnectConsentPOST(ctx *middlewares.AutheliaCtx) {
var (
userSession session.UserSession
consent *model.OAuth2ConsentSession
- client *oidc.Client
+ client oidc.Client
handled bool
)
@@ -90,12 +90,12 @@ func OpenIDConnectConsentPOST(ctx *middlewares.AutheliaCtx) {
consent.Grant()
if bodyJSON.PreConfigure {
- if client.Consent.Mode == oidc.ClientConsentModePreConfigured {
+ if client.GetConsentPolicy().Mode == oidc.ClientConsentModePreConfigured {
config := model.OAuth2ConsentPreConfig{
ClientID: consent.ClientID,
Subject: consent.Subject.UUID,
CreatedAt: time.Now(),
- ExpiresAt: sql.NullTime{Time: time.Now().Add(client.Consent.Duration), Valid: true},
+ ExpiresAt: sql.NullTime{Time: time.Now().Add(client.GetConsentPolicy().Duration), Valid: true},
Scopes: consent.GrantedScopes,
Audience: consent.GrantedAudience,
}
@@ -151,7 +151,7 @@ func OpenIDConnectConsentPOST(ctx *middlewares.AutheliaCtx) {
}
}
-func oidcConsentGetSessionsAndClient(ctx *middlewares.AutheliaCtx, consentID uuid.UUID) (userSession session.UserSession, consent *model.OAuth2ConsentSession, client *oidc.Client, handled bool) {
+func oidcConsentGetSessionsAndClient(ctx *middlewares.AutheliaCtx, consentID uuid.UUID) (userSession session.UserSession, consent *model.OAuth2ConsentSession, client oidc.Client, handled bool) {
var (
err error
)
@@ -185,7 +185,7 @@ func oidcConsentGetSessionsAndClient(ctx *middlewares.AutheliaCtx, consentID uui
return userSession, nil, nil, true
}
- switch client.Consent.Mode {
+ switch client.GetConsentPolicy().Mode {
case oidc.ClientConsentModeImplicit:
ctx.Logger.Errorf("Unable to perform OpenID Connect Consent for user '%s' and client id '%s': the client is using the implicit consent mode", userSession.Username, consent.ClientID)
ctx.ReplyForbidden()
diff --git a/internal/handlers/handler_oidc_userinfo.go b/internal/handlers/handler_oidc_userinfo.go
index 3e1314d72..c3dc4f9e6 100644
--- a/internal/handlers/handler_oidc_userinfo.go
+++ b/internal/handlers/handler_oidc_userinfo.go
@@ -23,7 +23,7 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
var (
tokenType fosite.TokenType
requester fosite.AccessRequester
- client *oidc.Client
+ client oidc.Client
err error
)
@@ -99,7 +99,7 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
ctx.Logger.Tracef("UserInfo Response with id '%s' on client with id '%s' is being sent with the following claims: %+v", requester.GetID(), clientID, claims)
- switch client.UserinfoSigningAlgorithm {
+ switch client.GetUserinfoSigningAlgorithm() {
case oidc.SigningAlgorithmRSAWithSHA256:
var jti uuid.UUID
@@ -129,6 +129,6 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
case oidc.SigningAlgorithmNone, "":
ctx.Providers.OpenIDConnect.Write(rw, req, claims)
default:
- ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(fosite.ErrServerError.WithHintf("Unsupported UserInfo signing algorithm '%s'.", client.UserinfoSigningAlgorithm)))
+ ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(fosite.ErrServerError.WithHintf("Unsupported UserInfo signing algorithm '%s'.", client.GetUserinfoSigningAlgorithm())))
}
}
diff --git a/internal/handlers/response.go b/internal/handlers/response.go
index b3de77c3c..9b8bda53f 100644
--- a/internal/handlers/response.go
+++ b/internal/handlers/response.go
@@ -178,7 +178,7 @@ func handleOIDCWorkflowResponseWithTargetURL(ctx *middlewares.AutheliaCtx, targe
func handleOIDCWorkflowResponseWithID(ctx *middlewares.AutheliaCtx, id string) {
var (
workflowID uuid.UUID
- client *oidc.Client
+ client oidc.Client
consent *model.OAuth2ConsentSession
err error
)
@@ -210,19 +210,19 @@ func handleOIDCWorkflowResponseWithID(ctx *middlewares.AutheliaCtx, id string) {
var userSession session.UserSession
if userSession, err = ctx.GetSession(); err != nil {
- ctx.Error(fmt.Errorf("unable to redirect for authorization/consent for client with id '%s' with consent challenge id '%s': failed to lookup session: %w", client.ID, consent.ChallengeID, err), messageAuthenticationFailed)
+ ctx.Error(fmt.Errorf("unable to redirect for authorization/consent for client with id '%s' with consent challenge id '%s': failed to lookup session: %w", client.GetID(), consent.ChallengeID, err), messageAuthenticationFailed)
return
}
if userSession.IsAnonymous() {
- ctx.Error(fmt.Errorf("unable to redirect for authorization/consent for client with id '%s' with consent challenge id '%s': user is anonymous", client.ID, consent.ChallengeID), messageAuthenticationFailed)
+ ctx.Error(fmt.Errorf("unable to redirect for authorization/consent for client with id '%s' with consent challenge id '%s': user is anonymous", client.GetID(), consent.ChallengeID), messageAuthenticationFailed)
return
}
if !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel) {
- ctx.Logger.Warnf("OpenID Connect client '%s' requires 2FA, cannot be redirected yet", client.ID)
+ ctx.Logger.Warnf("OpenID Connect client '%s' requires 2FA, cannot be redirected yet", client.GetID())
ctx.ReplyOK()
return
diff --git a/internal/handlers/types.go b/internal/handlers/types.go
index 6079f1ac0..b69fbce95 100644
--- a/internal/handlers/types.go
+++ b/internal/handlers/types.go
@@ -143,7 +143,7 @@ type PasswordPolicyBody struct {
}
type handlerAuthorizationConsent func(
- ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client,
+ ctx *middlewares.AutheliaCtx, issuer *url.URL, client oidc.Client,
userSession session.UserSession, subject uuid.UUID,
rw http.ResponseWriter, r *http.Request,
requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool)
diff --git a/internal/oidc/client.go b/internal/oidc/client.go
index 41d302d03..71b43d867 100644
--- a/internal/oidc/client.go
+++ b/internal/oidc/client.go
@@ -1,8 +1,10 @@
package oidc
import (
+ "github.com/go-crypt/crypt/algorithm"
"github.com/ory/fosite"
"github.com/ory/x/errorsx"
+ "gopkg.in/square/go-jose.v2"
"github.com/authelia/authelia/v4/internal/authentication"
"github.com/authelia/authelia/v4/internal/authorization"
@@ -11,8 +13,8 @@ import (
)
// NewClient creates a new Client.
-func NewClient(config schema.OpenIDConnectClientConfiguration) (client *Client) {
- client = &Client{
+func NewClient(config schema.OpenIDConnectClientConfiguration) (client Client) {
+ base := &BaseClient{
ID: config.ID,
Description: config.Description,
Secret: config.Secret,
@@ -40,14 +42,165 @@ func NewClient(config schema.OpenIDConnectClientConfiguration) (client *Client)
}
for _, mode := range config.ResponseModes {
- client.ResponseModes = append(client.ResponseModes, fosite.ResponseModeType(mode))
+ base.ResponseModes = append(base.ResponseModes, fosite.ResponseModeType(mode))
+ }
+
+ if config.TokenEndpointAuthMethod != "" && config.TokenEndpointAuthMethod != "auto" {
+ client = &FullClient{
+ BaseClient: base,
+ TokenEndpointAuthMethod: config.TokenEndpointAuthMethod,
+ }
+ } else {
+ client = base
}
return client
}
+// GetID returns the ID.
+func (c *BaseClient) GetID() string {
+ return c.ID
+}
+
+// GetDescription returns the Description.
+func (c *BaseClient) GetDescription() string {
+ if c.Description == "" {
+ c.Description = c.GetID()
+ }
+
+ return c.Description
+}
+
+// GetSecret returns the Secret.
+func (c *BaseClient) GetSecret() algorithm.Digest {
+ return c.Secret
+}
+
+// GetSectorIdentifier returns the SectorIdentifier for this client.
+func (c *BaseClient) GetSectorIdentifier() string {
+ return c.SectorIdentifier
+}
+
+// GetHashedSecret returns the Secret.
+func (c *BaseClient) GetHashedSecret() (secret []byte) {
+ if c.Secret == nil {
+ return []byte(nil)
+ }
+
+ return []byte(c.Secret.Encode())
+}
+
+// GetRedirectURIs returns the RedirectURIs.
+func (c *BaseClient) GetRedirectURIs() (redirectURIs []string) {
+ return c.RedirectURIs
+}
+
+// GetGrantTypes returns the GrantTypes.
+func (c *BaseClient) GetGrantTypes() fosite.Arguments {
+ if len(c.GrantTypes) == 0 {
+ return fosite.Arguments{"authorization_code"}
+ }
+
+ return c.GrantTypes
+}
+
+// GetResponseTypes returns the ResponseTypes.
+func (c *BaseClient) GetResponseTypes() fosite.Arguments {
+ if len(c.ResponseTypes) == 0 {
+ return fosite.Arguments{"code"}
+ }
+
+ return c.ResponseTypes
+}
+
+// GetScopes returns the Scopes.
+func (c *BaseClient) GetScopes() fosite.Arguments {
+ return c.Scopes
+}
+
+// GetAudience returns the Audience.
+func (c *BaseClient) GetAudience() fosite.Arguments {
+ return c.Audience
+}
+
+// GetResponseModes returns the valid response modes for this client.
+//
+// Implements the fosite.ResponseModeClient.
+func (c *BaseClient) GetResponseModes() []fosite.ResponseModeType {
+ return c.ResponseModes
+}
+
+// GetUserinfoSigningAlgorithm returns the UserinfoSigningAlgorithm.
+func (c *BaseClient) GetUserinfoSigningAlgorithm() string {
+ if c.UserinfoSigningAlgorithm == "" {
+ c.UserinfoSigningAlgorithm = SigningAlgorithmNone
+ }
+
+ return c.UserinfoSigningAlgorithm
+}
+
+// GetPAREnforcement returns EnforcePAR.
+func (c *BaseClient) GetPAREnforcement() bool {
+ return c.EnforcePAR
+}
+
+// GetPKCEEnforcement returns EnforcePKCE.
+func (c *BaseClient) GetPKCEEnforcement() bool {
+ return c.EnforcePKCE
+}
+
+// GetPKCEChallengeMethodEnforcement returns EnforcePKCEChallengeMethod.
+func (c *BaseClient) GetPKCEChallengeMethodEnforcement() bool {
+ return c.EnforcePKCEChallengeMethod
+}
+
+// GetPKCEChallengeMethod returns PKCEChallengeMethod.
+func (c *BaseClient) GetPKCEChallengeMethod() string {
+ return c.PKCEChallengeMethod
+}
+
+// GetAuthorizationPolicy returns Policy.
+func (c *BaseClient) GetAuthorizationPolicy() authorization.Level {
+ return c.Policy
+}
+
+// GetConsentPolicy returns Consent.
+func (c *BaseClient) GetConsentPolicy() ClientConsent {
+ return c.Consent
+}
+
+// GetConsentResponseBody returns the proper consent response body for this session.OIDCWorkflowSession.
+func (c *BaseClient) GetConsentResponseBody(consent *model.OAuth2ConsentSession) ConsentGetResponseBody {
+ body := ConsentGetResponseBody{
+ ClientID: c.ID,
+ ClientDescription: c.Description,
+ PreConfiguration: c.Consent.Mode == ClientConsentModePreConfigured,
+ }
+
+ if consent != nil {
+ body.Scopes = consent.RequestedScopes
+ body.Audience = consent.RequestedAudience
+ }
+
+ return body
+}
+
+// IsPublic returns the value of the Public property.
+func (c *BaseClient) IsPublic() bool {
+ return c.Public
+}
+
+// IsAuthenticationLevelSufficient returns if the provided authentication.Level is sufficient for the client of the AutheliaClient.
+func (c *BaseClient) IsAuthenticationLevelSufficient(level authentication.Level) bool {
+ if level == authentication.NotAuthenticated {
+ return false
+ }
+
+ return authorization.IsAuthLevelSufficient(level, c.Policy)
+}
+
// ValidatePKCEPolicy is a helper function to validate PKCE policy constraints on a per-client basis.
-func (c *Client) ValidatePKCEPolicy(r fosite.Requester) (err error) {
+func (c *BaseClient) ValidatePKCEPolicy(r fosite.Requester) (err error) {
form := r.GetRequestForm()
if c.EnforcePKCE {
@@ -70,7 +223,7 @@ func (c *Client) ValidatePKCEPolicy(r fosite.Requester) (err error) {
}
// ValidatePARPolicy is a helper function to validate additional policy constraints on a per-client basis.
-func (c *Client) ValidatePARPolicy(r fosite.Requester, prefix string) (err error) {
+func (c *BaseClient) ValidatePARPolicy(r fosite.Requester, prefix string) (err error) {
if c.EnforcePAR {
if !IsPushedAuthorizedRequest(r, prefix) {
switch requestURI := r.GetRequestForm().Get(FormParameterRequestURI); requestURI {
@@ -87,7 +240,7 @@ func (c *Client) ValidatePARPolicy(r fosite.Requester, prefix string) (err error
// ValidateResponseModePolicy is an additional check to the response mode parameter to ensure if it's omitted that the
// default response mode for the fosite.AuthorizeRequester is permitted.
-func (c *Client) ValidateResponseModePolicy(r fosite.AuthorizeRequester) (err error) {
+func (c *BaseClient) ValidateResponseModePolicy(r fosite.AuthorizeRequester) (err error) {
if r.GetResponseMode() != fosite.ResponseModeDefault {
return nil
}
@@ -109,91 +262,52 @@ func (c *Client) ValidateResponseModePolicy(r fosite.AuthorizeRequester) (err er
return errorsx.WithStack(fosite.ErrUnsupportedResponseMode.WithHintf(`The request omitted the response_mode making the default response_mode "%s" based on the other authorization request parameters but registered OAuth 2.0 client doesn't support this response_mode`, m))
}
-// IsAuthenticationLevelSufficient returns if the provided authentication.Level is sufficient for the client of the AutheliaClient.
-func (c *Client) IsAuthenticationLevelSufficient(level authentication.Level) bool {
- if level == authentication.NotAuthenticated {
- return false
- }
-
- return authorization.IsAuthLevelSufficient(level, c.Policy)
-}
-
-// GetSectorIdentifier returns the SectorIdentifier for this client.
-func (c *Client) GetSectorIdentifier() string {
- return c.SectorIdentifier
-}
-
-// GetConsentResponseBody returns the proper consent response body for this session.OIDCWorkflowSession.
-func (c *Client) GetConsentResponseBody(consent *model.OAuth2ConsentSession) ConsentGetResponseBody {
- body := ConsentGetResponseBody{
- ClientID: c.ID,
- ClientDescription: c.Description,
- PreConfiguration: c.Consent.Mode == ClientConsentModePreConfigured,
- }
-
- if consent != nil {
- body.Scopes = consent.RequestedScopes
- body.Audience = consent.RequestedAudience
- }
-
- return body
+// GetRequestURIs is an array of request_uri values that are pre-registered by the RP for use at the OP. Servers MAY
+// cache the contents of the files referenced by these URIs and not retrieve them at the time they are used in a request.
+// OPs can require that request_uri values used be pre-registered with the require_request_uri_registration
+// discovery parameter.
+func (c *FullClient) GetRequestURIs() []string {
+ return c.RequestURIs
}
-// GetID returns the ID.
-func (c *Client) GetID() string {
- return c.ID
+// GetJSONWebKeys returns the JSON Web Key Set containing the public key used by the client to authenticate.
+func (c *FullClient) GetJSONWebKeys() *jose.JSONWebKeySet {
+ return c.JSONWebKeys
}
-// GetHashedSecret returns the Secret.
-func (c *Client) GetHashedSecret() (secret []byte) {
- if c.Secret == nil {
- return []byte(nil)
- }
-
- return []byte(c.Secret.Encode())
+// GetJSONWebKeysURI returns the URL for lookup of JSON Web Key Set containing the
+// public key used by the client to authenticate.
+func (c *FullClient) GetJSONWebKeysURI() string {
+ return c.JSONWebKeysURI
}
-// GetRedirectURIs returns the RedirectURIs.
-func (c *Client) GetRedirectURIs() (redirectURIs []string) {
- return c.RedirectURIs
+// GetRequestObjectSigningAlgorithm returns the JWS [JWS] alg algorithm [JWA] that MUST be used for signing Request
+// Objects sent to the OP. All Request Objects from this Client MUST be rejected, if not signed with this algorithm.
+func (c *FullClient) GetRequestObjectSigningAlgorithm() string {
+ return c.RequestObjectSigningAlgorithm
}
-// GetGrantTypes returns the GrantTypes.
-func (c *Client) GetGrantTypes() fosite.Arguments {
- if len(c.GrantTypes) == 0 {
- return fosite.Arguments{"authorization_code"}
+// GetTokenEndpointAuthMethod returns the requested Client Authentication Method for the Token Endpoint. The options are
+// client_secret_post, client_secret_basic, client_secret_jwt, private_key_jwt, and none.
+func (c *FullClient) GetTokenEndpointAuthMethod() string {
+ if c.TokenEndpointAuthMethod == "" {
+ if c.Public {
+ c.TokenEndpointAuthMethod = ClientAuthMethodNone
+ } else {
+ c.TokenEndpointAuthMethod = ClientAuthMethodClientSecretPost
+ }
}
- return c.GrantTypes
+ return c.TokenEndpointAuthMethod
}
-// GetResponseTypes returns the ResponseTypes.
-func (c *Client) GetResponseTypes() fosite.Arguments {
- if len(c.ResponseTypes) == 0 {
- return fosite.Arguments{"code"}
+// GetTokenEndpointAuthSigningAlgorithm returns the JWS [JWS] alg algorithm [JWA] that MUST be used for signing the JWT
+// [JWT] used to authenticate the Client at the Token Endpoint for the private_key_jwt and client_secret_jwt
+// authentication methods.
+func (c *FullClient) GetTokenEndpointAuthSigningAlgorithm() string {
+ if c.TokenEndpointAuthSigningAlgorithm == "" {
+ c.TokenEndpointAuthSigningAlgorithm = SigningAlgorithmRSAWithSHA256
}
- return c.ResponseTypes
-}
-
-// GetScopes returns the Scopes.
-func (c *Client) GetScopes() fosite.Arguments {
- return c.Scopes
-}
-
-// IsPublic returns the value of the Public property.
-func (c *Client) IsPublic() bool {
- return c.Public
-}
-
-// GetAudience returns the Audience.
-func (c *Client) GetAudience() fosite.Arguments {
- return c.Audience
-}
-
-// GetResponseModes returns the valid response modes for this client.
-//
-// Implements the fosite.ResponseModeClient.
-func (c *Client) GetResponseModes() []fosite.ResponseModeType {
- return c.ResponseModes
+ return c.TokenEndpointAuthSigningAlgorithm
}
diff --git a/internal/oidc/client_test.go b/internal/oidc/client_test.go
index 1546644c2..a4be63346 100644
--- a/internal/oidc/client_test.go
+++ b/internal/oidc/client_test.go
@@ -7,6 +7,7 @@ import (
"github.com/ory/fosite"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
+ "gopkg.in/square/go-jose.v2"
"github.com/authelia/authelia/v4/internal/authentication"
"github.com/authelia/authelia/v4/internal/authorization"
@@ -15,36 +16,136 @@ import (
)
func TestNewClient(t *testing.T) {
- blankConfig := schema.OpenIDConnectClientConfiguration{}
- blankClient := NewClient(blankConfig)
- assert.Equal(t, "", blankClient.ID)
- assert.Equal(t, "", blankClient.Description)
- assert.Equal(t, "", blankClient.Description)
- assert.Len(t, blankClient.ResponseModes, 0)
-
- exampleConfig := schema.OpenIDConnectClientConfiguration{
- ID: "myapp",
- Description: "My App",
- Policy: "two_factor",
- Secret: MustDecodeSecret("$plaintext$abcdef"),
- RedirectURIs: []string{"https://google.com/callback"},
+ config := schema.OpenIDConnectClientConfiguration{}
+ client := NewClient(config)
+ assert.Equal(t, "", client.GetID())
+ assert.Equal(t, "", client.GetDescription())
+ assert.Len(t, client.GetResponseModes(), 0)
+ assert.Len(t, client.GetResponseTypes(), 1)
+ assert.Equal(t, "", client.GetSectorIdentifier())
+
+ bclient, ok := client.(*BaseClient)
+ require.True(t, ok)
+ assert.Equal(t, "", bclient.UserinfoSigningAlgorithm)
+ assert.Equal(t, SigningAlgorithmNone, client.GetUserinfoSigningAlgorithm())
+
+ _, ok = client.(*FullClient)
+ assert.False(t, ok)
+
+ config = schema.OpenIDConnectClientConfiguration{
+ ID: myclient,
+ Description: myclientdesc,
+ Policy: twofactor,
+ Secret: MustDecodeSecret(badsecret),
+ RedirectURIs: []string{examplecom},
Scopes: schema.DefaultOpenIDConnectClientConfiguration.Scopes,
ResponseTypes: schema.DefaultOpenIDConnectClientConfiguration.ResponseTypes,
GrantTypes: schema.DefaultOpenIDConnectClientConfiguration.GrantTypes,
ResponseModes: schema.DefaultOpenIDConnectClientConfiguration.ResponseModes,
}
- exampleClient := NewClient(exampleConfig)
- assert.Equal(t, "myapp", exampleClient.ID)
- require.Len(t, exampleClient.ResponseModes, 3)
- assert.Equal(t, fosite.ResponseModeFormPost, exampleClient.ResponseModes[0])
- assert.Equal(t, fosite.ResponseModeQuery, exampleClient.ResponseModes[1])
- assert.Equal(t, fosite.ResponseModeFragment, exampleClient.ResponseModes[2])
- assert.Equal(t, authorization.TwoFactor, exampleClient.Policy)
+ client = NewClient(config)
+ assert.Equal(t, myclient, client.GetID())
+ require.Len(t, client.GetResponseModes(), 1)
+ assert.Equal(t, fosite.ResponseModeFormPost, client.GetResponseModes()[0])
+ assert.Equal(t, authorization.TwoFactor, client.GetAuthorizationPolicy())
+
+ config = schema.OpenIDConnectClientConfiguration{
+ TokenEndpointAuthMethod: ClientAuthMethodClientSecretBasic,
+ }
+
+ client = NewClient(config)
+
+ fclient, ok := client.(*FullClient)
+
+ var niljwks *jose.JSONWebKeySet
+
+ require.True(t, ok)
+ assert.Equal(t, "", fclient.UserinfoSigningAlgorithm)
+ assert.Equal(t, ClientAuthMethodClientSecretBasic, fclient.TokenEndpointAuthMethod)
+ assert.Equal(t, ClientAuthMethodClientSecretBasic, fclient.GetTokenEndpointAuthMethod())
+ assert.Equal(t, SigningAlgorithmNone, client.GetUserinfoSigningAlgorithm())
+ assert.Equal(t, "", fclient.TokenEndpointAuthSigningAlgorithm)
+ assert.Equal(t, SigningAlgorithmRSAWithSHA256, fclient.GetTokenEndpointAuthSigningAlgorithm())
+ assert.Equal(t, "", fclient.RequestObjectSigningAlgorithm)
+ assert.Equal(t, "", fclient.GetRequestObjectSigningAlgorithm())
+ assert.Equal(t, "", fclient.JSONWebKeysURI)
+ assert.Equal(t, "", fclient.GetJSONWebKeysURI())
+ assert.Equal(t, niljwks, fclient.JSONWebKeys)
+ assert.Equal(t, niljwks, fclient.GetJSONWebKeys())
+ assert.Equal(t, []string(nil), fclient.RequestURIs)
+ assert.Equal(t, []string(nil), fclient.GetRequestURIs())
+}
+
+func TestBaseClient_ValidatePARPolicy(t *testing.T) {
+ testCases := []struct {
+ name string
+ client *BaseClient
+ have *fosite.Request
+ expected string
+ }{
+ {
+ "ShouldNotEnforcePAR",
+ &BaseClient{
+ EnforcePAR: false,
+ },
+ &fosite.Request{},
+ "",
+ },
+ {
+ "ShouldEnforcePARAndErrorWithoutCorrectRequestURI",
+ &BaseClient{
+ EnforcePAR: true,
+ },
+ &fosite.Request{
+ Form: map[string][]string{
+ FormParameterRequestURI: {"https://google.com"},
+ },
+ },
+ "invalid_request",
+ },
+ {
+ "ShouldEnforcePARAndErrorWithEmptyRequestURI",
+ &BaseClient{
+ EnforcePAR: true,
+ },
+ &fosite.Request{
+ Form: map[string][]string{
+ FormParameterRequestURI: {""},
+ },
+ },
+ "invalid_request",
+ },
+ {
+ "ShouldEnforcePARAndNotErrorWithCorrectRequestURI",
+ &BaseClient{
+ EnforcePAR: true,
+ },
+ &fosite.Request{
+ Form: map[string][]string{
+ FormParameterRequestURI: {urnPARPrefix + "abc"},
+ },
+ },
+ "",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ err := tc.client.ValidatePARPolicy(tc.have, urnPARPrefix)
+
+ switch tc.expected {
+ case "":
+ assert.NoError(t, err)
+ default:
+ assert.EqualError(t, err, tc.expected)
+ }
+ })
+ }
}
func TestIsAuthenticationLevelSufficient(t *testing.T) {
- c := Client{}
+ c := &FullClient{BaseClient: &BaseClient{}}
c.Policy = authorization.Bypass
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated))
@@ -68,7 +169,7 @@ func TestIsAuthenticationLevelSufficient(t *testing.T) {
}
func TestClient_GetConsentResponseBody(t *testing.T) {
- c := Client{}
+ c := &FullClient{BaseClient: &BaseClient{}}
consentRequestBody := c.GetConsentResponseBody(nil)
assert.Equal(t, "", consentRequestBody.ClientID)
@@ -76,56 +177,56 @@ func TestClient_GetConsentResponseBody(t *testing.T) {
assert.Equal(t, []string(nil), consentRequestBody.Scopes)
assert.Equal(t, []string(nil), consentRequestBody.Audience)
- c.ID = "myclient"
- c.Description = "My Client"
+ c.ID = myclient
+ c.Description = myclientdesc
consent := &model.OAuth2ConsentSession{
- RequestedAudience: []string{"https://example.com"},
- RequestedScopes: []string{"openid", "groups"},
+ RequestedAudience: []string{examplecom},
+ RequestedScopes: []string{ScopeOpenID, ScopeGroups},
}
- expectedScopes := []string{"openid", "groups"}
- expectedAudiences := []string{"https://example.com"}
+ expectedScopes := []string{ScopeOpenID, ScopeGroups}
+ expectedAudiences := []string{examplecom}
consentRequestBody = c.GetConsentResponseBody(consent)
- assert.Equal(t, "myclient", consentRequestBody.ClientID)
- assert.Equal(t, "My Client", consentRequestBody.ClientDescription)
+ assert.Equal(t, myclient, consentRequestBody.ClientID)
+ assert.Equal(t, myclientdesc, consentRequestBody.ClientDescription)
assert.Equal(t, expectedScopes, consentRequestBody.Scopes)
assert.Equal(t, expectedAudiences, consentRequestBody.Audience)
}
func TestClient_GetAudience(t *testing.T) {
- c := Client{}
+ c := &FullClient{BaseClient: &BaseClient{}}
audience := c.GetAudience()
assert.Len(t, audience, 0)
- c.Audience = []string{"https://example.com"}
+ c.Audience = []string{examplecom}
audience = c.GetAudience()
require.Len(t, audience, 1)
- assert.Equal(t, "https://example.com", audience[0])
+ assert.Equal(t, examplecom, audience[0])
}
func TestClient_GetScopes(t *testing.T) {
- c := Client{}
+ c := &FullClient{BaseClient: &BaseClient{}}
scopes := c.GetScopes()
assert.Len(t, scopes, 0)
- c.Scopes = []string{"openid"}
+ c.Scopes = []string{ScopeOpenID}
scopes = c.GetScopes()
require.Len(t, scopes, 1)
- assert.Equal(t, "openid", scopes[0])
+ assert.Equal(t, ScopeOpenID, scopes[0])
}
func TestClient_GetGrantTypes(t *testing.T) {
- c := Client{}
+ c := &FullClient{BaseClient: &BaseClient{}}
grantTypes := c.GetGrantTypes()
require.Len(t, grantTypes, 1)
- assert.Equal(t, "authorization_code", grantTypes[0])
+ assert.Equal(t, GrantTypeAuthorizationCode, grantTypes[0])
c.GrantTypes = []string{"device_code"}
@@ -135,55 +236,55 @@ func TestClient_GetGrantTypes(t *testing.T) {
}
func TestClient_Hashing(t *testing.T) {
- c := Client{}
+ c := &FullClient{BaseClient: &BaseClient{}}
hashedSecret := c.GetHashedSecret()
assert.Equal(t, []byte(nil), hashedSecret)
- c.Secret = MustDecodeSecret("$plaintext$a_bad_secret")
+ c.Secret = MustDecodeSecret(badsecret)
assert.True(t, c.Secret.MatchBytes([]byte("a_bad_secret")))
}
func TestClient_GetHashedSecret(t *testing.T) {
- c := Client{}
+ c := &FullClient{BaseClient: &BaseClient{}}
hashedSecret := c.GetHashedSecret()
assert.Equal(t, []byte(nil), hashedSecret)
- c.Secret = MustDecodeSecret("$plaintext$a_bad_secret")
+ c.Secret = MustDecodeSecret(badsecret)
hashedSecret = c.GetHashedSecret()
- assert.Equal(t, []byte("$plaintext$a_bad_secret"), hashedSecret)
+ assert.Equal(t, []byte(badsecret), hashedSecret)
}
func TestClient_GetID(t *testing.T) {
- c := Client{}
+ c := &FullClient{BaseClient: &BaseClient{}}
id := c.GetID()
assert.Equal(t, "", id)
- c.ID = "myid"
+ c.ID = myclient
id = c.GetID()
- assert.Equal(t, "myid", id)
+ assert.Equal(t, myclient, id)
}
func TestClient_GetRedirectURIs(t *testing.T) {
- c := Client{}
+ c := &FullClient{BaseClient: &BaseClient{}}
redirectURIs := c.GetRedirectURIs()
require.Len(t, redirectURIs, 0)
- c.RedirectURIs = []string{"https://example.com/oauth2/callback"}
+ c.RedirectURIs = []string{examplecom}
redirectURIs = c.GetRedirectURIs()
require.Len(t, redirectURIs, 1)
- assert.Equal(t, "https://example.com/oauth2/callback", redirectURIs[0])
+ assert.Equal(t, examplecom, redirectURIs[0])
}
func TestClient_GetResponseModes(t *testing.T) {
- c := Client{}
+ c := &FullClient{BaseClient: &BaseClient{}}
responseModes := c.GetResponseModes()
require.Len(t, responseModes, 0)
@@ -202,18 +303,18 @@ func TestClient_GetResponseModes(t *testing.T) {
}
func TestClient_GetResponseTypes(t *testing.T) {
- c := Client{}
+ c := &FullClient{BaseClient: &BaseClient{}}
responseTypes := c.GetResponseTypes()
require.Len(t, responseTypes, 1)
- assert.Equal(t, "code", responseTypes[0])
+ assert.Equal(t, ResponseTypeAuthorizationCodeFlow, responseTypes[0])
- c.ResponseTypes = []string{"code", "id_token"}
+ c.ResponseTypes = []string{ResponseTypeAuthorizationCodeFlow, ResponseTypeImplicitFlowIDToken}
responseTypes = c.GetResponseTypes()
require.Len(t, responseTypes, 2)
- assert.Equal(t, "code", responseTypes[0])
- assert.Equal(t, "id_token", responseTypes[1])
+ assert.Equal(t, ResponseTypeAuthorizationCodeFlow, responseTypes[0])
+ assert.Equal(t, ResponseTypeImplicitFlowIDToken, responseTypes[1])
}
func TestNewClientPKCE(t *testing.T) {
@@ -290,9 +391,9 @@ func TestNewClientPKCE(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
client := NewClient(tc.have)
- assert.Equal(t, tc.expectedEnforcePKCE, client.EnforcePKCE)
- assert.Equal(t, tc.expectedEnforcePKCEChallengeMethod, client.EnforcePKCEChallengeMethod)
- assert.Equal(t, tc.expected, client.PKCEChallengeMethod)
+ assert.Equal(t, tc.expectedEnforcePKCE, client.GetPKCEEnforcement())
+ assert.Equal(t, tc.expectedEnforcePKCEChallengeMethod, client.GetPKCEChallengeMethodEnforcement())
+ assert.Equal(t, tc.expected, client.GetPKCEChallengeMethod())
if tc.r != nil {
err := client.ValidatePKCEPolicy(tc.r)
@@ -355,7 +456,7 @@ func TestNewClientPAR(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
client := NewClient(tc.have)
- assert.Equal(t, tc.expected, client.EnforcePAR)
+ assert.Equal(t, tc.expected, client.GetPAREnforcement())
if tc.r != nil {
err := client.ValidatePARPolicy(tc.r, urnPARPrefix)
@@ -437,7 +538,7 @@ func TestNewClientResponseModes(t *testing.T) {
}
func TestClient_IsPublic(t *testing.T) {
- c := Client{}
+ c := &FullClient{BaseClient: &BaseClient{}}
assert.False(t, c.IsPublic())
diff --git a/internal/oidc/config.go b/internal/oidc/config.go
index 1cc7bf098..1db4e757a 100644
--- a/internal/oidc/config.go
+++ b/internal/oidc/config.go
@@ -169,12 +169,6 @@ type LifespanConfig struct {
RefreshToken time.Duration
}
-const (
- PromptNone = none
- PromptLogin = "login"
- PromptConsent = "consent"
-)
-
// LoadHandlers reloads the handlers based on the current configuration.
func (c *Config) LoadHandlers(store *Store, strategy jwt.Signer) {
validator := openid.NewOpenIDConnectRequestValidator(strategy, c)
diff --git a/internal/oidc/const.go b/internal/oidc/const.go
index db8c3a23d..01670e317 100644
--- a/internal/oidc/const.go
+++ b/internal/oidc/const.go
@@ -69,15 +69,12 @@ const (
GrantTypeImplicit = implicit
GrantTypeRefreshToken = "refresh_token"
GrantTypeAuthorizationCode = "authorization_code"
- GrantTypePassword = "password"
- GrantTypeClientCredentials = "client_credentials"
)
// Client Auth Method strings.
const (
ClientAuthMethodClientSecretBasic = "client_secret_basic"
ClientAuthMethodClientSecretPost = "client_secret_post"
- ClientAuthMethodClientSecretJWT = "client_secret_jwt"
ClientAuthMethodNone = "none"
)
@@ -117,6 +114,13 @@ const (
FormParameterCodeChallengeMethod = "code_challenge_method"
)
+const (
+ PromptNone = none
+ PromptLogin = "login"
+ PromptConsent = "consent"
+ // PromptCreate = "create" // This prompt value is currently unused.
+)
+
// Endpoints.
const (
EndpointAuthorization = "authorization"
diff --git a/internal/oidc/const_test.go b/internal/oidc/const_test.go
new file mode 100644
index 000000000..b5e3d915b
--- /dev/null
+++ b/internal/oidc/const_test.go
@@ -0,0 +1,12 @@
+package oidc
+
+const (
+ myclient = "myclient"
+ myclientdesc = "My Client"
+ onefactor = "one_factor"
+ twofactor = "two_factor"
+ examplecom = "https://example.com"
+ examplecomsid = "example.com"
+ badsecret = "$plaintext$a_bad_secret"
+ badhmac = "asbdhaaskmdlkamdklasmdlkams"
+)
diff --git a/internal/oidc/discovery.go b/internal/oidc/discovery.go
index 996e5e3f5..e57bad146 100644
--- a/internal/oidc/discovery.go
+++ b/internal/oidc/discovery.go
@@ -5,70 +5,76 @@ import (
)
// NewOpenIDConnectWellKnownConfiguration generates a new OpenIDConnectWellKnownConfiguration.
-func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration, clients map[string]*Client) (config OpenIDConnectWellKnownConfiguration) {
+func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration) (config OpenIDConnectWellKnownConfiguration) {
config = OpenIDConnectWellKnownConfiguration{
- CommonDiscoveryOptions: CommonDiscoveryOptions{
- SubjectTypesSupported: []string{
- SubjectTypePublic,
+ OAuth2WellKnownConfiguration: OAuth2WellKnownConfiguration{
+ CommonDiscoveryOptions: CommonDiscoveryOptions{
+ SubjectTypesSupported: []string{
+ SubjectTypePublic,
+ SubjectTypePairwise,
+ },
+ ResponseTypesSupported: []string{
+ ResponseTypeAuthorizationCodeFlow,
+ ResponseTypeImplicitFlowIDToken,
+ ResponseTypeImplicitFlowToken,
+ ResponseTypeImplicitFlowBoth,
+ ResponseTypeHybridFlowIDToken,
+ ResponseTypeHybridFlowToken,
+ ResponseTypeHybridFlowBoth,
+ },
+ GrantTypesSupported: []string{
+ GrantTypeAuthorizationCode,
+ GrantTypeImplicit,
+ GrantTypeRefreshToken,
+ },
+ ResponseModesSupported: []string{
+ ResponseModeFormPost,
+ ResponseModeQuery,
+ ResponseModeFragment,
+ },
+ ScopesSupported: []string{
+ ScopeOfflineAccess,
+ ScopeOpenID,
+ ScopeProfile,
+ ScopeGroups,
+ ScopeEmail,
+ },
+ ClaimsSupported: []string{
+ ClaimAuthenticationMethodsReference,
+ ClaimAudience,
+ ClaimAuthorizedParty,
+ ClaimClientIdentifier,
+ ClaimExpirationTime,
+ ClaimIssuedAt,
+ ClaimIssuer,
+ ClaimJWTID,
+ ClaimRequestedAt,
+ ClaimSubject,
+ ClaimAuthenticationTime,
+ ClaimNonce,
+ ClaimPreferredEmail,
+ ClaimEmailVerified,
+ ClaimEmailAlts,
+ ClaimGroups,
+ ClaimPreferredUsername,
+ ClaimFullName,
+ },
+ TokenEndpointAuthMethodsSupported: []string{
+ ClientAuthMethodClientSecretBasic,
+ ClientAuthMethodClientSecretPost,
+ ClientAuthMethodNone,
+ },
},
- ResponseTypesSupported: []string{
- ResponseTypeAuthorizationCodeFlow,
- ResponseTypeImplicitFlowIDToken,
- ResponseTypeImplicitFlowToken,
- ResponseTypeImplicitFlowBoth,
- ResponseTypeHybridFlowIDToken,
- ResponseTypeHybridFlowToken,
- ResponseTypeHybridFlowBoth,
+ OAuth2DiscoveryOptions: OAuth2DiscoveryOptions{
+ CodeChallengeMethodsSupported: []string{
+ PKCEChallengeMethodSHA256,
+ },
},
- GrantTypesSupported: []string{
- GrantTypeAuthorizationCode,
- GrantTypeImplicit,
- GrantTypeRefreshToken,
- },
- ResponseModesSupported: []string{
- ResponseModeFormPost,
- ResponseModeQuery,
- ResponseModeFragment,
- },
- ScopesSupported: []string{
- ScopeOfflineAccess,
- ScopeOpenID,
- ScopeProfile,
- ScopeGroups,
- ScopeEmail,
- },
- ClaimsSupported: []string{
- ClaimAuthenticationMethodsReference,
- ClaimAudience,
- ClaimAuthorizedParty,
- ClaimClientIdentifier,
- ClaimExpirationTime,
- ClaimIssuedAt,
- ClaimIssuer,
- ClaimJWTID,
- ClaimRequestedAt,
- ClaimSubject,
- ClaimAuthenticationTime,
- ClaimNonce,
- ClaimPreferredEmail,
- ClaimEmailVerified,
- ClaimEmailAlts,
- ClaimGroups,
- ClaimPreferredUsername,
- ClaimFullName,
- },
- TokenEndpointAuthMethodsSupported: []string{
- ClientAuthMethodClientSecretBasic,
- ClientAuthMethodClientSecretPost,
- ClientAuthMethodClientSecretJWT,
- ClientAuthMethodNone,
- },
- },
- OAuth2DiscoveryOptions: OAuth2DiscoveryOptions{
- CodeChallengeMethodsSupported: []string{
- PKCEChallengeMethodSHA256,
+ OAuth2PushedAuthorizationDiscoveryOptions: &OAuth2PushedAuthorizationDiscoveryOptions{
+ RequirePushedAuthorizationRequests: c.PAR.Enforce,
},
},
+
OpenIDConnectDiscoveryOptions: OpenIDConnectDiscoveryOptions{
IDTokenSigningAlgValuesSupported: []string{
SigningAlgorithmRSAWithSHA256,
@@ -77,35 +83,110 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration
SigningAlgorithmNone,
SigningAlgorithmRSAWithSHA256,
},
- RequestObjectSigningAlgValuesSupported: []string{
- SigningAlgorithmNone,
- SigningAlgorithmRSAWithSHA256,
- },
},
- PushedAuthorizationDiscoveryOptions: PushedAuthorizationDiscoveryOptions{
- RequirePushedAuthorizationRequests: c.PAR.Enforce,
+ OpenIDConnectFrontChannelLogoutDiscoveryOptions: &OpenIDConnectFrontChannelLogoutDiscoveryOptions{},
+ OpenIDConnectBackChannelLogoutDiscoveryOptions: &OpenIDConnectBackChannelLogoutDiscoveryOptions{},
+ OpenIDConnectPromptCreateDiscoveryOptions: &OpenIDConnectPromptCreateDiscoveryOptions{
+ PromptValuesSupported: []string{
+ PromptNone,
+ PromptConsent,
+ },
},
}
- var pairwise, public bool
+ if c.EnablePKCEPlainChallenge {
+ config.CodeChallengeMethodsSupported = append(config.CodeChallengeMethodsSupported, PKCEChallengeMethodPlain)
+ }
+
+ return config
+}
+
+// Copy the values of the OAuth2WellKnownConfiguration and return it as a new struct.
+func (opts OAuth2WellKnownConfiguration) Copy() (optsCopy OAuth2WellKnownConfiguration) {
+ optsCopy = OAuth2WellKnownConfiguration{
+ CommonDiscoveryOptions: opts.CommonDiscoveryOptions,
+ OAuth2DiscoveryOptions: opts.OAuth2DiscoveryOptions,
+ }
- for _, client := range clients {
- if pairwise && public {
- break
- }
+ if opts.OAuth2DeviceAuthorizationGrantDiscoveryOptions != nil {
+ optsCopy.OAuth2DeviceAuthorizationGrantDiscoveryOptions = &OAuth2DeviceAuthorizationGrantDiscoveryOptions{}
+ *optsCopy.OAuth2DeviceAuthorizationGrantDiscoveryOptions = *opts.OAuth2DeviceAuthorizationGrantDiscoveryOptions
+ }
- if client.SectorIdentifier != "" {
- pairwise = true
- }
+ if opts.OAuth2MutualTLSClientAuthenticationDiscoveryOptions != nil {
+ optsCopy.OAuth2MutualTLSClientAuthenticationDiscoveryOptions = &OAuth2MutualTLSClientAuthenticationDiscoveryOptions{}
+ *optsCopy.OAuth2MutualTLSClientAuthenticationDiscoveryOptions = *opts.OAuth2MutualTLSClientAuthenticationDiscoveryOptions
}
- if pairwise {
- config.SubjectTypesSupported = append(config.SubjectTypesSupported, SubjectTypePairwise)
+ if opts.OAuth2IssuerIdentificationDiscoveryOptions != nil {
+ optsCopy.OAuth2IssuerIdentificationDiscoveryOptions = &OAuth2IssuerIdentificationDiscoveryOptions{}
+ *optsCopy.OAuth2IssuerIdentificationDiscoveryOptions = *opts.OAuth2IssuerIdentificationDiscoveryOptions
}
- if c.EnablePKCEPlainChallenge {
- config.CodeChallengeMethodsSupported = append(config.CodeChallengeMethodsSupported, PKCEChallengeMethodPlain)
+ if opts.OAuth2JWTIntrospectionResponseDiscoveryOptions != nil {
+ optsCopy.OAuth2JWTIntrospectionResponseDiscoveryOptions = &OAuth2JWTIntrospectionResponseDiscoveryOptions{}
+ *optsCopy.OAuth2JWTIntrospectionResponseDiscoveryOptions = *opts.OAuth2JWTIntrospectionResponseDiscoveryOptions
}
- return config
+ if opts.OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions != nil {
+ optsCopy.OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions = &OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions{}
+ *optsCopy.OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions = *opts.OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions
+ }
+
+ if opts.OAuth2PushedAuthorizationDiscoveryOptions != nil {
+ optsCopy.OAuth2PushedAuthorizationDiscoveryOptions = &OAuth2PushedAuthorizationDiscoveryOptions{}
+ *optsCopy.OAuth2PushedAuthorizationDiscoveryOptions = *opts.OAuth2PushedAuthorizationDiscoveryOptions
+ }
+
+ return optsCopy
+}
+
+// Copy the values of the OpenIDConnectWellKnownConfiguration and return it as a new struct.
+func (opts OpenIDConnectWellKnownConfiguration) Copy() (optsCopy OpenIDConnectWellKnownConfiguration) {
+ optsCopy = OpenIDConnectWellKnownConfiguration{
+ OAuth2WellKnownConfiguration: opts.OAuth2WellKnownConfiguration.Copy(),
+ OpenIDConnectDiscoveryOptions: opts.OpenIDConnectDiscoveryOptions,
+ }
+
+ if opts.OpenIDConnectFrontChannelLogoutDiscoveryOptions != nil {
+ optsCopy.OpenIDConnectFrontChannelLogoutDiscoveryOptions = &OpenIDConnectFrontChannelLogoutDiscoveryOptions{}
+ *optsCopy.OpenIDConnectFrontChannelLogoutDiscoveryOptions = *opts.OpenIDConnectFrontChannelLogoutDiscoveryOptions
+ }
+
+ if opts.OpenIDConnectBackChannelLogoutDiscoveryOptions != nil {
+ optsCopy.OpenIDConnectBackChannelLogoutDiscoveryOptions = &OpenIDConnectBackChannelLogoutDiscoveryOptions{}
+ *optsCopy.OpenIDConnectBackChannelLogoutDiscoveryOptions = *opts.OpenIDConnectBackChannelLogoutDiscoveryOptions
+ }
+
+ if opts.OpenIDConnectSessionManagementDiscoveryOptions != nil {
+ optsCopy.OpenIDConnectSessionManagementDiscoveryOptions = &OpenIDConnectSessionManagementDiscoveryOptions{}
+ *optsCopy.OpenIDConnectSessionManagementDiscoveryOptions = *opts.OpenIDConnectSessionManagementDiscoveryOptions
+ }
+
+ if opts.OpenIDConnectRPInitiatedLogoutDiscoveryOptions != nil {
+ optsCopy.OpenIDConnectRPInitiatedLogoutDiscoveryOptions = &OpenIDConnectRPInitiatedLogoutDiscoveryOptions{}
+ *optsCopy.OpenIDConnectRPInitiatedLogoutDiscoveryOptions = *opts.OpenIDConnectRPInitiatedLogoutDiscoveryOptions
+ }
+
+ if opts.OpenIDConnectPromptCreateDiscoveryOptions != nil {
+ optsCopy.OpenIDConnectPromptCreateDiscoveryOptions = &OpenIDConnectPromptCreateDiscoveryOptions{}
+ *optsCopy.OpenIDConnectPromptCreateDiscoveryOptions = *opts.OpenIDConnectPromptCreateDiscoveryOptions
+ }
+
+ if opts.OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions != nil {
+ optsCopy.OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions = &OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions{}
+ *optsCopy.OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions = *opts.OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions
+ }
+
+ if opts.OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions != nil {
+ optsCopy.OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions = &OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions{}
+ *optsCopy.OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions = *opts.OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions
+ }
+
+ if opts.OpenIDFederationDiscoveryOptions != nil {
+ optsCopy.OpenIDFederationDiscoveryOptions = &OpenIDFederationDiscoveryOptions{}
+ *optsCopy.OpenIDFederationDiscoveryOptions = *opts.OpenIDFederationDiscoveryOptions
+ }
+
+ return optsCopy
}
diff --git a/internal/oidc/discovery_test.go b/internal/oidc/discovery_test.go
index 63ad18b65..34ca1f732 100644
--- a/internal/oidc/discovery_test.go
+++ b/internal/oidc/discovery_test.go
@@ -13,51 +13,51 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
desc string
pkcePlainChallenge bool
enforcePAR bool
- clients map[string]*Client
+ clients map[string]Client
expectCodeChallengeMethodsSupported, expectSubjectTypesSupported []string
}{
{
desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublic",
pkcePlainChallenge: false,
- clients: map[string]*Client{"a": {}},
+ clients: map[string]Client{"a": &BaseClient{}},
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256},
- expectSubjectTypesSupported: []string{SubjectTypePublic},
+ expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
},
{
desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublic",
pkcePlainChallenge: true,
- clients: map[string]*Client{"a": {}},
+ clients: map[string]Client{"a": &BaseClient{}},
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
- expectSubjectTypesSupported: []string{SubjectTypePublic},
+ expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
},
{
desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublicPairwise",
pkcePlainChallenge: false,
- clients: map[string]*Client{"a": {SectorIdentifier: "yes"}},
+ clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}},
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256},
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
},
{
desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublicPairwise",
pkcePlainChallenge: true,
- clients: map[string]*Client{"a": {SectorIdentifier: "yes"}},
+ clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}},
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
},
{
desc: "ShouldHaveTokenAuthMethodsNone",
pkcePlainChallenge: true,
- clients: map[string]*Client{"a": {SectorIdentifier: "yes"}},
+ clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}},
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
},
{
desc: "ShouldHaveTokenAuthMethodsNone",
pkcePlainChallenge: true,
- clients: map[string]*Client{
- "a": {SectorIdentifier: "yes"},
- "b": {SectorIdentifier: "yes"},
+ clients: map[string]Client{
+ "a": &BaseClient{SectorIdentifier: "yes"},
+ "b": &BaseClient{SectorIdentifier: "yes"},
},
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
@@ -73,7 +73,7 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
},
}
- actual := NewOpenIDConnectWellKnownConfiguration(&c, tc.clients)
+ actual := NewOpenIDConnectWellKnownConfiguration(&c)
for _, codeChallengeMethod := range tc.expectCodeChallengeMethodsSupported {
assert.Contains(t, actual.CodeChallengeMethodsSupported, codeChallengeMethod)
}
diff --git a/internal/oidc/provider.go b/internal/oidc/provider.go
index 54bd1595d..b8333351b 100644
--- a/internal/oidc/provider.go
+++ b/internal/oidc/provider.go
@@ -37,17 +37,14 @@ func NewOpenIDConnectProvider(config *schema.OpenIDConnectConfiguration, store s
provider.Config.LoadHandlers(provider.Store, provider.KeyManager.Strategy())
- provider.discovery = NewOpenIDConnectWellKnownConfiguration(config, provider.Store.clients)
+ provider.discovery = NewOpenIDConnectWellKnownConfiguration(config)
return provider, nil
}
// GetOAuth2WellKnownConfiguration returns the discovery document for the OAuth Configuration.
func (p *OpenIDConnectProvider) GetOAuth2WellKnownConfiguration(issuer string) OAuth2WellKnownConfiguration {
- options := OAuth2WellKnownConfiguration{
- CommonDiscoveryOptions: p.discovery.CommonDiscoveryOptions,
- OAuth2DiscoveryOptions: p.discovery.OAuth2DiscoveryOptions,
- }
+ options := p.discovery.OAuth2WellKnownConfiguration.Copy()
options.Issuer = issuer
@@ -63,13 +60,7 @@ func (p *OpenIDConnectProvider) GetOAuth2WellKnownConfiguration(issuer string) O
// GetOpenIDConnectWellKnownConfiguration returns the discovery document for the OpenID Configuration.
func (p *OpenIDConnectProvider) GetOpenIDConnectWellKnownConfiguration(issuer string) OpenIDConnectWellKnownConfiguration {
- options := OpenIDConnectWellKnownConfiguration{
- CommonDiscoveryOptions: p.discovery.CommonDiscoveryOptions,
- OAuth2DiscoveryOptions: p.discovery.OAuth2DiscoveryOptions,
- OpenIDConnectDiscoveryOptions: p.discovery.OpenIDConnectDiscoveryOptions,
- OpenIDConnectFrontChannelLogoutDiscoveryOptions: p.discovery.OpenIDConnectFrontChannelLogoutDiscoveryOptions,
- OpenIDConnectBackChannelLogoutDiscoveryOptions: p.discovery.OpenIDConnectBackChannelLogoutDiscoveryOptions,
- }
+ options := p.discovery.Copy()
options.Issuer = issuer
diff --git a/internal/oidc/provider_test.go b/internal/oidc/provider_test.go
index 3045c6fc3..4a1a0b115 100644
--- a/internal/oidc/provider_test.go
+++ b/internal/oidc/provider_test.go
@@ -27,15 +27,15 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
EnablePKCEPlainChallenge: true,
- HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
+ HMACSecret: badhmac,
Clients: []schema.OpenIDConnectClientConfiguration{
{
- ID: "a-client",
- Secret: MustDecodeSecret("$plaintext$a-client-secret"),
- SectorIdentifier: url.URL{Host: "google.com"},
- Policy: "one_factor",
+ ID: myclient,
+ Secret: MustDecodeSecret(badsecret),
+ SectorIdentifier: url.URL{Host: examplecomsid},
+ Policy: onefactor,
RedirectURIs: []string{
- "https://google.com",
+ examplecom,
},
},
},
@@ -43,7 +43,7 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing
assert.NoError(t, err)
- disco := provider.GetOpenIDConnectWellKnownConfiguration("https://example.com")
+ disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom)
assert.Len(t, disco.SubjectTypesSupported, 2)
assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic)
@@ -58,12 +58,12 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *tes
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
- HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
+ HMACSecret: badhmac,
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "a-client",
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
- Policy: "one_factor",
+ Policy: onefactor,
RedirectURIs: []string{
"https://google.com",
},
@@ -72,7 +72,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *tes
ID: "b-client",
Description: "Normal Description",
Secret: MustDecodeSecret("$plaintext$b-client-secret"),
- Policy: "two_factor",
+ Policy: twofactor,
RedirectURIs: []string{
"https://google.com",
},
@@ -103,7 +103,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
{
ID: "a-client",
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
- Policy: "one_factor",
+ Policy: onefactor,
RedirectURIs: []string{
"https://google.com",
},
@@ -113,9 +113,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
assert.NoError(t, err)
- disco := provider.GetOpenIDConnectWellKnownConfiguration("https://example.com")
+ disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom)
- assert.Equal(t, "https://example.com", disco.Issuer)
+ assert.Equal(t, examplecom, disco.Issuer)
assert.Equal(t, "https://example.com/jwks.json", disco.JWKSURI)
assert.Equal(t, "https://example.com/api/oidc/authorization", disco.AuthorizationEndpoint)
assert.Equal(t, "https://example.com/api/oidc/token", disco.TokenEndpoint)
@@ -139,8 +139,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
assert.Contains(t, disco.ResponseModesSupported, ResponseModeQuery)
assert.Contains(t, disco.ResponseModesSupported, ResponseModeFragment)
- assert.Len(t, disco.SubjectTypesSupported, 1)
+ assert.Len(t, disco.SubjectTypesSupported, 2)
assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic)
+ assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePairwise)
assert.Len(t, disco.ResponseTypesSupported, 7)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeAuthorizationCodeFlow)
@@ -151,10 +152,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowToken)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowBoth)
- assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 4)
+ assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 3)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost)
- assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone)
assert.Len(t, disco.GrantTypesSupported, 3)
@@ -169,9 +169,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
assert.Contains(t, disco.UserinfoSigningAlgValuesSupported, SigningAlgorithmRSAWithSHA256)
assert.Contains(t, disco.UserinfoSigningAlgValuesSupported, SigningAlgorithmNone)
- assert.Len(t, disco.RequestObjectSigningAlgValuesSupported, 2)
- assert.Contains(t, disco.RequestObjectSigningAlgValuesSupported, SigningAlgorithmRSAWithSHA256)
- assert.Contains(t, disco.RequestObjectSigningAlgValuesSupported, SigningAlgorithmNone)
+ assert.Len(t, disco.RequestObjectSigningAlgValuesSupported, 0)
assert.Len(t, disco.ClaimsSupported, 18)
assert.Contains(t, disco.ClaimsSupported, ClaimAuthenticationMethodsReference)
@@ -203,7 +201,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
{
ID: "a-client",
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
- Policy: "one_factor",
+ Policy: onefactor,
RedirectURIs: []string{
"https://google.com",
},
@@ -213,9 +211,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
assert.NoError(t, err)
- disco := provider.GetOAuth2WellKnownConfiguration("https://example.com")
+ disco := provider.GetOAuth2WellKnownConfiguration(examplecom)
- assert.Equal(t, "https://example.com", disco.Issuer)
+ assert.Equal(t, examplecom, disco.Issuer)
assert.Equal(t, "https://example.com/jwks.json", disco.JWKSURI)
assert.Equal(t, "https://example.com/api/oidc/authorization", disco.AuthorizationEndpoint)
assert.Equal(t, "https://example.com/api/oidc/token", disco.TokenEndpoint)
@@ -238,8 +236,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
assert.Contains(t, disco.ResponseModesSupported, ResponseModeQuery)
assert.Contains(t, disco.ResponseModesSupported, ResponseModeFragment)
- assert.Len(t, disco.SubjectTypesSupported, 1)
+ assert.Len(t, disco.SubjectTypesSupported, 2)
assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic)
+ assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePairwise)
assert.Len(t, disco.ResponseTypesSupported, 7)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeAuthorizationCodeFlow)
@@ -250,10 +249,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowToken)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowBoth)
- assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 4)
+ assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 3)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost)
- assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone)
assert.Len(t, disco.GrantTypesSupported, 3)
@@ -292,7 +290,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
{
ID: "a-client",
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
- Policy: "one_factor",
+ Policy: onefactor,
RedirectURIs: []string{
"https://google.com",
},
@@ -302,7 +300,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
assert.NoError(t, err)
- disco := provider.GetOpenIDConnectWellKnownConfiguration("https://example.com")
+ disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom)
require.Len(t, disco.CodeChallengeMethodsSupported, 2)
assert.Equal(t, PKCEChallengeMethodSHA256, disco.CodeChallengeMethodsSupported[0])
diff --git a/internal/oidc/store.go b/internal/oidc/store.go
index a5a181c2b..2f21b368c 100644
--- a/internal/oidc/store.go
+++ b/internal/oidc/store.go
@@ -24,7 +24,7 @@ func NewStore(config *schema.OpenIDConnectConfiguration, provider storage.Provid
store = &Store{
provider: provider,
- clients: map[string]*Client{},
+ clients: map[string]Client{},
}
for _, client := range config.Clients {
@@ -72,11 +72,11 @@ func (s *Store) GetClientPolicy(id string) (level authorization.Level) {
return authorization.TwoFactor
}
- return client.Policy
+ return client.GetAuthorizationPolicy()
}
// GetFullClient returns a fosite.Client asserted as an Client matching the provided id.
-func (s *Store) GetFullClient(id string) (client *Client, err error) {
+func (s *Store) GetFullClient(id string) (client Client, err error) {
client, ok := s.clients[id]
if !ok {
return nil, fosite.ErrInvalidClient
diff --git a/internal/oidc/store_test.go b/internal/oidc/store_test.go
index 580e864e4..def1d4e8e 100644
--- a/internal/oidc/store_test.go
+++ b/internal/oidc/store_test.go
@@ -4,6 +4,7 @@ import (
"context"
"testing"
+ "github.com/ory/fosite"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -17,23 +18,23 @@ func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) {
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
Clients: []schema.OpenIDConnectClientConfiguration{
{
- ID: "myclient",
- Description: "myclient desc",
- Policy: "one_factor",
+ ID: myclient,
+ Description: myclientdesc,
+ Policy: onefactor,
Scopes: []string{ScopeOpenID, ScopeProfile},
Secret: MustDecodeSecret("$plaintext$mysecret"),
},
{
ID: "myotherclient",
- Description: "myclient desc",
- Policy: "two_factor",
+ Description: myclientdesc,
+ Policy: twofactor,
Scopes: []string{ScopeOpenID, ScopeProfile},
Secret: MustDecodeSecret("$plaintext$mysecret"),
},
},
}, nil)
- policyOne := s.GetClientPolicy("myclient")
+ policyOne := s.GetClientPolicy(myclient)
assert.Equal(t, authorization.OneFactor, policyOne)
policyTwo := s.GetClientPolicy("myotherclient")
@@ -49,9 +50,9 @@ func TestOpenIDConnectStore_GetInternalClient(t *testing.T) {
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
Clients: []schema.OpenIDConnectClientConfiguration{
{
- ID: "myclient",
- Description: "myclient desc",
- Policy: "one_factor",
+ ID: myclient,
+ Description: myclientdesc,
+ Policy: onefactor,
Scopes: []string{ScopeOpenID, ScopeProfile},
Secret: MustDecodeSecret("$plaintext$mysecret"),
},
@@ -62,17 +63,19 @@ func TestOpenIDConnectStore_GetInternalClient(t *testing.T) {
assert.EqualError(t, err, "invalid_client")
assert.Nil(t, client)
- client, err = s.GetClient(context.Background(), "myclient")
+ client, err = s.GetClient(context.Background(), myclient)
require.NoError(t, err)
require.NotNil(t, client)
- assert.Equal(t, "myclient", client.GetID())
+ assert.Equal(t, myclient, client.GetID())
}
func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) {
+ id := myclient
+
c1 := schema.OpenIDConnectClientConfiguration{
- ID: "myclient",
- Description: "myclient desc",
- Policy: "one_factor",
+ ID: id,
+ Description: myclientdesc,
+ Policy: onefactor,
Scopes: []string{ScopeOpenID, ScopeProfile},
Secret: MustDecodeSecret("$plaintext$mysecret"),
}
@@ -83,24 +86,24 @@ func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) {
Clients: []schema.OpenIDConnectClientConfiguration{c1},
}, nil)
- client, err := s.GetFullClient(c1.ID)
+ client, err := s.GetFullClient(id)
require.NoError(t, err)
require.NotNil(t, client)
- assert.Equal(t, client.ID, c1.ID)
- assert.Equal(t, client.Description, c1.Description)
- assert.Equal(t, client.Scopes, c1.Scopes)
- assert.Equal(t, client.GrantTypes, c1.GrantTypes)
- assert.Equal(t, client.ResponseTypes, c1.ResponseTypes)
- assert.Equal(t, client.RedirectURIs, c1.RedirectURIs)
- assert.Equal(t, client.Policy, authorization.OneFactor)
- assert.Equal(t, client.Secret.Encode(), "$plaintext$mysecret")
+ assert.Equal(t, id, client.GetID())
+ assert.Equal(t, myclientdesc, client.GetDescription())
+ assert.Equal(t, fosite.Arguments(c1.Scopes), client.GetScopes())
+ assert.Equal(t, fosite.Arguments([]string{GrantTypeAuthorizationCode}), client.GetGrantTypes())
+ assert.Equal(t, fosite.Arguments([]string{ResponseTypeAuthorizationCodeFlow}), client.GetResponseTypes())
+ assert.Equal(t, []string(nil), client.GetRedirectURIs())
+ assert.Equal(t, authorization.OneFactor, client.GetAuthorizationPolicy())
+ assert.Equal(t, "$plaintext$mysecret", client.GetSecret().Encode())
}
func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
c1 := schema.OpenIDConnectClientConfiguration{
- ID: "myclient",
- Description: "myclient desc",
- Policy: "one_factor",
+ ID: myclient,
+ Description: myclientdesc,
+ Policy: onefactor,
Scopes: []string{ScopeOpenID, ScopeProfile},
Secret: MustDecodeSecret("$plaintext$mysecret"),
}
@@ -122,16 +125,16 @@ func TestOpenIDConnectStore_IsValidClientID(t *testing.T) {
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
Clients: []schema.OpenIDConnectClientConfiguration{
{
- ID: "myclient",
- Description: "myclient desc",
- Policy: "one_factor",
+ ID: myclient,
+ Description: myclientdesc,
+ Policy: onefactor,
Scopes: []string{ScopeOpenID, ScopeProfile},
Secret: MustDecodeSecret("$plaintext$mysecret"),
},
},
}, nil)
- validClient := s.IsValidClientID("myclient")
+ validClient := s.IsValidClientID(myclient)
invalidClient := s.IsValidClientID("myinvalidclient")
assert.True(t, validClient)
diff --git a/internal/oidc/types.go b/internal/oidc/types.go
index 7403f2fed..8606fe3a3 100644
--- a/internal/oidc/types.go
+++ b/internal/oidc/types.go
@@ -12,6 +12,7 @@ import (
"github.com/ory/herodot"
"gopkg.in/square/go-jose.v2"
+ "github.com/authelia/authelia/v4/internal/authentication"
"github.com/authelia/authelia/v4/internal/authorization"
"github.com/authelia/authelia/v4/internal/model"
"github.com/authelia/authelia/v4/internal/storage"
@@ -97,17 +98,19 @@ type OpenIDConnectProvider struct {
// openid.OpenIDConnectRequestStorage, and partially implements rfc7523.RFC7523KeyStorage.
type Store struct {
provider storage.Provider
- clients map[string]*Client
+ clients map[string]Client
}
-// Client represents the client internally.
-type Client struct {
+// BaseClient is the base for all clients.
+type BaseClient struct {
ID string
Description string
Secret algorithm.Digest
SectorIdentifier string
Public bool
+ EnforcePAR bool
+
EnforcePKCE bool
EnforcePKCEChallengeMethod bool
PKCEChallengeMethod string
@@ -119,8 +122,6 @@ type Client struct {
ResponseTypes []string
ResponseModes []fosite.ResponseModeType
- EnforcePAR bool
-
UserinfoSigningAlgorithm string
Policy authorization.Level
@@ -128,6 +129,43 @@ type Client struct {
Consent ClientConsent
}
+// FullClient is the client with comprehensive supported features.
+type FullClient struct {
+ *BaseClient
+
+ RequestURIs []string
+ JSONWebKeys *jose.JSONWebKeySet
+ JSONWebKeysURI string
+ RequestObjectSigningAlgorithm string
+ TokenEndpointAuthMethod string
+ TokenEndpointAuthSigningAlgorithm string
+}
+
+// Client represents the internal client definitions.
+type Client interface {
+ fosite.Client
+ fosite.ResponseModeClient
+
+ GetDescription() string
+ GetSecret() algorithm.Digest
+ GetSectorIdentifier() string
+ GetConsentResponseBody(consent *model.OAuth2ConsentSession) ConsentGetResponseBody
+ GetUserinfoSigningAlgorithm() string
+
+ GetPAREnforcement() bool
+ GetPKCEEnforcement() bool
+ GetPKCEChallengeMethodEnforcement() bool
+ GetPKCEChallengeMethod() string
+ GetAuthorizationPolicy() authorization.Level
+ GetConsentPolicy() ClientConsent
+
+ IsAuthenticationLevelSufficient(level authentication.Level) bool
+
+ ValidatePKCEPolicy(r fosite.Requester) (err error)
+ ValidatePARPolicy(r fosite.Requester, prefix string) (err error)
+ ValidateResponseModePolicy(r fosite.AuthorizeRequester) (err error)
+}
+
// NewClientConsent converts the schema.OpenIDConnectClientConsentConfig into a oidc.ClientConsent.
func NewClientConsent(mode string, duration *time.Duration) ClientConsent {
switch mode {
@@ -344,6 +382,12 @@ type CommonDiscoveryOptions struct {
Client if it is given.
*/
OPTOSURI string `json:"op_tos_uri,omitempty"`
+
+ /*
+ A JWT containing metadata values about the authorization server as claims. This is a string value consisting of
+ the entire signed JWT. A "signed_metadata" metadata value SHOULD NOT appear as a claim in the JWT.
+ */
+ SignedMetadata string `json:"signed_metadata,omitempty"`
}
// OAuth2DiscoveryOptions represents the discovery options specific to OAuth 2.0.
@@ -427,6 +471,98 @@ type OAuth2DiscoveryOptions struct {
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported,omitempty"`
}
+type OAuth2JWTIntrospectionResponseDiscoveryOptions struct {
+ /*
+ OPTIONAL. JSON array containing a list of the JWS [RFC7515] signing algorithms ("alg" values) as defined in JWA
+ [RFC7518] supported by the introspection endpoint to sign the response.
+ */
+ IntrospectionSigningAlgValuesSupported []string `json:"introspection_signing_alg_values_supported,omitempty"`
+
+ /*
+ OPTIONAL. JSON array containing a list of the JWE [RFC7516] encryption algorithms ("alg" values) as defined in
+ JWA [RFC7518] supported by the introspection endpoint to encrypt the content encryption key for introspection
+ responses (content key encryption).
+ */
+ IntrospectionEncryptionAlgValuesSupported []string `json:"introspection_encryption_alg_values_supported"`
+
+ /*
+ OPTIONAL. JSON array containing a list of the JWE [RFC7516] encryption algorithms ("enc" values) as defined in
+ JWA [RFC7518] supported by the introspection endpoint to encrypt the response (content encryption).
+ */
+ IntrospectionEncryptionEncValuesSupported []string `json:"introspection_encryption_enc_values_supported"`
+}
+
+type OAuth2DeviceAuthorizationGrantDiscoveryOptions struct {
+ /*
+ OPTIONAL. URL of the authorization server's device authorization endpoint, as defined in Section 3.1.
+ */
+ DeviceAuthorizationEndpoint string `json:"device_authorization_endpoint"`
+}
+
+type OAuth2MutualTLSClientAuthenticationDiscoveryOptions struct {
+ /*
+ OPTIONAL. Boolean value indicating server support for mutual-TLS client certificate-bound access tokens. If
+ omitted, the default value is false.
+ */
+ TLSClientCertificateBoundAccessTokens bool `json:"tls_client_certificate_bound_access_tokens"`
+
+ /*
+ OPTIONAL. A JSON object containing alternative authorization server endpoints that, when present, an OAuth
+ client intending to do mutual TLS uses in preference to the conventional endpoints. The parameter value itself
+ consists of one or more endpoint parameters, such as token_endpoint, revocation_endpoint,
+ introspection_endpoint, etc., conventionally defined for the top level of authorization server metadata. An
+ OAuth client intending to do mutual TLS (for OAuth client authentication and/or to acquire or use
+ certificate-bound tokens) when making a request directly to the authorization server MUST use the alias URL of
+ the endpoint within the mtls_endpoint_aliases, when present, in preference to the endpoint URL of the same name
+ at the top level of metadata. When an endpoint is not present in mtls_endpoint_aliases, then the client uses the
+ conventional endpoint URL defined at the top level of the authorization server metadata. Metadata parameters
+ within mtls_endpoint_aliases that do not define endpoints to which an OAuth client makes a direct request have
+ no meaning and SHOULD be ignored.
+ */
+ MutualTLSEndpointAliases struct {
+ AuthorizationEndpoint string `json:"authorization_endpoint,omitempty"`
+ TokenEndpoint string `json:"token_endpoint,omitempty"`
+ IntrospectionEndpoint string `json:"introspection_endpoint,omitempty"`
+ RevocationEndpoint string `json:"revocation_endpoint,omitempty"`
+ EndSessionEndpoint string `json:"end_session_endpoint,omitempty"`
+ UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"`
+ BackChannelAuthenticationEndpoint string `json:"backchannel_authentication_endpoint,omitempty"`
+ FederationRegistrationEndpoint string `json:"federation_registration_endpoint,omitempty"`
+ PushedAuthorizationRequestEndpoint string `json:"pushed_authorization_request_endpoint,omitempty"`
+ RegistrationEndpoint string `json:"registration_endpoint,omitempty"`
+ } `json:"mtls_endpoint_aliases"`
+}
+
+type OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions struct {
+ /*
+ Indicates where authorization request needs to be protected as Request Object and provided through either
+ request or request_uri parameter.
+ */
+ RequireSignedRequestObject bool `json:"require_signed_request_object"`
+}
+
+type OAuth2IssuerIdentificationDiscoveryOptions struct {
+ AuthorizationResponseIssuerParameterSupported bool `json:"authorization_response_iss_parameter_supported"`
+}
+
+// OAuth2PushedAuthorizationDiscoveryOptions represents the well known discovery document specific to the
+// OAuth 2.0 Pushed Authorization Requests (RFC9126) implementation.
+//
+// OAuth 2.0 Pushed Authorization Requests: https://datatracker.ietf.org/doc/html/rfc9126#section-5
+type OAuth2PushedAuthorizationDiscoveryOptions struct {
+ /*
+ The URL of the pushed authorization request endpoint at which a client can post an authorization request to
+ exchange for a "request_uri" value usable at the authorization server.
+ */
+ PushedAuthorizationRequestEndpoint string `json:"pushed_authorization_request_endpoint"`
+
+ /*
+ Boolean parameter indicating whether the authorization server accepts authorization request data only via PAR.
+ If omitted, the default value is "false".
+ */
+ RequirePushedAuthorizationRequests bool `json:"require_pushed_authorization_requests"`
+}
+
// OpenIDConnectDiscoveryOptions represents the discovery options specific to OpenID Connect.
type OpenIDConnectDiscoveryOptions struct {
/*
@@ -553,6 +689,12 @@ type OpenIDConnectDiscoveryOptions struct {
ClaimLocalesSupported []string `json:"claims_locales_supported,omitempty"`
/*
+ OPTIONAL. Boolean value specifying whether the OP supports use of the request parameter, with true indicating
+ support. If omitted, the default value is false.
+ */
+ RequestParameterSupported bool `json:"request_parameter_supported"`
+
+ /*
OPTIONAL. Boolean value specifying whether the OP supports use of the request_uri parameter, with true indicating
support. If omitted, the default value is true.
*/
@@ -612,39 +754,202 @@ type OpenIDConnectBackChannelLogoutDiscoveryOptions struct {
BackChannelLogoutSessionSupported bool `json:"backchannel_logout_session_supported"`
}
-// PushedAuthorizationDiscoveryOptions represents the well known discovery document specific to the
-// OAuth 2.0 Pushed Authorization Requests (RFC9126) implementation.
+// OpenIDConnectSessionManagementDiscoveryOptions represents the discovery options specific to OpenID Connect 1.0
+// Session Management.
//
-// OAuth 2.0 Pushed Authorization Requests: https://datatracker.ietf.org/doc/html/rfc9126#section-5
-type PushedAuthorizationDiscoveryOptions struct {
+// To support OpenID Connect Session Management, the RP needs to obtain the Session Management related OP metadata. This
+// OP metadata is normally obtained via the OP's Discovery response, as described in OpenID Connect Discovery 1.0, or
+// MAY be learned via other mechanisms. This OpenID Provider Metadata parameter MUST be included in the Server's
+// discovery responses when Session Management and Discovery are supported.
+//
+// See Also:
+//
+// OpenID Connect 1.0 Session Management: https://openid.net/specs/openid-connect-session-1_0.html
+type OpenIDConnectSessionManagementDiscoveryOptions struct {
/*
- The URL of the pushed authorization request endpoint at which a client can post an authorization request to
- exchange for a "request_uri" value usable at the authorization server.
+ REQUIRED. URL of an OP iframe that supports cross-origin communications for session state information with the
+ RP Client, using the HTML5 postMessage API. This URL MUST use the https scheme and MAY contain port, path, and
+ query parameter components. The page is loaded from an invisible iframe embedded in an RP page so that it can
+ run in the OP's security context. It accepts postMessage requests from the relevant RP iframe and uses
+ postMessage to post back the login status of the End-User at the OP.
*/
- PushedAuthorizationRequestEndpoint string `json:"pushed_authorization_request_endpoint,omitempty"`
+ CheckSessionIFrame string `json:"check_session_iframe"`
+}
+// OpenIDConnectRPInitiatedLogoutDiscoveryOptions represents the discovery options specific to
+// OpenID Connect RP-Initiated Logout 1.0.
+//
+// To support OpenID Connect RP-Initiated Logout, the RP needs to obtain the RP-Initiated Logout related OP metadata.
+// This OP metadata is normally obtained via the OP's Discovery response, as described in OpenID Connect Discovery 1.0,
+// or MAY be learned via other mechanisms. This OpenID Provider Metadata parameter MUST be included in the Server's
+// discovery responses when RP-Initiated Logout and Discovery are supported.
+//
+// See Also:
+//
+// OpenID Connect RP-Initiated Logout 1.0: https://openid.net/specs/openid-connect-rpinitiated-1_0.html
+type OpenIDConnectRPInitiatedLogoutDiscoveryOptions struct {
/*
- Boolean parameter indicating whether the authorization server accepts authorization request data only via PAR.
- If omitted, the default value is "false".
+ REQUIRED. URL at the OP to which an RP can perform a redirect to request that the End-User be logged out at the
+ OP. This URL MUST use the https scheme and MAY contain port, path, and query parameter components.
*/
- RequirePushedAuthorizationRequests bool `json:"require_pushed_authorization_requests"`
+ EndSessionEndpoint string `json:"end_session_endpoint"`
+}
+
+// OpenIDConnectPromptCreateDiscoveryOptions represents the discovery options specific to Initiating User Registration
+// via OpenID Connect 1.0 functionality.
+//
+// This specification extends the OpenID Connect Discovery Metadata Section 3.
+//
+// See Also:
+//
+// Initiating User Registration via OpenID Connect 1.0: https://openid.net/specs/openid-connect-prompt-create-1_0.html
+type OpenIDConnectPromptCreateDiscoveryOptions struct {
+ /*
+ OPTIONAL. JSON array containing the list of prompt values that this OP supports.
+
+ This metadata element is OPTIONAL in the context of the OpenID Provider not supporting the create value. If
+ omitted, the Relying Party should assume that this specification is not supported. The OpenID Provider MAY
+ provide this metadata element even if it doesn't support the create value.
+ Specific to this specification, a value of create in the array indicates to the Relying party that this OpenID
+ Provider supports this specification. If an OpenID Provider supports this specification it MUST define this metadata
+ element in the openid-configuration file. Additionally, if this metadata element is defined by the OpenID
+ Provider, the OP must also specify all other prompt values which it supports.
+ See Also:
+ OpenID.PromptCreate: https://openid.net/specs/openid-connect-prompt-create-1_0.html
+ */
+ PromptValuesSupported []string `json:"prompt_values_supported,omitempty"`
+}
+
+// OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions represents the discovery options specific to
+// OpenID Connect Client-Initiated Backchannel Authentication Flow - Core 1.0
+//
+// The following authorization server metadata parameters are introduced by this specification for OPs publishing their
+// support of the CIBA flow and details thereof.
+//
+// See Also:
+//
+// OpenID Connect Client-Initiated Backchannel Authentication Flow - Core 1.0:
+// https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html#rfc.section.4
+type OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions struct {
+ /*
+ REQUIRED. URL of the OP's Backchannel Authentication Endpoint as defined in Section 7.
+ */
+ BackChannelAuthenticationEndpoint string `json:"backchannel_authentication_endpoint"`
+
+ /*
+ REQUIRED. JSON array containing one or more of the following values: poll, ping, and push.
+ */
+ BackChannelTokenDeliveryModesSupported []string `json:"backchannel_token_delivery_modes_supported"`
+
+ /*
+ OPTIONAL. JSON array containing a list of the JWS signing algorithms (alg values) supported by the OP for signed
+ authentication requests, which are described in Section 7.1.1. If omitted, signed authentication requests are
+ not supported by the OP.
+ */
+ BackChannelAuthRequestSigningAlgValuesSupported []string `json:"backchannel_authentication_request_signing_alg_values_supported,omitempty"`
+
+ /*
+ OPTIONAL. Boolean value specifying whether the OP supports the use of the user_code parameter, with true
+ indicating support. If omitted, the default value is false.
+ */
+ BackChannelUserCodeParameterSupported bool `json:"backchannel_user_code_parameter_supported"`
+}
+
+// OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions represents the discovery options specific to
+// JWT Secured Authorization Response Mode for OAuth 2.0 (JARM).
+//
+// Authorization servers SHOULD publish the supported algorithms for signing and encrypting the JWT of an authorization
+// response by utilizing OAuth 2.0 Authorization Server Metadata [RFC8414] parameters. The following parameters are
+// introduced by this specification.
+//
+// See Also:
+//
+// JWT Secured Authorization Response Mode for OAuth 2.0 (JARM):
+// https://openid.net/specs/oauth-v2-jarm.html#name-authorization-server-metada
+type OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions struct {
+ /*
+ OPTIONAL. A JSON array containing a list of the JWS [RFC7515] signing algorithms (alg values) supported by the
+ authorization endpoint to sign the response.
+ */
+ AuthorizationSigningAlgValuesSupported []string `json:"authorization_signing_alg_values_supported,omitempty"`
+
+ /*
+ OPTIONAL. A JSON array containing a list of the JWE [RFC7516] encryption algorithms (alg values) supported by
+ the authorization endpoint to encrypt the response.
+ */
+ AuthorizationEncryptionAlgValuesSupported []string `json:"authorization_encryption_alg_values_supported,omitempty"`
+
+ /*
+ OPTIONAL. A JSON array containing a list of the JWE [RFC7516] encryption algorithms (enc values) supported by
+ the authorization endpoint to encrypt the response.
+ */
+ AuthorizationEncryptionEncValuesSupported []string `json:"authorization_encryption_enc_values_supported,omitempty"`
+}
+
+type OpenIDFederationDiscoveryOptions struct {
+ /*
+ OPTIONAL. URL of the OP's federation-specific Dynamic Client Registration Endpoint. If the OP supports explicit
+ client registration as described in Section 10.2, then this claim is REQUIRED.
+ */
+ FederationRegistrationEndpoint string `json:"federation_registration_endpoint,omitempty"`
+
+ /*
+ REQUIRED. Array specifying the federation types supported. Federation-type values defined by this specification
+ are automatic and explicit.
+ */
+ ClientRegistrationTypesSupported []string `json:"client_registration_types_supported"`
+
+ /*
+ OPTIONAL. A JSON Object defining the client authentications supported for each endpoint. The endpoint names are
+ defined in the IANA "OAuth Authorization Server Metadata" registry [IANA.OAuth.Parameters]. Other endpoints and
+ authentication methods are possible if made recognizable according to established standards and not in conflict
+ with the operating principles of this specification. In OpenID Connect Core, no client authentication is
+ performed at the authentication endpoint. Instead, the request itself is authenticated. The OP maps information
+ in the request (like the redirect_uri) to information it has gained on the client through static or dynamic
+ registration. If the mapping is successful, the request can be processed. If the RP uses Automatic Registration,
+ as defined in Section 10.1, the OP has no prior knowledge of the RP. Therefore, the OP must start by gathering
+ information about the RP using the process outlined in Section 6. Once it has the RP's metadata, the OP can
+ verify the request in the same way as if it had known the RP's metadata beforehand. To make the request
+ verification more secure, we demand the use of a client authentication or verification method that proves that
+ the RP is in possession of a key that appears in the RP's metadata.
+ */
+ RequestAuthenticationMethodsSupported []string `json:"request_authentication_methods_supported,omitempty"`
+
+ /*
+ OPTIONAL. JSON array containing a list of the JWS signing algorithms (alg values) supported for the signature on
+ the JWT [RFC7519] used in the request_object contained in the request parameter of an authorization request or
+ in the private_key_jwt of a pushed authorization request. This entry MUST be present if either of these
+ authentication methods are specified in the request_authentication_methods_supported entry. No default
+ algorithms are implied if this entry is omitted. Servers SHOULD support RS256. The value none MUST NOT be used.
+ */
+ RequestAuthenticationSigningAlgValuesSupproted []string `json:"request_authentication_signing_alg_values_supported,omitempty"`
}
// OAuth2WellKnownConfiguration represents the well known discovery document specific to OAuth 2.0.
type OAuth2WellKnownConfiguration struct {
CommonDiscoveryOptions
OAuth2DiscoveryOptions
- PushedAuthorizationDiscoveryOptions
+ *OAuth2DeviceAuthorizationGrantDiscoveryOptions
+ *OAuth2MutualTLSClientAuthenticationDiscoveryOptions
+ *OAuth2IssuerIdentificationDiscoveryOptions
+ *OAuth2JWTIntrospectionResponseDiscoveryOptions
+ *OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions
+ *OAuth2PushedAuthorizationDiscoveryOptions
}
// OpenIDConnectWellKnownConfiguration represents the well known discovery document specific to OpenID Connect.
type OpenIDConnectWellKnownConfiguration struct {
- CommonDiscoveryOptions
- OAuth2DiscoveryOptions
- PushedAuthorizationDiscoveryOptions
+ OAuth2WellKnownConfiguration
+
OpenIDConnectDiscoveryOptions
- OpenIDConnectFrontChannelLogoutDiscoveryOptions
- OpenIDConnectBackChannelLogoutDiscoveryOptions
+ *OpenIDConnectFrontChannelLogoutDiscoveryOptions
+ *OpenIDConnectBackChannelLogoutDiscoveryOptions
+ *OpenIDConnectSessionManagementDiscoveryOptions
+ *OpenIDConnectRPInitiatedLogoutDiscoveryOptions
+ *OpenIDConnectPromptCreateDiscoveryOptions
+ *OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions
+ *OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions
+ *OpenIDFederationDiscoveryOptions
}
// OpenIDConnectContext represents the context implementation that is used by some OpenID Connect 1.0 implementations.
diff --git a/internal/oidc/types_test.go b/internal/oidc/types_test.go
index b84461a07..d604ad009 100644
--- a/internal/oidc/types_test.go
+++ b/internal/oidc/types_test.go
@@ -40,7 +40,7 @@ func TestNewSessionWithAuthorizeRequest(t *testing.T) {
Request: fosite.Request{
ID: requestID.String(),
Form: formValues,
- Client: &Client{ID: "example"},
+ Client: &BaseClient{ID: "example"},
},
}
@@ -50,7 +50,7 @@ func TestNewSessionWithAuthorizeRequest(t *testing.T) {
requested := time.Unix(1647332518, 0)
authAt := time.Unix(1647332500, 0)
- issuer := "https://example.com"
+ issuer := examplecom
amr := []string{AMRPasswordBasedAuthentication}
consent := &model.OAuth2ConsentSession{
diff --git a/internal/utils/strings.go b/internal/utils/strings.go
index 3e9dc12cd..1afdfc89d 100644
--- a/internal/utils/strings.go
+++ b/internal/utils/strings.go
@@ -200,8 +200,8 @@ func URLsFromStringSlice(urls []string) []url.URL {
}
// OriginFromURL returns an origin url.URL given another url.URL.
-func OriginFromURL(u url.URL) (origin url.URL) {
- return url.URL{
+func OriginFromURL(u *url.URL) (origin *url.URL) {
+ return &url.URL{
Scheme: u.Scheme,
Host: u.Host,
}
diff --git a/internal/utils/strings_test.go b/internal/utils/strings_test.go
index fe7c6742e..c12978739 100644
--- a/internal/utils/strings_test.go
+++ b/internal/utils/strings_test.go
@@ -242,7 +242,7 @@ func TestOriginFromURL(t *testing.T) {
google, err := url.Parse("https://google.com/abc?a=123#five")
assert.NoError(t, err)
- origin := OriginFromURL(*google)
+ origin := OriginFromURL(google)
assert.Equal(t, "https://google.com", origin.String())
}