feat(oidc): client authentication modes (#5150)
This adds a feature to OpenID Connect 1.0 where clients can be restricted to a specific client authentication mode, as well as implements some backend requirements for the private_key_jwt client authentication mode (and potentially the tls_client_auth / self_signed_tls_client_auth client authentication modes). It also adds some improvements to configuration defaults and validations which will for now be warnings but likely be made into errors. Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>pull/4779/head^2
parent
db130dad48
commit
3d2da0b070
|
@ -1480,12 +1480,6 @@ notifier:
|
||||||
# - email
|
# - email
|
||||||
# - profile
|
# - 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.
|
## Response Types configures which responses this client can be sent.
|
||||||
## It's not recommended to define this unless you know what you're doing.
|
## It's not recommended to define this unless you know what you're doing.
|
||||||
# response_types:
|
# response_types:
|
||||||
|
@ -1495,7 +1489,14 @@ notifier:
|
||||||
# response_modes:
|
# response_modes:
|
||||||
# - form_post
|
# - form_post
|
||||||
# - query
|
# - 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.
|
## The policy to require for this client; one_factor or two_factor.
|
||||||
# authorization_policy: two_factor
|
# authorization_policy: two_factor
|
||||||
|
|
|
@ -451,9 +451,40 @@ 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
|
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.
|
or claims required which can be matched with the above guide.
|
||||||
|
|
||||||
|
#### response_types
|
||||||
|
|
||||||
|
{{< confkey type="list(string)" default="code" required="no" >}}
|
||||||
|
|
||||||
|
*__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. 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" 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. 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.
|
||||||
|
|
||||||
|
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
|
#### grant_types
|
||||||
|
|
||||||
{{< confkey type="list(string)" default="refresh_token, authorization_code" required="no" >}}
|
{{< 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.*
|
*__Important Note:__ It is recommended that this isn't configured at this time unless you know what you're doing.*
|
||||||
|
|
||||||
|
@ -462,28 +493,6 @@ The list of grant types this client is permitted to use in order to obtain acces
|
||||||
See the [Grant Types](../../integration/openid-connect/introduction.md#grant-types) section of the
|
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.
|
[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.*
|
|
||||||
|
|
||||||
A list of response types this client supports.
|
|
||||||
|
|
||||||
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" >}}
|
|
||||||
|
|
||||||
*__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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
#### authorization_policy
|
#### authorization_policy
|
||||||
|
|
||||||
{{< confkey type="string" default="two_factor" required="no" >}}
|
{{< confkey type="string" default="two_factor" required="no" >}}
|
||||||
|
@ -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
|
See the [integration guide](../../integration/openid-connect/introduction.md#user-information-signing-algorithm) for
|
||||||
more information.
|
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
|
#### consent_mode
|
||||||
|
|
||||||
{{< confkey type="string" default="auto" required="no" >}}
|
{{< 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
|
[token lifespan]: https://docs.apigee.com/api-platform/antipatterns/oauth-long-expiration
|
||||||
[OpenID Connect 1.0]: https://openid.net/connect/
|
[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
|
[JWT]: https://datatracker.ietf.org/doc/html/rfc7519
|
||||||
[RFC6234]: https://datatracker.ietf.org/doc/html/rfc6234
|
[RFC6234]: https://datatracker.ietf.org/doc/html/rfc6234
|
||||||
[RFC4648]: https://datatracker.ietf.org/doc/html/rfc4648
|
[RFC4648]: https://datatracker.ietf.org/doc/html/rfc4648
|
||||||
|
|
|
@ -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 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
|
[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
|
||||||
|
|
||||||
|
|
|
@ -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
|
See the [configuration documentation](../../configuration/identity-providers/open-id-connect.md) for information on how
|
||||||
to configure the Authelia [OpenID Connect 1.0] Provider.
|
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
|
## 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
|
### openid
|
||||||
|
|
||||||
This is the default scope for [OpenID Connect 1.0]. This field is forced on every client by the configuration validation
|
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
|
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.
|
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
|
Generally unless an application supports this and actively requests this scope they should not be granted this scope via
|
||||||
the client configuration.
|
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
|
### groups
|
||||||
|
|
||||||
This scope includes the groups the authentication backend reports the user is a member of in the [Claims] of the
|
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
|
The following section describes advanced parameters which can be used in various endpoints as well as their related
|
||||||
configuration options.
|
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
|
### Response Types
|
||||||
|
|
||||||
The following describes the supported response types. See the [OAuth 2.0 Multiple Response Type Encoding Practices] for
|
The following describes the supported response types. See the [OAuth 2.0 Multiple Response Type Encoding Practices] for
|
||||||
more technical information.
|
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 | Values |
|
| Flow Type | Value | Default [Response Modes](#response-modes) Values |
|
||||||
|:-------------------------:|:---------------------:|
|
|:-------------------------:|:---------------------:|:------------------------------------------------:|
|
||||||
| [Authorization Code Flow] | `code` |
|
| [Authorization Code Flow] | `code` | `form_post`, `query` |
|
||||||
| [Implicit Flow] | `token id_token` |
|
| [Implicit Flow] | `id_token token` | `form_post`, `fragment` |
|
||||||
| [Implicit Flow] | `id_token` |
|
| [Implicit Flow] | `id_token` | `form_post`, `fragment` |
|
||||||
| [Implicit Flow] | `token` |
|
| [Implicit Flow] | `token` | `form_post`, `fragment` |
|
||||||
| [Hybrid Flow] | `code token` |
|
| [Hybrid Flow] | `code token` | `form_post`, `fragment` |
|
||||||
| [Hybrid Flow] | `code id_token` |
|
| [Hybrid Flow] | `code id_token` | `form_post`, `fragment` |
|
||||||
| [Hybrid Flow] | `code token id_token` |
|
| [Hybrid Flow] | `code id_token token` | `form_post`, `fragment` |
|
||||||
|
|
||||||
[Authorization Code Flow]: https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth
|
[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
|
[Implicit Flow]: https://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth
|
||||||
|
@ -139,16 +131,60 @@ more technical information.
|
||||||
### Response Modes
|
### Response Modes
|
||||||
|
|
||||||
The following describes the supported response modes. See the [OAuth 2.0 Multiple Response Type Encoding Practices] for
|
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 |
|
| Name | Value |
|
||||||
|:---------------------:|:-----------:|
|
|:---------------------:|:-----------:|
|
||||||
|
| [OAuth 2.0 Form Post] | `form_post` |
|
||||||
| Query String | `query` |
|
| Query String | `query` |
|
||||||
| Fragment | `fragment` |
|
| 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
|
[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
|
## Authentication Method References
|
||||||
|
|
||||||
Authelia currently supports adding the `amr` [Claim] to the [ID Token] utilizing the [RFC8176] Authentication Method
|
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
|
[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
|
[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
|
[Token]: https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
|
||||||
[UserInfo]: https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
|
[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
|
[Introspection]: https://datatracker.ietf.org/doc/html/rfc7662
|
||||||
[Revocation]: https://datatracker.ietf.org/doc/html/rfc7009
|
[Revocation]: https://datatracker.ietf.org/doc/html/rfc7009
|
||||||
[Proof Key Code Exchange]: https://www.rfc-editor.org/rfc/rfc7636.html
|
[Proof Key Code Exchange]: https://www.rfc-editor.org/rfc/rfc7636.html
|
||||||
|
|
|
@ -87,7 +87,7 @@ Below you will find commented examples of the following configuration:
|
||||||
|
|
||||||
### Example
|
### 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
|
[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
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,7 @@ available in [Kubernetes]. You would likely have to build your own [HAProxy] ima
|
||||||
|
|
||||||
### Envoy
|
### 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
|
[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
|
||||||
|
|
||||||
|
|
|
@ -1480,12 +1480,6 @@ notifier:
|
||||||
# - email
|
# - email
|
||||||
# - profile
|
# - 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.
|
## Response Types configures which responses this client can be sent.
|
||||||
## It's not recommended to define this unless you know what you're doing.
|
## It's not recommended to define this unless you know what you're doing.
|
||||||
# response_types:
|
# response_types:
|
||||||
|
@ -1495,7 +1489,14 @@ notifier:
|
||||||
# response_modes:
|
# response_modes:
|
||||||
# - form_post
|
# - form_post
|
||||||
# - query
|
# - 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.
|
## The policy to require for this client; one_factor or two_factor.
|
||||||
# authorization_policy: two_factor
|
# authorization_policy: two_factor
|
||||||
|
|
|
@ -64,6 +64,8 @@ type OpenIDConnectClientConfiguration struct {
|
||||||
ResponseTypes []string `koanf:"response_types"`
|
ResponseTypes []string `koanf:"response_types"`
|
||||||
ResponseModes []string `koanf:"response_modes"`
|
ResponseModes []string `koanf:"response_modes"`
|
||||||
|
|
||||||
|
TokenEndpointAuthMethod string `koanf:"token_endpoint_auth_method"`
|
||||||
|
|
||||||
Policy string `koanf:"authorization_policy"`
|
Policy string `koanf:"authorization_policy"`
|
||||||
|
|
||||||
EnforcePAR bool `koanf:"enforce_par"`
|
EnforcePAR bool `koanf:"enforce_par"`
|
||||||
|
@ -91,9 +93,8 @@ var defaultOIDCClientConsentPreConfiguredDuration = time.Hour * 24 * 7
|
||||||
var DefaultOpenIDConnectClientConfiguration = OpenIDConnectClientConfiguration{
|
var DefaultOpenIDConnectClientConfiguration = OpenIDConnectClientConfiguration{
|
||||||
Policy: "two_factor",
|
Policy: "two_factor",
|
||||||
Scopes: []string{"openid", "groups", "profile", "email"},
|
Scopes: []string{"openid", "groups", "profile", "email"},
|
||||||
GrantTypes: []string{"refresh_token", "authorization_code"},
|
|
||||||
ResponseTypes: []string{"code"},
|
ResponseTypes: []string{"code"},
|
||||||
ResponseModes: []string{"form_post", "query", "fragment"},
|
ResponseModes: []string{"form_post"},
|
||||||
|
|
||||||
UserinfoSigningAlgorithm: "none",
|
UserinfoSigningAlgorithm: "none",
|
||||||
ConsentMode: "auto",
|
ConsentMode: "auto",
|
||||||
|
|
|
@ -45,6 +45,7 @@ var Keys = []string{
|
||||||
"identity_providers.oidc.clients[].grant_types",
|
"identity_providers.oidc.clients[].grant_types",
|
||||||
"identity_providers.oidc.clients[].response_types",
|
"identity_providers.oidc.clients[].response_types",
|
||||||
"identity_providers.oidc.clients[].response_modes",
|
"identity_providers.oidc.clients[].response_modes",
|
||||||
|
"identity_providers.oidc.clients[].token_endpoint_auth_method",
|
||||||
"identity_providers.oidc.clients[].authorization_policy",
|
"identity_providers.oidc.clients[].authorization_policy",
|
||||||
"identity_providers.oidc.clients[].enforce_par",
|
"identity_providers.oidc.clients[].enforce_par",
|
||||||
"identity_providers.oidc.clients[].enforce_pkce",
|
"identity_providers.oidc.clients[].enforce_pkce",
|
||||||
|
|
|
@ -59,7 +59,7 @@ func ValidateAccessControl(config *schema.Configuration, validator *schema.Struc
|
||||||
}
|
}
|
||||||
|
|
||||||
if !IsPolicyValid(config.AccessControl.DefaultPolicy) {
|
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 {
|
if config.AccessControl.Networks != nil {
|
||||||
|
@ -92,8 +92,13 @@ func ValidateRules(config *schema.Configuration, validator *schema.StructValidat
|
||||||
|
|
||||||
validateDomains(rulePosition, rule, validator)
|
validateDomains(rulePosition, rule, validator)
|
||||||
|
|
||||||
if !IsPolicyValid(rule.Policy) {
|
switch rule.Policy {
|
||||||
validator.Push(fmt.Errorf(errFmtAccessControlRuleInvalidPolicy, ruleDescriptor(rulePosition, rule), 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)
|
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) {
|
func validateMethods(rulePosition int, rule schema.ACLRule, validator *schema.StructValidator) {
|
||||||
for _, method := range rule.Methods {
|
invalid, duplicates := validateList(rule.Methods, validACLHTTPMethodVerbs, true)
|
||||||
if !utils.IsStringInSliceFold(method, validACLHTTPMethodVerbs) {
|
|
||||||
validator.Push(fmt.Errorf(errFmtAccessControlRuleMethodInvalid, ruleDescriptor(rulePosition, rule), method, strings.Join(validACLHTTPMethodVerbs, "', '")))
|
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) {
|
} 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 == "" {
|
if config.AccessControl.Rules[i].Query[j][k].Key == "" {
|
||||||
|
|
|
@ -58,7 +58,7 @@ func (suite *AccessControl) TestShouldValidateEitherDomainsOrDomainsRegex() {
|
||||||
suite.Assert().Len(suite.validator.Warnings(), 0)
|
suite.Assert().Len(suite.validator.Warnings(), 0)
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
func (suite *AccessControl) TestShouldRaiseErrorInvalidDefaultPolicy() {
|
||||||
|
@ -69,7 +69,7 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidDefaultPolicy() {
|
||||||
suite.Assert().Len(suite.validator.Warnings(), 0)
|
suite.Assert().Len(suite.validator.Warnings(), 0)
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
func (suite *AccessControl) TestShouldRaiseErrorInvalidNetworkGroupNetwork() {
|
||||||
|
@ -141,10 +141,10 @@ func (suite *AccessControl) TestShouldRaiseErrorsWithEmptyRules() {
|
||||||
suite.Assert().Len(suite.validator.Warnings(), 0)
|
suite.Assert().Len(suite.validator.Warnings(), 0)
|
||||||
suite.Require().Len(suite.validator.Errors(), 4)
|
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()[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: rule 'policy' option '' is invalid: must be one of 'deny', 'two_factor', 'one_factor' or 'bypass'")
|
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: rule is invalid: must have the option 'domain' or 'domain_regex' configured")
|
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: rule 'policy' option 'wrong' is invalid: must be one of 'deny', 'two_factor', 'one_factor' or 'bypass'")
|
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() {
|
func (suite *AccessControl) TestShouldRaiseErrorInvalidPolicy() {
|
||||||
|
@ -160,7 +160,7 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidPolicy() {
|
||||||
suite.Assert().Len(suite.validator.Warnings(), 0)
|
suite.Assert().Len(suite.validator.Warnings(), 0)
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
func (suite *AccessControl) TestShouldRaiseErrorInvalidNetwork() {
|
||||||
|
@ -194,7 +194,24 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidMethod() {
|
||||||
suite.Assert().Len(suite.validator.Warnings(), 0)
|
suite.Assert().Len(suite.validator.Warnings(), 0)
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
func (suite *AccessControl) TestShouldRaiseErrorInvalidSubject() {
|
||||||
|
@ -367,13 +384,13 @@ func (suite *AccessControl) TestShouldErrorOnInvalidRulesQuery() {
|
||||||
suite.Assert().Len(suite.validator.Warnings(), 0)
|
suite.Assert().Len(suite.validator.Warnings(), 0)
|
||||||
suite.Require().Len(suite.validator.Errors(), 7)
|
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()[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 invalid: must have a value")
|
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 invalid: must have a value")
|
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' with value 'not' is invalid: must be one of 'present', 'absent', 'equal', 'not equal', 'pattern', 'not pattern'")
|
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()[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()[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")
|
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) {
|
func TestAccessControl(t *testing.T) {
|
||||||
|
|
|
@ -71,7 +71,7 @@ func ValidatePasswordConfiguration(config *schema.Password, validator *schema.St
|
||||||
case utils.IsStringInSlice(config.Algorithm, validHashAlgorithms):
|
case utils.IsStringInSlice(config.Algorithm, validHashAlgorithms):
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordUnknownAlg, config.Algorithm, strings.Join(validHashAlgorithms, "', '")))
|
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordUnknownAlg, strJoinOr(validHashAlgorithms), config.Algorithm))
|
||||||
}
|
}
|
||||||
|
|
||||||
validateFileAuthenticationBackendPasswordConfigArgon2(config, validator)
|
validateFileAuthenticationBackendPasswordConfigArgon2(config, validator)
|
||||||
|
@ -89,7 +89,7 @@ func validateFileAuthenticationBackendPasswordConfigArgon2(config *schema.Passwo
|
||||||
case utils.IsStringInSlice(config.Argon2.Variant, validArgon2Variants):
|
case utils.IsStringInSlice(config.Argon2.Variant, validArgon2Variants):
|
||||||
break
|
break
|
||||||
default:
|
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 {
|
switch {
|
||||||
|
@ -147,7 +147,7 @@ func validateFileAuthenticationBackendPasswordConfigSHA2Crypt(config *schema.Pas
|
||||||
case utils.IsStringInSlice(config.SHA2Crypt.Variant, validSHA2CryptVariants):
|
case utils.IsStringInSlice(config.SHA2Crypt.Variant, validSHA2CryptVariants):
|
||||||
break
|
break
|
||||||
default:
|
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 {
|
switch {
|
||||||
|
@ -176,7 +176,7 @@ func validateFileAuthenticationBackendPasswordConfigPBKDF2(config *schema.Passwo
|
||||||
case utils.IsStringInSlice(config.PBKDF2.Variant, validPBKDF2Variants):
|
case utils.IsStringInSlice(config.PBKDF2.Variant, validPBKDF2Variants):
|
||||||
break
|
break
|
||||||
default:
|
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 {
|
switch {
|
||||||
|
@ -205,7 +205,7 @@ func validateFileAuthenticationBackendPasswordConfigBCrypt(config *schema.Passwo
|
||||||
case utils.IsStringInSlice(config.BCrypt.Variant, validBCryptVariants):
|
case utils.IsStringInSlice(config.BCrypt.Variant, validBCryptVariants):
|
||||||
break
|
break
|
||||||
default:
|
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 {
|
switch {
|
||||||
|
@ -369,7 +369,7 @@ func validateLDAPAuthenticationBackendImplementation(config *schema.Authenticati
|
||||||
case schema.LDAPImplementationGLAuth:
|
case schema.LDAPImplementationGLAuth:
|
||||||
implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth
|
implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth
|
||||||
default:
|
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{}
|
tlsconfig := &schema.TLSConfig{}
|
||||||
|
|
|
@ -256,7 +256,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidArgon2
|
||||||
suite.Assert().Len(suite.validator.Warnings(), 0)
|
suite.Assert().Len(suite.validator.Warnings(), 0)
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidSHA2CryptVariant() {
|
||||||
|
@ -270,7 +270,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidSHA2Cr
|
||||||
suite.Assert().Len(suite.validator.Warnings(), 0)
|
suite.Assert().Len(suite.validator.Warnings(), 0)
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidSHA2CryptSaltLength() {
|
||||||
|
@ -298,7 +298,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidPBKDF2
|
||||||
suite.Assert().Len(suite.validator.Warnings(), 0)
|
suite.Assert().Len(suite.validator.Warnings(), 0)
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidBCryptVariant() {
|
||||||
|
@ -312,7 +312,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidBCrypt
|
||||||
suite.Assert().Len(suite.validator.Warnings(), 0)
|
suite.Assert().Len(suite.validator.Warnings(), 0)
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenSHA2CryptOptionsTooLow() {
|
||||||
|
@ -497,7 +497,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenBadAlgorith
|
||||||
suite.Assert().Len(suite.validator.Warnings(), 0)
|
suite.Assert().Len(suite.validator.Warnings(), 0)
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
func (suite *FileBasedAuthenticationBackend) TestShouldSetDefaultValues() {
|
||||||
|
@ -609,7 +609,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenImplementat
|
||||||
suite.Assert().Len(suite.validator.Warnings(), 0)
|
suite.Assert().Len(suite.validator.Warnings(), 0)
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
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()[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()[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()[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() {
|
func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultGroupNameAttribute() {
|
||||||
|
@ -823,7 +823,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseWhenUsersFilterDoesN
|
||||||
suite.Assert().Len(suite.validator.Warnings(), 0)
|
suite.Assert().Len(suite.validator.Warnings(), 0)
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
func (suite *LDAPAuthenticationBackendSuite) TestShouldHelpDetectNoInputPlaceholder() {
|
||||||
|
@ -834,7 +834,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldHelpDetectNoInputPlacehol
|
||||||
suite.Assert().Len(suite.validator.Warnings(), 0)
|
suite.Assert().Len(suite.validator.Warnings(), 0)
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultTLSMinimumVersion() {
|
||||||
|
@ -986,7 +986,7 @@ func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldRaiseErrorOnIn
|
||||||
validateLDAPAuthenticationBackendURL(suite.config.LDAP, suite.validator)
|
validateLDAPAuthenticationBackendURL(suite.config.LDAP, suite.validator)
|
||||||
|
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldRaiseErrorOnInvalidURLWithBadCharacters() {
|
||||||
|
|
|
@ -78,7 +78,7 @@ func validateDefault2FAMethod(config *schema.Configuration, validator *schema.St
|
||||||
}
|
}
|
||||||
|
|
||||||
if !utils.IsStringInSlice(config.Default2FAMethod, validDefault2FAMethods) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
@ -98,6 +98,6 @@ func validateDefault2FAMethod(config *schema.Configuration, validator *schema.St
|
||||||
}
|
}
|
||||||
|
|
||||||
if !utils.IsStringInSlice(config.Default2FAMethod, enabledMethods) {
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -221,7 +221,7 @@ func TestValidateDefault2FAMethod(t *testing.T) {
|
||||||
TOTP: schema.TOTPConfiguration{Disable: true},
|
TOTP: schema.TOTPConfiguration{Disable: true},
|
||||||
},
|
},
|
||||||
expectedErrs: []string{
|
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},
|
Webauthn: schema.WebauthnConfiguration{Disable: true},
|
||||||
},
|
},
|
||||||
expectedErrs: []string{
|
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},
|
DuoAPI: schema.DuoAPIConfiguration{Disable: true},
|
||||||
},
|
},
|
||||||
expectedErrs: []string{
|
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",
|
Default2FAMethod: "duo",
|
||||||
},
|
},
|
||||||
expectedErrs: []string{
|
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'",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
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.
|
// Authentication Backend Error constants.
|
||||||
|
@ -105,19 +105,19 @@ const (
|
||||||
errFmtLDAPAuthBackendURLNotParsable = "authentication_backend: ldap: option " +
|
errFmtLDAPAuthBackendURLNotParsable = "authentication_backend: ldap: option " +
|
||||||
"'url' could not be parsed: %w"
|
"'url' could not be parsed: %w"
|
||||||
errFmtLDAPAuthBackendURLInvalidScheme = "authentication_backend: ldap: option " +
|
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 " +
|
errFmtLDAPAuthBackendFilterEnclosingParenthesis = "authentication_backend: ldap: option " +
|
||||||
"'%s' must contain enclosing parenthesis: '%s' should probably be '(%s)'"
|
"'%s' must contain enclosing parenthesis: '%s' should probably be '(%s)'"
|
||||||
errFmtLDAPAuthBackendFilterMissingPlaceholder = "authentication_backend: ldap: option " +
|
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.
|
// TOTP Error constants.
|
||||||
const (
|
const (
|
||||||
errFmtTOTPInvalidAlgorithm = "totp: option 'algorithm' must be one of '%s' but it is configured as '%s'"
|
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 is configured as '%d'"
|
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 is 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 is configured as '%d'" //nolint:gosec
|
errFmtTOTPInvalidSecretSize = "totp: option 'secret_size' must be %d or higher but it's configured as '%d'" //nolint:gosec
|
||||||
)
|
)
|
||||||
|
|
||||||
// Storage Error constants.
|
// Storage Error constants.
|
||||||
|
@ -128,14 +128,14 @@ const (
|
||||||
errFmtStorageUserPassMustBeProvided = "storage: %s: option 'username' and 'password' are required" //nolint:gosec
|
errFmtStorageUserPassMustBeProvided = "storage: %s: option 'username' and 'password' are required" //nolint:gosec
|
||||||
errFmtStorageOptionMustBeProvided = "storage: %s: option '%s' is required"
|
errFmtStorageOptionMustBeProvided = "storage: %s: option '%s' is required"
|
||||||
errFmtStorageTLSConfigInvalid = "storage: %s: tls: %w"
|
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"
|
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"
|
warnFmtStoragePostgreSQLInvalidSSLDeprecated = "storage: postgres: ssl: the ssl configuration options are deprecated and we recommend the tls options instead"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Telemetry Error constants.
|
// Telemetry Error constants.
|
||||||
const (
|
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.
|
// 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'"
|
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"
|
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', " +
|
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"
|
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"
|
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"
|
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" +
|
errFmtOIDCClientsDuplicateID = "identity_providers: oidc: clients: option 'id' must be unique for every client but one or more clients share the following 'id' values %s"
|
||||||
"id's must be unique"
|
errFmtOIDCClientsWithEmptyID = "identity_providers: oidc: clients: option 'id' is required but was absent on the clients in positions %s"
|
||||||
errFmtOIDCClientsWithEmptyID = "identity_providers: oidc: one or more clients have been configured with " +
|
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"
|
||||||
"an empty id"
|
|
||||||
|
|
||||||
errFmtOIDCClientInvalidSecret = "identity_providers: oidc: client '%s': option 'secret' is required"
|
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"
|
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 " +
|
"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"
|
"for the openid connect confidential client type"
|
||||||
errFmtOIDCClientRedirectURIAbsolute = "identity_providers: oidc: client '%s': option 'redirect_uris' has an " +
|
errFmtOIDCClientRedirectURIAbsolute = "identity_providers: oidc: client '%s': option 'redirect_uris' has an " +
|
||||||
"invalid value: redirect uri '%s' must have the scheme but it is absent"
|
"invalid value: redirect uri '%s' must have a scheme but it's 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'"
|
|
||||||
errFmtOIDCClientInvalidConsentMode = "identity_providers: oidc: client '%s': consent: option 'mode' must be one of " +
|
errFmtOIDCClientInvalidConsentMode = "identity_providers: oidc: client '%s': consent: option 'mode' must be one of " +
|
||||||
"'%s' but it is configured as '%s'"
|
"%s but it's configured as '%s'"
|
||||||
errFmtOIDCClientInvalidEntry = "identity_providers: oidc: client '%s': option '%s' must only have the values " +
|
errFmtOIDCClientInvalidEntries = "identity_providers: oidc: client '%s': option '%s' must only have the values " +
|
||||||
"'%s' but one option is configured as '%s'"
|
"%s but the values %s are present"
|
||||||
errFmtOIDCClientInvalidUserinfoAlgorithm = "identity_providers: oidc: client '%s': option " +
|
errFmtOIDCClientInvalidEntryDuplicates = "identity_providers: oidc: client '%s': option '%s' must have unique values but the values %s are duplicated"
|
||||||
"'userinfo_signing_algorithm' must be one of '%s' but it is configured as '%s'"
|
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 " +
|
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'"
|
"'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 " +
|
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"
|
"'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 " +
|
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"
|
"'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 " +
|
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"
|
"configured to an unsafe value, it should be above 8 but it's configured to %d"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Webauthn Error constants.
|
// Webauthn Error constants.
|
||||||
const (
|
const (
|
||||||
errFmtWebauthnConveyancePreference = "webauthn: option 'attestation_conveyance_preference' must be one of '%s' 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 'discouraged', 'preferred', 'required' but it is configured as '%s'"
|
errFmtWebauthnUserVerification = "webauthn: option 'user_verification' must be one of %s but it's configured as '%s'"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Access Control error constants.
|
// Access Control error constants.
|
||||||
const (
|
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'"
|
"configured as '%s'"
|
||||||
errFmtAccessControlDefaultPolicyWithoutRules = "access control: 'default_policy' option '%s' is invalid: when " +
|
errFmtAccessControlDefaultPolicyWithoutRules = "access control: 'default_policy' option '%s' is invalid: when " +
|
||||||
"no rules are specified it must be 'two_factor' or 'one_factor'"
|
"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"
|
"network '%s' is not a valid IP or CIDR notation"
|
||||||
errFmtAccessControlWarnNoRulesDefaultPolicy = "access control: no rules have been specified so the " +
|
errFmtAccessControlWarnNoRulesDefaultPolicy = "access control: no rules have been specified so the " +
|
||||||
"'default_policy' of '%s' is going to be applied to all requests"
|
"'default_policy' of '%s' is going to be applied to all requests"
|
||||||
errFmtAccessControlRuleNoDomains = "access control: rule %s: rule is invalid: must have the option " +
|
errFmtAccessControlRuleNoDomains = "access control: rule %s: option 'domain' or 'domain_regex' must be present but are both absent"
|
||||||
"'domain' or 'domain_regex' configured"
|
errFmtAccessControlRuleNoPolicy = "access control: rule %s: option 'policy' must be present but it's absent"
|
||||||
errFmtAccessControlRuleInvalidPolicy = "access control: rule %s: rule 'policy' option '%s' " +
|
errFmtAccessControlRuleInvalidPolicy = "access control: rule %s: option 'policy' must be one of %s but it's configured as '%s'"
|
||||||
"is invalid: must be one of 'deny', 'two_factor', 'one_factor' or 'bypass'"
|
|
||||||
errAccessControlRuleBypassPolicyInvalidWithSubjects = "access control: rule %s: 'policy' option 'bypass' is " +
|
errAccessControlRuleBypassPolicyInvalidWithSubjects = "access control: rule %s: 'policy' option 'bypass' is " +
|
||||||
"not supported when 'subject' option is configured: see " +
|
"not supported when 'subject' option is configured: see " +
|
||||||
"https://www.authelia.com/c/acl#bypass"
|
"https://www.authelia.com/c/acl#bypass"
|
||||||
|
@ -221,39 +226,35 @@ const (
|
||||||
"valid Group Name, IP, or CIDR notation"
|
"valid Group Name, IP, or CIDR notation"
|
||||||
errFmtAccessControlRuleSubjectInvalid = "access control: rule %s: 'subject' option '%s' is " +
|
errFmtAccessControlRuleSubjectInvalid = "access control: rule %s: 'subject' option '%s' is " +
|
||||||
"invalid: must start with 'user:' or 'group:'"
|
"invalid: must start with 'user:' or 'group:'"
|
||||||
errFmtAccessControlRuleMethodInvalid = "access control: rule %s: 'methods' option '%s' is " +
|
errFmtAccessControlRuleInvalidEntries = "access control: rule %s: option '%s' must only have the values %s but the values %s are present"
|
||||||
"invalid: must be one of '%s'"
|
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' with value '%s' is " +
|
errFmtAccessControlRuleQueryInvalid = "access control: rule %s: query: option 'operator' must be one of %s but it's configured as '%s'"
|
||||||
"invalid: must be one of '%s'"
|
errFmtAccessControlRuleQueryInvalidNoValue = "access control: rule %s: query: option '%s' is required but it's absent"
|
||||||
errFmtAccessControlRuleQueryInvalidNoValue = "access control: rule %s: 'query' option '%s' is " +
|
errFmtAccessControlRuleQueryInvalidNoValueOperator = "access control: rule %s: query: option '%s' must be present when the option 'operator' is '%s' but it's absent"
|
||||||
"invalid: must have a value"
|
errFmtAccessControlRuleQueryInvalidValue = "access control: rule %s: query: option '%s' must not be present when the option 'operator' is '%s' but it's present"
|
||||||
errFmtAccessControlRuleQueryInvalidNoValueOperator = "access control: rule %s: 'query' option '%s' is " +
|
errFmtAccessControlRuleQueryInvalidValueParse = "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 " +
|
|
||||||
"invalid: %w"
|
"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"
|
"invalid: expected type was string but got %T"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Theme Error constants.
|
// Theme Error constants.
|
||||||
const (
|
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.
|
// NTP Error constants.
|
||||||
const (
|
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.
|
// Session error constants.
|
||||||
const (
|
const (
|
||||||
errFmtSessionOptionRequired = "session: option '%s' is required"
|
errFmtSessionOptionRequired = "session: option '%s' is required"
|
||||||
errFmtSessionLegacyAndWarning = "session: option 'domain' and option 'cookies' can't be specified at the same time"
|
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"
|
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"
|
errFmtSessionRedisHostRequired = "session: redis: option 'host' is required"
|
||||||
errFmtSessionRedisHostOrNodesRequired = "session: redis: option 'host' or the 'high_availability' option 'nodes' is required"
|
errFmtSessionRedisHostOrNodesRequired = "session: redis: option 'host' or the 'high_availability' option 'nodes' is required"
|
||||||
errFmtSessionRedisTLSConfigInvalid = "session: redis: tls: %w"
|
errFmtSessionRedisTLSConfigInvalid = "session: redis: tls: %w"
|
||||||
|
@ -261,8 +262,8 @@ const (
|
||||||
errFmtSessionRedisSentinelMissingName = "session: redis: high_availability: option 'sentinel_name' is required"
|
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"
|
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'"
|
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 is 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"
|
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"
|
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"
|
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"
|
errFmtServerPathNoForwardSlashes = "server: option 'path' must not contain any forward slashes"
|
||||||
errFmtServerPathAlphaNum = "server: option 'path' must only contain alpha numeric characters"
|
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'"
|
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 is 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'"
|
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"
|
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"
|
errFmtServerEndpointsAuthzInvalidName = "server: endpoints: authz: %s: contains invalid characters"
|
||||||
|
@ -302,7 +303,7 @@ const (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
errPasswordPolicyMultipleDefined = "password_policy: only a single password policy mechanism can be specified"
|
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"
|
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 (
|
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.
|
// Error constants.
|
||||||
const (
|
const (
|
||||||
errFmtInvalidDefault2FAMethod = "option 'default_2fa_method' is configured as '%s' but must be one of " +
|
errFmtInvalidDefault2FAMethod = "option 'default_2fa_method' must be one of %s but it's configured as '%s'"
|
||||||
"the following values: '%s'"
|
errFmtInvalidDefault2FAMethodDisabled = "option 'default_2fa_method' must be one of the enabled options %s but it's configured as '%s'"
|
||||||
errFmtInvalidDefault2FAMethodDisabled = "option 'default_2fa_method' is configured as '%s' " +
|
|
||||||
"but must be one of the following enabled method values: '%s'"
|
|
||||||
|
|
||||||
errFmtReplacedConfigurationKey = "invalid configuration key '%s' was replaced by '%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"
|
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"
|
errFilePHashing = "config key incorrect: authentication_backend.file.password_hashing should be authentication_backend.file.password"
|
||||||
|
@ -357,6 +356,10 @@ const (
|
||||||
authzImplementationExtAuthz = "ExtAuthz"
|
authzImplementationExtAuthz = "ExtAuthz"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
auto = "auto"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
validAuthzImplementations = []string{"AuthRequest", "ForwardAuth", authzImplementationExtAuthz, authzImplementationLegacy}
|
validAuthzImplementations = []string{"AuthRequest", "ForwardAuth", authzImplementationExtAuthz, authzImplementationLegacy}
|
||||||
validAuthzAuthnStrategies = []string{"CookieSession", "HeaderAuthorization", "HeaderProxyAuthorization", "HeaderAuthRequestProxyAuthorization", "HeaderLegacy"}
|
validAuthzAuthnStrategies = []string{"CookieSession", "HeaderAuthorization", "HeaderProxyAuthorization", "HeaderAuthRequestProxyAuthorization", "HeaderLegacy"}
|
||||||
|
@ -372,7 +375,7 @@ var (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
validStoragePostgreSQLSSLModes = []string{"disable", "require", "verify-ca", "verify-full"}
|
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"}
|
validSessionSameSiteValues = []string{"none", "lax", "strict"}
|
||||||
validLogLevels = []string{"trace", "debug", "info", "warn", "error"}
|
validLogLevels = []string{"trace", "debug", "info", "warn", "error"}
|
||||||
validWebauthnConveyancePreferences = []string{string(protocol.PreferNoAttestation), string(protocol.PreferIndirectAttestation), string(protocol.PreferDirectAttestation)}
|
validWebauthnConveyancePreferences = []string{string(protocol.PreferNoAttestation), string(protocol.PreferIndirectAttestation), string(protocol.PreferDirectAttestation)}
|
||||||
|
@ -389,19 +392,38 @@ var (
|
||||||
|
|
||||||
var validDefault2FAMethods = []string{"totp", "webauthn", "mobile_push"}
|
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 (
|
var (
|
||||||
validOIDCScopes = []string{oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeProfile, oidc.ScopeGroups, oidc.ScopeOfflineAccess}
|
validOIDCCORSEndpoints = []string{oidc.EndpointAuthorization, oidc.EndpointPushedAuthorizationRequest, oidc.EndpointToken, oidc.EndpointIntrospection, oidc.EndpointRevocation, oidc.EndpointUserinfo}
|
||||||
validOIDCGrantTypes = []string{oidc.GrantTypeImplicit, oidc.GrantTypeRefreshToken, oidc.GrantTypeAuthorizationCode, oidc.GrantTypePassword, oidc.GrantTypeClientCredentials}
|
|
||||||
validOIDCResponseModes = []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment}
|
validOIDCClientScopes = []string{oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeProfile, oidc.ScopeGroups, oidc.ScopeOfflineAccess}
|
||||||
validOIDCUserinfoAlgorithms = []string{oidc.SigningAlgorithmNone, oidc.SigningAlgorithmRSAWithSHA256}
|
validOIDCClientUserinfoAlgorithms = []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()}
|
||||||
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 (
|
var (
|
||||||
reKeyReplacer = regexp.MustCompile(`\[\d+]`)
|
reKeyReplacer = regexp.MustCompile(`\[\d+]`)
|
||||||
reDomainCharacters = regexp.MustCompile(`^[a-z0-9-]+(\.[a-z0-9-]+)+[a-z0-9]$`)
|
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{
|
var replacedKeys = map[string]string{
|
||||||
|
|
|
@ -22,6 +22,11 @@ func TestValidateDuo(t *testing.T) {
|
||||||
have: &schema.Configuration{},
|
have: &schema.Configuration{},
|
||||||
expected: schema.DuoAPIConfiguration{Disable: true},
|
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",
|
desc: "ShouldNotDisableDuo",
|
||||||
have: &schema.Configuration{DuoAPI: schema.DuoAPIConfiguration{
|
have: &schema.Configuration{DuoAPI: schema.DuoAPIConfiguration{
|
||||||
|
@ -46,7 +51,7 @@ func TestValidateDuo(t *testing.T) {
|
||||||
IntegrationKey: "test",
|
IntegrationKey: "test",
|
||||||
},
|
},
|
||||||
errs: []string{
|
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",
|
SecretKey: "test",
|
||||||
},
|
},
|
||||||
errs: []string{
|
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",
|
SecretKey: "test",
|
||||||
},
|
},
|
||||||
errs: []string{
|
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",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package validator
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -125,10 +126,10 @@ func validateOIDCOptionsCORSAllowedOriginsFromClientRedirectURIs(config *schema.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
origin := utils.OriginFromURL(*uri)
|
origin := utils.OriginFromURL(uri)
|
||||||
|
|
||||||
if !utils.IsURLInSlice(origin, config.CORS.AllowedOrigins) {
|
if !utils.IsURLInSlice(*origin, config.CORS.AllowedOrigins) {
|
||||||
config.CORS.AllowedOrigins = append(config.CORS.AllowedOrigins, origin)
|
config.CORS.AllowedOrigins = append(config.CORS.AllowedOrigins, *origin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,113 +138,135 @@ func validateOIDCOptionsCORSAllowedOriginsFromClientRedirectURIs(config *schema.
|
||||||
func validateOIDCOptionsCORSEndpoints(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
func validateOIDCOptionsCORSEndpoints(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
||||||
for _, endpoint := range config.CORS.Endpoints {
|
for _, endpoint := range config.CORS.Endpoints {
|
||||||
if !utils.IsStringInSlice(endpoint, validOIDCCORSEndpoints) {
|
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) {
|
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 {
|
for c, client := range config.Clients {
|
||||||
if client.ID == "" {
|
if client.ID == "" {
|
||||||
invalidID = true
|
blankClientIDs = append(blankClientIDs, "#"+strconv.Itoa(c+1))
|
||||||
} else {
|
} else {
|
||||||
if client.Description == "" {
|
if client.Description == "" {
|
||||||
config.Clients[c].Description = client.ID
|
config.Clients[c].Description = client.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
if utils.IsStringInSliceFold(client.ID, ids) {
|
if id := strings.ToLower(client.ID); utils.IsStringInSlice(id, clientIDs) {
|
||||||
duplicateIDs = true
|
if !utils.IsStringInSlice(id, duplicateClientIDs) {
|
||||||
}
|
duplicateClientIDs = append(duplicateClientIDs, id)
|
||||||
ids = append(ids, client.ID)
|
}
|
||||||
}
|
} else {
|
||||||
|
clientIDs = append(clientIDs, 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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if client.Policy == "" {
|
validateOIDCClient(c, config, val, errDeprecatedFunc)
|
||||||
config.Clients[c].Policy = schema.DefaultOpenIDConnectClientConfiguration.Policy
|
|
||||||
} else if client.Policy != policyOneFactor && client.Policy != policyTwoFactor {
|
|
||||||
val.Push(fmt.Errorf(errFmtOIDCClientInvalidPolicy, client.ID, client.Policy))
|
|
||||||
}
|
|
||||||
|
|
||||||
switch client.PKCEChallengeMethod {
|
|
||||||
case "", "plain", "S256":
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
val.Push(fmt.Errorf(errFmtOIDCClientInvalidPKCEChallengeMethod, client.ID, client.PKCEChallengeMethod))
|
|
||||||
}
|
|
||||||
|
|
||||||
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 invalidID {
|
if errDeprecated {
|
||||||
val.Push(fmt.Errorf(errFmtOIDCClientsWithEmptyID))
|
val.PushWarning(fmt.Errorf(errFmtOIDCClientsDeprecated))
|
||||||
}
|
}
|
||||||
|
|
||||||
if duplicateIDs {
|
if len(blankClientIDs) != 0 {
|
||||||
val.Push(fmt.Errorf(errFmtOIDCClientsDuplicateID))
|
val.Push(fmt.Errorf(errFmtOIDCClientsWithEmptyID, buildJoinedString(", ", "or", "", blankClientIDs)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(duplicateClientIDs) != 0 {
|
||||||
|
val.Push(fmt.Errorf(errFmtOIDCClientsDuplicateID, strJoinOr(duplicateClientIDs)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDCClientSectorIdentifier(client schema.OpenIDConnectClientConfiguration, val *schema.StructValidator) {
|
func validateOIDCClient(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator, errDeprecatedFunc func()) {
|
||||||
if client.SectorIdentifier.String() != "" {
|
if config.Clients[c].Public {
|
||||||
if utils.IsURLHostComponent(client.SectorIdentifier) || utils.IsURLHostComponentWithPort(client.SectorIdentifier) {
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if client.SectorIdentifier.Scheme != "" {
|
if config.Clients[c].SectorIdentifier.Scheme != "" {
|
||||||
val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "scheme", client.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 != "" {
|
if config.Clients[c].SectorIdentifier.Path != "" {
|
||||||
val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "path", client.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 != "" {
|
if config.Clients[c].SectorIdentifier.RawQuery != "" {
|
||||||
val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "query", client.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 != "" {
|
if config.Clients[c].SectorIdentifier.Fragment != "" {
|
||||||
val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "fragment", client.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 config.Clients[c].SectorIdentifier.User != nil {
|
||||||
if client.SectorIdentifier.User.Username() != "" {
|
if config.Clients[c].SectorIdentifier.User.Username() != "" {
|
||||||
val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "username", client.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 {
|
if _, set := config.Clients[c].SectorIdentifier.User.Password(); set {
|
||||||
val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifierWithoutValue, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "password"))
|
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 == "" {
|
} else if config.Clients[c].SectorIdentifier.Host == "" {
|
||||||
val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifierHost, client.ID, client.SectorIdentifier.String()))
|
val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifierHost, config.Clients[c].ID, config.Clients[c].SectorIdentifier.String()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDCClientConsentMode(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
func validateOIDCClientConsentMode(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
||||||
switch {
|
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 {
|
if config.Clients[c].ConsentPreConfiguredDuration != nil {
|
||||||
config.Clients[c].ConsentMode = oidc.ClientConsentModePreConfigured.String()
|
config.Clients[c].ConsentMode = oidc.ClientConsentModePreConfigured.String()
|
||||||
} else {
|
} else {
|
||||||
|
@ -252,7 +275,7 @@ func validateOIDCClientConsentMode(c int, config *schema.OpenIDConnectConfigurat
|
||||||
case utils.IsStringInSlice(config.Clients[c].ConsentMode, validOIDCClientConsentModes):
|
case utils.IsStringInSlice(config.Clients[c].ConsentMode, validOIDCClientConsentModes):
|
||||||
break
|
break
|
||||||
default:
|
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 {
|
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 {
|
if len(config.Clients[c].Scopes) == 0 {
|
||||||
config.Clients[c].Scopes = schema.DefaultOpenIDConnectClientConfiguration.Scopes
|
config.Clients[c].Scopes = schema.DefaultOpenIDConnectClientConfiguration.Scopes
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !utils.IsStringInSlice(oidc.ScopeOpenID, config.Clients[c].Scopes) {
|
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 {
|
invalid, duplicates := validateList(config.Clients[c].Scopes, validOIDCClientScopes, true)
|
||||||
if !utils.IsStringInSlice(scope, validOIDCScopes) {
|
|
||||||
val.Push(fmt.Errorf(
|
if len(invalid) != 0 {
|
||||||
errFmtOIDCClientInvalidEntry,
|
val.Push(fmt.Errorf(errFmtOIDCClientInvalidEntries, config.Clients[c].ID, attrOIDCScopes, strJoinOr(validOIDCClientScopes), strJoinAnd(invalid)))
|
||||||
config.Clients[c].ID, "scopes", strings.Join(validOIDCScopes, "', '"), scope))
|
}
|
||||||
}
|
|
||||||
|
if len(duplicates) != 0 {
|
||||||
|
errDeprecatedFunc()
|
||||||
|
|
||||||
|
val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidEntryDuplicates, config.Clients[c].ID, attrOIDCScopes, strJoinAnd(duplicates)))
|
||||||
|
}
|
||||||
|
|
||||||
|
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 validateOIDCClientGrantTypes(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
func validateOIDCClientResponseTypes(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator, errDeprecatedFunc func()) {
|
||||||
if len(config.Clients[c].GrantTypes) == 0 {
|
|
||||||
config.Clients[c].GrantTypes = schema.DefaultOpenIDConnectClientConfiguration.GrantTypes
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateOIDCClientResponseTypes(c int, config *schema.OpenIDConnectConfiguration, _ *schema.StructValidator) {
|
|
||||||
if len(config.Clients[c].ResponseTypes) == 0 {
|
if len(config.Clients[c].ResponseTypes) == 0 {
|
||||||
config.Clients[c].ResponseTypes = schema.DefaultOpenIDConnectClientConfiguration.ResponseTypes
|
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 {
|
if len(config.Clients[c].ResponseModes) == 0 {
|
||||||
config.Clients[c].ResponseModes = schema.DefaultOpenIDConnectClientConfiguration.ResponseModes
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, responseMode := range config.Clients[c].ResponseModes {
|
invalid, duplicates := validateList(config.Clients[c].ResponseModes, validOIDCClientResponseModes, true)
|
||||||
if !utils.IsStringInSlice(responseMode, validOIDCResponseModes) {
|
|
||||||
validator.Push(fmt.Errorf(
|
if len(invalid) != 0 {
|
||||||
errFmtOIDCClientInvalidEntry,
|
val.Push(fmt.Errorf(errFmtOIDCClientInvalidEntries, config.Clients[c].ID, attrOIDCResponseModes, strJoinOr(validOIDCClientResponseModes), strJoinAnd(invalid)))
|
||||||
config.Clients[c].ID, "response_modes", strings.Join(validOIDCResponseModes, "', '"), responseMode))
|
}
|
||||||
|
|
||||||
|
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)))
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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(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 config.Clients[c].Public {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val.Push(fmt.Errorf(errFmtOIDCClientRedirectURIPublic, config.Clients[c].ID, oauth2InstalledApp))
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsedRedirectURI, err = url.Parse(redirectURI); err != nil {
|
||||||
|
val.Push(fmt.Errorf(errFmtOIDCClientRedirectURICantBeParsed, config.Clients[c].ID, redirectURI, err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
func validateOIDDClientUserinfoAlgorithm(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
||||||
if config.Clients[c].UserinfoSigningAlgorithm == "" {
|
if config.Clients[c].UserinfoSigningAlgorithm == "" {
|
||||||
config.Clients[c].UserinfoSigningAlgorithm = schema.DefaultOpenIDConnectClientConfiguration.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))
|
if !utils.IsStringInSlice(config.Clients[c].UserinfoSigningAlgorithm, validOIDCClientUserinfoAlgorithms) {
|
||||||
}
|
val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue,
|
||||||
}
|
config.Clients[c].ID, attrOIDCUsrSigAlg, strJoinOr(validOIDCClientUserinfoAlgorithms), config.Clients[c].UserinfoSigningAlgorithm))
|
||||||
|
|
||||||
func validateOIDCClientRedirectURIs(client schema.OpenIDConnectClientConfiguration, val *schema.StructValidator) {
|
|
||||||
for _, redirectURI := range client.RedirectURIs {
|
|
||||||
if redirectURI == oauth2InstalledApp {
|
|
||||||
if client.Public {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
val.Push(fmt.Errorf(errFmtOIDCClientRedirectURIPublic, client.ID, oauth2InstalledApp))
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
parsedURL, err := url.Parse(redirectURI)
|
|
||||||
if err != nil {
|
|
||||||
val.Push(fmt.Errorf(errFmtOIDCClientRedirectURICantBeParsed, client.ID, redirectURI, err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !parsedURL.IsAbs() || (!client.Public && parsedURL.Scheme == "") {
|
|
||||||
val.Push(fmt.Errorf(errFmtOIDCClientRedirectURIAbsolute, client.ID, redirectURI))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -100,7 +100,7 @@ func NewKeyMapPattern(key string) (pattern *regexp.Regexp, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if i < n {
|
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])?")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -101,6 +101,20 @@ func TestSpecificErrorKeys(t *testing.T) {
|
||||||
assert.EqualError(t, errs[4], specificErrorKeys["authentication_backend.file.hashing.algorithm"])
|
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) {
|
func TestReplacedErrors(t *testing.T) {
|
||||||
configKeys := []string{
|
configKeys := []string{
|
||||||
"authentication_backend.ldap.skip_verify",
|
"authentication_backend.ldap.skip_verify",
|
||||||
|
|
|
@ -2,7 +2,6 @@ package validator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
"github.com/authelia/authelia/v4/internal/utils"
|
"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) {
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,5 +40,5 @@ func TestShouldRaiseErrorOnInvalidLoggingLevel(t *testing.T) {
|
||||||
assert.Len(t, validator.Warnings(), 0)
|
assert.Len(t, validator.Warnings(), 0)
|
||||||
require.Len(t, validator.Errors(), 1)
|
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'")
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,10 @@ import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"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"))
|
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.
|
File Tests.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -49,5 +49,5 @@ func TestShouldRaiseErrorOnInvalidNTPVersion(t *testing.T) {
|
||||||
|
|
||||||
require.Len(t, validator.Errors(), 1)
|
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'")
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ func TestValidatePasswordPolicy(t *testing.T) {
|
||||||
},
|
},
|
||||||
expectedErrs: []string{
|
expectedErrs: []string{
|
||||||
"password_policy: only a single password policy mechanism can be specified",
|
"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",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -155,13 +155,13 @@ func validateServerEndpointsAuthzEndpoint(config *schema.Configuration, name str
|
||||||
config.Server.Endpoints.Authz[name] = endpoint
|
config.Server.Endpoints.Authz[name] = endpoint
|
||||||
default:
|
default:
|
||||||
if !utils.IsStringInSlice(endpoint.Implementation, validAuthzImplementations) {
|
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 {
|
} else {
|
||||||
validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzLegacyInvalidImplementation, name))
|
validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzLegacyInvalidImplementation, name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if !utils.IsStringInSlice(endpoint.Implementation, validAuthzImplementations) {
|
} 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) {
|
if !reAuthzEndpointName.MatchString(name) {
|
||||||
|
@ -180,7 +180,7 @@ func validateServerEndpointsAuthzStrategies(name string, strategies []schema.Ser
|
||||||
names = append(names, strategy.Name)
|
names = append(names, strategy.Name)
|
||||||
|
|
||||||
if !utils.IsStringInSlice(strategy.Name, validAuthzAuthnStrategies) {
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -314,14 +314,18 @@ func TestServerAuthzEndpointErrors(t *testing.T) {
|
||||||
map[string]schema.ServerAuthzEndpoint{
|
map[string]schema.ServerAuthzEndpoint{
|
||||||
"example": {Implementation: "zero"},
|
"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",
|
"ShouldErrorOnInvalidEndpointImplementationLegacy",
|
||||||
map[string]schema.ServerAuthzEndpoint{
|
map[string]schema.ServerAuthzEndpoint{
|
||||||
"legacy": {Implementation: "zero"},
|
"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",
|
"ShouldErrorOnInvalidEndpointLegacyImplementation",
|
||||||
|
@ -335,7 +339,9 @@ func TestServerAuthzEndpointErrors(t *testing.T) {
|
||||||
map[string]schema.ServerAuthzEndpoint{
|
map[string]schema.ServerAuthzEndpoint{
|
||||||
"example": {Implementation: "ExtAuthz", AuthnStrategies: []schema.ServerAuthzEndpointAuthnStrategy{{Name: "bad-name"}}},
|
"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",
|
"ShouldErrorOnDuplicateName",
|
||||||
|
|
|
@ -45,7 +45,7 @@ func validateSession(config *schema.SessionConfiguration, validator *schema.Stru
|
||||||
if config.SameSite == "" {
|
if config.SameSite == "" {
|
||||||
config.SameSite = schema.DefaultSessionConfiguration.SameSite
|
config.SameSite = schema.DefaultSessionConfiguration.SameSite
|
||||||
} else if !utils.IsStringInSlice(config.SameSite, validSessionSameSiteValues) {
|
} 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)
|
cookies := len(config.Cookies)
|
||||||
|
@ -73,7 +73,7 @@ func validateSession(config *schema.SessionConfiguration, validator *schema.Stru
|
||||||
|
|
||||||
func validateSessionCookieDomains(config *schema.SessionConfiguration, validator *schema.StructValidator) {
|
func validateSessionCookieDomains(config *schema.SessionConfiguration, validator *schema.StructValidator) {
|
||||||
if len(config.Cookies) == 0 {
|
if len(config.Cookies) == 0 {
|
||||||
validator.Push(fmt.Errorf(errFmtSessionOptionRequired, "domain"))
|
validator.Push(fmt.Errorf(errFmtSessionOptionRequired, "cookies"))
|
||||||
}
|
}
|
||||||
|
|
||||||
domains := make([]string, 0)
|
domains := make([]string, 0)
|
||||||
|
@ -182,7 +182,7 @@ func validateSessionSameSite(i int, config *schema.SessionConfiguration, validat
|
||||||
config.Cookies[i].SameSite = schema.DefaultSessionConfiguration.SameSite
|
config.Cookies[i].SameSite = schema.DefaultSessionConfiguration.SameSite
|
||||||
}
|
}
|
||||||
} else if !utils.IsStringInSlice(config.Cookies[i].SameSite, validSessionSameSiteValues) {
|
} 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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -95,7 +95,7 @@ func TestShouldSetDefaultSessionDomainsValues(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[]string{
|
[]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,
|
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()
|
validator := schema.NewStructValidator()
|
||||||
|
@ -302,7 +320,7 @@ func TestShouldRaiseErrorWhenRedisHasHostnameButNoPort(t *testing.T) {
|
||||||
|
|
||||||
assert.False(t, validator.HasWarnings())
|
assert.False(t, validator.HasWarnings())
|
||||||
assert.Len(t, validator.Errors(), 1)
|
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) {
|
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"}},
|
{"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},
|
{"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},
|
{"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"}},
|
{"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())
|
assert.False(t, validator.HasWarnings())
|
||||||
require.Len(t, validator.Errors(), 2)
|
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()[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', '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', or 'strict' but it's configured as 'NOne'")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldNotRaiseErrorWhenSameSiteSetCorrectly(t *testing.T) {
|
func TestShouldNotRaiseErrorWhenSameSiteSetCorrectly(t *testing.T) {
|
||||||
|
|
|
@ -3,7 +3,6 @@ package validator
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
"github.com/authelia/authelia/v4/internal/utils"
|
"github.com/authelia/authelia/v4/internal/utils"
|
||||||
|
@ -92,7 +91,7 @@ func validatePostgreSQLConfiguration(config *schema.PostgreSQLStorageConfigurati
|
||||||
case config.SSL.Mode == "":
|
case config.SSL.Mode == "":
|
||||||
config.SSL.Mode = schema.DefaultPostgreSQLStorageConfiguration.SSL.Mode
|
config.SSL.Mode = schema.DefaultPostgreSQLStorageConfiguration.SSL.Mode
|
||||||
case !utils.IsStringInSlice(config.SSL.Mode, validStoragePostgreSQLSSLModes):
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -360,7 +360,7 @@ func (suite *StorageSuite) TestShouldValidatePostgresSSLModeMustBeValid() {
|
||||||
|
|
||||||
suite.Assert().Len(suite.validator.Warnings(), 1)
|
suite.Assert().Len(suite.validator.Warnings(), 1)
|
||||||
suite.Require().Len(suite.validator.Errors(), 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() {
|
func (suite *StorageSuite) TestShouldRaiseErrorOnNoEncryptionKey() {
|
||||||
|
|
|
@ -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")}}},
|
||||||
&schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("udp://0.0.0.0:9959")}}},
|
&schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("udp://0.0.0.0:9959")}}},
|
||||||
nil,
|
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'"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ package validator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
"github.com/authelia/authelia/v4/internal/utils"
|
"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) {
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ func (suite *Theme) TestShouldRaiseErrorWhenInvalidThemeProvided() {
|
||||||
suite.Assert().Len(suite.validator.Warnings(), 0)
|
suite.Assert().Len(suite.validator.Warnings(), 0)
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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) {
|
func TestThemes(t *testing.T) {
|
||||||
|
|
|
@ -24,7 +24,7 @@ func ValidateTOTP(config *schema.Configuration, validator *schema.StructValidato
|
||||||
config.TOTP.Algorithm = strings.ToUpper(config.TOTP.Algorithm)
|
config.TOTP.Algorithm = strings.ToUpper(config.TOTP.Algorithm)
|
||||||
|
|
||||||
if !utils.IsStringInSlice(config.TOTP.Algorithm, schema.TOTPPossibleAlgorithms) {
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,9 @@ func TestValidateTOTP(t *testing.T) {
|
||||||
Skew: schema.DefaultTOTPConfiguration.Skew,
|
Skew: schema.DefaultTOTPConfiguration.Skew,
|
||||||
Issuer: "abc",
|
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",
|
desc: "ShouldRaiseErrorWhenInvalidTOTPValue",
|
||||||
|
@ -69,10 +71,10 @@ func TestValidateTOTP(t *testing.T) {
|
||||||
Issuer: "abc",
|
Issuer: "abc",
|
||||||
},
|
},
|
||||||
errs: []string{
|
errs: []string{
|
||||||
"totp: option 'algorithm' must be one of 'SHA1', 'SHA256', 'SHA512' but it is configured as 'SHA3'",
|
"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 is configured as '5'",
|
"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 is configured as '20'",
|
"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 is configured as '10'",
|
"totp: option 'secret_size' must be 20 or higher but it's configured as '10'",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/net/publicsuffix"
|
"golang.org/x/net/publicsuffix"
|
||||||
|
|
||||||
|
"github.com/authelia/authelia/v4/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func isCookieDomainAPublicSuffix(domain string) (valid bool) {
|
func isCookieDomainAPublicSuffix(domain string) (valid bool) {
|
||||||
|
@ -13,3 +15,95 @@ func isCookieDomainAPublicSuffix(domain string) (valid bool) {
|
||||||
|
|
||||||
return len(strings.TrimLeft(domain, ".")) == len(suffix)
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package validator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
"github.com/authelia/authelia/v4/internal/utils"
|
"github.com/authelia/authelia/v4/internal/utils"
|
||||||
|
@ -22,13 +21,13 @@ func ValidateWebauthn(config *schema.Configuration, validator *schema.StructVali
|
||||||
case config.Webauthn.ConveyancePreference == "":
|
case config.Webauthn.ConveyancePreference == "":
|
||||||
config.Webauthn.ConveyancePreference = schema.DefaultWebauthnConfiguration.ConveyancePreference
|
config.Webauthn.ConveyancePreference = schema.DefaultWebauthnConfiguration.ConveyancePreference
|
||||||
case !utils.IsStringInSlice(string(config.Webauthn.ConveyancePreference), validWebauthnConveyancePreferences):
|
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 {
|
switch {
|
||||||
case config.Webauthn.UserVerification == "":
|
case config.Webauthn.UserVerification == "":
|
||||||
config.Webauthn.UserVerification = schema.DefaultWebauthnConfiguration.UserVerification
|
config.Webauthn.UserVerification = schema.DefaultWebauthnConfiguration.UserVerification
|
||||||
case !utils.IsStringInSlice(string(config.Webauthn.UserVerification), validWebauthnUserVerificationRequirement):
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,6 +93,6 @@ func TestWebauthnShouldRaiseErrorsOnInvalidOptions(t *testing.T) {
|
||||||
|
|
||||||
require.Len(t, validator.Errors(), 2)
|
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()[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 'discouraged', 'preferred', 'required' but it is configured as 'yes'")
|
assert.EqualError(t, validator.Errors()[1], "webauthn: option 'user_verification' must be one of 'none', 'indirect', or 'direct' but it's configured as 'yes'")
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr
|
||||||
var (
|
var (
|
||||||
requester fosite.AuthorizeRequester
|
requester fosite.AuthorizeRequester
|
||||||
responder fosite.AuthorizeResponder
|
responder fosite.AuthorizeResponder
|
||||||
client *oidc.Client
|
client oidc.Client
|
||||||
authTime time.Time
|
authTime time.Time
|
||||||
issuer *url.URL
|
issuer *url.URL
|
||||||
err error
|
err error
|
||||||
|
@ -117,7 +117,7 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr
|
||||||
|
|
||||||
extraClaims := oidcGrantRequests(requester, consent, &userSession)
|
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.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."))
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var client *oidc.Client
|
var client oidc.Client
|
||||||
|
|
||||||
clientID := requester.GetClient().GetID()
|
clientID := requester.GetClient().GetID()
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ import (
|
||||||
"github.com/authelia/authelia/v4/internal/utils"
|
"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,
|
userSession session.UserSession,
|
||||||
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
|
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
|
||||||
var (
|
var (
|
||||||
|
@ -33,14 +33,14 @@ func handleOIDCAuthorizationConsent(ctx *middlewares.AutheliaCtx, issuer *url.UR
|
||||||
handler = handleOIDCAuthorizationConsentNotAuthenticated
|
handler = handleOIDCAuthorizationConsentNotAuthenticated
|
||||||
case client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel):
|
case client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel):
|
||||||
if subject, err = ctx.Providers.OpenIDConnect.GetSubject(ctx, client.GetSectorIdentifier(), userSession.Username); err != nil {
|
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)
|
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrSubjectCouldNotLookup)
|
||||||
|
|
||||||
return nil, true
|
return nil, true
|
||||||
}
|
}
|
||||||
|
|
||||||
switch client.Consent.Mode {
|
switch client.GetConsentPolicy().Mode {
|
||||||
case oidc.ClientConsentModeExplicit:
|
case oidc.ClientConsentModeExplicit:
|
||||||
handler = handleOIDCAuthorizationConsentModeExplicit
|
handler = handleOIDCAuthorizationConsentModeExplicit
|
||||||
case oidc.ClientConsentModeImplicit:
|
case oidc.ClientConsentModeImplicit:
|
||||||
|
@ -56,7 +56,7 @@ func handleOIDCAuthorizationConsent(ctx *middlewares.AutheliaCtx, issuer *url.UR
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if subject, err = ctx.Providers.OpenIDConnect.GetSubject(ctx, client.GetSectorIdentifier(), userSession.Username); err != nil {
|
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)
|
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)
|
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,
|
_ session.UserSession, _ uuid.UUID,
|
||||||
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
|
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
|
||||||
redirectionURL := handleOIDCAuthorizationConsentGetRedirectionURL(issuer, nil, requester)
|
redirectionURL := handleOIDCAuthorizationConsentGetRedirectionURL(issuer, nil, requester)
|
||||||
|
@ -79,17 +79,17 @@ func handleOIDCAuthorizationConsentNotAuthenticated(_ *middlewares.AutheliaCtx,
|
||||||
return nil, true
|
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,
|
userSession session.UserSession, subject uuid.UUID,
|
||||||
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
|
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
|
||||||
var (
|
var (
|
||||||
err error
|
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 {
|
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)
|
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 {
|
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)
|
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 {
|
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)
|
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave)
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ func handleOIDCAuthorizationConsentGenerate(ctx *middlewares.AutheliaCtx, issuer
|
||||||
return consent, true
|
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) {
|
userSession session.UserSession, rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) {
|
||||||
var location *url.URL
|
var location *url.URL
|
||||||
|
|
||||||
|
@ -130,14 +130,14 @@ func handleOIDCAuthorizationConsentRedirect(ctx *middlewares.AutheliaCtx, issuer
|
||||||
|
|
||||||
location.RawQuery = query.Encode()
|
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 {
|
} else {
|
||||||
location = handleOIDCAuthorizationConsentGetRedirectionURL(issuer, consent, requester)
|
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)
|
http.Redirect(rw, r, location.String(), http.StatusFound)
|
||||||
}
|
}
|
||||||
|
@ -170,7 +170,7 @@ func handleOIDCAuthorizationConsentGetRedirectionURL(issuer *url.URL, consent *m
|
||||||
return redirectURL
|
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
|
var sid uint32
|
||||||
|
|
||||||
if client == nil {
|
if client == nil {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
"github.com/authelia/authelia/v4/internal/session"
|
"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,
|
userSession session.UserSession, subject uuid.UUID,
|
||||||
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
|
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
|
||||||
var (
|
var (
|
||||||
|
@ -28,7 +28,7 @@ func handleOIDCAuthorizationConsentModeExplicit(ctx *middlewares.AutheliaCtx, is
|
||||||
return handleOIDCAuthorizationConsentGenerate(ctx, issuer, client, userSession, subject, rw, r, requester)
|
return handleOIDCAuthorizationConsentGenerate(ctx, issuer, client, userSession, subject, rw, r, requester)
|
||||||
default:
|
default:
|
||||||
if consentID, err = uuid.ParseBytes(bytesConsentID); err != nil {
|
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)
|
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,
|
userSession session.UserSession, subject uuid.UUID, consentID uuid.UUID,
|
||||||
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
|
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
|
||||||
var (
|
var (
|
||||||
|
@ -47,7 +47,7 @@ func handleOIDCAuthorizationConsentModeExplicitWithID(ctx *middlewares.AutheliaC
|
||||||
)
|
)
|
||||||
|
|
||||||
if consentID.ID() == 0 {
|
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)
|
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 {
|
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)
|
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() {
|
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)
|
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup)
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ func handleOIDCAuthorizationConsentModeExplicitWithID(ctx *middlewares.AutheliaC
|
||||||
}
|
}
|
||||||
|
|
||||||
if !consent.CanGrant() {
|
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)
|
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotPerform)
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ func handleOIDCAuthorizationConsentModeExplicitWithID(ctx *middlewares.AutheliaC
|
||||||
|
|
||||||
if !consent.IsAuthorized() {
|
if !consent.IsAuthorized() {
|
||||||
if consent.Responded() {
|
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)
|
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, fosite.ErrAccessDenied)
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
"github.com/authelia/authelia/v4/internal/session"
|
"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,
|
userSession session.UserSession, subject uuid.UUID,
|
||||||
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
|
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
|
||||||
var (
|
var (
|
||||||
|
@ -26,7 +26,7 @@ func handleOIDCAuthorizationConsentModeImplicit(ctx *middlewares.AutheliaCtx, is
|
||||||
return handleOIDCAuthorizationConsentModeImplicitWithoutID(ctx, issuer, client, userSession, subject, rw, r, requester)
|
return handleOIDCAuthorizationConsentModeImplicitWithoutID(ctx, issuer, client, userSession, subject, rw, r, requester)
|
||||||
default:
|
default:
|
||||||
if consentID, err = uuid.ParseBytes(bytesConsentID); err != nil {
|
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)
|
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,
|
userSession session.UserSession, subject uuid.UUID, consentID uuid.UUID,
|
||||||
rw http.ResponseWriter, _ *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
|
rw http.ResponseWriter, _ *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
|
||||||
var (
|
var (
|
||||||
|
@ -45,7 +45,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaC
|
||||||
)
|
)
|
||||||
|
|
||||||
if consentID.ID() == 0 {
|
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)
|
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 {
|
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)
|
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() {
|
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)
|
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup)
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaC
|
||||||
}
|
}
|
||||||
|
|
||||||
if !consent.CanGrant() {
|
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)
|
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotPerform)
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaC
|
||||||
consent.Grant()
|
consent.Grant()
|
||||||
|
|
||||||
if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSessionResponse(ctx, *consent, false); err != nil {
|
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)
|
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave)
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaC
|
||||||
return consent, false
|
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,
|
_ session.UserSession, subject uuid.UUID,
|
||||||
rw http.ResponseWriter, _ *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
|
rw http.ResponseWriter, _ *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
|
||||||
var (
|
var (
|
||||||
|
@ -97,7 +97,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithoutID(ctx *middlewares.Authel
|
||||||
)
|
)
|
||||||
|
|
||||||
if consent, err = model.NewOAuth2ConsentSession(subject, requester); err != nil {
|
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)
|
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 {
|
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)
|
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 {
|
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)
|
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave)
|
||||||
|
|
||||||
|
@ -123,7 +123,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithoutID(ctx *middlewares.Authel
|
||||||
consent.Grant()
|
consent.Grant()
|
||||||
|
|
||||||
if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSessionResponse(ctx, *consent, false); err != nil {
|
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)
|
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave)
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ import (
|
||||||
"github.com/authelia/authelia/v4/internal/storage"
|
"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,
|
userSession session.UserSession, subject uuid.UUID,
|
||||||
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
|
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
|
||||||
var (
|
var (
|
||||||
|
@ -32,7 +32,7 @@ func handleOIDCAuthorizationConsentModePreConfigured(ctx *middlewares.AutheliaCt
|
||||||
return handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx, issuer, client, userSession, subject, rw, r, requester)
|
return handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx, issuer, client, userSession, subject, rw, r, requester)
|
||||||
default:
|
default:
|
||||||
if consentID, err = uuid.ParseBytes(bytesConsentID); err != nil {
|
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)
|
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,
|
userSession session.UserSession, subject uuid.UUID, consentID uuid.UUID,
|
||||||
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
|
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
|
||||||
var (
|
var (
|
||||||
|
@ -52,7 +52,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.Auth
|
||||||
)
|
)
|
||||||
|
|
||||||
if consentID.ID() == 0 {
|
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)
|
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 {
|
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)
|
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() {
|
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)
|
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup)
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.Auth
|
||||||
}
|
}
|
||||||
|
|
||||||
if !consent.CanGrant() {
|
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)
|
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 {
|
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)
|
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}
|
consent.PreConfiguration = sql.NullInt64{Int64: config.ID, Valid: true}
|
||||||
|
|
||||||
if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSessionResponse(ctx, *consent, false); err != nil {
|
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)
|
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave)
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.Auth
|
||||||
|
|
||||||
if !consent.IsAuthorized() {
|
if !consent.IsAuthorized() {
|
||||||
if consent.Responded() {
|
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)
|
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, fosite.ErrAccessDenied)
|
||||||
|
|
||||||
|
@ -124,7 +124,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.Auth
|
||||||
return consent, false
|
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,
|
userSession session.UserSession, subject uuid.UUID,
|
||||||
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
|
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
|
||||||
var (
|
var (
|
||||||
|
@ -133,7 +133,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx *middlewares.A
|
||||||
)
|
)
|
||||||
|
|
||||||
if config, err = handleOIDCAuthorizationConsentModePreConfiguredGetPreConfig(ctx, client, subject, requester); err != nil {
|
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)
|
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 {
|
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)
|
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 {
|
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)
|
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 {
|
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)
|
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}
|
consent.PreConfiguration = sql.NullInt64{Int64: config.ID, Valid: true}
|
||||||
|
|
||||||
if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSessionResponse(ctx, *consent, false); err != nil {
|
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)
|
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave)
|
||||||
|
|
||||||
|
@ -183,12 +183,12 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx *middlewares.A
|
||||||
return consent, false
|
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 (
|
var (
|
||||||
rows *storage.ConsentPreConfigRows
|
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 {
|
if rows, err = ctx.Providers.StorageProvider.LoadOAuth2ConsentPreConfigurations(ctx, client.GetID(), subject); err != nil {
|
||||||
return nil, fmt.Errorf("error loading rows: %w", err)
|
return nil, fmt.Errorf("error loading rows: %w", err)
|
||||||
|
@ -196,7 +196,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredGetPreConfig(ctx *middleware
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := rows.Close(); err != nil {
|
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() {
|
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
|
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
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ func OpenIDConnectConsentGET(ctx *middlewares.AutheliaCtx) {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
consent *model.OAuth2ConsentSession
|
consent *model.OAuth2ConsentSession
|
||||||
client *oidc.Client
|
client oidc.Client
|
||||||
handled bool
|
handled bool
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ func OpenIDConnectConsentPOST(ctx *middlewares.AutheliaCtx) {
|
||||||
var (
|
var (
|
||||||
userSession session.UserSession
|
userSession session.UserSession
|
||||||
consent *model.OAuth2ConsentSession
|
consent *model.OAuth2ConsentSession
|
||||||
client *oidc.Client
|
client oidc.Client
|
||||||
handled bool
|
handled bool
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -90,12 +90,12 @@ func OpenIDConnectConsentPOST(ctx *middlewares.AutheliaCtx) {
|
||||||
consent.Grant()
|
consent.Grant()
|
||||||
|
|
||||||
if bodyJSON.PreConfigure {
|
if bodyJSON.PreConfigure {
|
||||||
if client.Consent.Mode == oidc.ClientConsentModePreConfigured {
|
if client.GetConsentPolicy().Mode == oidc.ClientConsentModePreConfigured {
|
||||||
config := model.OAuth2ConsentPreConfig{
|
config := model.OAuth2ConsentPreConfig{
|
||||||
ClientID: consent.ClientID,
|
ClientID: consent.ClientID,
|
||||||
Subject: consent.Subject.UUID,
|
Subject: consent.Subject.UUID,
|
||||||
CreatedAt: time.Now(),
|
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,
|
Scopes: consent.GrantedScopes,
|
||||||
Audience: consent.GrantedAudience,
|
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 (
|
var (
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
@ -185,7 +185,7 @@ func oidcConsentGetSessionsAndClient(ctx *middlewares.AutheliaCtx, consentID uui
|
||||||
return userSession, nil, nil, true
|
return userSession, nil, nil, true
|
||||||
}
|
}
|
||||||
|
|
||||||
switch client.Consent.Mode {
|
switch client.GetConsentPolicy().Mode {
|
||||||
case oidc.ClientConsentModeImplicit:
|
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.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()
|
ctx.ReplyForbidden()
|
||||||
|
|
|
@ -23,7 +23,7 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
|
||||||
var (
|
var (
|
||||||
tokenType fosite.TokenType
|
tokenType fosite.TokenType
|
||||||
requester fosite.AccessRequester
|
requester fosite.AccessRequester
|
||||||
client *oidc.Client
|
client oidc.Client
|
||||||
err error
|
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)
|
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:
|
case oidc.SigningAlgorithmRSAWithSHA256:
|
||||||
var jti uuid.UUID
|
var jti uuid.UUID
|
||||||
|
|
||||||
|
@ -129,6 +129,6 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
|
||||||
case oidc.SigningAlgorithmNone, "":
|
case oidc.SigningAlgorithmNone, "":
|
||||||
ctx.Providers.OpenIDConnect.Write(rw, req, claims)
|
ctx.Providers.OpenIDConnect.Write(rw, req, claims)
|
||||||
default:
|
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())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -178,7 +178,7 @@ func handleOIDCWorkflowResponseWithTargetURL(ctx *middlewares.AutheliaCtx, targe
|
||||||
func handleOIDCWorkflowResponseWithID(ctx *middlewares.AutheliaCtx, id string) {
|
func handleOIDCWorkflowResponseWithID(ctx *middlewares.AutheliaCtx, id string) {
|
||||||
var (
|
var (
|
||||||
workflowID uuid.UUID
|
workflowID uuid.UUID
|
||||||
client *oidc.Client
|
client oidc.Client
|
||||||
consent *model.OAuth2ConsentSession
|
consent *model.OAuth2ConsentSession
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
@ -210,19 +210,19 @@ func handleOIDCWorkflowResponseWithID(ctx *middlewares.AutheliaCtx, id string) {
|
||||||
var userSession session.UserSession
|
var userSession session.UserSession
|
||||||
|
|
||||||
if userSession, err = ctx.GetSession(); err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if userSession.IsAnonymous() {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel) {
|
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()
|
ctx.ReplyOK()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
@ -143,7 +143,7 @@ type PasswordPolicyBody struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type handlerAuthorizationConsent func(
|
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,
|
userSession session.UserSession, subject uuid.UUID,
|
||||||
rw http.ResponseWriter, r *http.Request,
|
rw http.ResponseWriter, r *http.Request,
|
||||||
requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool)
|
requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool)
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package oidc
|
package oidc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/go-crypt/crypt/algorithm"
|
||||||
"github.com/ory/fosite"
|
"github.com/ory/fosite"
|
||||||
"github.com/ory/x/errorsx"
|
"github.com/ory/x/errorsx"
|
||||||
|
"gopkg.in/square/go-jose.v2"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/authentication"
|
"github.com/authelia/authelia/v4/internal/authentication"
|
||||||
"github.com/authelia/authelia/v4/internal/authorization"
|
"github.com/authelia/authelia/v4/internal/authorization"
|
||||||
|
@ -11,8 +13,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewClient creates a new Client.
|
// NewClient creates a new Client.
|
||||||
func NewClient(config schema.OpenIDConnectClientConfiguration) (client *Client) {
|
func NewClient(config schema.OpenIDConnectClientConfiguration) (client Client) {
|
||||||
client = &Client{
|
base := &BaseClient{
|
||||||
ID: config.ID,
|
ID: config.ID,
|
||||||
Description: config.Description,
|
Description: config.Description,
|
||||||
Secret: config.Secret,
|
Secret: config.Secret,
|
||||||
|
@ -40,14 +42,165 @@ func NewClient(config schema.OpenIDConnectClientConfiguration) (client *Client)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, mode := range config.ResponseModes {
|
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
|
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.
|
// 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()
|
form := r.GetRequestForm()
|
||||||
|
|
||||||
if c.EnforcePKCE {
|
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.
|
// 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 c.EnforcePAR {
|
||||||
if !IsPushedAuthorizedRequest(r, prefix) {
|
if !IsPushedAuthorizedRequest(r, prefix) {
|
||||||
switch requestURI := r.GetRequestForm().Get(FormParameterRequestURI); requestURI {
|
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
|
// 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.
|
// 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 {
|
if r.GetResponseMode() != fosite.ResponseModeDefault {
|
||||||
return nil
|
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))
|
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.
|
// GetRequestURIs is an array of request_uri values that are pre-registered by the RP for use at the OP. Servers MAY
|
||||||
func (c *Client) IsAuthenticationLevelSufficient(level authentication.Level) bool {
|
// cache the contents of the files referenced by these URIs and not retrieve them at the time they are used in a request.
|
||||||
if level == authentication.NotAuthenticated {
|
// OPs can require that request_uri values used be pre-registered with the require_request_uri_registration
|
||||||
return false
|
// discovery parameter.
|
||||||
|
func (c *FullClient) GetRequestURIs() []string {
|
||||||
|
return c.RequestURIs
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 authorization.IsAuthLevelSufficient(level, c.Policy)
|
return c.TokenEndpointAuthMethod
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSectorIdentifier returns the SectorIdentifier for this client.
|
// GetTokenEndpointAuthSigningAlgorithm returns the JWS [JWS] alg algorithm [JWA] that MUST be used for signing the JWT
|
||||||
func (c *Client) GetSectorIdentifier() string {
|
// [JWT] used to authenticate the Client at the Token Endpoint for the private_key_jwt and client_secret_jwt
|
||||||
return c.SectorIdentifier
|
// authentication methods.
|
||||||
}
|
func (c *FullClient) GetTokenEndpointAuthSigningAlgorithm() string {
|
||||||
|
if c.TokenEndpointAuthSigningAlgorithm == "" {
|
||||||
// GetConsentResponseBody returns the proper consent response body for this session.OIDCWorkflowSession.
|
c.TokenEndpointAuthSigningAlgorithm = SigningAlgorithmRSAWithSHA256
|
||||||
func (c *Client) GetConsentResponseBody(consent *model.OAuth2ConsentSession) ConsentGetResponseBody {
|
|
||||||
body := ConsentGetResponseBody{
|
|
||||||
ClientID: c.ID,
|
|
||||||
ClientDescription: c.Description,
|
|
||||||
PreConfiguration: c.Consent.Mode == ClientConsentModePreConfigured,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if consent != nil {
|
return c.TokenEndpointAuthSigningAlgorithm
|
||||||
body.Scopes = consent.RequestedScopes
|
|
||||||
body.Audience = consent.RequestedAudience
|
|
||||||
}
|
|
||||||
|
|
||||||
return body
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetID returns the ID.
|
|
||||||
func (c *Client) GetID() string {
|
|
||||||
return c.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHashedSecret returns the Secret.
|
|
||||||
func (c *Client) GetHashedSecret() (secret []byte) {
|
|
||||||
if c.Secret == nil {
|
|
||||||
return []byte(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
return []byte(c.Secret.Encode())
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRedirectURIs returns the RedirectURIs.
|
|
||||||
func (c *Client) GetRedirectURIs() (redirectURIs []string) {
|
|
||||||
return c.RedirectURIs
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGrantTypes returns the GrantTypes.
|
|
||||||
func (c *Client) GetGrantTypes() fosite.Arguments {
|
|
||||||
if len(c.GrantTypes) == 0 {
|
|
||||||
return fosite.Arguments{"authorization_code"}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.GrantTypes
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetResponseTypes returns the ResponseTypes.
|
|
||||||
func (c *Client) GetResponseTypes() fosite.Arguments {
|
|
||||||
if len(c.ResponseTypes) == 0 {
|
|
||||||
return fosite.Arguments{"code"}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"github.com/ory/fosite"
|
"github.com/ory/fosite"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"gopkg.in/square/go-jose.v2"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/authentication"
|
"github.com/authelia/authelia/v4/internal/authentication"
|
||||||
"github.com/authelia/authelia/v4/internal/authorization"
|
"github.com/authelia/authelia/v4/internal/authorization"
|
||||||
|
@ -15,36 +16,136 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewClient(t *testing.T) {
|
func TestNewClient(t *testing.T) {
|
||||||
blankConfig := schema.OpenIDConnectClientConfiguration{}
|
config := schema.OpenIDConnectClientConfiguration{}
|
||||||
blankClient := NewClient(blankConfig)
|
client := NewClient(config)
|
||||||
assert.Equal(t, "", blankClient.ID)
|
assert.Equal(t, "", client.GetID())
|
||||||
assert.Equal(t, "", blankClient.Description)
|
assert.Equal(t, "", client.GetDescription())
|
||||||
assert.Equal(t, "", blankClient.Description)
|
assert.Len(t, client.GetResponseModes(), 0)
|
||||||
assert.Len(t, blankClient.ResponseModes, 0)
|
assert.Len(t, client.GetResponseTypes(), 1)
|
||||||
|
assert.Equal(t, "", client.GetSectorIdentifier())
|
||||||
|
|
||||||
exampleConfig := schema.OpenIDConnectClientConfiguration{
|
bclient, ok := client.(*BaseClient)
|
||||||
ID: "myapp",
|
require.True(t, ok)
|
||||||
Description: "My App",
|
assert.Equal(t, "", bclient.UserinfoSigningAlgorithm)
|
||||||
Policy: "two_factor",
|
assert.Equal(t, SigningAlgorithmNone, client.GetUserinfoSigningAlgorithm())
|
||||||
Secret: MustDecodeSecret("$plaintext$abcdef"),
|
|
||||||
RedirectURIs: []string{"https://google.com/callback"},
|
_, 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,
|
Scopes: schema.DefaultOpenIDConnectClientConfiguration.Scopes,
|
||||||
ResponseTypes: schema.DefaultOpenIDConnectClientConfiguration.ResponseTypes,
|
ResponseTypes: schema.DefaultOpenIDConnectClientConfiguration.ResponseTypes,
|
||||||
GrantTypes: schema.DefaultOpenIDConnectClientConfiguration.GrantTypes,
|
GrantTypes: schema.DefaultOpenIDConnectClientConfiguration.GrantTypes,
|
||||||
ResponseModes: schema.DefaultOpenIDConnectClientConfiguration.ResponseModes,
|
ResponseModes: schema.DefaultOpenIDConnectClientConfiguration.ResponseModes,
|
||||||
}
|
}
|
||||||
|
|
||||||
exampleClient := NewClient(exampleConfig)
|
client = NewClient(config)
|
||||||
assert.Equal(t, "myapp", exampleClient.ID)
|
assert.Equal(t, myclient, client.GetID())
|
||||||
require.Len(t, exampleClient.ResponseModes, 3)
|
require.Len(t, client.GetResponseModes(), 1)
|
||||||
assert.Equal(t, fosite.ResponseModeFormPost, exampleClient.ResponseModes[0])
|
assert.Equal(t, fosite.ResponseModeFormPost, client.GetResponseModes()[0])
|
||||||
assert.Equal(t, fosite.ResponseModeQuery, exampleClient.ResponseModes[1])
|
assert.Equal(t, authorization.TwoFactor, client.GetAuthorizationPolicy())
|
||||||
assert.Equal(t, fosite.ResponseModeFragment, exampleClient.ResponseModes[2])
|
|
||||||
assert.Equal(t, authorization.TwoFactor, exampleClient.Policy)
|
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) {
|
func TestIsAuthenticationLevelSufficient(t *testing.T) {
|
||||||
c := Client{}
|
c := &FullClient{BaseClient: &BaseClient{}}
|
||||||
|
|
||||||
c.Policy = authorization.Bypass
|
c.Policy = authorization.Bypass
|
||||||
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated))
|
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated))
|
||||||
|
@ -68,7 +169,7 @@ func TestIsAuthenticationLevelSufficient(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_GetConsentResponseBody(t *testing.T) {
|
func TestClient_GetConsentResponseBody(t *testing.T) {
|
||||||
c := Client{}
|
c := &FullClient{BaseClient: &BaseClient{}}
|
||||||
|
|
||||||
consentRequestBody := c.GetConsentResponseBody(nil)
|
consentRequestBody := c.GetConsentResponseBody(nil)
|
||||||
assert.Equal(t, "", consentRequestBody.ClientID)
|
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.Scopes)
|
||||||
assert.Equal(t, []string(nil), consentRequestBody.Audience)
|
assert.Equal(t, []string(nil), consentRequestBody.Audience)
|
||||||
|
|
||||||
c.ID = "myclient"
|
c.ID = myclient
|
||||||
c.Description = "My Client"
|
c.Description = myclientdesc
|
||||||
|
|
||||||
consent := &model.OAuth2ConsentSession{
|
consent := &model.OAuth2ConsentSession{
|
||||||
RequestedAudience: []string{"https://example.com"},
|
RequestedAudience: []string{examplecom},
|
||||||
RequestedScopes: []string{"openid", "groups"},
|
RequestedScopes: []string{ScopeOpenID, ScopeGroups},
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedScopes := []string{"openid", "groups"}
|
expectedScopes := []string{ScopeOpenID, ScopeGroups}
|
||||||
expectedAudiences := []string{"https://example.com"}
|
expectedAudiences := []string{examplecom}
|
||||||
|
|
||||||
consentRequestBody = c.GetConsentResponseBody(consent)
|
consentRequestBody = c.GetConsentResponseBody(consent)
|
||||||
assert.Equal(t, "myclient", consentRequestBody.ClientID)
|
assert.Equal(t, myclient, consentRequestBody.ClientID)
|
||||||
assert.Equal(t, "My Client", consentRequestBody.ClientDescription)
|
assert.Equal(t, myclientdesc, consentRequestBody.ClientDescription)
|
||||||
assert.Equal(t, expectedScopes, consentRequestBody.Scopes)
|
assert.Equal(t, expectedScopes, consentRequestBody.Scopes)
|
||||||
assert.Equal(t, expectedAudiences, consentRequestBody.Audience)
|
assert.Equal(t, expectedAudiences, consentRequestBody.Audience)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_GetAudience(t *testing.T) {
|
func TestClient_GetAudience(t *testing.T) {
|
||||||
c := Client{}
|
c := &FullClient{BaseClient: &BaseClient{}}
|
||||||
|
|
||||||
audience := c.GetAudience()
|
audience := c.GetAudience()
|
||||||
assert.Len(t, audience, 0)
|
assert.Len(t, audience, 0)
|
||||||
|
|
||||||
c.Audience = []string{"https://example.com"}
|
c.Audience = []string{examplecom}
|
||||||
|
|
||||||
audience = c.GetAudience()
|
audience = c.GetAudience()
|
||||||
require.Len(t, audience, 1)
|
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) {
|
func TestClient_GetScopes(t *testing.T) {
|
||||||
c := Client{}
|
c := &FullClient{BaseClient: &BaseClient{}}
|
||||||
|
|
||||||
scopes := c.GetScopes()
|
scopes := c.GetScopes()
|
||||||
assert.Len(t, scopes, 0)
|
assert.Len(t, scopes, 0)
|
||||||
|
|
||||||
c.Scopes = []string{"openid"}
|
c.Scopes = []string{ScopeOpenID}
|
||||||
|
|
||||||
scopes = c.GetScopes()
|
scopes = c.GetScopes()
|
||||||
require.Len(t, scopes, 1)
|
require.Len(t, scopes, 1)
|
||||||
assert.Equal(t, "openid", scopes[0])
|
assert.Equal(t, ScopeOpenID, scopes[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_GetGrantTypes(t *testing.T) {
|
func TestClient_GetGrantTypes(t *testing.T) {
|
||||||
c := Client{}
|
c := &FullClient{BaseClient: &BaseClient{}}
|
||||||
|
|
||||||
grantTypes := c.GetGrantTypes()
|
grantTypes := c.GetGrantTypes()
|
||||||
require.Len(t, grantTypes, 1)
|
require.Len(t, grantTypes, 1)
|
||||||
assert.Equal(t, "authorization_code", grantTypes[0])
|
assert.Equal(t, GrantTypeAuthorizationCode, grantTypes[0])
|
||||||
|
|
||||||
c.GrantTypes = []string{"device_code"}
|
c.GrantTypes = []string{"device_code"}
|
||||||
|
|
||||||
|
@ -135,55 +236,55 @@ func TestClient_GetGrantTypes(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_Hashing(t *testing.T) {
|
func TestClient_Hashing(t *testing.T) {
|
||||||
c := Client{}
|
c := &FullClient{BaseClient: &BaseClient{}}
|
||||||
|
|
||||||
hashedSecret := c.GetHashedSecret()
|
hashedSecret := c.GetHashedSecret()
|
||||||
assert.Equal(t, []byte(nil), hashedSecret)
|
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")))
|
assert.True(t, c.Secret.MatchBytes([]byte("a_bad_secret")))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_GetHashedSecret(t *testing.T) {
|
func TestClient_GetHashedSecret(t *testing.T) {
|
||||||
c := Client{}
|
c := &FullClient{BaseClient: &BaseClient{}}
|
||||||
|
|
||||||
hashedSecret := c.GetHashedSecret()
|
hashedSecret := c.GetHashedSecret()
|
||||||
assert.Equal(t, []byte(nil), hashedSecret)
|
assert.Equal(t, []byte(nil), hashedSecret)
|
||||||
|
|
||||||
c.Secret = MustDecodeSecret("$plaintext$a_bad_secret")
|
c.Secret = MustDecodeSecret(badsecret)
|
||||||
|
|
||||||
hashedSecret = c.GetHashedSecret()
|
hashedSecret = c.GetHashedSecret()
|
||||||
assert.Equal(t, []byte("$plaintext$a_bad_secret"), hashedSecret)
|
assert.Equal(t, []byte(badsecret), hashedSecret)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_GetID(t *testing.T) {
|
func TestClient_GetID(t *testing.T) {
|
||||||
c := Client{}
|
c := &FullClient{BaseClient: &BaseClient{}}
|
||||||
|
|
||||||
id := c.GetID()
|
id := c.GetID()
|
||||||
assert.Equal(t, "", id)
|
assert.Equal(t, "", id)
|
||||||
|
|
||||||
c.ID = "myid"
|
c.ID = myclient
|
||||||
|
|
||||||
id = c.GetID()
|
id = c.GetID()
|
||||||
assert.Equal(t, "myid", id)
|
assert.Equal(t, myclient, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_GetRedirectURIs(t *testing.T) {
|
func TestClient_GetRedirectURIs(t *testing.T) {
|
||||||
c := Client{}
|
c := &FullClient{BaseClient: &BaseClient{}}
|
||||||
|
|
||||||
redirectURIs := c.GetRedirectURIs()
|
redirectURIs := c.GetRedirectURIs()
|
||||||
require.Len(t, redirectURIs, 0)
|
require.Len(t, redirectURIs, 0)
|
||||||
|
|
||||||
c.RedirectURIs = []string{"https://example.com/oauth2/callback"}
|
c.RedirectURIs = []string{examplecom}
|
||||||
|
|
||||||
redirectURIs = c.GetRedirectURIs()
|
redirectURIs = c.GetRedirectURIs()
|
||||||
require.Len(t, redirectURIs, 1)
|
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) {
|
func TestClient_GetResponseModes(t *testing.T) {
|
||||||
c := Client{}
|
c := &FullClient{BaseClient: &BaseClient{}}
|
||||||
|
|
||||||
responseModes := c.GetResponseModes()
|
responseModes := c.GetResponseModes()
|
||||||
require.Len(t, responseModes, 0)
|
require.Len(t, responseModes, 0)
|
||||||
|
@ -202,18 +303,18 @@ func TestClient_GetResponseModes(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_GetResponseTypes(t *testing.T) {
|
func TestClient_GetResponseTypes(t *testing.T) {
|
||||||
c := Client{}
|
c := &FullClient{BaseClient: &BaseClient{}}
|
||||||
|
|
||||||
responseTypes := c.GetResponseTypes()
|
responseTypes := c.GetResponseTypes()
|
||||||
require.Len(t, responseTypes, 1)
|
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()
|
responseTypes = c.GetResponseTypes()
|
||||||
require.Len(t, responseTypes, 2)
|
require.Len(t, responseTypes, 2)
|
||||||
assert.Equal(t, "code", responseTypes[0])
|
assert.Equal(t, ResponseTypeAuthorizationCodeFlow, responseTypes[0])
|
||||||
assert.Equal(t, "id_token", responseTypes[1])
|
assert.Equal(t, ResponseTypeImplicitFlowIDToken, responseTypes[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewClientPKCE(t *testing.T) {
|
func TestNewClientPKCE(t *testing.T) {
|
||||||
|
@ -290,9 +391,9 @@ func TestNewClientPKCE(t *testing.T) {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
client := NewClient(tc.have)
|
client := NewClient(tc.have)
|
||||||
|
|
||||||
assert.Equal(t, tc.expectedEnforcePKCE, client.EnforcePKCE)
|
assert.Equal(t, tc.expectedEnforcePKCE, client.GetPKCEEnforcement())
|
||||||
assert.Equal(t, tc.expectedEnforcePKCEChallengeMethod, client.EnforcePKCEChallengeMethod)
|
assert.Equal(t, tc.expectedEnforcePKCEChallengeMethod, client.GetPKCEChallengeMethodEnforcement())
|
||||||
assert.Equal(t, tc.expected, client.PKCEChallengeMethod)
|
assert.Equal(t, tc.expected, client.GetPKCEChallengeMethod())
|
||||||
|
|
||||||
if tc.r != nil {
|
if tc.r != nil {
|
||||||
err := client.ValidatePKCEPolicy(tc.r)
|
err := client.ValidatePKCEPolicy(tc.r)
|
||||||
|
@ -355,7 +456,7 @@ func TestNewClientPAR(t *testing.T) {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
client := NewClient(tc.have)
|
client := NewClient(tc.have)
|
||||||
|
|
||||||
assert.Equal(t, tc.expected, client.EnforcePAR)
|
assert.Equal(t, tc.expected, client.GetPAREnforcement())
|
||||||
|
|
||||||
if tc.r != nil {
|
if tc.r != nil {
|
||||||
err := client.ValidatePARPolicy(tc.r, urnPARPrefix)
|
err := client.ValidatePARPolicy(tc.r, urnPARPrefix)
|
||||||
|
@ -437,7 +538,7 @@ func TestNewClientResponseModes(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_IsPublic(t *testing.T) {
|
func TestClient_IsPublic(t *testing.T) {
|
||||||
c := Client{}
|
c := &FullClient{BaseClient: &BaseClient{}}
|
||||||
|
|
||||||
assert.False(t, c.IsPublic())
|
assert.False(t, c.IsPublic())
|
||||||
|
|
||||||
|
|
|
@ -169,12 +169,6 @@ type LifespanConfig struct {
|
||||||
RefreshToken time.Duration
|
RefreshToken time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
PromptNone = none
|
|
||||||
PromptLogin = "login"
|
|
||||||
PromptConsent = "consent"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LoadHandlers reloads the handlers based on the current configuration.
|
// LoadHandlers reloads the handlers based on the current configuration.
|
||||||
func (c *Config) LoadHandlers(store *Store, strategy jwt.Signer) {
|
func (c *Config) LoadHandlers(store *Store, strategy jwt.Signer) {
|
||||||
validator := openid.NewOpenIDConnectRequestValidator(strategy, c)
|
validator := openid.NewOpenIDConnectRequestValidator(strategy, c)
|
||||||
|
|
|
@ -69,15 +69,12 @@ const (
|
||||||
GrantTypeImplicit = implicit
|
GrantTypeImplicit = implicit
|
||||||
GrantTypeRefreshToken = "refresh_token"
|
GrantTypeRefreshToken = "refresh_token"
|
||||||
GrantTypeAuthorizationCode = "authorization_code"
|
GrantTypeAuthorizationCode = "authorization_code"
|
||||||
GrantTypePassword = "password"
|
|
||||||
GrantTypeClientCredentials = "client_credentials"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Client Auth Method strings.
|
// Client Auth Method strings.
|
||||||
const (
|
const (
|
||||||
ClientAuthMethodClientSecretBasic = "client_secret_basic"
|
ClientAuthMethodClientSecretBasic = "client_secret_basic"
|
||||||
ClientAuthMethodClientSecretPost = "client_secret_post"
|
ClientAuthMethodClientSecretPost = "client_secret_post"
|
||||||
ClientAuthMethodClientSecretJWT = "client_secret_jwt"
|
|
||||||
ClientAuthMethodNone = "none"
|
ClientAuthMethodNone = "none"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -117,6 +114,13 @@ const (
|
||||||
FormParameterCodeChallengeMethod = "code_challenge_method"
|
FormParameterCodeChallengeMethod = "code_challenge_method"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PromptNone = none
|
||||||
|
PromptLogin = "login"
|
||||||
|
PromptConsent = "consent"
|
||||||
|
// PromptCreate = "create" // This prompt value is currently unused.
|
||||||
|
)
|
||||||
|
|
||||||
// Endpoints.
|
// Endpoints.
|
||||||
const (
|
const (
|
||||||
EndpointAuthorization = "authorization"
|
EndpointAuthorization = "authorization"
|
||||||
|
|
|
@ -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"
|
||||||
|
)
|
|
@ -5,70 +5,76 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewOpenIDConnectWellKnownConfiguration generates a new OpenIDConnectWellKnownConfiguration.
|
// 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{
|
config = OpenIDConnectWellKnownConfiguration{
|
||||||
CommonDiscoveryOptions: CommonDiscoveryOptions{
|
OAuth2WellKnownConfiguration: OAuth2WellKnownConfiguration{
|
||||||
SubjectTypesSupported: []string{
|
CommonDiscoveryOptions: CommonDiscoveryOptions{
|
||||||
SubjectTypePublic,
|
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{
|
OAuth2DiscoveryOptions: OAuth2DiscoveryOptions{
|
||||||
ResponseTypeAuthorizationCodeFlow,
|
CodeChallengeMethodsSupported: []string{
|
||||||
ResponseTypeImplicitFlowIDToken,
|
PKCEChallengeMethodSHA256,
|
||||||
ResponseTypeImplicitFlowToken,
|
},
|
||||||
ResponseTypeImplicitFlowBoth,
|
|
||||||
ResponseTypeHybridFlowIDToken,
|
|
||||||
ResponseTypeHybridFlowToken,
|
|
||||||
ResponseTypeHybridFlowBoth,
|
|
||||||
},
|
},
|
||||||
GrantTypesSupported: []string{
|
OAuth2PushedAuthorizationDiscoveryOptions: &OAuth2PushedAuthorizationDiscoveryOptions{
|
||||||
GrantTypeAuthorizationCode,
|
RequirePushedAuthorizationRequests: c.PAR.Enforce,
|
||||||
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,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
OpenIDConnectDiscoveryOptions: OpenIDConnectDiscoveryOptions{
|
OpenIDConnectDiscoveryOptions: OpenIDConnectDiscoveryOptions{
|
||||||
IDTokenSigningAlgValuesSupported: []string{
|
IDTokenSigningAlgValuesSupported: []string{
|
||||||
SigningAlgorithmRSAWithSHA256,
|
SigningAlgorithmRSAWithSHA256,
|
||||||
|
@ -77,30 +83,15 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration
|
||||||
SigningAlgorithmNone,
|
SigningAlgorithmNone,
|
||||||
SigningAlgorithmRSAWithSHA256,
|
SigningAlgorithmRSAWithSHA256,
|
||||||
},
|
},
|
||||||
RequestObjectSigningAlgValuesSupported: []string{
|
},
|
||||||
SigningAlgorithmNone,
|
OpenIDConnectFrontChannelLogoutDiscoveryOptions: &OpenIDConnectFrontChannelLogoutDiscoveryOptions{},
|
||||||
SigningAlgorithmRSAWithSHA256,
|
OpenIDConnectBackChannelLogoutDiscoveryOptions: &OpenIDConnectBackChannelLogoutDiscoveryOptions{},
|
||||||
|
OpenIDConnectPromptCreateDiscoveryOptions: &OpenIDConnectPromptCreateDiscoveryOptions{
|
||||||
|
PromptValuesSupported: []string{
|
||||||
|
PromptNone,
|
||||||
|
PromptConsent,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PushedAuthorizationDiscoveryOptions: PushedAuthorizationDiscoveryOptions{
|
|
||||||
RequirePushedAuthorizationRequests: c.PAR.Enforce,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var pairwise, public bool
|
|
||||||
|
|
||||||
for _, client := range clients {
|
|
||||||
if pairwise && public {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if client.SectorIdentifier != "" {
|
|
||||||
pairwise = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if pairwise {
|
|
||||||
config.SubjectTypesSupported = append(config.SubjectTypesSupported, SubjectTypePairwise)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.EnablePKCEPlainChallenge {
|
if c.EnablePKCEPlainChallenge {
|
||||||
|
@ -109,3 +100,93 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration
|
||||||
|
|
||||||
return config
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.OAuth2DeviceAuthorizationGrantDiscoveryOptions != nil {
|
||||||
|
optsCopy.OAuth2DeviceAuthorizationGrantDiscoveryOptions = &OAuth2DeviceAuthorizationGrantDiscoveryOptions{}
|
||||||
|
*optsCopy.OAuth2DeviceAuthorizationGrantDiscoveryOptions = *opts.OAuth2DeviceAuthorizationGrantDiscoveryOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.OAuth2MutualTLSClientAuthenticationDiscoveryOptions != nil {
|
||||||
|
optsCopy.OAuth2MutualTLSClientAuthenticationDiscoveryOptions = &OAuth2MutualTLSClientAuthenticationDiscoveryOptions{}
|
||||||
|
*optsCopy.OAuth2MutualTLSClientAuthenticationDiscoveryOptions = *opts.OAuth2MutualTLSClientAuthenticationDiscoveryOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.OAuth2IssuerIdentificationDiscoveryOptions != nil {
|
||||||
|
optsCopy.OAuth2IssuerIdentificationDiscoveryOptions = &OAuth2IssuerIdentificationDiscoveryOptions{}
|
||||||
|
*optsCopy.OAuth2IssuerIdentificationDiscoveryOptions = *opts.OAuth2IssuerIdentificationDiscoveryOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.OAuth2JWTIntrospectionResponseDiscoveryOptions != nil {
|
||||||
|
optsCopy.OAuth2JWTIntrospectionResponseDiscoveryOptions = &OAuth2JWTIntrospectionResponseDiscoveryOptions{}
|
||||||
|
*optsCopy.OAuth2JWTIntrospectionResponseDiscoveryOptions = *opts.OAuth2JWTIntrospectionResponseDiscoveryOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -13,51 +13,51 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
||||||
desc string
|
desc string
|
||||||
pkcePlainChallenge bool
|
pkcePlainChallenge bool
|
||||||
enforcePAR bool
|
enforcePAR bool
|
||||||
clients map[string]*Client
|
clients map[string]Client
|
||||||
|
|
||||||
expectCodeChallengeMethodsSupported, expectSubjectTypesSupported []string
|
expectCodeChallengeMethodsSupported, expectSubjectTypesSupported []string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublic",
|
desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublic",
|
||||||
pkcePlainChallenge: false,
|
pkcePlainChallenge: false,
|
||||||
clients: map[string]*Client{"a": {}},
|
clients: map[string]Client{"a": &BaseClient{}},
|
||||||
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256},
|
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256},
|
||||||
expectSubjectTypesSupported: []string{SubjectTypePublic},
|
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublic",
|
desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublic",
|
||||||
pkcePlainChallenge: true,
|
pkcePlainChallenge: true,
|
||||||
clients: map[string]*Client{"a": {}},
|
clients: map[string]Client{"a": &BaseClient{}},
|
||||||
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
|
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
|
||||||
expectSubjectTypesSupported: []string{SubjectTypePublic},
|
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublicPairwise",
|
desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublicPairwise",
|
||||||
pkcePlainChallenge: false,
|
pkcePlainChallenge: false,
|
||||||
clients: map[string]*Client{"a": {SectorIdentifier: "yes"}},
|
clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}},
|
||||||
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256},
|
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256},
|
||||||
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
|
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublicPairwise",
|
desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublicPairwise",
|
||||||
pkcePlainChallenge: true,
|
pkcePlainChallenge: true,
|
||||||
clients: map[string]*Client{"a": {SectorIdentifier: "yes"}},
|
clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}},
|
||||||
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
|
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
|
||||||
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
|
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "ShouldHaveTokenAuthMethodsNone",
|
desc: "ShouldHaveTokenAuthMethodsNone",
|
||||||
pkcePlainChallenge: true,
|
pkcePlainChallenge: true,
|
||||||
clients: map[string]*Client{"a": {SectorIdentifier: "yes"}},
|
clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}},
|
||||||
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
|
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
|
||||||
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
|
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "ShouldHaveTokenAuthMethodsNone",
|
desc: "ShouldHaveTokenAuthMethodsNone",
|
||||||
pkcePlainChallenge: true,
|
pkcePlainChallenge: true,
|
||||||
clients: map[string]*Client{
|
clients: map[string]Client{
|
||||||
"a": {SectorIdentifier: "yes"},
|
"a": &BaseClient{SectorIdentifier: "yes"},
|
||||||
"b": {SectorIdentifier: "yes"},
|
"b": &BaseClient{SectorIdentifier: "yes"},
|
||||||
},
|
},
|
||||||
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
|
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
|
||||||
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
|
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 {
|
for _, codeChallengeMethod := range tc.expectCodeChallengeMethodsSupported {
|
||||||
assert.Contains(t, actual.CodeChallengeMethodsSupported, codeChallengeMethod)
|
assert.Contains(t, actual.CodeChallengeMethodsSupported, codeChallengeMethod)
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,17 +37,14 @@ func NewOpenIDConnectProvider(config *schema.OpenIDConnectConfiguration, store s
|
||||||
|
|
||||||
provider.Config.LoadHandlers(provider.Store, provider.KeyManager.Strategy())
|
provider.Config.LoadHandlers(provider.Store, provider.KeyManager.Strategy())
|
||||||
|
|
||||||
provider.discovery = NewOpenIDConnectWellKnownConfiguration(config, provider.Store.clients)
|
provider.discovery = NewOpenIDConnectWellKnownConfiguration(config)
|
||||||
|
|
||||||
return provider, nil
|
return provider, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOAuth2WellKnownConfiguration returns the discovery document for the OAuth Configuration.
|
// GetOAuth2WellKnownConfiguration returns the discovery document for the OAuth Configuration.
|
||||||
func (p *OpenIDConnectProvider) GetOAuth2WellKnownConfiguration(issuer string) OAuth2WellKnownConfiguration {
|
func (p *OpenIDConnectProvider) GetOAuth2WellKnownConfiguration(issuer string) OAuth2WellKnownConfiguration {
|
||||||
options := OAuth2WellKnownConfiguration{
|
options := p.discovery.OAuth2WellKnownConfiguration.Copy()
|
||||||
CommonDiscoveryOptions: p.discovery.CommonDiscoveryOptions,
|
|
||||||
OAuth2DiscoveryOptions: p.discovery.OAuth2DiscoveryOptions,
|
|
||||||
}
|
|
||||||
|
|
||||||
options.Issuer = issuer
|
options.Issuer = issuer
|
||||||
|
|
||||||
|
@ -63,13 +60,7 @@ func (p *OpenIDConnectProvider) GetOAuth2WellKnownConfiguration(issuer string) O
|
||||||
|
|
||||||
// GetOpenIDConnectWellKnownConfiguration returns the discovery document for the OpenID Configuration.
|
// GetOpenIDConnectWellKnownConfiguration returns the discovery document for the OpenID Configuration.
|
||||||
func (p *OpenIDConnectProvider) GetOpenIDConnectWellKnownConfiguration(issuer string) OpenIDConnectWellKnownConfiguration {
|
func (p *OpenIDConnectProvider) GetOpenIDConnectWellKnownConfiguration(issuer string) OpenIDConnectWellKnownConfiguration {
|
||||||
options := OpenIDConnectWellKnownConfiguration{
|
options := p.discovery.Copy()
|
||||||
CommonDiscoveryOptions: p.discovery.CommonDiscoveryOptions,
|
|
||||||
OAuth2DiscoveryOptions: p.discovery.OAuth2DiscoveryOptions,
|
|
||||||
OpenIDConnectDiscoveryOptions: p.discovery.OpenIDConnectDiscoveryOptions,
|
|
||||||
OpenIDConnectFrontChannelLogoutDiscoveryOptions: p.discovery.OpenIDConnectFrontChannelLogoutDiscoveryOptions,
|
|
||||||
OpenIDConnectBackChannelLogoutDiscoveryOptions: p.discovery.OpenIDConnectBackChannelLogoutDiscoveryOptions,
|
|
||||||
}
|
|
||||||
|
|
||||||
options.Issuer = issuer
|
options.Issuer = issuer
|
||||||
|
|
||||||
|
|
|
@ -27,15 +27,15 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
||||||
EnablePKCEPlainChallenge: true,
|
EnablePKCEPlainChallenge: true,
|
||||||
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
HMACSecret: badhmac,
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||||
{
|
{
|
||||||
ID: "a-client",
|
ID: myclient,
|
||||||
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
|
Secret: MustDecodeSecret(badsecret),
|
||||||
SectorIdentifier: url.URL{Host: "google.com"},
|
SectorIdentifier: url.URL{Host: examplecomsid},
|
||||||
Policy: "one_factor",
|
Policy: onefactor,
|
||||||
RedirectURIs: []string{
|
RedirectURIs: []string{
|
||||||
"https://google.com",
|
examplecom,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -43,7 +43,7 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
disco := provider.GetOpenIDConnectWellKnownConfiguration("https://example.com")
|
disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom)
|
||||||
|
|
||||||
assert.Len(t, disco.SubjectTypesSupported, 2)
|
assert.Len(t, disco.SubjectTypesSupported, 2)
|
||||||
assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic)
|
assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic)
|
||||||
|
@ -58,12 +58,12 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *tes
|
||||||
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
||||||
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
HMACSecret: badhmac,
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||||
{
|
{
|
||||||
ID: "a-client",
|
ID: "a-client",
|
||||||
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
|
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
|
||||||
Policy: "one_factor",
|
Policy: onefactor,
|
||||||
RedirectURIs: []string{
|
RedirectURIs: []string{
|
||||||
"https://google.com",
|
"https://google.com",
|
||||||
},
|
},
|
||||||
|
@ -72,7 +72,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *tes
|
||||||
ID: "b-client",
|
ID: "b-client",
|
||||||
Description: "Normal Description",
|
Description: "Normal Description",
|
||||||
Secret: MustDecodeSecret("$plaintext$b-client-secret"),
|
Secret: MustDecodeSecret("$plaintext$b-client-secret"),
|
||||||
Policy: "two_factor",
|
Policy: twofactor,
|
||||||
RedirectURIs: []string{
|
RedirectURIs: []string{
|
||||||
"https://google.com",
|
"https://google.com",
|
||||||
},
|
},
|
||||||
|
@ -103,7 +103,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
|
||||||
{
|
{
|
||||||
ID: "a-client",
|
ID: "a-client",
|
||||||
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
|
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
|
||||||
Policy: "one_factor",
|
Policy: onefactor,
|
||||||
RedirectURIs: []string{
|
RedirectURIs: []string{
|
||||||
"https://google.com",
|
"https://google.com",
|
||||||
},
|
},
|
||||||
|
@ -113,9 +113,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
|
||||||
|
|
||||||
assert.NoError(t, err)
|
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/jwks.json", disco.JWKSURI)
|
||||||
assert.Equal(t, "https://example.com/api/oidc/authorization", disco.AuthorizationEndpoint)
|
assert.Equal(t, "https://example.com/api/oidc/authorization", disco.AuthorizationEndpoint)
|
||||||
assert.Equal(t, "https://example.com/api/oidc/token", disco.TokenEndpoint)
|
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, ResponseModeQuery)
|
||||||
assert.Contains(t, disco.ResponseModesSupported, ResponseModeFragment)
|
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, SubjectTypePublic)
|
||||||
|
assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePairwise)
|
||||||
|
|
||||||
assert.Len(t, disco.ResponseTypesSupported, 7)
|
assert.Len(t, disco.ResponseTypesSupported, 7)
|
||||||
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeAuthorizationCodeFlow)
|
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, ResponseTypeHybridFlowToken)
|
||||||
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowBoth)
|
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, ClientAuthMethodClientSecretBasic)
|
||||||
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost)
|
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost)
|
||||||
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT)
|
|
||||||
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone)
|
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone)
|
||||||
|
|
||||||
assert.Len(t, disco.GrantTypesSupported, 3)
|
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, SigningAlgorithmRSAWithSHA256)
|
||||||
assert.Contains(t, disco.UserinfoSigningAlgValuesSupported, SigningAlgorithmNone)
|
assert.Contains(t, disco.UserinfoSigningAlgValuesSupported, SigningAlgorithmNone)
|
||||||
|
|
||||||
assert.Len(t, disco.RequestObjectSigningAlgValuesSupported, 2)
|
assert.Len(t, disco.RequestObjectSigningAlgValuesSupported, 0)
|
||||||
assert.Contains(t, disco.RequestObjectSigningAlgValuesSupported, SigningAlgorithmRSAWithSHA256)
|
|
||||||
assert.Contains(t, disco.RequestObjectSigningAlgValuesSupported, SigningAlgorithmNone)
|
|
||||||
|
|
||||||
assert.Len(t, disco.ClaimsSupported, 18)
|
assert.Len(t, disco.ClaimsSupported, 18)
|
||||||
assert.Contains(t, disco.ClaimsSupported, ClaimAuthenticationMethodsReference)
|
assert.Contains(t, disco.ClaimsSupported, ClaimAuthenticationMethodsReference)
|
||||||
|
@ -203,7 +201,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
|
||||||
{
|
{
|
||||||
ID: "a-client",
|
ID: "a-client",
|
||||||
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
|
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
|
||||||
Policy: "one_factor",
|
Policy: onefactor,
|
||||||
RedirectURIs: []string{
|
RedirectURIs: []string{
|
||||||
"https://google.com",
|
"https://google.com",
|
||||||
},
|
},
|
||||||
|
@ -213,9 +211,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
|
||||||
|
|
||||||
assert.NoError(t, err)
|
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/jwks.json", disco.JWKSURI)
|
||||||
assert.Equal(t, "https://example.com/api/oidc/authorization", disco.AuthorizationEndpoint)
|
assert.Equal(t, "https://example.com/api/oidc/authorization", disco.AuthorizationEndpoint)
|
||||||
assert.Equal(t, "https://example.com/api/oidc/token", disco.TokenEndpoint)
|
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, ResponseModeQuery)
|
||||||
assert.Contains(t, disco.ResponseModesSupported, ResponseModeFragment)
|
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, SubjectTypePublic)
|
||||||
|
assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePairwise)
|
||||||
|
|
||||||
assert.Len(t, disco.ResponseTypesSupported, 7)
|
assert.Len(t, disco.ResponseTypesSupported, 7)
|
||||||
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeAuthorizationCodeFlow)
|
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, ResponseTypeHybridFlowToken)
|
||||||
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowBoth)
|
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, ClientAuthMethodClientSecretBasic)
|
||||||
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost)
|
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost)
|
||||||
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT)
|
|
||||||
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone)
|
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone)
|
||||||
|
|
||||||
assert.Len(t, disco.GrantTypesSupported, 3)
|
assert.Len(t, disco.GrantTypesSupported, 3)
|
||||||
|
@ -292,7 +290,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
|
||||||
{
|
{
|
||||||
ID: "a-client",
|
ID: "a-client",
|
||||||
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
|
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
|
||||||
Policy: "one_factor",
|
Policy: onefactor,
|
||||||
RedirectURIs: []string{
|
RedirectURIs: []string{
|
||||||
"https://google.com",
|
"https://google.com",
|
||||||
},
|
},
|
||||||
|
@ -302,7 +300,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
disco := provider.GetOpenIDConnectWellKnownConfiguration("https://example.com")
|
disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom)
|
||||||
|
|
||||||
require.Len(t, disco.CodeChallengeMethodsSupported, 2)
|
require.Len(t, disco.CodeChallengeMethodsSupported, 2)
|
||||||
assert.Equal(t, PKCEChallengeMethodSHA256, disco.CodeChallengeMethodsSupported[0])
|
assert.Equal(t, PKCEChallengeMethodSHA256, disco.CodeChallengeMethodsSupported[0])
|
||||||
|
|
|
@ -24,7 +24,7 @@ func NewStore(config *schema.OpenIDConnectConfiguration, provider storage.Provid
|
||||||
|
|
||||||
store = &Store{
|
store = &Store{
|
||||||
provider: provider,
|
provider: provider,
|
||||||
clients: map[string]*Client{},
|
clients: map[string]Client{},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, client := range config.Clients {
|
for _, client := range config.Clients {
|
||||||
|
@ -72,11 +72,11 @@ func (s *Store) GetClientPolicy(id string) (level authorization.Level) {
|
||||||
return authorization.TwoFactor
|
return authorization.TwoFactor
|
||||||
}
|
}
|
||||||
|
|
||||||
return client.Policy
|
return client.GetAuthorizationPolicy()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFullClient returns a fosite.Client asserted as an Client matching the provided id.
|
// 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]
|
client, ok := s.clients[id]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fosite.ErrInvalidClient
|
return nil, fosite.ErrInvalidClient
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ory/fosite"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
@ -17,23 +18,23 @@ func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) {
|
||||||
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||||
{
|
{
|
||||||
ID: "myclient",
|
ID: myclient,
|
||||||
Description: "myclient desc",
|
Description: myclientdesc,
|
||||||
Policy: "one_factor",
|
Policy: onefactor,
|
||||||
Scopes: []string{ScopeOpenID, ScopeProfile},
|
Scopes: []string{ScopeOpenID, ScopeProfile},
|
||||||
Secret: MustDecodeSecret("$plaintext$mysecret"),
|
Secret: MustDecodeSecret("$plaintext$mysecret"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "myotherclient",
|
ID: "myotherclient",
|
||||||
Description: "myclient desc",
|
Description: myclientdesc,
|
||||||
Policy: "two_factor",
|
Policy: twofactor,
|
||||||
Scopes: []string{ScopeOpenID, ScopeProfile},
|
Scopes: []string{ScopeOpenID, ScopeProfile},
|
||||||
Secret: MustDecodeSecret("$plaintext$mysecret"),
|
Secret: MustDecodeSecret("$plaintext$mysecret"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
policyOne := s.GetClientPolicy("myclient")
|
policyOne := s.GetClientPolicy(myclient)
|
||||||
assert.Equal(t, authorization.OneFactor, policyOne)
|
assert.Equal(t, authorization.OneFactor, policyOne)
|
||||||
|
|
||||||
policyTwo := s.GetClientPolicy("myotherclient")
|
policyTwo := s.GetClientPolicy("myotherclient")
|
||||||
|
@ -49,9 +50,9 @@ func TestOpenIDConnectStore_GetInternalClient(t *testing.T) {
|
||||||
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||||
{
|
{
|
||||||
ID: "myclient",
|
ID: myclient,
|
||||||
Description: "myclient desc",
|
Description: myclientdesc,
|
||||||
Policy: "one_factor",
|
Policy: onefactor,
|
||||||
Scopes: []string{ScopeOpenID, ScopeProfile},
|
Scopes: []string{ScopeOpenID, ScopeProfile},
|
||||||
Secret: MustDecodeSecret("$plaintext$mysecret"),
|
Secret: MustDecodeSecret("$plaintext$mysecret"),
|
||||||
},
|
},
|
||||||
|
@ -62,17 +63,19 @@ func TestOpenIDConnectStore_GetInternalClient(t *testing.T) {
|
||||||
assert.EqualError(t, err, "invalid_client")
|
assert.EqualError(t, err, "invalid_client")
|
||||||
assert.Nil(t, client)
|
assert.Nil(t, client)
|
||||||
|
|
||||||
client, err = s.GetClient(context.Background(), "myclient")
|
client, err = s.GetClient(context.Background(), myclient)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, client)
|
require.NotNil(t, client)
|
||||||
assert.Equal(t, "myclient", client.GetID())
|
assert.Equal(t, myclient, client.GetID())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) {
|
func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) {
|
||||||
|
id := myclient
|
||||||
|
|
||||||
c1 := schema.OpenIDConnectClientConfiguration{
|
c1 := schema.OpenIDConnectClientConfiguration{
|
||||||
ID: "myclient",
|
ID: id,
|
||||||
Description: "myclient desc",
|
Description: myclientdesc,
|
||||||
Policy: "one_factor",
|
Policy: onefactor,
|
||||||
Scopes: []string{ScopeOpenID, ScopeProfile},
|
Scopes: []string{ScopeOpenID, ScopeProfile},
|
||||||
Secret: MustDecodeSecret("$plaintext$mysecret"),
|
Secret: MustDecodeSecret("$plaintext$mysecret"),
|
||||||
}
|
}
|
||||||
|
@ -83,24 +86,24 @@ func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) {
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{c1},
|
Clients: []schema.OpenIDConnectClientConfiguration{c1},
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
client, err := s.GetFullClient(c1.ID)
|
client, err := s.GetFullClient(id)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, client)
|
require.NotNil(t, client)
|
||||||
assert.Equal(t, client.ID, c1.ID)
|
assert.Equal(t, id, client.GetID())
|
||||||
assert.Equal(t, client.Description, c1.Description)
|
assert.Equal(t, myclientdesc, client.GetDescription())
|
||||||
assert.Equal(t, client.Scopes, c1.Scopes)
|
assert.Equal(t, fosite.Arguments(c1.Scopes), client.GetScopes())
|
||||||
assert.Equal(t, client.GrantTypes, c1.GrantTypes)
|
assert.Equal(t, fosite.Arguments([]string{GrantTypeAuthorizationCode}), client.GetGrantTypes())
|
||||||
assert.Equal(t, client.ResponseTypes, c1.ResponseTypes)
|
assert.Equal(t, fosite.Arguments([]string{ResponseTypeAuthorizationCodeFlow}), client.GetResponseTypes())
|
||||||
assert.Equal(t, client.RedirectURIs, c1.RedirectURIs)
|
assert.Equal(t, []string(nil), client.GetRedirectURIs())
|
||||||
assert.Equal(t, client.Policy, authorization.OneFactor)
|
assert.Equal(t, authorization.OneFactor, client.GetAuthorizationPolicy())
|
||||||
assert.Equal(t, client.Secret.Encode(), "$plaintext$mysecret")
|
assert.Equal(t, "$plaintext$mysecret", client.GetSecret().Encode())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
|
func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
|
||||||
c1 := schema.OpenIDConnectClientConfiguration{
|
c1 := schema.OpenIDConnectClientConfiguration{
|
||||||
ID: "myclient",
|
ID: myclient,
|
||||||
Description: "myclient desc",
|
Description: myclientdesc,
|
||||||
Policy: "one_factor",
|
Policy: onefactor,
|
||||||
Scopes: []string{ScopeOpenID, ScopeProfile},
|
Scopes: []string{ScopeOpenID, ScopeProfile},
|
||||||
Secret: MustDecodeSecret("$plaintext$mysecret"),
|
Secret: MustDecodeSecret("$plaintext$mysecret"),
|
||||||
}
|
}
|
||||||
|
@ -122,16 +125,16 @@ func TestOpenIDConnectStore_IsValidClientID(t *testing.T) {
|
||||||
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||||
{
|
{
|
||||||
ID: "myclient",
|
ID: myclient,
|
||||||
Description: "myclient desc",
|
Description: myclientdesc,
|
||||||
Policy: "one_factor",
|
Policy: onefactor,
|
||||||
Scopes: []string{ScopeOpenID, ScopeProfile},
|
Scopes: []string{ScopeOpenID, ScopeProfile},
|
||||||
Secret: MustDecodeSecret("$plaintext$mysecret"),
|
Secret: MustDecodeSecret("$plaintext$mysecret"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
validClient := s.IsValidClientID("myclient")
|
validClient := s.IsValidClientID(myclient)
|
||||||
invalidClient := s.IsValidClientID("myinvalidclient")
|
invalidClient := s.IsValidClientID("myinvalidclient")
|
||||||
|
|
||||||
assert.True(t, validClient)
|
assert.True(t, validClient)
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/ory/herodot"
|
"github.com/ory/herodot"
|
||||||
"gopkg.in/square/go-jose.v2"
|
"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/authorization"
|
||||||
"github.com/authelia/authelia/v4/internal/model"
|
"github.com/authelia/authelia/v4/internal/model"
|
||||||
"github.com/authelia/authelia/v4/internal/storage"
|
"github.com/authelia/authelia/v4/internal/storage"
|
||||||
|
@ -97,17 +98,19 @@ type OpenIDConnectProvider struct {
|
||||||
// openid.OpenIDConnectRequestStorage, and partially implements rfc7523.RFC7523KeyStorage.
|
// openid.OpenIDConnectRequestStorage, and partially implements rfc7523.RFC7523KeyStorage.
|
||||||
type Store struct {
|
type Store struct {
|
||||||
provider storage.Provider
|
provider storage.Provider
|
||||||
clients map[string]*Client
|
clients map[string]Client
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client represents the client internally.
|
// BaseClient is the base for all clients.
|
||||||
type Client struct {
|
type BaseClient struct {
|
||||||
ID string
|
ID string
|
||||||
Description string
|
Description string
|
||||||
Secret algorithm.Digest
|
Secret algorithm.Digest
|
||||||
SectorIdentifier string
|
SectorIdentifier string
|
||||||
Public bool
|
Public bool
|
||||||
|
|
||||||
|
EnforcePAR bool
|
||||||
|
|
||||||
EnforcePKCE bool
|
EnforcePKCE bool
|
||||||
EnforcePKCEChallengeMethod bool
|
EnforcePKCEChallengeMethod bool
|
||||||
PKCEChallengeMethod string
|
PKCEChallengeMethod string
|
||||||
|
@ -119,8 +122,6 @@ type Client struct {
|
||||||
ResponseTypes []string
|
ResponseTypes []string
|
||||||
ResponseModes []fosite.ResponseModeType
|
ResponseModes []fosite.ResponseModeType
|
||||||
|
|
||||||
EnforcePAR bool
|
|
||||||
|
|
||||||
UserinfoSigningAlgorithm string
|
UserinfoSigningAlgorithm string
|
||||||
|
|
||||||
Policy authorization.Level
|
Policy authorization.Level
|
||||||
|
@ -128,6 +129,43 @@ type Client struct {
|
||||||
Consent ClientConsent
|
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.
|
// NewClientConsent converts the schema.OpenIDConnectClientConsentConfig into a oidc.ClientConsent.
|
||||||
func NewClientConsent(mode string, duration *time.Duration) ClientConsent {
|
func NewClientConsent(mode string, duration *time.Duration) ClientConsent {
|
||||||
switch mode {
|
switch mode {
|
||||||
|
@ -344,6 +382,12 @@ type CommonDiscoveryOptions struct {
|
||||||
Client if it is given.
|
Client if it is given.
|
||||||
*/
|
*/
|
||||||
OPTOSURI string `json:"op_tos_uri,omitempty"`
|
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.
|
// 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"`
|
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.
|
// OpenIDConnectDiscoveryOptions represents the discovery options specific to OpenID Connect.
|
||||||
type OpenIDConnectDiscoveryOptions struct {
|
type OpenIDConnectDiscoveryOptions struct {
|
||||||
/*
|
/*
|
||||||
|
@ -552,6 +688,12 @@ type OpenIDConnectDiscoveryOptions struct {
|
||||||
*/
|
*/
|
||||||
ClaimLocalesSupported []string `json:"claims_locales_supported,omitempty"`
|
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
|
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.
|
support. If omitted, the default value is true.
|
||||||
|
@ -612,39 +754,202 @@ type OpenIDConnectBackChannelLogoutDiscoveryOptions struct {
|
||||||
BackChannelLogoutSessionSupported bool `json:"backchannel_logout_session_supported"`
|
BackChannelLogoutSessionSupported bool `json:"backchannel_logout_session_supported"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PushedAuthorizationDiscoveryOptions represents the well known discovery document specific to the
|
// OpenIDConnectSessionManagementDiscoveryOptions represents the discovery options specific to OpenID Connect 1.0
|
||||||
// OAuth 2.0 Pushed Authorization Requests (RFC9126) implementation.
|
// Session Management.
|
||||||
//
|
//
|
||||||
// OAuth 2.0 Pushed Authorization Requests: https://datatracker.ietf.org/doc/html/rfc9126#section-5
|
// To support OpenID Connect Session Management, the RP needs to obtain the Session Management related OP metadata. This
|
||||||
type PushedAuthorizationDiscoveryOptions struct {
|
// 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
|
REQUIRED. URL of an OP iframe that supports cross-origin communications for session state information with the
|
||||||
exchange for a "request_uri" value usable at the authorization server.
|
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 {
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
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"`
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Boolean parameter indicating whether the authorization server accepts authorization request data only via PAR.
|
REQUIRED. JSON array containing one or more of the following values: poll, ping, and push.
|
||||||
If omitted, the default value is "false".
|
|
||||||
*/
|
*/
|
||||||
RequirePushedAuthorizationRequests bool `json:"require_pushed_authorization_requests"`
|
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.
|
// OAuth2WellKnownConfiguration represents the well known discovery document specific to OAuth 2.0.
|
||||||
type OAuth2WellKnownConfiguration struct {
|
type OAuth2WellKnownConfiguration struct {
|
||||||
CommonDiscoveryOptions
|
CommonDiscoveryOptions
|
||||||
OAuth2DiscoveryOptions
|
OAuth2DiscoveryOptions
|
||||||
PushedAuthorizationDiscoveryOptions
|
*OAuth2DeviceAuthorizationGrantDiscoveryOptions
|
||||||
|
*OAuth2MutualTLSClientAuthenticationDiscoveryOptions
|
||||||
|
*OAuth2IssuerIdentificationDiscoveryOptions
|
||||||
|
*OAuth2JWTIntrospectionResponseDiscoveryOptions
|
||||||
|
*OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions
|
||||||
|
*OAuth2PushedAuthorizationDiscoveryOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenIDConnectWellKnownConfiguration represents the well known discovery document specific to OpenID Connect.
|
// OpenIDConnectWellKnownConfiguration represents the well known discovery document specific to OpenID Connect.
|
||||||
type OpenIDConnectWellKnownConfiguration struct {
|
type OpenIDConnectWellKnownConfiguration struct {
|
||||||
CommonDiscoveryOptions
|
OAuth2WellKnownConfiguration
|
||||||
OAuth2DiscoveryOptions
|
|
||||||
PushedAuthorizationDiscoveryOptions
|
|
||||||
OpenIDConnectDiscoveryOptions
|
OpenIDConnectDiscoveryOptions
|
||||||
OpenIDConnectFrontChannelLogoutDiscoveryOptions
|
*OpenIDConnectFrontChannelLogoutDiscoveryOptions
|
||||||
OpenIDConnectBackChannelLogoutDiscoveryOptions
|
*OpenIDConnectBackChannelLogoutDiscoveryOptions
|
||||||
|
*OpenIDConnectSessionManagementDiscoveryOptions
|
||||||
|
*OpenIDConnectRPInitiatedLogoutDiscoveryOptions
|
||||||
|
*OpenIDConnectPromptCreateDiscoveryOptions
|
||||||
|
*OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions
|
||||||
|
*OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions
|
||||||
|
*OpenIDFederationDiscoveryOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenIDConnectContext represents the context implementation that is used by some OpenID Connect 1.0 implementations.
|
// OpenIDConnectContext represents the context implementation that is used by some OpenID Connect 1.0 implementations.
|
||||||
|
|
|
@ -40,7 +40,7 @@ func TestNewSessionWithAuthorizeRequest(t *testing.T) {
|
||||||
Request: fosite.Request{
|
Request: fosite.Request{
|
||||||
ID: requestID.String(),
|
ID: requestID.String(),
|
||||||
Form: formValues,
|
Form: formValues,
|
||||||
Client: &Client{ID: "example"},
|
Client: &BaseClient{ID: "example"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ func TestNewSessionWithAuthorizeRequest(t *testing.T) {
|
||||||
|
|
||||||
requested := time.Unix(1647332518, 0)
|
requested := time.Unix(1647332518, 0)
|
||||||
authAt := time.Unix(1647332500, 0)
|
authAt := time.Unix(1647332500, 0)
|
||||||
issuer := "https://example.com"
|
issuer := examplecom
|
||||||
amr := []string{AMRPasswordBasedAuthentication}
|
amr := []string{AMRPasswordBasedAuthentication}
|
||||||
|
|
||||||
consent := &model.OAuth2ConsentSession{
|
consent := &model.OAuth2ConsentSession{
|
||||||
|
|
|
@ -200,8 +200,8 @@ func URLsFromStringSlice(urls []string) []url.URL {
|
||||||
}
|
}
|
||||||
|
|
||||||
// OriginFromURL returns an origin url.URL given another url.URL.
|
// OriginFromURL returns an origin url.URL given another url.URL.
|
||||||
func OriginFromURL(u url.URL) (origin url.URL) {
|
func OriginFromURL(u *url.URL) (origin *url.URL) {
|
||||||
return url.URL{
|
return &url.URL{
|
||||||
Scheme: u.Scheme,
|
Scheme: u.Scheme,
|
||||||
Host: u.Host,
|
Host: u.Host,
|
||||||
}
|
}
|
||||||
|
|
|
@ -242,7 +242,7 @@ func TestOriginFromURL(t *testing.T) {
|
||||||
google, err := url.Parse("https://google.com/abc?a=123#five")
|
google, err := url.Parse("https://google.com/abc?a=123#five")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
origin := OriginFromURL(*google)
|
origin := OriginFromURL(google)
|
||||||
assert.Equal(t, "https://google.com", origin.String())
|
assert.Equal(t, "https://google.com", origin.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue