diff --git a/config.template.yml b/config.template.yml index 1058fecc1..077237358 100644 --- a/config.template.yml +++ b/config.template.yml @@ -1480,12 +1480,6 @@ notifier: # - email # - profile - ## Grant Types configures which grants this client can obtain. - ## It's not recommended to define this unless you know what you're doing. - # grant_types: - # - refresh_token - # - authorization_code - ## Response Types configures which responses this client can be sent. ## It's not recommended to define this unless you know what you're doing. # response_types: @@ -1495,7 +1489,14 @@ notifier: # response_modes: # - form_post # - query - # - fragment + + ## Grant Types configures which grants this client can obtain. + ## It's not recommended to define this unless you know what you're doing. + # grant_types: + # - authorization_code + + ## The permitted client authentication method for the Token Endpoint for this client. + # token_endpoint_auth_method: client_secret_basic ## The policy to require for this client; one_factor or two_factor. # authorization_policy: two_factor diff --git a/docs/content/en/configuration/identity-providers/open-id-connect.md b/docs/content/en/configuration/identity-providers/open-id-connect.md index 9c5f1b44d..a01f33f3c 100644 --- a/docs/content/en/configuration/identity-providers/open-id-connect.md +++ b/docs/content/en/configuration/identity-providers/open-id-connect.md @@ -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 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 -{{< 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.* @@ -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 [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 {{< 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 more information. +#### token_endpoint_auth_method + +{{< confkey type="string" default="" required="no" >}} + +The registered client authentication mechanism used by this client for the [Token Endpoint]. If no method is defined +the confidential client type will accept any supported method. The public client type defaults to `none` as this +is required by the specification. This may be required as a breaking change in future versions. +Supported values are `client_secret_basic`, `client_secret_post`, and `none`. + +See the [integration guide](../../integration/openid-connect/introduction.md#client-authentication-method) for +more information. + #### consent_mode {{< confkey type="string" default="auto" required="no" >}} @@ -565,6 +586,7 @@ To integrate Authelia's [OpenID Connect 1.0] implementation with a relying party [token lifespan]: https://docs.apigee.com/api-platform/antipatterns/oauth-long-expiration [OpenID Connect 1.0]: https://openid.net/connect/ +[Token Endpoint]: https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint [JWT]: https://datatracker.ietf.org/doc/html/rfc7519 [RFC6234]: https://datatracker.ietf.org/doc/html/rfc6234 [RFC4648]: https://datatracker.ietf.org/doc/html/rfc4648 diff --git a/docs/content/en/configuration/methods/files.md b/docs/content/en/configuration/methods/files.md index 667d38df3..8793cf889 100644 --- a/docs/content/en/configuration/methods/files.md +++ b/docs/content/en/configuration/methods/files.md @@ -61,7 +61,7 @@ authelia --config configuration.yml,config-acl.yml,config-other.yml ``` Authelia's configuration files use the YAML format. A template with all possible options can be found at the root of the -repository [here](https://github.com/authelia/authelia/blob/master/config.template.yml). +repository {{< github-link name="here" path="config.template.yml" >}}. *__Important Note:__ You should not have configuration sections such as Access Control Rules or OpenID Connect clients configured in multiple files. If you wish to split these into their own files that is fine, but if you have two files that diff --git a/docs/content/en/configuration/prologue/introduction.md b/docs/content/en/configuration/prologue/introduction.md index 76763c33a..586d61935 100644 --- a/docs/content/en/configuration/prologue/introduction.md +++ b/docs/content/en/configuration/prologue/introduction.md @@ -16,9 +16,8 @@ toc: true We document the configuration in two ways: -1. The [YAML] configuration template - [config.template.yml](https://github.com/authelia/authelia/blob/master/config.template.yml) has comments with very - limited documentation on the effective use of a particular option. All documentation lines start with `##`. Lines +1. The [YAML] configuration template {{< github-link path="config.template.yml" >}} has comments with very limited + documentation on the effective use of a particular option. All documentation lines start with `##`. Lines starting with a single `#` are [YAML] configuration options which are commented to disable them or as examples. 2. This documentation site. Generally each section of the configuration is in its own section of the documentation site. Each configuration option is listed in its relevant section as a heading, under that heading generally are two diff --git a/docs/content/en/contributing/development/environment.md b/docs/content/en/contributing/development/environment.md index 96fd84e29..008b87fc5 100644 --- a/docs/content/en/contributing/development/environment.md +++ b/docs/content/en/contributing/development/environment.md @@ -38,6 +38,23 @@ The additional tools are recommended: * [yamllint] * [VSCodium] or [GoLand] +## Certificate + +Authelia utilizes a self-signed Root CA certificate for the development environment. This allows us to sign elements of +the CI process uniformly and only trust a single additional Root CA Certificate. The private key for this certificate is +maintained by the [Core Team] so if you need an additional certificate signed for this purpose please reach out to them. + +While developing for Authelia you may also want to trust this Root CA. It is critical that you are aware of what this +means if you decide to do so. + +1. It will allow us to generate trusted certificates for machines this is installed on. +2. If compromised there is no formal revocation process at this time as we are not a certified CA. +3. Trusting Root CA's is not necessary for the development process it only makes it smoother. +4. Trusting additional Root CA's for prolonged periods is not generally a good idea. + +If you'd still like to trust the Root CA Certificate it's located (encoded as a PEM) in the main git repository at + [/internal/suites/common/pki/ca/ca.public.crt](https://github.com/authelia/authelia/blob/master/internal/suites/common/pki/ca/ca.public.crt). + ## Scripts There is a scripting context provided with __Authelia__ which can easily be configured. It allows running integration diff --git a/docs/content/en/integration/deployment/bare-metal.md b/docs/content/en/integration/deployment/bare-metal.md index 0e8126be3..8e03e00fb 100644 --- a/docs/content/en/integration/deployment/bare-metal.md +++ b/docs/content/en/integration/deployment/bare-metal.md @@ -25,8 +25,8 @@ bootstrapping *Authelia*. We publish two example [systemd] unit files: -* [authelia.service](https://github.com/authelia/authelia/blob/master/authelia.service) -* [authelia@.service](https://github.com/authelia/authelia/blob/master/authelia%40.service) +* {{< github-link path="authelia.service" >}} +* {{< github-link path="authelia@.service" >}} ## Arch Linux diff --git a/docs/content/en/integration/kubernetes/istio.md b/docs/content/en/integration/kubernetes/istio.md index c1c4927e3..60360c6b0 100644 --- a/docs/content/en/integration/kubernetes/istio.md +++ b/docs/content/en/integration/kubernetes/istio.md @@ -13,7 +13,7 @@ toc: true --- Istio uses [Envoy](../proxies/envoy.md) as an Ingress. This means it has a relatively comprehensive integration option. -Istio is supported with Authelia v4.37.0 and higher via [Envoy]'s [external authorization] filter. +Istio is supported with Authelia v4.37.0 and higher via the [Envoy] proxy [external authorization] filter. [external authorization]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_authz/v3/ext_authz.proto.html#extensions-filters-http-ext-authz-v3-extauthz diff --git a/docs/content/en/integration/openid-connect/introduction.md b/docs/content/en/integration/openid-connect/introduction.md index 6ab7ba46c..37cca969d 100644 --- a/docs/content/en/integration/openid-connect/introduction.md +++ b/docs/content/en/integration/openid-connect/introduction.md @@ -21,8 +21,15 @@ documentation for some [OpenID Connect 1.0] Relying Party implementations. See the [configuration documentation](../../configuration/identity-providers/open-id-connect.md) for information on how to configure the Authelia [OpenID Connect 1.0] Provider. +This page is intended as an integration reference point for any implementers who wish to integrate an +[OpenID Connect 1.0] Relying Party (client application) either as a developer or user of the third party Reyling Party. + ## Scope Definitions +The following scope definitions describe each scope supported and the associated effects including the individual claims +returned by granting this scope. By default we do not issue any claims which reveal the users identity which allows +administrators semi-granular control over which claims the client is entitled to. + ### openid This is the default scope for [OpenID Connect 1.0]. This field is forced on every client by the configuration validation @@ -54,9 +61,16 @@ This scope is a special scope designed to allow applications to obtain a [Refres an application on behalf of a user. A [Refresh Token] is a special [Access Token] that allows refreshing previously issued token credentials, effectively it allows the relying party to obtain new tokens periodically. +As per [OpenID Connect 1.0] Section 11 [Offline Access] can only be granted during the [Authorization Code Flow] or a +[Hybrid Flow]. The [Refresh Token] will only ever be returned at the [Token Endpoint] when the client is exchanging +their [OAuth 2.0 Authorization Code]. + Generally unless an application supports this and actively requests this scope they should not be granted this scope via the client configuration. +It is also important to note that we treat a [Refresh Token] as single use and reissue a new [Refresh Token] during the +refresh flow. + ### groups This scope includes the groups the authentication backend reports the user is a member of in the [Claims] of the @@ -92,43 +106,21 @@ This scope includes the profile information the authentication backend reports a The following section describes advanced parameters which can be used in various endpoints as well as their related configuration options. -### Grant Types - -The following describes the various [OAuth 2.0] and [OpenID Connect 1.0] grant types and their support level. The value -field is both the required value for the `grant_type` parameter in the authorization request and the `grant_types` -configuration option. - -| Grant Type | Supported | Value | Notes | -|:-----------------------------------------------:|:---------:|:----------------------------------------------:|:-------------------------------------------------------------------:| -| [OAuth 2.0 Authorization Code] | Yes | `authorization_code` | | -| [OAuth 2.0 Resource Owner Password Credentials] | No | `password` | This Grant Type has been deprecated and should not normally be used | -| [OAuth 2.0 Client Credentials] | Yes | `client_credentials` | | -| [OAuth 2.0 Implicit] | Yes | `implicit` | This Grant Type has been deprecated and should not normally be used | -| [OAuth 2.0 Refresh Token] | Yes | `refresh_token` | | -| [OAuth 2.0 Device Code] | No | `urn:ietf:params:oauth:grant-type:device_code` | | -| - -[OAuth 2.0 Authorization Code]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.1 -[OAuth 2.0 Implicit]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.2 -[OAuth 2.0 Resource Owner Password Credentials]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.3 -[OAuth 2.0 Client Credentials]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.4 -[OAuth 2.0 Refresh Token]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.5 -[OAuth 2.0 Device Code]: https://datatracker.ietf.org/doc/html/rfc8628#section-3.4 - ### Response Types The following describes the supported response types. See the [OAuth 2.0 Multiple Response Type Encoding Practices] for -more technical information. +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 | -|:-------------------------:|:---------------------:| -| [Authorization Code Flow] | `code` | -| [Implicit Flow] | `token id_token` | -| [Implicit Flow] | `id_token` | -| [Implicit Flow] | `token` | -| [Hybrid Flow] | `code token` | -| [Hybrid Flow] | `code id_token` | -| [Hybrid Flow] | `code token id_token` | +| Flow Type | Value | Default [Response Modes](#response-modes) Values | +|:-------------------------:|:---------------------:|:------------------------------------------------:| +| [Authorization Code Flow] | `code` | `form_post`, `query` | +| [Implicit Flow] | `id_token token` | `form_post`, `fragment` | +| [Implicit Flow] | `id_token` | `form_post`, `fragment` | +| [Implicit Flow] | `token` | `form_post`, `fragment` | +| [Hybrid Flow] | `code token` | `form_post`, `fragment` | +| [Hybrid Flow] | `code id_token` | `form_post`, `fragment` | +| [Hybrid Flow] | `code id_token token` | `form_post`, `fragment` | [Authorization Code Flow]: https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth [Implicit Flow]: https://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth @@ -139,16 +131,60 @@ more technical information. ### Response Modes The following describes the supported response modes. See the [OAuth 2.0 Multiple Response Type Encoding Practices] for -more technical information. +more technical information. The default response modes of a client is based on the [Response Types](#response-types) +configuration. | Name | Value | |:---------------------:|:-----------:| +| [OAuth 2.0 Form Post] | `form_post` | | Query String | `query` | | Fragment | `fragment` | -| [OAuth 2.0 Form Post] | `form_post` | [OAuth 2.0 Form Post]: https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html +### Grant Types + +The following describes the various [OAuth 2.0] and [OpenID Connect 1.0] grant types and their support level. The value +field is both the required value for the `grant_type` parameter in the authorization request and the `grant_types` +configuration option. + +| Grant Type | Supported | Value | Notes | +|:-----------------------------------------------:|:---------:|:----------------------------------------------:|:-----------------------------------------------------------------------------------------------:| +| [OAuth 2.0 Authorization Code] | Yes | `authorization_code` | | +| [OAuth 2.0 Resource Owner Password Credentials] | No | `password` | This Grant Type has been deprecated and should not normally be used | +| [OAuth 2.0 Client Credentials] | No | `client_credentials` | | +| [OAuth 2.0 Implicit] | Yes | `implicit` | This Grant Type has been deprecated and should not normally be used | +| [OAuth 2.0 Refresh Token] | Yes | `refresh_token` | This Grant Type should genreally only be used for clients which have the `offline_access` scope | +| [OAuth 2.0 Device Code] | No | `urn:ietf:params:oauth:grant-type:device_code` | | +| + +[OAuth 2.0 Authorization Code]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.1 +[OAuth 2.0 Implicit]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.2 +[OAuth 2.0 Resource Owner Password Credentials]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.3 +[OAuth 2.0 Client Credentials]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.4 +[OAuth 2.0 Refresh Token]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.5 +[OAuth 2.0 Device Code]: https://datatracker.ietf.org/doc/html/rfc8628#section-3.4 + +### Client Authentication Method + +The following describes the supported client authentication methods. See the [OpenID Connect 1.0 Client Authentication] +specification and the [OAuth 2.0 - Client Types] specification for more information. + +| Description | Value / Name | Supported Client Types | Default for Client Type | Assertion Type | +|:------------------------------------:|:-----------------------------:|:----------------------:|:-----------------------:|:--------------------------------------------------------:| +| Secret via HTTP Basic Auth Scheme | `client_secret_basic` | `confidential` | N/A | N/A | +| Secret via HTTP POST Body | `client_secret_post` | `confidential` | N/A | N/A | +| JWT (signed by secret) | `client_secret_jwt` | Not Supported | N/A | `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` | +| JWT (signed by private key) | `private_key_jwt` | Not Supported | N/A | `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` | +| [OAuth 2.0 Mutual-TLS] | `tls_client_auth` | Not Supported | N/A | N/A | +| [OAuth 2.0 Mutual-TLS] (Self Signed) | `self_signed_tls_client_auth` | Not Supported | N/A | N/A | +| No Authentication | `none` | `public` | `public` | N/A | + + +[OpenID Connect 1.0 Client Authentication]: https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication +[OAuth 2.0 Mutual-TLS]: https://datatracker.ietf.org/doc/html/rfc8705 +[OAuth 2.0 - Client Types]: https://datatracker.ietf.org/doc/html/rfc8705#section-2.1 + ## Authentication Method References Authelia currently supports adding the `amr` [Claim] to the [ID Token] utilizing the [RFC8176] Authentication Method @@ -289,10 +325,13 @@ The advantages of this approach are as follows: [JSON Web Key Set]: https://datatracker.ietf.org/doc/html/rfc7517#section-5 +[Offline Access]: https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess + [Authorization]: https://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint -[Pushed Authorization Requests]: https://datatracker.ietf.org/doc/html/rfc9126 [Token]: https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint [UserInfo]: https://openid.net/specs/openid-connect-core-1_0.html#UserInfo + +[Pushed Authorization Requests]: https://datatracker.ietf.org/doc/html/rfc9126 [Introspection]: https://datatracker.ietf.org/doc/html/rfc7662 [Revocation]: https://datatracker.ietf.org/doc/html/rfc7009 [Proof Key Code Exchange]: https://www.rfc-editor.org/rfc/rfc7636.html diff --git a/docs/content/en/integration/prologue/get-started.md b/docs/content/en/integration/prologue/get-started.md index 78e9d754a..3b5fe67b3 100644 --- a/docs/content/en/integration/prologue/get-started.md +++ b/docs/content/en/integration/prologue/get-started.md @@ -23,24 +23,31 @@ common scenarios however those using more advanced architectures are likely goin help with answering less specific questions about this and it may be possible if provided adequate information more specific questions may be answered. +1. Authelia *__MUST__* be served via the `https` scheme. This is not optional even for testing. This is a deliberate + design decision to improve security directly (by using encrypted communication) and indirectly by reducing complexity. + ### Forwarded Authentication Forwarded Authentication is a simple per-request authorization flow that checks the metadata of a request and a session cookie to determine if a user must be forwarded to the authentication portal. -Due to the fact a cookie is used, it's an intentional design decision that *__ALL__* applications/domains protected via +In addition to the `https` scheme requirement for Authelia itself: + +1. Due to the fact a cookie is used, it's an intentional design decision that *__ALL__* applications/domains protected via this method *__MUST__* use secure schemes (`https` and `wss`) for all of their communication. ### OpenID Connect -Only requires Authelia to be accessible via a secure scheme (`https`). +No additional requirements other than the use of the `https` scheme for Authelia itself exist excluding those mandated +by the relevant specifications. ## Configuration It's important to customize the configuration for *Authelia* in advance of deploying it. The configuration is static and -not configured via web GUI. You can find a -[configuration template](https://github.com/authelia/authelia/blob/master/config.template.yml) on GitHub which can be -used as a basis for configuration. +not configured via web GUI. You can find a configuration template named {{< github-link path="config.template.yml" >}} +on GitHub which can be used as a basis for configuration, alternatively *Authelia* will write this template relevant for +your version the first time it is started. Users should expect that they have to configure elements of this file as part +of initial setup. The important sections to consider in initial configuration are as follows: diff --git a/docs/content/en/integration/proxies/envoy.md b/docs/content/en/integration/proxies/envoy.md index 0d45b7cea..af37d3cc8 100644 --- a/docs/content/en/integration/proxies/envoy.md +++ b/docs/content/en/integration/proxies/envoy.md @@ -87,7 +87,7 @@ Below you will find commented examples of the following configuration: ### Example -Support for [Envoy] is possible with Authelia v4.37.0 and higher via [Envoy]'s [external authorization] filter. +Support for [Envoy] is possible with Authelia v4.37.0 and higher via the [Envoy] proxy [external authorization] filter. [external authorization]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_authz/v3/ext_authz.proto.html#extensions-filters-http-ext-authz-v3-extauthz diff --git a/docs/content/en/integration/proxies/support.md b/docs/content/en/integration/proxies/support.md index 6364fde3a..9b33f8de4 100644 --- a/docs/content/en/integration/proxies/support.md +++ b/docs/content/en/integration/proxies/support.md @@ -92,7 +92,7 @@ available in [Kubernetes]. You would likely have to build your own [HAProxy] ima ### Envoy -[Envoy] is supported with Authelia v4.37.0 and higher via [Envoy]'s [external authorization] filter. +[Envoy] is supported with Authelia v4.37.0 and higher via the [Envoy] proxy [external authorization] filter. [external authorization]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_authz/v3/ext_authz.proto.html#extensions-filters-http-ext-authz-v3-extauthz diff --git a/docs/content/en/overview/authentication/push-notification/index.md b/docs/content/en/overview/authentication/push-notification/index.md index c7e5ba725..bc6013c45 100644 --- a/docs/content/en/overview/authentication/push-notification/index.md +++ b/docs/content/en/overview/authentication/push-notification/index.md @@ -44,7 +44,7 @@ case you have multiple devices available, you will be asked to select your prefe ### Why don't I have access to the *Push Notification* option? It's likely that you have not configured __Authelia__ correctly. Please read this documentation again and be sure you -had a look at [config.template.yml](https://github.com/authelia/authelia/blob/master/config.template.yml) and +had a look at {{< github-link path="config.template.yml" >}} and [configuration documentation](../../../configuration/second-factor/duo.md). [Duo]: https://duo.com/ diff --git a/docs/layouts/shortcodes/github-link.html b/docs/layouts/shortcodes/github-link.html new file mode 100644 index 000000000..2f399ac4a --- /dev/null +++ b/docs/layouts/shortcodes/github-link.html @@ -0,0 +1,17 @@ +{{- $repo := "authelia/authelia" }}{{ with .Get "repo" }}{{ $repo = . }}{{ end }} +{{- $branch := printf "v%s" .Site.Data.misc.latest }}{{ with .Get "branch" }}{{ $branch = . }}{{ end }} +{{- $path := "" }}{{ with .Get "path" }}{{ $path = . }}{{ end }} +{{- $link := printf "https://github.com/%s/blob/%s/%s" $repo $branch (urlquery $path) }} +{{- $name := "" }} +{{- with .Get "name" }} +{{- $name = . }} +{{- else }} +{{- if (eq $repo "authelia/authelia") }} +{{- $name = $path }} +{{- else }} +{{- $name = printf "https://github.com/%s/blob/%s/%s" $repo $branch $path }} +{{- end }} +{{- end }} +{{- "" -}} +{{ $name }} +{{- "" -}} diff --git a/go.mod b/go.mod index 0b1a6b1b6..0755ac7db 100644 --- a/go.mod +++ b/go.mod @@ -27,17 +27,17 @@ require ( github.com/knadh/koanf/providers/env v0.1.0 github.com/knadh/koanf/providers/posflag v0.1.0 github.com/knadh/koanf/providers/rawbytes v0.1.0 - github.com/knadh/koanf/v2 v2.0.0 + github.com/knadh/koanf/v2 v2.0.1 github.com/mattn/go-sqlite3 v1.14.16 github.com/mitchellh/mapstructure v1.5.0 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/ory/fosite v0.44.0 - github.com/ory/herodot v0.10.1 - github.com/ory/x v0.0.550 + github.com/ory/herodot v0.10.2 + github.com/ory/x v0.0.552 github.com/otiai10/copy v1.10.0 github.com/pkg/errors v0.9.1 github.com/pquerna/otp v1.4.0 - github.com/prometheus/client_golang v1.14.0 + github.com/prometheus/client_golang v1.15.0 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 @@ -98,8 +98,8 @@ require ( github.com/philhofer/fwd v1.1.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.37.0 // indirect - github.com/prometheus/procfs v0.8.0 // indirect + github.com/prometheus/common v0.42.0 // indirect + github.com/prometheus/procfs v0.9.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect github.com/spf13/afero v1.9.5 // indirect @@ -116,7 +116,7 @@ require ( github.com/ysmood/leakless v0.8.0 // indirect golang.org/x/crypto v0.7.0 // indirect golang.org/x/mod v0.10.0 // indirect - golang.org/x/oauth2 v0.4.0 // indirect + golang.org/x/oauth2 v0.5.0 // indirect golang.org/x/sys v0.7.0 // indirect golang.org/x/tools v0.7.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 0d4470ec5..f7f87f0c3 100644 --- a/go.sum +++ b/go.sum @@ -44,10 +44,7 @@ github.com/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4 h1:vdT7QwBh github.com/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4/go.mod h1:SvXOG8ElV28oAiG9zv91SDe5+9PfIr7PPccpr8YyXNs= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -62,7 +59,6 @@ github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBW github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -138,15 +134,10 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-ldap/ldap/v3 v3.4.4 h1:qPjipEpt+qDa6SI/h1fzuGWoRUY+qqQ9sOZq67/PYUs= github.com/go-ldap/ldap/v3 v3.4.4/go.mod h1:fe1MsuN5eJJ1FeLT/LEBVdWfNWKh459R7aXgXtJC+aI= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-rod/rod v0.112.8 h1:lYFnHv/lFyjW/Ye0IhyKLeHw/zfhHbSTqawoCi2z/nI= @@ -194,7 +185,6 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -216,7 +206,6 @@ github.com/google/go-tpm v0.3.3 h1:P/ZFNBZYXRxc+z7i5uyd8VP7MaDteuLZInzrH2idRGo= github.com/google/go-tpm v0.3.3/go.mod h1:9Hyn3rgnzWF9XBWVk6ml6A6hNkbWjNFlDQL51BeghL4= github.com/google/go-tpm-tools v0.0.0-20190906225433-1614c142f845/go.mod h1:AVfHadzbdzHo54inR2x1v640jdi1YSi3NauM2DUsxk0= github.com/google/go-tpm-tools v0.2.0/go.mod h1:npUd03rQ60lxN7tzeBJreG38RvWwme2N1reF/eeiBk4= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -270,15 +259,9 @@ github.com/jandelgado/gcov2lcov v1.0.5/go.mod h1:NnSxK6TMlg1oGDBfGelGbjgorT5/L3c github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY= @@ -295,14 +278,13 @@ github.com/knadh/koanf/providers/posflag v0.1.0 h1:mKJlLrKPcAP7Ootf4pBZWJ6J+4wHY github.com/knadh/koanf/providers/posflag v0.1.0/go.mod h1:SYg03v/t8ISBNrMBRMlojH8OsKowbkXV7giIbBVgbz0= github.com/knadh/koanf/providers/rawbytes v0.1.0 h1:dpzgu2KO6uf6oCb4aP05KDmKmAmI51k5pe8RYKQ0qME= github.com/knadh/koanf/providers/rawbytes v0.1.0/go.mod h1:mMTB1/IcJ/yE++A2iEZbY1MLygX7vttU+C+S/YmPu9c= -github.com/knadh/koanf/v2 v2.0.0 h1:XPQ5ilNnwnNaHrfQ1YpTVhUAjcGHnEKA+lRpipQv02Y= -github.com/knadh/koanf/v2 v2.0.0/go.mod h1:ZeiIlIDXTE7w1lMT6UVcNiRAS2/rCeLn/GdLNvY1Dus= +github.com/knadh/koanf/v2 v2.0.1 h1:1dYGITt1I23x8cfx8ZnldtezdyaZtfAuRtIFOiRzK7g= +github.com/knadh/koanf/v2 v2.0.1/go.mod h1:ZeiIlIDXTE7w1lMT6UVcNiRAS2/rCeLn/GdLNvY1Dus= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -329,15 +311,9 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oleiade/reflections v1.0.1 h1:D1XO3LVEYroYskEsoSiGItp9RUxG6jWnCVvrqH0HHQM= @@ -349,10 +325,10 @@ github.com/ory/go-acc v0.2.9-0.20230103102148-6b1c9a70dbbe h1:rvu4obdvqR0fkSIJ8I github.com/ory/go-acc v0.2.9-0.20230103102148-6b1c9a70dbbe/go.mod h1:z4n3u6as84LbV4YmgjHhnwtccQqzf4cZlSk9f1FhygI= github.com/ory/go-convenience v0.1.0 h1:zouLKfF2GoSGnJwGq+PE/nJAE6dj2Zj5QlTgmMTsTS8= github.com/ory/go-convenience v0.1.0/go.mod h1:uEY/a60PL5c12nYz4V5cHY03IBmwIAEm8TWB0yn9KNs= -github.com/ory/herodot v0.10.1 h1:espcYXC2VJrgYXAhKCOX+3pvD9eD7cT2Ceqad9yWEXc= -github.com/ory/herodot v0.10.1/go.mod h1:MMNmY6MG1uB6fnXYFaHoqdV23DTWctlPsmRCeq/2+wc= -github.com/ory/x v0.0.550 h1:bcaOdUW/jHByoT8U6cfraBpRCtNtUB8lNBD97LTrB+Y= -github.com/ory/x v0.0.550/go.mod h1:oRVemI3SQQOLvOCJWIRinHQKlgmay/NbwSyRUIsS/Yk= +github.com/ory/herodot v0.10.2 h1:gGvNMHgAwWzdP/eo+roSiT5CGssygHSjDU7MSQNlJ4E= +github.com/ory/herodot v0.10.2/go.mod h1:MMNmY6MG1uB6fnXYFaHoqdV23DTWctlPsmRCeq/2+wc= +github.com/ory/x v0.0.552 h1:vgDw7FFQ7Ama3iyDLbjElY2Um1/ub82iIubK0pUj81M= +github.com/ory/x v0.0.552/go.mod h1:oRVemI3SQQOLvOCJWIRinHQKlgmay/NbwSyRUIsS/Yk= github.com/otiai10/copy v1.10.0 h1:znyI7l134wNg/wDktoVQPxPkgvhDfGCYUasey+h0rDQ= github.com/otiai10/copy v1.10.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww= github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= @@ -367,7 +343,6 @@ github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoU github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= @@ -377,34 +352,21 @@ github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg= github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_golang v1.15.0 h1:5fCgGYogn0hFdhyhLbw7hEsWxufKtY9klyvdNfFlFhM= +github.com/prometheus/client_golang v1.15.0/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= -github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= -github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -417,8 +379,6 @@ github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1Avp github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -568,7 +528,6 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -592,10 +551,7 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= @@ -609,10 +565,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M= -golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= +golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -635,7 +589,6 @@ golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -644,7 +597,6 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -657,8 +609,6 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -666,17 +616,13 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210629170331-7dc0b73dc9fb/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -864,7 +810,6 @@ google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= @@ -876,9 +821,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/commands/crypto_hash.go b/internal/commands/crypto_hash.go index 7c85f3ef3..06cad1e7c 100644 --- a/internal/commands/crypto_hash.go +++ b/internal/commands/crypto_hash.go @@ -61,6 +61,7 @@ func newCryptoHashGenerateCmd(ctx *CmdCtx) (cmd *cobra.Command) { Short: cmdAutheliaCryptoHashGenerateShort, Long: cmdAutheliaCryptoHashGenerateLong, Example: cmdAutheliaCryptoHashGenerateExample, + Args: cobra.NoArgs, PreRunE: ctx.ChainRunE( ctx.ConfigSetDefaultsRunE(defaults), ctx.CryptoHashGenerateMapFlagsRunE, diff --git a/internal/configuration/config.template.yml b/internal/configuration/config.template.yml index 1058fecc1..077237358 100644 --- a/internal/configuration/config.template.yml +++ b/internal/configuration/config.template.yml @@ -1480,12 +1480,6 @@ notifier: # - email # - profile - ## Grant Types configures which grants this client can obtain. - ## It's not recommended to define this unless you know what you're doing. - # grant_types: - # - refresh_token - # - authorization_code - ## Response Types configures which responses this client can be sent. ## It's not recommended to define this unless you know what you're doing. # response_types: @@ -1495,7 +1489,14 @@ notifier: # response_modes: # - form_post # - query - # - fragment + + ## Grant Types configures which grants this client can obtain. + ## It's not recommended to define this unless you know what you're doing. + # grant_types: + # - authorization_code + + ## The permitted client authentication method for the Token Endpoint for this client. + # token_endpoint_auth_method: client_secret_basic ## The policy to require for this client; one_factor or two_factor. # authorization_policy: two_factor diff --git a/internal/configuration/provider_test.go b/internal/configuration/provider_test.go index f91b9629b..6c1658948 100644 --- a/internal/configuration/provider_test.go +++ b/internal/configuration/provider_test.go @@ -401,6 +401,9 @@ func TestShouldNotReadConfigurationOnFSAccessDenied(t *testing.T) { func TestShouldLoadDirectoryConfiguration(t *testing.T) { dir := t.TempDir() + cfg := filepath.Join(dir, "myconf.yml") + assert.NoError(t, testCreateFile(cfg, "server:\n port: 9091\n", 0700)) + val := schema.NewStructValidator() _, _, err := Load(val, NewFileSource(dir)) @@ -416,3 +419,70 @@ func testSetEnv(t *testing.T, key, value string) { func testCreateFile(path, value string, perm os.FileMode) (err error) { return os.WriteFile(path, []byte(value), perm) } + +func TestShouldErrorOnNoPath(t *testing.T) { + val := schema.NewStructValidator() + _, _, err := Load(val, NewFileSource("")) + + assert.NoError(t, err) + assert.Len(t, val.Errors(), 1) + assert.ErrorContains(t, val.Errors()[0], "invalid file path source configuration") +} + +func TestShouldErrorOnInvalidPath(t *testing.T) { + dir := t.TempDir() + cfg := filepath.Join(dir, "invalid-folder/config") + + val := schema.NewStructValidator() + _, _, err := Load(val, NewFileSource(cfg)) + + assert.NoError(t, err) + assert.Len(t, val.Errors(), 1) + assert.ErrorContains(t, val.Errors()[0], fmt.Sprintf("stat %s: no such file or directory", cfg)) +} + +func TestShouldErrorOnDirFSPermissionDenied(t *testing.T) { + if runtime.GOOS == constWindows { + t.Skip("skipping test due to being on windows") + } + + dir := t.TempDir() + err := os.Chmod(dir, 0200) + assert.NoError(t, err) + + val := schema.NewStructValidator() + _, _, err = Load(val, NewFileSource(dir)) + + assert.NoError(t, err) + assert.Len(t, val.Errors(), 1) + assert.ErrorContains(t, val.Errors()[0], fmt.Sprintf("open %s: permission denied", dir)) +} + +func TestShouldSkipDirOnLoad(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "some-dir") + + err := os.Mkdir(path, 0700) + assert.NoError(t, err) + + val := schema.NewStructValidator() + _, _, err = Load(val, NewFileSource(dir)) + + assert.NoError(t, err) + assert.Len(t, val.Errors(), 0) + assert.Len(t, val.Warnings(), 0) +} + +func TestShouldFailIfYmlIsInvalid(t *testing.T) { + dir := t.TempDir() + + cfg := filepath.Join(dir, "myconf.yml") + assert.NoError(t, testCreateFile(cfg, "an invalid contend\n", 0700)) + + val := schema.NewStructValidator() + _, _, err := Load(val, NewFileSource(dir)) + + assert.NoError(t, err) + assert.Len(t, val.Errors(), 1) + assert.ErrorContains(t, val.Errors()[0], "unmarshal errors") +} diff --git a/internal/configuration/schema/identity_providers.go b/internal/configuration/schema/identity_providers.go index 57376dc87..d253a4d07 100644 --- a/internal/configuration/schema/identity_providers.go +++ b/internal/configuration/schema/identity_providers.go @@ -64,6 +64,8 @@ type OpenIDConnectClientConfiguration struct { ResponseTypes []string `koanf:"response_types"` ResponseModes []string `koanf:"response_modes"` + TokenEndpointAuthMethod string `koanf:"token_endpoint_auth_method"` + Policy string `koanf:"authorization_policy"` EnforcePAR bool `koanf:"enforce_par"` @@ -91,9 +93,8 @@ var defaultOIDCClientConsentPreConfiguredDuration = time.Hour * 24 * 7 var DefaultOpenIDConnectClientConfiguration = OpenIDConnectClientConfiguration{ Policy: "two_factor", Scopes: []string{"openid", "groups", "profile", "email"}, - GrantTypes: []string{"refresh_token", "authorization_code"}, ResponseTypes: []string{"code"}, - ResponseModes: []string{"form_post", "query", "fragment"}, + ResponseModes: []string{"form_post"}, UserinfoSigningAlgorithm: "none", ConsentMode: "auto", diff --git a/internal/configuration/schema/keys.go b/internal/configuration/schema/keys.go index 526913918..85923eb1a 100644 --- a/internal/configuration/schema/keys.go +++ b/internal/configuration/schema/keys.go @@ -45,6 +45,7 @@ var Keys = []string{ "identity_providers.oidc.clients[].grant_types", "identity_providers.oidc.clients[].response_types", "identity_providers.oidc.clients[].response_modes", + "identity_providers.oidc.clients[].token_endpoint_auth_method", "identity_providers.oidc.clients[].authorization_policy", "identity_providers.oidc.clients[].enforce_par", "identity_providers.oidc.clients[].enforce_pkce", diff --git a/internal/configuration/template_test.go b/internal/configuration/template_test.go index 3be14868f..aab4b26ae 100644 --- a/internal/configuration/template_test.go +++ b/internal/configuration/template_test.go @@ -25,6 +25,23 @@ func TestShouldGenerateConfiguration(t *testing.T) { assert.NoError(t, err) } +func TestNotShouldGenerateConfigurationifExists(t *testing.T) { + dir := t.TempDir() + + cfg := filepath.Join(dir, "config.yml") + + created, err := EnsureConfigurationExists(cfg) + assert.NoError(t, err) + assert.True(t, created) + + created, err = EnsureConfigurationExists(cfg) + assert.NoError(t, err) + assert.False(t, created) + + _, err = os.Stat(cfg) + assert.NoError(t, err) +} + func TestShouldNotGenerateConfigurationOnFSAccessDenied(t *testing.T) { if runtime.GOOS == constWindows { t.Skip("skipping test due to being on windows") diff --git a/internal/configuration/validator/access_control.go b/internal/configuration/validator/access_control.go index 994d7559c..93f1efa4c 100644 --- a/internal/configuration/validator/access_control.go +++ b/internal/configuration/validator/access_control.go @@ -59,7 +59,7 @@ func ValidateAccessControl(config *schema.Configuration, validator *schema.Struc } if !IsPolicyValid(config.AccessControl.DefaultPolicy) { - validator.Push(fmt.Errorf(errFmtAccessControlDefaultPolicyValue, strings.Join(validACLRulePolicies, "', '"), config.AccessControl.DefaultPolicy)) + validator.Push(fmt.Errorf(errFmtAccessControlDefaultPolicyValue, strJoinOr(validACLRulePolicies), config.AccessControl.DefaultPolicy)) } if config.AccessControl.Networks != nil { @@ -92,8 +92,13 @@ func ValidateRules(config *schema.Configuration, validator *schema.StructValidat validateDomains(rulePosition, rule, validator) - if !IsPolicyValid(rule.Policy) { - validator.Push(fmt.Errorf(errFmtAccessControlRuleInvalidPolicy, ruleDescriptor(rulePosition, rule), rule.Policy)) + switch rule.Policy { + case "": + validator.Push(fmt.Errorf(errFmtAccessControlRuleNoPolicy, ruleDescriptor(rulePosition, rule))) + default: + if !IsPolicyValid(rule.Policy) { + validator.Push(fmt.Errorf(errFmtAccessControlRuleInvalidPolicy, ruleDescriptor(rulePosition, rule), strJoinOr(validACLRulePolicies), rule.Policy)) + } } validateNetworks(rulePosition, rule, config.AccessControl, validator) @@ -156,10 +161,14 @@ func validateSubjects(rulePosition int, rule schema.ACLRule, validator *schema.S } func validateMethods(rulePosition int, rule schema.ACLRule, validator *schema.StructValidator) { - for _, method := range rule.Methods { - if !utils.IsStringInSliceFold(method, validACLHTTPMethodVerbs) { - validator.Push(fmt.Errorf(errFmtAccessControlRuleMethodInvalid, ruleDescriptor(rulePosition, rule), method, strings.Join(validACLHTTPMethodVerbs, "', '"))) - } + invalid, duplicates := validateList(rule.Methods, validACLHTTPMethodVerbs, true) + + if len(invalid) != 0 { + validator.Push(fmt.Errorf(errFmtAccessControlRuleInvalidEntries, ruleDescriptor(rulePosition, rule), "methods", strJoinOr(validACLHTTPMethodVerbs), strJoinAnd(invalid))) + } + + if len(duplicates) != 0 { + validator.Push(fmt.Errorf(errFmtAccessControlRuleInvalidDuplicates, ruleDescriptor(rulePosition, rule), "methods", strJoinAnd(duplicates))) } } @@ -177,7 +186,7 @@ func validateQuery(i int, rule schema.ACLRule, config *schema.Configuration, val } } } else if !utils.IsStringInSliceFold(config.AccessControl.Rules[i].Query[j][k].Operator, validACLRuleOperators) { - validator.Push(fmt.Errorf(errFmtAccessControlRuleQueryInvalid, ruleDescriptor(i+1, rule), config.AccessControl.Rules[i].Query[j][k].Operator, strings.Join(validACLRuleOperators, "', '"))) + validator.Push(fmt.Errorf(errFmtAccessControlRuleQueryInvalid, ruleDescriptor(i+1, rule), strJoinOr(validACLRuleOperators), config.AccessControl.Rules[i].Query[j][k].Operator)) } if config.AccessControl.Rules[i].Query[j][k].Key == "" { diff --git a/internal/configuration/validator/access_control_test.go b/internal/configuration/validator/access_control_test.go index 0671455a1..1543959e0 100644 --- a/internal/configuration/validator/access_control_test.go +++ b/internal/configuration/validator/access_control_test.go @@ -58,7 +58,7 @@ func (suite *AccessControl) TestShouldValidateEitherDomainsOrDomainsRegex() { suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - assert.EqualError(suite.T(), suite.validator.Errors()[0], "access control: rule #3: rule is invalid: must have the option 'domain' or 'domain_regex' configured") + assert.EqualError(suite.T(), suite.validator.Errors()[0], "access control: rule #3: option 'domain' or 'domain_regex' must be present but are both absent") } func (suite *AccessControl) TestShouldRaiseErrorInvalidDefaultPolicy() { @@ -69,7 +69,7 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidDefaultPolicy() { suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "access control: option 'default_policy' must be one of 'bypass', 'one_factor', 'two_factor', 'deny' but it is configured as 'invalid'") + suite.Assert().EqualError(suite.validator.Errors()[0], "access control: option 'default_policy' must be one of 'bypass', 'one_factor', 'two_factor', or 'deny' but it's configured as 'invalid'") } func (suite *AccessControl) TestShouldRaiseErrorInvalidNetworkGroupNetwork() { @@ -141,10 +141,10 @@ func (suite *AccessControl) TestShouldRaiseErrorsWithEmptyRules() { suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 4) - suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1: rule is invalid: must have the option 'domain' or 'domain_regex' configured") - suite.Assert().EqualError(suite.validator.Errors()[1], "access control: rule #1: rule 'policy' option '' is invalid: must be one of 'deny', 'two_factor', 'one_factor' or 'bypass'") - suite.Assert().EqualError(suite.validator.Errors()[2], "access control: rule #2: rule is invalid: must have the option 'domain' or 'domain_regex' configured") - suite.Assert().EqualError(suite.validator.Errors()[3], "access control: rule #2: rule 'policy' option 'wrong' is invalid: must be one of 'deny', 'two_factor', 'one_factor' or 'bypass'") + suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1: option 'domain' or 'domain_regex' must be present but are both absent") + suite.Assert().EqualError(suite.validator.Errors()[1], "access control: rule #1: option 'policy' must be present but it's absent") + suite.Assert().EqualError(suite.validator.Errors()[2], "access control: rule #2: option 'domain' or 'domain_regex' must be present but are both absent") + suite.Assert().EqualError(suite.validator.Errors()[3], "access control: rule #2: option 'policy' must be one of 'bypass', 'one_factor', 'two_factor', or 'deny' but it's configured as 'wrong'") } func (suite *AccessControl) TestShouldRaiseErrorInvalidPolicy() { @@ -160,7 +160,7 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidPolicy() { suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): rule 'policy' option 'invalid' is invalid: must be one of 'deny', 'two_factor', 'one_factor' or 'bypass'") + suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): option 'policy' must be one of 'bypass', 'one_factor', 'two_factor', or 'deny' but it's configured as 'invalid'") } func (suite *AccessControl) TestShouldRaiseErrorInvalidNetwork() { @@ -194,7 +194,24 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidMethod() { suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): 'methods' option 'HOP' is invalid: must be one of 'GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'TRACE', 'CONNECT', 'OPTIONS', 'COPY', 'LOCK', 'MKCOL', 'MOVE', 'PROPFIND', 'PROPPATCH', 'UNLOCK'") + suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): option 'methods' must only have the values 'GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'TRACE', 'CONNECT', 'OPTIONS', 'COPY', 'LOCK', 'MKCOL', 'MOVE', 'PROPFIND', 'PROPPATCH', or 'UNLOCK' but the values 'HOP' are present") +} + +func (suite *AccessControl) TestShouldRaiseErrorDuplicateMethod() { + suite.config.AccessControl.Rules = []schema.ACLRule{ + { + Domains: []string{"public.example.com"}, + Policy: "bypass", + Methods: []string{"GET", "GET"}, + }, + } + + ValidateRules(suite.config, suite.validator) + + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Require().Len(suite.validator.Errors(), 1) + + suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): option 'methods' must have unique values but the values 'GET' are duplicated") } func (suite *AccessControl) TestShouldRaiseErrorInvalidSubject() { @@ -367,13 +384,13 @@ func (suite *AccessControl) TestShouldErrorOnInvalidRulesQuery() { suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 7) - suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): 'query' option 'value' is invalid: must have a value when the operator is 'equal'") - suite.Assert().EqualError(suite.validator.Errors()[1], "access control: rule #2 (domain 'public.example.com'): 'query' option 'key' is invalid: must have a value") - suite.Assert().EqualError(suite.validator.Errors()[2], "access control: rule #5 (domain 'public.example.com'): 'query' option 'key' is invalid: must have a value") - suite.Assert().EqualError(suite.validator.Errors()[3], "access control: rule #6 (domain 'public.example.com'): 'query' option 'operator' with value 'not' is invalid: must be one of 'present', 'absent', 'equal', 'not equal', 'pattern', 'not pattern'") - suite.Assert().EqualError(suite.validator.Errors()[4], "access control: rule #7 (domain 'public.example.com'): 'query' option 'value' is invalid: error parsing regexp: missing closing ): `(bad pattern`") - suite.Assert().EqualError(suite.validator.Errors()[5], "access control: rule #8 (domain 'public.example.com'): 'query' option 'value' is invalid: must not have a value when the operator is 'present'") - suite.Assert().EqualError(suite.validator.Errors()[6], "access control: rule #9 (domain 'public.example.com'): 'query' option 'value' is invalid: expected type was string but got int") + suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): query: option 'value' must be present when the option 'operator' is 'equal' but it's absent") + suite.Assert().EqualError(suite.validator.Errors()[1], "access control: rule #2 (domain 'public.example.com'): query: option 'key' is required but it's absent") + suite.Assert().EqualError(suite.validator.Errors()[2], "access control: rule #5 (domain 'public.example.com'): query: option 'key' is required but it's absent") + suite.Assert().EqualError(suite.validator.Errors()[3], "access control: rule #6 (domain 'public.example.com'): query: option 'operator' must be one of 'present', 'absent', 'equal', 'not equal', 'pattern', or 'not pattern' but it's configured as 'not'") + suite.Assert().EqualError(suite.validator.Errors()[4], "access control: rule #7 (domain 'public.example.com'): query: option 'value' is invalid: error parsing regexp: missing closing ): `(bad pattern`") + suite.Assert().EqualError(suite.validator.Errors()[5], "access control: rule #8 (domain 'public.example.com'): query: option 'value' must not be present when the option 'operator' is 'present' but it's present") + suite.Assert().EqualError(suite.validator.Errors()[6], "access control: rule #9 (domain 'public.example.com'): query: option 'value' is invalid: expected type was string but got int") } func TestAccessControl(t *testing.T) { diff --git a/internal/configuration/validator/authentication.go b/internal/configuration/validator/authentication.go index bcd64fabf..fb209f179 100644 --- a/internal/configuration/validator/authentication.go +++ b/internal/configuration/validator/authentication.go @@ -71,7 +71,7 @@ func ValidatePasswordConfiguration(config *schema.Password, validator *schema.St case utils.IsStringInSlice(config.Algorithm, validHashAlgorithms): break default: - validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordUnknownAlg, config.Algorithm, strings.Join(validHashAlgorithms, "', '"))) + validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordUnknownAlg, strJoinOr(validHashAlgorithms), config.Algorithm)) } validateFileAuthenticationBackendPasswordConfigArgon2(config, validator) @@ -89,7 +89,7 @@ func validateFileAuthenticationBackendPasswordConfigArgon2(config *schema.Passwo case utils.IsStringInSlice(config.Argon2.Variant, validArgon2Variants): break default: - validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashArgon2, config.Argon2.Variant, strings.Join(validArgon2Variants, "', '"))) + validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashArgon2, strJoinOr(validArgon2Variants), config.Argon2.Variant)) } switch { @@ -147,7 +147,7 @@ func validateFileAuthenticationBackendPasswordConfigSHA2Crypt(config *schema.Pas case utils.IsStringInSlice(config.SHA2Crypt.Variant, validSHA2CryptVariants): break default: - validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashSHA2Crypt, config.SHA2Crypt.Variant, strings.Join(validSHA2CryptVariants, "', '"))) + validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashSHA2Crypt, strJoinOr(validSHA2CryptVariants), config.SHA2Crypt.Variant)) } switch { @@ -176,7 +176,7 @@ func validateFileAuthenticationBackendPasswordConfigPBKDF2(config *schema.Passwo case utils.IsStringInSlice(config.PBKDF2.Variant, validPBKDF2Variants): break default: - validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashPBKDF2, config.PBKDF2.Variant, strings.Join(validPBKDF2Variants, "', '"))) + validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashPBKDF2, strJoinOr(validPBKDF2Variants), config.PBKDF2.Variant)) } switch { @@ -205,7 +205,7 @@ func validateFileAuthenticationBackendPasswordConfigBCrypt(config *schema.Passwo case utils.IsStringInSlice(config.BCrypt.Variant, validBCryptVariants): break default: - validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashBCrypt, config.BCrypt.Variant, strings.Join(validBCryptVariants, "', '"))) + validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashBCrypt, strJoinOr(validBCryptVariants), config.BCrypt.Variant)) } switch { @@ -369,7 +369,7 @@ func validateLDAPAuthenticationBackendImplementation(config *schema.Authenticati case schema.LDAPImplementationGLAuth: implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth default: - validator.Push(fmt.Errorf(errFmtLDAPAuthBackendImplementation, config.LDAP.Implementation, strings.Join(validLDAPImplementations, "', '"))) + validator.Push(fmt.Errorf(errFmtLDAPAuthBackendImplementation, strJoinOr(validLDAPImplementations), config.LDAP.Implementation)) } tlsconfig := &schema.TLSConfig{} diff --git a/internal/configuration/validator/authentication_test.go b/internal/configuration/validator/authentication_test.go index cc540f064..9abec6e1a 100644 --- a/internal/configuration/validator/authentication_test.go +++ b/internal/configuration/validator/authentication_test.go @@ -256,7 +256,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidArgon2 suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: argon2: option 'variant' is configured as 'invalid' but must be one of the following values: 'argon2id', 'id', 'argon2i', 'i', 'argon2d', 'd'") + suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: argon2: option 'variant' must be one of 'argon2id', 'id', 'argon2i', 'i', 'argon2d', or 'd' but it's configured as 'invalid'") } func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidSHA2CryptVariant() { @@ -270,7 +270,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidSHA2Cr suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: sha2crypt: option 'variant' is configured as 'invalid' but must be one of the following values: 'sha256', 'sha512'") + suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: sha2crypt: option 'variant' must be one of 'sha256' or 'sha512' but it's configured as 'invalid'") } func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidSHA2CryptSaltLength() { @@ -298,7 +298,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidPBKDF2 suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: pbkdf2: option 'variant' is configured as 'invalid' but must be one of the following values: 'sha1', 'sha224', 'sha256', 'sha384', 'sha512'") + suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: pbkdf2: option 'variant' must be one of 'sha1', 'sha224', 'sha256', 'sha384', or 'sha512' but it's configured as 'invalid'") } func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidBCryptVariant() { @@ -312,7 +312,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidBCrypt suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: bcrypt: option 'variant' is configured as 'invalid' but must be one of the following values: 'standard', 'sha256'") + suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: bcrypt: option 'variant' must be one of 'standard' or 'sha256' but it's configured as 'invalid'") } func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenSHA2CryptOptionsTooLow() { @@ -497,7 +497,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenBadAlgorith suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: option 'algorithm' is configured as 'bogus' but must be one of the following values: 'sha2crypt', 'pbkdf2', 'scrypt', 'bcrypt', 'argon2'") + suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: option 'algorithm' must be one of 'sha2crypt', 'pbkdf2', 'scrypt', 'bcrypt', or 'argon2' but it's configured as 'bogus'") } func (suite *FileBasedAuthenticationBackend) TestShouldSetDefaultValues() { @@ -609,7 +609,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenImplementat suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'implementation' is configured as 'masd' but must be one of the following values: 'custom', 'activedirectory', 'rfc2307bis', 'freeipa', 'lldap', 'glauth'") + suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'implementation' must be one of 'custom', 'activedirectory', 'rfc2307bis', 'freeipa', 'lldap', or 'glauth' but it's configured as 'masd'") } func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenURLNotProvided() { @@ -755,7 +755,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorOnBadFilterPlac suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'users_filter' has an invalid placeholder: '{0}' has been removed, please use '{input}' instead") suite.Assert().EqualError(suite.validator.Errors()[1], "authentication_backend: ldap: option 'groups_filter' has an invalid placeholder: '{0}' has been removed, please use '{input}' instead") suite.Assert().EqualError(suite.validator.Errors()[2], "authentication_backend: ldap: option 'groups_filter' has an invalid placeholder: '{1}' has been removed, please use '{username}' instead") - suite.Assert().EqualError(suite.validator.Errors()[3], "authentication_backend: ldap: option 'users_filter' must contain the placeholder '{input}' but it is required") + suite.Assert().EqualError(suite.validator.Errors()[3], "authentication_backend: ldap: option 'users_filter' must contain the placeholder '{input}' but it's absent") } func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultGroupNameAttribute() { @@ -823,7 +823,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseWhenUsersFilterDoesN suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'users_filter' must contain the placeholder '{username_attribute}' but it is required") + suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'users_filter' must contain the placeholder '{username_attribute}' but it's absent") } func (suite *LDAPAuthenticationBackendSuite) TestShouldHelpDetectNoInputPlaceholder() { @@ -834,7 +834,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldHelpDetectNoInputPlacehol suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'users_filter' must contain the placeholder '{input}' but it is required") + suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'users_filter' must contain the placeholder '{input}' but it's absent") } func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultTLSMinimumVersion() { @@ -986,7 +986,7 @@ func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldRaiseErrorOnIn validateLDAPAuthenticationBackendURL(suite.config.LDAP, suite.validator) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'url' must have either the 'ldap' or 'ldaps' scheme but it is configured as 'http'") + suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'url' must have either the 'ldap' or 'ldaps' scheme but it's configured as 'http'") } func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldRaiseErrorOnInvalidURLWithBadCharacters() { diff --git a/internal/configuration/validator/configuration.go b/internal/configuration/validator/configuration.go index bacfee8ea..df3d5e328 100644 --- a/internal/configuration/validator/configuration.go +++ b/internal/configuration/validator/configuration.go @@ -78,7 +78,7 @@ func validateDefault2FAMethod(config *schema.Configuration, validator *schema.St } if !utils.IsStringInSlice(config.Default2FAMethod, validDefault2FAMethods) { - validator.Push(fmt.Errorf(errFmtInvalidDefault2FAMethod, config.Default2FAMethod, strings.Join(validDefault2FAMethods, "', '"))) + validator.Push(fmt.Errorf(errFmtInvalidDefault2FAMethod, strJoinOr(validDefault2FAMethods), config.Default2FAMethod)) return } @@ -98,6 +98,6 @@ func validateDefault2FAMethod(config *schema.Configuration, validator *schema.St } if !utils.IsStringInSlice(config.Default2FAMethod, enabledMethods) { - validator.Push(fmt.Errorf(errFmtInvalidDefault2FAMethodDisabled, config.Default2FAMethod, strings.Join(enabledMethods, "', '"))) + validator.Push(fmt.Errorf(errFmtInvalidDefault2FAMethodDisabled, strJoinOr(enabledMethods), config.Default2FAMethod)) } } diff --git a/internal/configuration/validator/configuration_test.go b/internal/configuration/validator/configuration_test.go index 8092bc6b0..f40aaea1c 100644 --- a/internal/configuration/validator/configuration_test.go +++ b/internal/configuration/validator/configuration_test.go @@ -221,7 +221,7 @@ func TestValidateDefault2FAMethod(t *testing.T) { TOTP: schema.TOTPConfiguration{Disable: true}, }, expectedErrs: []string{ - "option 'default_2fa_method' is configured as 'totp' but must be one of the following enabled method values: 'webauthn', 'mobile_push'", + "option 'default_2fa_method' must be one of the enabled options 'webauthn' or 'mobile_push' but it's configured as 'totp'", }, }, { @@ -236,7 +236,7 @@ func TestValidateDefault2FAMethod(t *testing.T) { WebAuthn: schema.WebAuthnConfiguration{Disable: true}, }, expectedErrs: []string{ - "option 'default_2fa_method' is configured as 'webauthn' but must be one of the following enabled method values: 'totp', 'mobile_push'", + "option 'default_2fa_method' must be one of the enabled options 'totp' or 'mobile_push' but it's configured as 'webauthn'", }, }, { @@ -246,7 +246,7 @@ func TestValidateDefault2FAMethod(t *testing.T) { DuoAPI: schema.DuoAPIConfiguration{Disable: true}, }, expectedErrs: []string{ - "option 'default_2fa_method' is configured as 'mobile_push' but must be one of the following enabled method values: 'totp', 'webauthn'", + "option 'default_2fa_method' must be one of the enabled options 'totp' or 'webauthn' but it's configured as 'mobile_push'", }, }, { @@ -255,7 +255,7 @@ func TestValidateDefault2FAMethod(t *testing.T) { Default2FAMethod: "duo", }, expectedErrs: []string{ - "option 'default_2fa_method' is configured as 'duo' but must be one of the following values: 'totp', 'webauthn', 'mobile_push'", + "option 'default_2fa_method' must be one of 'totp', 'webauthn', or 'mobile_push' but it's configured as 'duo'", }, }, } diff --git a/internal/configuration/validator/const.go b/internal/configuration/validator/const.go index e8a24e585..3fbaa8ded 100644 --- a/internal/configuration/validator/const.go +++ b/internal/configuration/validator/const.go @@ -67,7 +67,7 @@ const ( ) const ( - errSuffixMustBeOneOf = "is configured as '%s' but must be one of the following values: '%s'" + errSuffixMustBeOneOf = "must be one of %s but it's configured as '%s'" ) // Authentication Backend Error constants. @@ -105,19 +105,19 @@ const ( errFmtLDAPAuthBackendURLNotParsable = "authentication_backend: ldap: option " + "'url' could not be parsed: %w" errFmtLDAPAuthBackendURLInvalidScheme = "authentication_backend: ldap: option " + - "'url' must have either the 'ldap' or 'ldaps' scheme but it is configured as '%s'" + "'url' must have either the 'ldap' or 'ldaps' scheme but it's configured as '%s'" errFmtLDAPAuthBackendFilterEnclosingParenthesis = "authentication_backend: ldap: option " + "'%s' must contain enclosing parenthesis: '%s' should probably be '(%s)'" errFmtLDAPAuthBackendFilterMissingPlaceholder = "authentication_backend: ldap: option " + - "'%s' must contain the placeholder '{%s}' but it is required" + "'%s' must contain the placeholder '{%s}' but it's absent" ) // TOTP Error constants. const ( - errFmtTOTPInvalidAlgorithm = "totp: option 'algorithm' must be one of '%s' but it is configured as '%s'" - errFmtTOTPInvalidPeriod = "totp: option 'period' option must be 15 or more but it is configured as '%d'" - errFmtTOTPInvalidDigits = "totp: option 'digits' must be 6 or 8 but it is configured as '%d'" - errFmtTOTPInvalidSecretSize = "totp: option 'secret_size' must be %d or higher but it is configured as '%d'" //nolint:gosec + errFmtTOTPInvalidAlgorithm = "totp: option 'algorithm' must be one of %s but it's configured as '%s'" + errFmtTOTPInvalidPeriod = "totp: option 'period' option must be 15 or more but it's configured as '%d'" + errFmtTOTPInvalidDigits = "totp: option 'digits' must be 6 or 8 but it's configured as '%d'" + errFmtTOTPInvalidSecretSize = "totp: option 'secret_size' must be %d or higher but it's configured as '%d'" //nolint:gosec ) // Storage Error constants. @@ -128,14 +128,14 @@ const ( errFmtStorageUserPassMustBeProvided = "storage: %s: option 'username' and 'password' are required" //nolint:gosec errFmtStorageOptionMustBeProvided = "storage: %s: option '%s' is required" errFmtStorageTLSConfigInvalid = "storage: %s: tls: %w" - errFmtStoragePostgreSQLInvalidSSLMode = "storage: postgres: ssl: option 'mode' must be one of '%s' but it is configured as '%s'" + errFmtStoragePostgreSQLInvalidSSLMode = "storage: postgres: ssl: option 'mode' must be one of %s but it's configured as '%s'" errFmtStoragePostgreSQLInvalidSSLAndTLSConfig = "storage: postgres: can't define both 'tls' and 'ssl' configuration options" warnFmtStoragePostgreSQLInvalidSSLDeprecated = "storage: postgres: ssl: the ssl configuration options are deprecated and we recommend the tls options instead" ) // Telemetry Error constants. const ( - errFmtTelemetryMetricsScheme = "telemetry: metrics: option 'address' must have a scheme 'tcp://' but it is configured as '%s'" + errFmtTelemetryMetricsScheme = "telemetry: metrics: option 'address' must have a scheme 'tcp://' but it's configured as '%s'" ) // OpenID Error constants. @@ -148,17 +148,16 @@ const ( errFmtOIDCCertificateMismatch = "identity_providers: oidc: option 'issuer_private_key' does not appear to be the private key the certificate provided by option 'issuer_certificate_chain'" errFmtOIDCCertificateChain = "identity_providers: oidc: option 'issuer_certificate_chain' produced an error during validation of the chain: %w" errFmtOIDCEnforcePKCEInvalidValue = "identity_providers: oidc: option 'enforce_pkce' must be 'never', " + - "'public_clients_only' or 'always', but it is configured as '%s'" + "'public_clients_only' or 'always', but it's configured as '%s'" errFmtOIDCCORSInvalidOrigin = "identity_providers: oidc: cors: option 'allowed_origins' contains an invalid value '%s' as it has a %s: origins must only be scheme, hostname, and an optional port" errFmtOIDCCORSInvalidOriginWildcard = "identity_providers: oidc: cors: option 'allowed_origins' contains the wildcard origin '*' with more than one origin but the wildcard origin must be defined by itself" errFmtOIDCCORSInvalidOriginWildcardWithClients = "identity_providers: oidc: cors: option 'allowed_origins' contains the wildcard origin '*' cannot be specified with option 'allowed_origins_from_client_redirect_uris' enabled" - errFmtOIDCCORSInvalidEndpoint = "identity_providers: oidc: cors: option 'endpoints' contains an invalid value '%s': must be one of '%s'" + errFmtOIDCCORSInvalidEndpoint = "identity_providers: oidc: cors: option 'endpoints' contains an invalid value '%s': must be one of %s" - errFmtOIDCClientsDuplicateID = "identity_providers: oidc: one or more clients have the same id but all client" + - "id's must be unique" - errFmtOIDCClientsWithEmptyID = "identity_providers: oidc: one or more clients have been configured with " + - "an empty id" + errFmtOIDCClientsDuplicateID = "identity_providers: oidc: clients: option 'id' must be unique for every client but one or more clients share the following 'id' values %s" + errFmtOIDCClientsWithEmptyID = "identity_providers: oidc: clients: option 'id' is required but was absent on the clients in positions %s" + errFmtOIDCClientsDeprecated = "identity_providers: oidc: clients: warnings for clients above indicate deprecated functionality and it's strongly suggested these issues are checked and fixed if they're legitimate issues or reported if they are not as in a future version these warnings will become errors" errFmtOIDCClientInvalidSecret = "identity_providers: oidc: client '%s': option 'secret' is required" errFmtOIDCClientInvalidSecretPlainText = "identity_providers: oidc: client '%s': option 'secret' is plaintext but it should be a hashed value as plaintext values are deprecated and will be removed when oidc becomes stable" @@ -170,36 +169,43 @@ const ( "redirect uri '%s' when option 'public' is false but this is invalid as this uri is not valid " + "for the openid connect confidential client type" errFmtOIDCClientRedirectURIAbsolute = "identity_providers: oidc: client '%s': option 'redirect_uris' has an " + - "invalid value: redirect uri '%s' must have the scheme but it is absent" - errFmtOIDCClientInvalidPolicy = "identity_providers: oidc: client '%s': option 'policy' must be 'one_factor' " + - "or 'two_factor' but it is configured as '%s'" - errFmtOIDCClientInvalidPKCEChallengeMethod = "identity_providers: oidc: client '%s': option 'pkce_challenge_method' must be 'plain' " + - "or 'S256' but it is configured as '%s'" + "invalid value: redirect uri '%s' must have a scheme but it's absent" errFmtOIDCClientInvalidConsentMode = "identity_providers: oidc: client '%s': consent: option 'mode' must be one of " + - "'%s' but it is configured as '%s'" - errFmtOIDCClientInvalidEntry = "identity_providers: oidc: client '%s': option '%s' must only have the values " + - "'%s' but one option is configured as '%s'" - errFmtOIDCClientInvalidUserinfoAlgorithm = "identity_providers: oidc: client '%s': option " + - "'userinfo_signing_algorithm' must be one of '%s' but it is configured as '%s'" + "%s but it's configured as '%s'" + errFmtOIDCClientInvalidEntries = "identity_providers: oidc: client '%s': option '%s' must only have the values " + + "%s but the values %s are present" + errFmtOIDCClientInvalidEntryDuplicates = "identity_providers: oidc: client '%s': option '%s' must have unique values but the values %s are duplicated" + errFmtOIDCClientInvalidValue = "identity_providers: oidc: client '%s': option " + + "'%s' must be one of %s but it's configured as '%s'" + errFmtOIDCClientInvalidTokenEndpointAuthMethod = "identity_providers: oidc: client '%s': option " + + "'token_endpoint_auth_method' must be one of %s when configured as the confidential client type unless it only includes implicit flow response types such as %s but it's configured as '%s'" + errFmtOIDCClientInvalidTokenEndpointAuthMethodPublic = "identity_providers: oidc: client '%s': option " + + "'token_endpoint_auth_method' must be 'none' when configured as the public client type but it's configured as '%s'" errFmtOIDCClientInvalidSectorIdentifier = "identity_providers: oidc: client '%s': option " + "'sector_identifier' with value '%s': must be a URL with only the host component for example '%s' but it has a %s with the value '%s'" errFmtOIDCClientInvalidSectorIdentifierWithoutValue = "identity_providers: oidc: client '%s': option " + "'sector_identifier' with value '%s': must be a URL with only the host component for example '%s' but it has a %s" errFmtOIDCClientInvalidSectorIdentifierHost = "identity_providers: oidc: client '%s': option " + "'sector_identifier' with value '%s': must be a URL with only the host component but appears to be invalid" + errFmtOIDCClientInvalidGrantTypeMatch = "identity_providers: oidc: client '%s': option " + + "'grant_types' should only have grant type values which are valid with the configured 'response_types' for the client but '%s' expects a response type %s such as %s but the response types are %s" + errFmtOIDCClientInvalidGrantTypeRefresh = "identity_providers: oidc: client '%s': option " + + "'grant_types' should only have the 'refresh_token' value if the client is also configured with the 'offline_access' scope" + errFmtOIDCClientInvalidRefreshTokenOptionWithoutCodeResponseType = "identity_providers: oidc: client '%s': option " + + "'%s' should only have the values %s if the client is also configured with a 'response_type' such as %s which respond with authorization codes" errFmtOIDCServerInsecureParameterEntropy = "openid connect provider: SECURITY ISSUE - minimum parameter entropy is " + "configured to an unsafe value, it should be above 8 but it's configured to %d" ) // WebAuthn Error constants. const ( - errFmtWebAuthnConveyancePreference = "webauthn: option 'attestation_conveyance_preference' must be one of '%s' but it is configured as '%s'" - errFmtWebAuthnUserVerification = "webauthn: option 'user_verification' must be one of 'discouraged', 'preferred', 'required' but it is configured as '%s'" + errFmtWebAuthnConveyancePreference = "webauthn: option 'attestation_conveyance_preference' must be one of %s but it's configured as '%s'" + errFmtWebAuthnUserVerification = "webauthn: option 'user_verification' must be one of %s but it's configured as '%s'" ) // Access Control error constants. const ( - errFmtAccessControlDefaultPolicyValue = "access control: option 'default_policy' must be one of '%s' but it is " + + errFmtAccessControlDefaultPolicyValue = "access control: option 'default_policy' must be one of %s but it's " + "configured as '%s'" errFmtAccessControlDefaultPolicyWithoutRules = "access control: 'default_policy' option '%s' is invalid: when " + "no rules are specified it must be 'two_factor' or 'one_factor'" @@ -207,10 +213,9 @@ const ( "network '%s' is not a valid IP or CIDR notation" errFmtAccessControlWarnNoRulesDefaultPolicy = "access control: no rules have been specified so the " + "'default_policy' of '%s' is going to be applied to all requests" - errFmtAccessControlRuleNoDomains = "access control: rule %s: rule is invalid: must have the option " + - "'domain' or 'domain_regex' configured" - errFmtAccessControlRuleInvalidPolicy = "access control: rule %s: rule 'policy' option '%s' " + - "is invalid: must be one of 'deny', 'two_factor', 'one_factor' or 'bypass'" + errFmtAccessControlRuleNoDomains = "access control: rule %s: option 'domain' or 'domain_regex' must be present but are both absent" + errFmtAccessControlRuleNoPolicy = "access control: rule %s: option 'policy' must be present but it's absent" + errFmtAccessControlRuleInvalidPolicy = "access control: rule %s: option 'policy' must be one of %s but it's configured as '%s'" errAccessControlRuleBypassPolicyInvalidWithSubjects = "access control: rule %s: 'policy' option 'bypass' is " + "not supported when 'subject' option is configured: see " + "https://www.authelia.com/c/acl#bypass" @@ -221,39 +226,35 @@ const ( "valid Group Name, IP, or CIDR notation" errFmtAccessControlRuleSubjectInvalid = "access control: rule %s: 'subject' option '%s' is " + "invalid: must start with 'user:' or 'group:'" - errFmtAccessControlRuleMethodInvalid = "access control: rule %s: 'methods' option '%s' is " + - "invalid: must be one of '%s'" - errFmtAccessControlRuleQueryInvalid = "access control: rule %s: 'query' option 'operator' with value '%s' is " + - "invalid: must be one of '%s'" - errFmtAccessControlRuleQueryInvalidNoValue = "access control: rule %s: 'query' option '%s' is " + - "invalid: must have a value" - errFmtAccessControlRuleQueryInvalidNoValueOperator = "access control: rule %s: 'query' option '%s' is " + - "invalid: must have a value when the operator is '%s'" - errFmtAccessControlRuleQueryInvalidValue = "access control: rule %s: 'query' option '%s' is " + - "invalid: must not have a value when the operator is '%s'" - errFmtAccessControlRuleQueryInvalidValueParse = "access control: rule %s: 'query' option '%s' is " + + errFmtAccessControlRuleInvalidEntries = "access control: rule %s: option '%s' must only have the values %s but the values %s are present" + errFmtAccessControlRuleInvalidDuplicates = "access control: rule %s: option '%s' must have unique values but the values %s are duplicated" + errFmtAccessControlRuleQueryInvalid = "access control: rule %s: query: option 'operator' must be one of %s but it's configured as '%s'" + errFmtAccessControlRuleQueryInvalidNoValue = "access control: rule %s: query: option '%s' is required but it's absent" + errFmtAccessControlRuleQueryInvalidNoValueOperator = "access control: rule %s: query: option '%s' must be present when the option 'operator' is '%s' but it's absent" + errFmtAccessControlRuleQueryInvalidValue = "access control: rule %s: query: option '%s' must not be present when the option 'operator' is '%s' but it's present" + errFmtAccessControlRuleQueryInvalidValueParse = "access control: rule %s: query: option '%s' is " + "invalid: %w" - errFmtAccessControlRuleQueryInvalidValueType = "access control: rule %s: 'query' option 'value' is " + + errFmtAccessControlRuleQueryInvalidValueType = "access control: rule %s: query: option 'value' is " + "invalid: expected type was string but got %T" ) // Theme Error constants. const ( - errFmtThemeName = "option 'theme' must be one of '%s' but it is configured as '%s'" + errFmtThemeName = "option 'theme' must be one of %s but it's configured as '%s'" ) // NTP Error constants. const ( - errFmtNTPVersion = "ntp: option 'version' must be either 3 or 4 but it is configured as '%d'" + errFmtNTPVersion = "ntp: option 'version' must be either 3 or 4 but it's configured as '%d'" ) // Session error constants. const ( errFmtSessionOptionRequired = "session: option '%s' is required" errFmtSessionLegacyAndWarning = "session: option 'domain' and option 'cookies' can't be specified at the same time" - errFmtSessionSameSite = "session: option 'same_site' must be one of '%s' but is configured as '%s'" + errFmtSessionSameSite = "session: option 'same_site' must be one of %s but it's configured as '%s'" errFmtSessionSecretRequired = "session: option 'secret' is required when using the '%s' provider" - errFmtSessionRedisPortRange = "session: redis: option 'port' must be between 1 and 65535 but is configured as '%d'" + errFmtSessionRedisPortRange = "session: redis: option 'port' must be between 1 and 65535 but it's configured as '%d'" errFmtSessionRedisHostRequired = "session: redis: option 'host' is required" errFmtSessionRedisHostOrNodesRequired = "session: redis: option 'host' or the 'high_availability' option 'nodes' is required" errFmtSessionRedisTLSConfigInvalid = "session: redis: tls: %w" @@ -261,8 +262,8 @@ const ( errFmtSessionRedisSentinelMissingName = "session: redis: high_availability: option 'sentinel_name' is required" errFmtSessionRedisSentinelNodeHostMissing = "session: redis: high_availability: option 'nodes': option 'host' is required for each node but one or more nodes are missing this" - errFmtSessionDomainMustBeRoot = "session: domain config %s: option 'domain' must be the domain you wish to protect not a wildcard domain but it is configured as '%s'" - errFmtSessionDomainSameSite = "session: domain config %s: option 'same_site' must be one of '%s' but is configured as '%s'" + errFmtSessionDomainMustBeRoot = "session: domain config %s: option 'domain' must be the domain you wish to protect not a wildcard domain but it's configured as '%s'" + errFmtSessionDomainSameSite = "session: domain config %s: option 'same_site' must be one of %s but it's configured as '%s'" errFmtSessionDomainRequired = "session: domain config %s: option 'domain' is required" errFmtSessionDomainHasPeriodPrefix = "session: domain config %s: option 'domain' has a prefix of '.' which is not supported or intended behaviour: you can use this at your own risk but we recommend removing it" errFmtSessionDomainDuplicate = "session: domain config %s: option 'domain' is a duplicate value for another configured session domain" @@ -291,8 +292,8 @@ const ( errFmtServerPathNoForwardSlashes = "server: option 'path' must not contain any forward slashes" errFmtServerPathAlphaNum = "server: option 'path' must only contain alpha numeric characters" - errFmtServerEndpointsAuthzImplementation = "server: endpoints: authz: %s: option 'implementation' must be one of '%s' but is configured as '%s'" - errFmtServerEndpointsAuthzStrategy = "server: endpoints: authz: %s: authn_strategies: option 'name' must be one of '%s' but is configured as '%s'" + errFmtServerEndpointsAuthzImplementation = "server: endpoints: authz: %s: option 'implementation' must be one of %s but it's configured as '%s'" + errFmtServerEndpointsAuthzStrategy = "server: endpoints: authz: %s: authn_strategies: option 'name' must be one of %s but it's configured as '%s'" errFmtServerEndpointsAuthzStrategyDuplicate = "server: endpoints: authz: %s: authn_strategies: duplicate strategy name detected with name '%s'" errFmtServerEndpointsAuthzPrefixDuplicate = "server: endpoints: authz: %s: endpoint starts with the same prefix as the '%s' endpoint with the '%s' implementation which accepts prefixes as part of its implementation" errFmtServerEndpointsAuthzInvalidName = "server: endpoints: authz: %s: contains invalid characters" @@ -302,7 +303,7 @@ const ( const ( errPasswordPolicyMultipleDefined = "password_policy: only a single password policy mechanism can be specified" - errFmtPasswordPolicyStandardMinLengthNotGreaterThanZero = "password_policy: standard: option 'min_length' must be greater than 0 but is configured as %d" + errFmtPasswordPolicyStandardMinLengthNotGreaterThanZero = "password_policy: standard: option 'min_length' must be greater than 0 but it's configured as %d" errFmtPasswordPolicyZXCVBNMinScoreInvalid = "password_policy: zxcvbn: option 'min_score' is invalid: must be between 1 and 4 but it's configured as %d" ) @@ -312,19 +313,17 @@ const ( ) const ( - errFmtDuoMissingOption = "duo_api: option '%s' is required when duo is enabled but it is missing" + errFmtDuoMissingOption = "duo_api: option '%s' is required when duo is enabled but it's absent" ) // Error constants. const ( - errFmtInvalidDefault2FAMethod = "option 'default_2fa_method' is configured as '%s' but must be one of " + - "the following values: '%s'" - errFmtInvalidDefault2FAMethodDisabled = "option 'default_2fa_method' is configured as '%s' " + - "but must be one of the following enabled method values: '%s'" + errFmtInvalidDefault2FAMethod = "option 'default_2fa_method' must be one of %s but it's configured as '%s'" + errFmtInvalidDefault2FAMethodDisabled = "option 'default_2fa_method' must be one of the enabled options %s but it's configured as '%s'" errFmtReplacedConfigurationKey = "invalid configuration key '%s' was replaced by '%s'" - errFmtLoggingLevelInvalid = "log: option 'level' must be one of '%s' but it is configured as '%s'" + errFmtLoggingLevelInvalid = "log: option 'level' must be one of %s but it's configured as '%s'" errFileHashing = "config key incorrect: authentication_backend.file.hashing should be authentication_backend.file.password" errFilePHashing = "config key incorrect: authentication_backend.file.password_hashing should be authentication_backend.file.password" @@ -357,6 +356,10 @@ const ( authzImplementationExtAuthz = "ExtAuthz" ) +const ( + auto = "auto" +) + var ( validAuthzImplementations = []string{"AuthRequest", "ForwardAuth", authzImplementationExtAuthz, authzImplementationLegacy} validAuthzAuthnStrategies = []string{"CookieSession", "HeaderAuthorization", "HeaderProxyAuthorization", "HeaderAuthRequestProxyAuthorization", "HeaderLegacy"} @@ -372,7 +375,7 @@ var ( var ( validStoragePostgreSQLSSLModes = []string{"disable", "require", "verify-ca", "verify-full"} - validThemeNames = []string{"light", "dark", "grey", "auto"} + validThemeNames = []string{"light", "dark", "grey", auto} validSessionSameSiteValues = []string{"none", "lax", "strict"} validLogLevels = []string{"trace", "debug", "info", "warn", "error"} validWebAuthnConveyancePreferences = []string{string(protocol.PreferNoAttestation), string(protocol.PreferIndirectAttestation), string(protocol.PreferDirectAttestation)} @@ -389,13 +392,32 @@ var ( var validDefault2FAMethods = []string{"totp", "webauthn", "mobile_push"} +const ( + attrOIDCScopes = "scopes" + attrOIDCResponseTypes = "response_types" + attrOIDCResponseModes = "response_modes" + attrOIDCGrantTypes = "grant_types" + attrOIDCRedirectURIs = "redirect_uris" + attrOIDCTokenAuthMethod = "token_endpoint_auth_method" + attrOIDCUsrSigAlg = "userinfo_signing_algorithm" + attrOIDCPKCEChallengeMethod = "pkce_challenge_method" +) + var ( - validOIDCScopes = []string{oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeProfile, oidc.ScopeGroups, oidc.ScopeOfflineAccess} - validOIDCGrantTypes = []string{oidc.GrantTypeImplicit, oidc.GrantTypeRefreshToken, oidc.GrantTypeAuthorizationCode, oidc.GrantTypePassword, oidc.GrantTypeClientCredentials} - validOIDCResponseModes = []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment} - validOIDCUserinfoAlgorithms = []string{oidc.SigningAlgorithmNone, oidc.SigningAlgorithmRSAWithSHA256} - validOIDCCORSEndpoints = []string{oidc.EndpointAuthorization, oidc.EndpointPushedAuthorizationRequest, oidc.EndpointToken, oidc.EndpointIntrospection, oidc.EndpointRevocation, oidc.EndpointUserinfo} - validOIDCClientConsentModes = []string{"auto", oidc.ClientConsentModeImplicit.String(), oidc.ClientConsentModeExplicit.String(), oidc.ClientConsentModePreConfigured.String()} + validOIDCCORSEndpoints = []string{oidc.EndpointAuthorization, oidc.EndpointPushedAuthorizationRequest, oidc.EndpointToken, oidc.EndpointIntrospection, oidc.EndpointRevocation, oidc.EndpointUserinfo} + + validOIDCClientScopes = []string{oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeProfile, oidc.ScopeGroups, oidc.ScopeOfflineAccess} + validOIDCClientUserinfoAlgorithms = []string{oidc.SigningAlgorithmNone, oidc.SigningAlgorithmRSAWithSHA256} + validOIDCClientConsentModes = []string{auto, oidc.ClientConsentModeImplicit.String(), oidc.ClientConsentModeExplicit.String(), oidc.ClientConsentModePreConfigured.String()} + validOIDCClientResponseModes = []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment} + validOIDCClientResponseTypes = []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowIDToken, oidc.ResponseTypeImplicitFlowToken, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeHybridFlowIDToken, oidc.ResponseTypeHybridFlowToken, oidc.ResponseTypeHybridFlowBoth} + validOIDCClientResponseTypesImplicitFlow = []string{oidc.ResponseTypeImplicitFlowIDToken, oidc.ResponseTypeImplicitFlowToken, oidc.ResponseTypeImplicitFlowBoth} + validOIDCClientResponseTypesHybridFlow = []string{oidc.ResponseTypeHybridFlowIDToken, oidc.ResponseTypeHybridFlowToken, oidc.ResponseTypeHybridFlowBoth} + validOIDCClientResponseTypesRefreshToken = []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeHybridFlowIDToken, oidc.ResponseTypeHybridFlowToken, oidc.ResponseTypeHybridFlowBoth} + validOIDCClientGrantTypes = []string{oidc.GrantTypeImplicit, oidc.GrantTypeRefreshToken, oidc.GrantTypeAuthorizationCode} + + validOIDCClientTokenEndpointAuthMethods = []string{oidc.ClientAuthMethodNone, oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic} + validOIDCClientTokenEndpointAuthMethodsConfidential = []string{oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic} ) var ( diff --git a/internal/configuration/validator/duo_test.go b/internal/configuration/validator/duo_test.go index ef4856b56..21cdbf6b1 100644 --- a/internal/configuration/validator/duo_test.go +++ b/internal/configuration/validator/duo_test.go @@ -22,6 +22,11 @@ func TestValidateDuo(t *testing.T) { have: &schema.Configuration{}, expected: schema.DuoAPIConfiguration{Disable: true}, }, + { + desc: "ShouldDisableDuoConfigured", + have: &schema.Configuration{DuoAPI: schema.DuoAPIConfiguration{Disable: true, Hostname: "example.com"}}, + expected: schema.DuoAPIConfiguration{Disable: true, Hostname: "example.com"}, + }, { desc: "ShouldNotDisableDuo", have: &schema.Configuration{DuoAPI: schema.DuoAPIConfiguration{ @@ -46,7 +51,7 @@ func TestValidateDuo(t *testing.T) { IntegrationKey: "test", }, errs: []string{ - "duo_api: option 'secret_key' is required when duo is enabled but it is missing", + "duo_api: option 'secret_key' is required when duo is enabled but it's absent", }, }, { @@ -60,7 +65,7 @@ func TestValidateDuo(t *testing.T) { SecretKey: "test", }, errs: []string{ - "duo_api: option 'integration_key' is required when duo is enabled but it is missing", + "duo_api: option 'integration_key' is required when duo is enabled but it's absent", }, }, { @@ -74,7 +79,7 @@ func TestValidateDuo(t *testing.T) { SecretKey: "test", }, errs: []string{ - "duo_api: option 'hostname' is required when duo is enabled but it is missing", + "duo_api: option 'hostname' is required when duo is enabled but it's absent", }, }, } diff --git a/internal/configuration/validator/identity_providers.go b/internal/configuration/validator/identity_providers.go index b9ea7e9b9..cb61e31db 100644 --- a/internal/configuration/validator/identity_providers.go +++ b/internal/configuration/validator/identity_providers.go @@ -3,6 +3,7 @@ package validator import ( "fmt" "net/url" + "strconv" "strings" "time" @@ -125,10 +126,10 @@ func validateOIDCOptionsCORSAllowedOriginsFromClientRedirectURIs(config *schema. continue } - origin := utils.OriginFromURL(*uri) + origin := utils.OriginFromURL(uri) - if !utils.IsURLInSlice(origin, config.CORS.AllowedOrigins) { - config.CORS.AllowedOrigins = append(config.CORS.AllowedOrigins, origin) + if !utils.IsURLInSlice(*origin, config.CORS.AllowedOrigins) { + config.CORS.AllowedOrigins = append(config.CORS.AllowedOrigins, *origin) } } } @@ -137,113 +138,135 @@ func validateOIDCOptionsCORSAllowedOriginsFromClientRedirectURIs(config *schema. func validateOIDCOptionsCORSEndpoints(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) { for _, endpoint := range config.CORS.Endpoints { if !utils.IsStringInSlice(endpoint, validOIDCCORSEndpoints) { - val.Push(fmt.Errorf(errFmtOIDCCORSInvalidEndpoint, endpoint, strings.Join(validOIDCCORSEndpoints, "', '"))) + val.Push(fmt.Errorf(errFmtOIDCCORSInvalidEndpoint, endpoint, strJoinOr(validOIDCCORSEndpoints))) } } } func validateOIDCClients(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) { - invalidID, duplicateIDs := false, false + var ( + errDeprecated bool - var ids []string + clientIDs, duplicateClientIDs, blankClientIDs []string + ) + + errDeprecatedFunc := func() { errDeprecated = true } for c, client := range config.Clients { if client.ID == "" { - invalidID = true + blankClientIDs = append(blankClientIDs, "#"+strconv.Itoa(c+1)) } else { if client.Description == "" { config.Clients[c].Description = client.ID } - if utils.IsStringInSliceFold(client.ID, ids) { - duplicateIDs = true - } - ids = append(ids, client.ID) - } - - if client.Public { - if client.Secret != nil { - val.Push(fmt.Errorf(errFmtOIDCClientPublicInvalidSecret, client.ID)) - } - } else { - if client.Secret == nil { - val.Push(fmt.Errorf(errFmtOIDCClientInvalidSecret, client.ID)) - } else if client.Secret.IsPlainText() { - val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidSecretPlainText, client.ID)) + if id := strings.ToLower(client.ID); utils.IsStringInSlice(id, clientIDs) { + if !utils.IsStringInSlice(id, duplicateClientIDs) { + duplicateClientIDs = append(duplicateClientIDs, id) + } + } else { + clientIDs = append(clientIDs, id) } } - if client.Policy == "" { - config.Clients[c].Policy = schema.DefaultOpenIDConnectClientConfiguration.Policy - } else if client.Policy != policyOneFactor && client.Policy != policyTwoFactor { - val.Push(fmt.Errorf(errFmtOIDCClientInvalidPolicy, client.ID, client.Policy)) - } - - 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) + validateOIDCClient(c, config, val, errDeprecatedFunc) } - if invalidID { - val.Push(fmt.Errorf(errFmtOIDCClientsWithEmptyID)) + if errDeprecated { + val.PushWarning(fmt.Errorf(errFmtOIDCClientsDeprecated)) } - if duplicateIDs { - val.Push(fmt.Errorf(errFmtOIDCClientsDuplicateID)) + if len(blankClientIDs) != 0 { + 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) { - if client.SectorIdentifier.String() != "" { - if utils.IsURLHostComponent(client.SectorIdentifier) || utils.IsURLHostComponentWithPort(client.SectorIdentifier) { +func validateOIDCClient(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator, errDeprecatedFunc func()) { + if config.Clients[c].Public { + if config.Clients[c].Secret != nil { + val.Push(fmt.Errorf(errFmtOIDCClientPublicInvalidSecret, config.Clients[c].ID)) + } + } else { + if config.Clients[c].Secret == nil { + val.Push(fmt.Errorf(errFmtOIDCClientInvalidSecret, config.Clients[c].ID)) + } else if config.Clients[c].Secret.IsPlainText() { + val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidSecretPlainText, config.Clients[c].ID)) + } + } + + 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 } - if client.SectorIdentifier.Scheme != "" { - val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "scheme", client.SectorIdentifier.Scheme)) + if config.Clients[c].SectorIdentifier.Scheme != "" { + val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, config.Clients[c].ID, config.Clients[c].SectorIdentifier.String(), config.Clients[c].SectorIdentifier.Host, "scheme", config.Clients[c].SectorIdentifier.Scheme)) - if client.SectorIdentifier.Path != "" { - val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "path", client.SectorIdentifier.Path)) + if config.Clients[c].SectorIdentifier.Path != "" { + val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, config.Clients[c].ID, config.Clients[c].SectorIdentifier.String(), config.Clients[c].SectorIdentifier.Host, "path", config.Clients[c].SectorIdentifier.Path)) } - if client.SectorIdentifier.RawQuery != "" { - val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "query", client.SectorIdentifier.RawQuery)) + if config.Clients[c].SectorIdentifier.RawQuery != "" { + val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, config.Clients[c].ID, config.Clients[c].SectorIdentifier.String(), config.Clients[c].SectorIdentifier.Host, "query", config.Clients[c].SectorIdentifier.RawQuery)) } - if client.SectorIdentifier.Fragment != "" { - val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "fragment", client.SectorIdentifier.Fragment)) + if config.Clients[c].SectorIdentifier.Fragment != "" { + val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, config.Clients[c].ID, config.Clients[c].SectorIdentifier.String(), config.Clients[c].SectorIdentifier.Host, "fragment", config.Clients[c].SectorIdentifier.Fragment)) } - if client.SectorIdentifier.User != nil { - if client.SectorIdentifier.User.Username() != "" { - val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "username", client.SectorIdentifier.User.Username())) + if config.Clients[c].SectorIdentifier.User != nil { + if config.Clients[c].SectorIdentifier.User.Username() != "" { + val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, config.Clients[c].ID, config.Clients[c].SectorIdentifier.String(), config.Clients[c].SectorIdentifier.Host, "username", config.Clients[c].SectorIdentifier.User.Username())) } - if _, set := client.SectorIdentifier.User.Password(); set { - val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifierWithoutValue, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "password")) + if _, set := config.Clients[c].SectorIdentifier.User.Password(); set { + val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifierWithoutValue, config.Clients[c].ID, config.Clients[c].SectorIdentifier.String(), config.Clients[c].SectorIdentifier.Host, "password")) } } - } else if client.SectorIdentifier.Host == "" { - val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifierHost, client.ID, client.SectorIdentifier.String())) + } else if config.Clients[c].SectorIdentifier.Host == "" { + val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifierHost, config.Clients[c].ID, config.Clients[c].SectorIdentifier.String())) } } } func validateOIDCClientConsentMode(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) { switch { - case utils.IsStringInSlice(config.Clients[c].ConsentMode, []string{"", "auto"}): + case utils.IsStringInSlice(config.Clients[c].ConsentMode, []string{"", auto}): if config.Clients[c].ConsentPreConfiguredDuration != nil { config.Clients[c].ConsentMode = oidc.ClientConsentModePreConfigured.String() } else { @@ -252,7 +275,7 @@ func validateOIDCClientConsentMode(c int, config *schema.OpenIDConnectConfigurat case utils.IsStringInSlice(config.Clients[c].ConsentMode, validOIDCClientConsentModes): break default: - val.Push(fmt.Errorf(errFmtOIDCClientInvalidConsentMode, config.Clients[c].ID, strings.Join(append(validOIDCClientConsentModes, "auto"), "', '"), config.Clients[c].ConsentMode)) + val.Push(fmt.Errorf(errFmtOIDCClientInvalidConsentMode, config.Clients[c].ID, strJoinOr(append(validOIDCClientConsentModes, auto)), config.Clients[c].ConsentMode)) } if config.Clients[c].ConsentMode == oidc.ClientConsentModePreConfigured.String() && config.Clients[c].ConsentPreConfiguredDuration == nil { @@ -260,92 +283,233 @@ func validateOIDCClientConsentMode(c int, config *schema.OpenIDConnectConfigurat } } -func validateOIDCClientScopes(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) { +func validateOIDCClientScopes(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator, errDeprecatedFunc func()) { if len(config.Clients[c].Scopes) == 0 { config.Clients[c].Scopes = schema.DefaultOpenIDConnectClientConfiguration.Scopes - return } if !utils.IsStringInSlice(oidc.ScopeOpenID, config.Clients[c].Scopes) { - config.Clients[c].Scopes = append(config.Clients[c].Scopes, oidc.ScopeOpenID) + config.Clients[c].Scopes = append([]string{oidc.ScopeOpenID}, config.Clients[c].Scopes...) } - for _, scope := range config.Clients[c].Scopes { - if !utils.IsStringInSlice(scope, validOIDCScopes) { - val.Push(fmt.Errorf( - errFmtOIDCClientInvalidEntry, - config.Clients[c].ID, "scopes", strings.Join(validOIDCScopes, "', '"), scope)) - } + invalid, duplicates := validateList(config.Clients[c].Scopes, validOIDCClientScopes, true) + + if len(invalid) != 0 { + val.Push(fmt.Errorf(errFmtOIDCClientInvalidEntries, config.Clients[c].ID, attrOIDCScopes, strJoinOr(validOIDCClientScopes), strJoinAnd(invalid))) + } + + 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) { - 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) { +func validateOIDCClientResponseTypes(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator, errDeprecatedFunc func()) { if len(config.Clients[c].ResponseTypes) == 0 { config.Clients[c].ResponseTypes = schema.DefaultOpenIDConnectClientConfiguration.ResponseTypes - return + } + + invalid, duplicates := validateList(config.Clients[c].ResponseTypes, validOIDCClientResponseTypes, true) + + if len(invalid) != 0 { + val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidEntries, config.Clients[c].ID, attrOIDCResponseTypes, strJoinOr(validOIDCClientResponseTypes), strJoinAnd(invalid))) + } + + if len(duplicates) != 0 { + errDeprecatedFunc() + + val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidEntryDuplicates, config.Clients[c].ID, attrOIDCResponseTypes, strJoinAnd(duplicates))) } } -func validateOIDCClientResponseModes(c int, config *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) { +func validateOIDCClientResponseModes(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator, errDeprecatedFunc func()) { if len(config.Clients[c].ResponseModes) == 0 { config.Clients[c].ResponseModes = schema.DefaultOpenIDConnectClientConfiguration.ResponseModes - return + + for _, responseType := range config.Clients[c].ResponseTypes { + switch responseType { + case oidc.ResponseTypeAuthorizationCodeFlow: + if !utils.IsStringInSlice(oidc.ResponseModeQuery, config.Clients[c].ResponseModes) { + config.Clients[c].ResponseModes = append(config.Clients[c].ResponseModes, oidc.ResponseModeQuery) + } + case oidc.ResponseTypeImplicitFlowIDToken, oidc.ResponseTypeImplicitFlowToken, oidc.ResponseTypeImplicitFlowBoth, + oidc.ResponseTypeHybridFlowIDToken, oidc.ResponseTypeHybridFlowToken, oidc.ResponseTypeHybridFlowBoth: + if !utils.IsStringInSlice(oidc.ResponseModeFragment, config.Clients[c].ResponseModes) { + config.Clients[c].ResponseModes = append(config.Clients[c].ResponseModes, oidc.ResponseModeFragment) + } + } + } } - for _, responseMode := range config.Clients[c].ResponseModes { - if !utils.IsStringInSlice(responseMode, validOIDCResponseModes) { - validator.Push(fmt.Errorf( - errFmtOIDCClientInvalidEntry, - config.Clients[c].ID, "response_modes", strings.Join(validOIDCResponseModes, "', '"), responseMode)) + invalid, duplicates := validateList(config.Clients[c].ResponseModes, validOIDCClientResponseModes, true) + + if len(invalid) != 0 { + val.Push(fmt.Errorf(errFmtOIDCClientInvalidEntries, config.Clients[c].ID, attrOIDCResponseModes, strJoinOr(validOIDCClientResponseModes), strJoinAnd(invalid))) + } + + if len(duplicates) != 0 { + errDeprecatedFunc() + + val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidEntryDuplicates, config.Clients[c].ID, attrOIDCResponseModes, strJoinAnd(duplicates))) + } +} + +func validateOIDCClientGrantTypes(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator, errDeprecatedFunc func()) { + if len(config.Clients[c].GrantTypes) == 0 { + validateOIDCClientGrantTypesSetDefaults(c, config) + } + + validateOIDCClientGrantTypesCheckRelated(c, config, val, errDeprecatedFunc) + + invalid, duplicates := validateList(config.Clients[c].GrantTypes, validOIDCClientGrantTypes, true) + + if len(invalid) != 0 { + val.Push(fmt.Errorf(errFmtOIDCClientInvalidEntries, config.Clients[c].ID, attrOIDCGrantTypes, strJoinOr(validOIDCClientGrantTypes), strJoinAnd(invalid))) + } + + 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) { if config.Clients[c].UserinfoSigningAlgorithm == "" { config.Clients[c].UserinfoSigningAlgorithm = schema.DefaultOpenIDConnectClientConfiguration.UserinfoSigningAlgorithm - } else if !utils.IsStringInSlice(config.Clients[c].UserinfoSigningAlgorithm, validOIDCUserinfoAlgorithms) { - val.Push(fmt.Errorf(errFmtOIDCClientInvalidUserinfoAlgorithm, - config.Clients[c].ID, strings.Join(validOIDCUserinfoAlgorithms, ", "), config.Clients[c].UserinfoSigningAlgorithm)) - } -} - -func 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 - } + } + + if !utils.IsStringInSlice(config.Clients[c].UserinfoSigningAlgorithm, validOIDCClientUserinfoAlgorithms) { + val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue, + config.Clients[c].ID, attrOIDCUsrSigAlg, strJoinOr(validOIDCClientUserinfoAlgorithms), config.Clients[c].UserinfoSigningAlgorithm)) } } diff --git a/internal/configuration/validator/identity_providers_test.go b/internal/configuration/validator/identity_providers_test.go index f61b05bdd..bdb11f3d0 100644 --- a/internal/configuration/validator/identity_providers_test.go +++ b/internal/configuration/validator/identity_providers_test.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "net/url" - "strings" "testing" "time" @@ -31,8 +30,8 @@ func TestShouldRaiseErrorWhenInvalidOIDCServerConfiguration(t *testing.T) { require.Len(t, validator.Errors(), 2) - assert.EqualError(t, validator.Errors()[0], errFmtOIDCNoPrivateKey) - assert.EqualError(t, validator.Errors()[1], errFmtOIDCNoClientsConfigured) + assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: option 'issuer_private_key' is required") + assert.EqualError(t, validator.Errors()[1], "identity_providers: oidc: option 'clients' must have one or more clients configured") } func TestShouldNotRaiseErrorWhenCORSEndpointsValid(t *testing.T) { @@ -80,7 +79,7 @@ func TestShouldRaiseErrorWhenCORSEndpointsNotValid(t *testing.T) { require.Len(t, validator.Errors(), 1) - assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: cors: option 'endpoints' contains an invalid value 'invalid_endpoint': must be one of 'authorization', 'pushed-authorization-request', 'token', 'introspection', 'revocation', 'userinfo'") + assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: cors: option 'endpoints' contains an invalid value 'invalid_endpoint': must be one of 'authorization', 'pushed-authorization-request', 'token', 'introspection', 'revocation', or 'userinfo'") } func TestShouldRaiseErrorWhenOIDCPKCEEnforceValueInvalid(t *testing.T) { @@ -97,8 +96,8 @@ func TestShouldRaiseErrorWhenOIDCPKCEEnforceValueInvalid(t *testing.T) { require.Len(t, validator.Errors(), 2) - assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: option 'enforce_pkce' must be 'never', 'public_clients_only' or 'always', but it is configured as 'invalid'") - assert.EqualError(t, validator.Errors()[1], errFmtOIDCNoClientsConfigured) + assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: option 'enforce_pkce' must be 'never', 'public_clients_only' or 'always', but it's configured as 'invalid'") + assert.EqualError(t, validator.Errors()[1], "identity_providers: oidc: option 'clients' must have one or more clients configured") } func TestShouldRaiseErrorWhenOIDCCORSOriginsHasInvalidValues(t *testing.T) { @@ -150,7 +149,7 @@ func TestShouldRaiseErrorWhenOIDCServerNoClients(t *testing.T) { require.Len(t, validator.Errors(), 1) - assert.EqualError(t, validator.Errors()[0], errFmtOIDCNoClientsConfigured) + assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: option 'clients' must have one or more clients configured") } func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { @@ -180,7 +179,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { }, Errors: []string{ "identity_providers: oidc: client '': option 'secret' is required", - "identity_providers: oidc: one or more clients have been configured with an empty id", + "identity_providers: oidc: clients: option 'id' is required but was absent on the clients in positions #1", }, }, { @@ -195,7 +194,9 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { }, }, }, - Errors: []string{"identity_providers: oidc: client 'client-1': option 'policy' must be 'one_factor' or 'two_factor' but it is configured as 'a-policy'"}, + Errors: []string{ + "identity_providers: oidc: client 'client-1': option 'policy' must be one of 'one_factor' or 'two_factor' but it's configured as 'a-policy'", + }, }, { Name: "ClientIDDuplicated", @@ -213,7 +214,9 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { RedirectURIs: []string{}, }, }, - Errors: []string{errFmtOIDCClientsDuplicateID}, + Errors: []string{ + "identity_providers: oidc: clients: option 'id' must be unique for every client but one or more clients share the following 'id' values 'client-x'", + }, }, { Name: "RedirectURIInvalid", @@ -228,7 +231,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { }, }, Errors: []string{ - fmt.Sprintf(errFmtOIDCClientRedirectURICantBeParsed, "client-check-uri-parse", "http://abc@%two", errors.New("parse \"http://abc@%two\": invalid URL escape \"%tw\"")), + "identity_providers: oidc: client 'client-check-uri-parse': option 'redirect_uris' has an invalid value: redirect uri 'http://abc@%two' could not be parsed: parse \"http://abc@%two\": invalid URL escape \"%tw\"", }, }, { @@ -244,7 +247,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { }, }, Errors: []string{ - fmt.Sprintf(errFmtOIDCClientRedirectURIAbsolute, "client-check-uri-abs", "google.com"), + "identity_providers: oidc: client 'client-check-uri-abs': option 'redirect_uris' has an invalid value: redirect uri 'google.com' must have a scheme but it's absent", }, }, { @@ -289,12 +292,12 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { }, }, Errors: []string{ - fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", exampleDotCom, "scheme", "https"), - fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", exampleDotCom, "path", "/path"), - fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", exampleDotCom, "query", "query=abc"), - fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", exampleDotCom, "fragment", "fragment"), - fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", exampleDotCom, "username", "user"), - fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifierWithoutValue, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", exampleDotCom, "password"), + "identity_providers: oidc: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a scheme with the value 'https'", + "identity_providers: oidc: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a path with the value '/path'", + "identity_providers: oidc: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a query with the value 'query=abc'", + "identity_providers: oidc: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a fragment with the value 'fragment'", + "identity_providers: oidc: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a username with the value 'user'", + "identity_providers: oidc: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a password", }, }, { @@ -311,7 +314,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { }, }, Errors: []string{ - fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifierHost, "client-invalid-sector", "example.com/path?query=abc#fragment"), + "identity_providers: oidc: client 'client-invalid-sector': option 'sector_identifier' with value 'example.com/path?query=abc#fragment': must be a URL with only the host component but appears to be invalid", }, }, { @@ -328,7 +331,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { }, }, Errors: []string{ - fmt.Sprintf(errFmtOIDCClientInvalidConsentMode, "client-bad-consent-mode", strings.Join(append(validOIDCClientConsentModes, "auto"), "', '"), "cap"), + "identity_providers: oidc: client 'client-bad-consent-mode': consent: option 'mode' must be one of 'auto', 'implicit', 'explicit', 'pre-configured', or 'auto' but it's configured as 'cap'", }, }, { @@ -345,7 +348,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { }, }, Errors: []string{ - fmt.Sprintf(errFmtOIDCClientInvalidPKCEChallengeMethod, "client-bad-pkce-mode", "abc"), + "identity_providers: oidc: client 'client-bad-pkce-mode': option 'pkce_challenge_method' must be one of 'plain' or 'S256' but it's configured as 'abc'", }, }, { @@ -362,7 +365,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { }, }, Errors: []string{ - fmt.Sprintf(errFmtOIDCClientInvalidPKCEChallengeMethod, "client-bad-pkce-mode-s256", "s256"), + "identity_providers: oidc: client 'client-bad-pkce-mode-s256': option 'pkce_challenge_method' must be one of 'plain' or 'S256' but it's configured as 's256'", }, }, } @@ -415,7 +418,7 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadScopes(t *testing.T) { ValidateIdentityProviders(config, validator) require.Len(t, validator.Errors(), 1) - assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'scopes' must only have the values 'openid', 'email', 'profile', 'groups', 'offline_access' but one option is configured as 'bad_scope'") + assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'scopes' must only have the values 'openid', 'email', 'profile', 'groups', or 'offline_access' but the values 'bad_scope' are present") } func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadGrantTypes(t *testing.T) { @@ -441,7 +444,7 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadGrantTypes(t *testing.T) ValidateIdentityProviders(config, validator) require.Len(t, validator.Errors(), 1) - assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'grant_types' must only have the values 'implicit', 'refresh_token', 'authorization_code', 'password', 'client_credentials' but one option is configured as 'bad_grant_type'") + assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'grant_types' must only have the values 'implicit', 'refresh_token', or 'authorization_code' but the values 'bad_grant_type' are present") } func TestShouldNotErrorOnCertificateValid(t *testing.T) { @@ -577,7 +580,7 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadResponseModes(t *testing ValidateIdentityProviders(config, validator) require.Len(t, validator.Errors(), 1) - assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'response_modes' must only have the values 'form_post', 'query', 'fragment' but one option is configured as 'bad_responsemode'") + assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'response_modes' must only have the values 'form_post', 'query', or 'fragment' but the values 'bad_responsemode' are present") } func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadUserinfoAlg(t *testing.T) { @@ -603,7 +606,7 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadUserinfoAlg(t *testing.T ValidateIdentityProviders(config, validator) require.Len(t, validator.Errors(), 1) - assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'userinfo_signing_algorithm' must be one of 'none, RS256' but it is configured as 'rs256'") + assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'userinfo_signing_algorithm' must be one of 'none' or 'RS256' but it's configured as 'rs256'") } func TestValidateIdentityProvidersShouldRaiseWarningOnSecurityIssue(t *testing.T) { @@ -668,8 +671,8 @@ func TestValidateIdentityProvidersShouldRaiseErrorsOnInvalidClientTypes(t *testi require.Len(t, validator.Errors(), 2) assert.Len(t, validator.Warnings(), 0) - assert.EqualError(t, validator.Errors()[0], fmt.Sprintf(errFmtOIDCClientPublicInvalidSecret, "client-with-invalid-secret")) - assert.EqualError(t, validator.Errors()[1], fmt.Sprintf(errFmtOIDCClientRedirectURIPublic, "client-with-bad-redirect-uri", oauth2InstalledApp)) + assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'client-with-invalid-secret': option 'secret' is required to be empty when option 'public' is true") + assert.EqualError(t, validator.Errors()[1], "identity_providers: oidc: client 'client-with-bad-redirect-uri': option 'redirect_uris' has the redirect uri 'urn:ietf:wg:oauth:2.0:oob' when option 'public' is false but this is invalid as this uri is not valid for the openid connect confidential client type") } func TestValidateIdentityProvidersShouldNotRaiseErrorsOnValidClientOptions(t *testing.T) { @@ -758,175 +761,28 @@ func TestValidateIdentityProvidersShouldRaiseWarningOnPlainTextClients(t *testin assert.EqualError(t, validator.Warnings()[0], "identity_providers: oidc: client 'client-with-invalid-secret_standard': option 'secret' is plaintext but it should be a hashed value as plaintext values are deprecated and will be removed when oidc becomes stable") } -func TestValidateIdentityProvidersShouldSetDefaultValues(t *testing.T) { - timeDay := time.Hour * 24 - - validator := schema.NewStructValidator() - config := &schema.IdentityProvidersConfiguration{ - OIDC: &schema.OpenIDConnectConfiguration{ - HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN", - IssuerPrivateKey: MustParseRSAPrivateKey(testKey1), - Clients: []schema.OpenIDConnectClientConfiguration{ - { - ID: "a-client", - Secret: MustDecodeSecret(goodOpenIDConnectClientSecret), - RedirectURIs: []string{ - "https://google.com", - }, - ConsentPreConfiguredDuration: &timeDay, - }, - { - ID: "b-client", - Description: "Normal Description", - Secret: MustDecodeSecret(goodOpenIDConnectClientSecret), - Policy: policyOneFactor, - UserinfoSigningAlgorithm: "RS256", - RedirectURIs: []string{ - "https://google.com", - }, - Scopes: []string{ - "groups", - }, - GrantTypes: []string{ - "refresh_token", - }, - ResponseTypes: []string{ - "token", - "code", - }, - ResponseModes: []string{ - "form_post", - "fragment", - }, - }, - { - ID: "c-client", - Secret: MustDecodeSecret(goodOpenIDConnectClientSecret), - RedirectURIs: []string{ - "https://google.com", - }, - ConsentMode: "implicit", - }, - { - ID: "d-client", - Secret: MustDecodeSecret(goodOpenIDConnectClientSecret), - RedirectURIs: []string{ - "https://google.com", - }, - ConsentMode: "explicit", - }, - { - ID: "e-client", - Secret: MustDecodeSecret(goodOpenIDConnectClientSecret), - RedirectURIs: []string{ - "https://google.com", - }, - ConsentMode: "pre-configured", +// All valid schemes are supported as defined in https://datatracker.ietf.org/doc/html/rfc8252#section-7.1 +func TestValidateOIDCClientRedirectURIsSupportingPrivateUseURISchemes(t *testing.T) { + have := &schema.OpenIDConnectConfiguration{ + Clients: []schema.OpenIDConnectClientConfiguration{ + { + ID: "owncloud", + RedirectURIs: []string{ + "https://www.mywebsite.com", + "http://www.mywebsite.com", + "oc://ios.owncloud.com", + // example given in the RFC https://datatracker.ietf.org/doc/html/rfc8252#section-7.1 + "com.example.app:/oauth2redirect/example-provider", + oauth2InstalledApp, }, }, }, } - ValidateIdentityProviders(config, validator) - - assert.Len(t, validator.Warnings(), 0) - assert.Len(t, validator.Errors(), 0) - - // Assert Clients[0] Policy is set to the default, and the default doesn't override Clients[1]'s Policy. - assert.Equal(t, policyTwoFactor, config.OIDC.Clients[0].Policy) - assert.Equal(t, policyOneFactor, config.OIDC.Clients[1].Policy) - - assert.Equal(t, "none", config.OIDC.Clients[0].UserinfoSigningAlgorithm) - assert.Equal(t, "RS256", config.OIDC.Clients[1].UserinfoSigningAlgorithm) - - // Assert Clients[0] DisplayName is set to the Clients[0] ID, and Clients[1]'s DisplayName is not overridden. - assert.Equal(t, config.OIDC.Clients[0].ID, config.OIDC.Clients[0].Description) - assert.Equal(t, "Normal Description", config.OIDC.Clients[1].Description) - - // Assert Clients[0] ends up configured with the default Scopes. - require.Len(t, config.OIDC.Clients[0].Scopes, 4) - assert.Equal(t, "openid", config.OIDC.Clients[0].Scopes[0]) - assert.Equal(t, "groups", config.OIDC.Clients[0].Scopes[1]) - assert.Equal(t, "profile", config.OIDC.Clients[0].Scopes[2]) - assert.Equal(t, "email", config.OIDC.Clients[0].Scopes[3]) - - // Assert Clients[1] ends up configured with the configured Scopes and the openid Scope. - require.Len(t, config.OIDC.Clients[1].Scopes, 2) - assert.Equal(t, "groups", config.OIDC.Clients[1].Scopes[0]) - assert.Equal(t, "openid", config.OIDC.Clients[1].Scopes[1]) - - // Assert Clients[0] ends up configured with the correct consent mode. - require.NotNil(t, config.OIDC.Clients[0].ConsentPreConfiguredDuration) - assert.Equal(t, time.Hour*24, *config.OIDC.Clients[0].ConsentPreConfiguredDuration) - assert.Equal(t, "pre-configured", config.OIDC.Clients[0].ConsentMode) - - // Assert Clients[1] ends up configured with the correct consent mode. - assert.Nil(t, config.OIDC.Clients[1].ConsentPreConfiguredDuration) - assert.Equal(t, "explicit", config.OIDC.Clients[1].ConsentMode) - - // Assert Clients[0] ends up configured with the default GrantTypes. - require.Len(t, config.OIDC.Clients[0].GrantTypes, 2) - assert.Equal(t, "refresh_token", config.OIDC.Clients[0].GrantTypes[0]) - assert.Equal(t, "authorization_code", config.OIDC.Clients[0].GrantTypes[1]) - - // Assert Clients[1] ends up configured with only the configured GrantTypes. - require.Len(t, config.OIDC.Clients[1].GrantTypes, 1) - assert.Equal(t, "refresh_token", config.OIDC.Clients[1].GrantTypes[0]) - - // Assert Clients[0] ends up configured with the default ResponseTypes. - require.Len(t, config.OIDC.Clients[0].ResponseTypes, 1) - assert.Equal(t, "code", config.OIDC.Clients[0].ResponseTypes[0]) - - // Assert Clients[1] ends up configured only with the configured ResponseTypes. - require.Len(t, config.OIDC.Clients[1].ResponseTypes, 2) - assert.Equal(t, "token", config.OIDC.Clients[1].ResponseTypes[0]) - assert.Equal(t, "code", config.OIDC.Clients[1].ResponseTypes[1]) - - // Assert Clients[0] ends up configured with the default ResponseModes. - require.Len(t, config.OIDC.Clients[0].ResponseModes, 3) - assert.Equal(t, "form_post", config.OIDC.Clients[0].ResponseModes[0]) - assert.Equal(t, "query", config.OIDC.Clients[0].ResponseModes[1]) - assert.Equal(t, "fragment", config.OIDC.Clients[0].ResponseModes[2]) - - // Assert Clients[1] ends up configured only with the configured ResponseModes. - require.Len(t, config.OIDC.Clients[1].ResponseModes, 2) - assert.Equal(t, "form_post", config.OIDC.Clients[1].ResponseModes[0]) - assert.Equal(t, "fragment", config.OIDC.Clients[1].ResponseModes[1]) - - assert.Equal(t, false, config.OIDC.EnableClientDebugMessages) - assert.Equal(t, time.Hour, config.OIDC.AccessTokenLifespan) - assert.Equal(t, time.Minute, config.OIDC.AuthorizeCodeLifespan) - assert.Equal(t, time.Hour, config.OIDC.IDTokenLifespan) - assert.Equal(t, time.Minute*90, config.OIDC.RefreshTokenLifespan) - - assert.Equal(t, "implicit", config.OIDC.Clients[2].ConsentMode) - assert.Nil(t, config.OIDC.Clients[2].ConsentPreConfiguredDuration) - - assert.Equal(t, "explicit", config.OIDC.Clients[3].ConsentMode) - assert.Nil(t, config.OIDC.Clients[3].ConsentPreConfiguredDuration) - - assert.Equal(t, "pre-configured", config.OIDC.Clients[4].ConsentMode) - assert.Equal(t, schema.DefaultOpenIDConnectClientConfiguration.ConsentPreConfiguredDuration, config.OIDC.Clients[4].ConsentPreConfiguredDuration) -} - -// All valid schemes are supported as defined in https://datatracker.ietf.org/doc/html/rfc8252#section-7.1 -func TestValidateOIDCClientRedirectURIsSupportingPrivateUseURISchemes(t *testing.T) { - conf := schema.OpenIDConnectClientConfiguration{ - ID: "owncloud", - RedirectURIs: []string{ - "https://www.mywebsite.com", - "http://www.mywebsite.com", - "oc://ios.owncloud.com", - // example given in the RFC https://datatracker.ietf.org/doc/html/rfc8252#section-7.1 - "com.example.app:/oauth2redirect/example-provider", - oauth2InstalledApp, - }, - } - t.Run("public", func(t *testing.T) { validator := schema.NewStructValidator() - conf.Public = true - validateOIDCClientRedirectURIs(conf, validator) + have.Clients[0].Public = true + validateOIDCClientRedirectURIs(0, have, validator, nil) assert.Len(t, validator.Warnings(), 0) assert.Len(t, validator.Errors(), 0) @@ -934,8 +790,8 @@ func TestValidateOIDCClientRedirectURIsSupportingPrivateUseURISchemes(t *testing t.Run("not public", func(t *testing.T) { validator := schema.NewStructValidator() - conf.Public = false - validateOIDCClientRedirectURIs(conf, validator) + have.Clients[0].Public = false + validateOIDCClientRedirectURIs(0, have, validator, nil) assert.Len(t, validator.Warnings(), 0) assert.Len(t, validator.Errors(), 1) @@ -945,6 +801,1143 @@ func TestValidateOIDCClientRedirectURIsSupportingPrivateUseURISchemes(t *testing }) } +func TestValidateOIDCClients(t *testing.T) { + type tcv struct { + Scopes []string + ResponseTypes []string + ResponseModes []string + GrantTypes []string + } + + testCasses := []struct { + name string + setup func(have *schema.OpenIDConnectConfiguration) + validate func(t *testing.T, have *schema.OpenIDConnectConfiguration) + have tcv + expected tcv + serrs []string // Soft errors which will be warnings before GA. + errs []string + }{ + { + "ShouldSetDefaultResponseTypeAndResponseModes", + nil, + nil, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldIncludeMinimalScope", + nil, + nil, + tcv{ + []string{oidc.ScopeEmail}, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldSetDefaultResponseModesFlowAuthorizeCode", + nil, + nil, + tcv{ + nil, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldSetDefaultResponseModesFlowImplicit", + nil, + nil, + tcv{ + nil, + []string{oidc.ResponseTypeImplicitFlowBoth}, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeImplicitFlowBoth}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeFragment}, + []string{oidc.GrantTypeImplicit}, + }, + nil, + nil, + }, + { + "ShouldSetDefaultResponseModesFlowHybrid", + nil, + nil, + tcv{ + nil, + []string{oidc.ResponseTypeHybridFlowBoth}, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeHybridFlowBoth}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeFragment}, + []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeImplicit}, + }, + nil, + nil, + }, + { + "ShouldSetDefaultResponseModesFlowMixedAuthorizeCodeHybrid", + nil, + nil, + tcv{ + nil, + []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeHybridFlowBoth}, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeHybridFlowBoth}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment}, + []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeImplicit}, + }, + nil, + nil, + }, + { + "ShouldSetDefaultResponseModesFlowMixedAuthorizeCodeImplicit", + nil, + nil, + tcv{ + nil, + []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowBoth}, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowBoth}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment}, + []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeImplicit}, + }, + nil, + nil, + }, + { + "ShouldSetDefaultResponseModesFlowMixedAll", + nil, + nil, + tcv{ + nil, + []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeHybridFlowBoth}, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeHybridFlowBoth}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment}, + []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeImplicit}, + }, + nil, + nil, + }, + { + "ShouldNotOverrideValues", + nil, + nil, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups}, + []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeHybridFlowBoth}, + []string{oidc.ResponseModeFormPost}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups}, + []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeHybridFlowBoth}, + []string{oidc.ResponseModeFormPost}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldRaiseErrorOnDuplicateScopes", + nil, + nil, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeOpenID}, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeOpenID}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + []string{ + "identity_providers: oidc: client 'test': option 'scopes' must have unique values but the values 'openid' are duplicated", + }, + nil, + }, + { + "ShouldRaiseErrorOnInvalidScopes", + nil, + nil, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeProfile, "group"}, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeProfile, "group"}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + []string{ + "identity_providers: oidc: client 'test': option 'scopes' must only have the values 'openid', 'email', 'profile', 'groups', or 'offline_access' but the values 'group' are present", + }, + }, + { + "ShouldRaiseErrorOnMissingAuthorizationCodeFlowResponseTypeWithRefreshTokenValues", + nil, + nil, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeOfflineAccess}, + []string{oidc.ResponseTypeImplicitFlowBoth}, + nil, + []string{oidc.GrantTypeImplicit, oidc.GrantTypeRefreshToken}, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeOfflineAccess}, + []string{oidc.ResponseTypeImplicitFlowBoth}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeFragment}, + []string{oidc.GrantTypeImplicit, oidc.GrantTypeRefreshToken}, + }, + []string{ + "identity_providers: oidc: client 'test': option 'scopes' should only have the values 'offline_access' or 'offline' if the client is also configured with a 'response_type' such as 'code', 'code id_token', 'code token', or 'code id_token token' which respond with authorization codes", + "identity_providers: oidc: client 'test': option 'grant_types' should only have the values 'refresh_token' if the client is also configured with a 'response_type' such as 'code', 'code id_token', 'code token', or 'code id_token token' which respond with authorization codes", + }, + nil, + }, + { + "ShouldRaiseErrorOnDuplicateResponseTypes", + nil, + nil, + tcv{ + nil, + []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeAuthorizationCodeFlow}, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment}, + []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeImplicit}, + }, + []string{ + "identity_providers: oidc: client 'test': option 'response_types' must have unique values but the values 'code' are duplicated", + }, + nil, + }, + { + "ShouldRaiseErrorOnInvalidResponseTypesOrder", + nil, + nil, + tcv{ + nil, + []string{oidc.ResponseTypeImplicitFlowBoth, "token id_token"}, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeImplicitFlowBoth, "token id_token"}, + []string{"form_post", "fragment"}, + []string{"implicit"}, + }, + []string{ + "identity_providers: oidc: client 'test': option 'response_types' must only have the values 'code', 'id_token', 'token', 'id_token token', 'code id_token', 'code token', or 'code id_token token' but the values 'token id_token' are present", + }, + nil, + }, + { + "ShouldRaiseErrorOnInvalidResponseTypes", + nil, + nil, + tcv{ + nil, + []string{"not_valid"}, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{"not_valid"}, + []string{oidc.ResponseModeFormPost}, + nil, + }, + []string{ + "identity_providers: oidc: client 'test': option 'response_types' must only have the values 'code', 'id_token', 'token', 'id_token token', 'code id_token', 'code token', or 'code id_token token' but the values 'not_valid' are present", + }, + nil, + }, + { + "ShouldRaiseErrorOnInvalidResponseModes", + nil, + nil, + tcv{ + nil, + nil, + []string{"not_valid"}, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{"not_valid"}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + []string{ + "identity_providers: oidc: client 'test': option 'response_modes' must only have the values 'form_post', 'query', or 'fragment' but the values 'not_valid' are present", + }, + }, + { + "ShouldRaiseErrorOnDuplicateResponseModes", + nil, + nil, + tcv{ + nil, + nil, + []string{oidc.ResponseModeQuery, oidc.ResponseModeQuery}, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeQuery, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + []string{ + "identity_providers: oidc: client 'test': option 'response_modes' must have unique values but the values 'query' are duplicated", + }, + nil, + }, + { + "ShouldRaiseErrorOnInvalidGrantTypes", + nil, + nil, + tcv{ + nil, + nil, + nil, + []string{"invalid"}, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{"invalid"}, + }, + nil, + []string{ + "identity_providers: oidc: client 'test': option 'grant_types' must only have the values 'implicit', 'refresh_token', or 'authorization_code' but the values 'invalid' are present", + }, + }, + { + "ShouldRaiseErrorOnDuplicateGrantTypes", + nil, + nil, + tcv{ + nil, + nil, + nil, + []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeAuthorizationCode}, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeAuthorizationCode}, + }, + []string{ + "identity_providers: oidc: client 'test': option 'grant_types' must have unique values but the values 'authorization_code' are duplicated", + }, + nil, + }, + { + "ShouldRaiseErrorOnGrantTypeRefreshTokenWithoutScopeOfflineAccess", + nil, + nil, + tcv{ + nil, + nil, + nil, + []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeRefreshToken}, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeRefreshToken}, + }, + []string{ + "identity_providers: oidc: client 'test': option 'grant_types' should only have the 'refresh_token' value if the client is also configured with the 'offline_access' scope", + }, + nil, + }, + { + "ShouldRaiseErrorOnGrantTypeAuthorizationCodeWithoutAuthorizationCodeOrHybridFlow", + nil, + nil, + tcv{ + nil, + []string{oidc.ResponseTypeImplicitFlowBoth}, + nil, + []string{oidc.GrantTypeAuthorizationCode}, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeImplicitFlowBoth}, + []string{"form_post", "fragment"}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + []string{ + "identity_providers: oidc: client 'test': option 'grant_types' should only have grant type values which are valid with the configured 'response_types' for the client but 'authorization_code' expects a response type for either the authorization code or hybrid flow such as 'code', 'code id_token', 'code token', or 'code id_token token' but the response types are 'id_token token'", + }, + nil, + }, + { + "ShouldRaiseErrorOnGrantTypeImplicitWithoutImplicitOrHybridFlow", + nil, + nil, + tcv{ + nil, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + nil, + []string{oidc.GrantTypeImplicit}, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeImplicit}, + }, + []string{ + "identity_providers: oidc: client 'test': option 'grant_types' should only have grant type values which are valid with the configured 'response_types' for the client but 'implicit' expects a response type for either the implicit or hybrid flow such as 'id_token', 'token', 'id_token token', 'code id_token', 'code token', or 'code id_token token' but the response types are 'code'", + }, + nil, + }, + { + "ShouldValidateCorrectRedirectURIsConfidentialClientType", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].RedirectURIs = []string{ + "https://google.com", + } + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, []string{"https://google.com"}, have.Clients[0].RedirectURIs) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldValidateCorrectRedirectURIsPublicClientType", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].Public = true + have.Clients[0].Secret = nil + have.Clients[0].RedirectURIs = []string{ + oauth2InstalledApp, + } + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, []string{oauth2InstalledApp}, have.Clients[0].RedirectURIs) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldRaiseErrorOnInvalidRedirectURIsPublicOnly", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].RedirectURIs = []string{ + "urn:ietf:wg:oauth:2.0:oob", + } + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, []string{oauth2InstalledApp}, have.Clients[0].RedirectURIs) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + []string{ + "identity_providers: oidc: client 'test': option 'redirect_uris' has the redirect uri 'urn:ietf:wg:oauth:2.0:oob' when option 'public' is false but this is invalid as this uri is not valid for the openid connect confidential client type", + }, + }, + { + "ShouldRaiseErrorOnInvalidRedirectURIsMalformedURI", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].RedirectURIs = []string{ + "http://abc@%two", + } + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, []string{"http://abc@%two"}, have.Clients[0].RedirectURIs) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + []string{ + "identity_providers: oidc: client 'test': option 'redirect_uris' has an invalid value: redirect uri 'http://abc@%two' could not be parsed: parse \"http://abc@%two\": invalid URL escape \"%tw\"", + }, + }, + { + "ShouldRaiseErrorOnInvalidRedirectURIsNotAbsolute", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].RedirectURIs = []string{ + "google.com", + } + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, []string{"google.com"}, have.Clients[0].RedirectURIs) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + []string{ + "identity_providers: oidc: client 'test': option 'redirect_uris' has an invalid value: redirect uri 'google.com' must have a scheme but it's absent", + }, + }, + { + "ShouldRaiseErrorOnDuplicateRedirectURI", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].RedirectURIs = []string{ + "https://google.com", + "https://google.com", + } + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, []string{"https://google.com", "https://google.com"}, have.Clients[0].RedirectURIs) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + []string{ + "identity_providers: oidc: client 'test': option 'redirect_uris' must have unique values but the values 'https://google.com' are duplicated", + }, + nil, + }, + { + "ShouldNotSetDefaultTokenEndpointClientAuthMethodConfidentialClientType", + nil, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, "", have.Clients[0].TokenEndpointAuthMethod) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldSetDefaultTokenEndpointClientAuthMethodPublicClientType", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].Public = true + have.Clients[0].Secret = nil + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, oidc.ClientAuthMethodNone, have.Clients[0].TokenEndpointAuthMethod) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldSetDefaultTokenEndpointClientAuthMethodConfidentialClientTypeImplicitFlow", + nil, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, oidc.ClientAuthMethodNone, have.Clients[0].TokenEndpointAuthMethod) + }, + tcv{ + nil, + []string{oidc.ResponseTypeImplicitFlowIDToken, oidc.ResponseTypeImplicitFlowToken, oidc.ResponseTypeImplicitFlowBoth}, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeImplicitFlowIDToken, oidc.ResponseTypeImplicitFlowToken, oidc.ResponseTypeImplicitFlowBoth}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeFragment}, + []string{oidc.GrantTypeImplicit}, + }, + nil, + nil, + }, + { + "ShouldNotOverrideValidClientAuthMethod", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].TokenEndpointAuthMethod = oidc.ClientAuthMethodClientSecretPost + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, oidc.ClientAuthMethodClientSecretPost, have.Clients[0].TokenEndpointAuthMethod) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldRaiseErrorOnInvalidClientAuthMethod", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].TokenEndpointAuthMethod = "client_credentials" + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, "client_credentials", have.Clients[0].TokenEndpointAuthMethod) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + []string{ + "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be one of 'none', 'client_secret_post', or 'client_secret_basic' but it's configured as 'client_credentials'", + }, + }, + { + "ShouldRaiseErrorOnInvalidClientAuthMethodForPublicClientType", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].TokenEndpointAuthMethod = oidc.ClientAuthMethodClientSecretBasic + have.Clients[0].Public = true + have.Clients[0].Secret = nil + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, oidc.ClientAuthMethodClientSecretBasic, have.Clients[0].TokenEndpointAuthMethod) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + []string{ + "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be 'none' when configured as the public client type but it's configured as 'client_secret_basic'", + }, + }, + { + "ShouldRaiseErrorOnInvalidClientAuthMethodForConfidentialClientTypeAuthorizationCodeFlow", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].TokenEndpointAuthMethod = oidc.ClientAuthMethodNone + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, oidc.ClientAuthMethodNone, have.Clients[0].TokenEndpointAuthMethod) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + []string{ + "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be one of 'client_secret_post' or 'client_secret_basic' when configured as the confidential client type unless it only includes implicit flow response types such as 'id_token', 'token', and 'id_token token' but it's configured as 'none'", + }, + }, + { + "ShouldRaiseErrorOnInvalidClientAuthMethodForConfidentialClientTypeHybridFlow", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].TokenEndpointAuthMethod = oidc.ClientAuthMethodNone + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, oidc.ClientAuthMethodNone, have.Clients[0].TokenEndpointAuthMethod) + }, + tcv{ + nil, + []string{oidc.ResponseTypeHybridFlowToken}, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeHybridFlowToken}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeFragment}, + []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeImplicit}, + }, + nil, + []string{ + "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be one of 'client_secret_post' or 'client_secret_basic' when configured as the confidential client type unless it only includes implicit flow response types such as 'id_token', 'token', and 'id_token token' but it's configured as 'none'", + }, + }, + { + "ShouldSetDefaultUserInfoAlg", + nil, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, oidc.SigningAlgorithmNone, have.Clients[0].UserinfoSigningAlgorithm) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldNotOverrideUserInfoAlg", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].UserinfoSigningAlgorithm = oidc.SigningAlgorithmRSAWithSHA256 + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, oidc.SigningAlgorithmRSAWithSHA256, have.Clients[0].UserinfoSigningAlgorithm) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldRaiseErrorOnInvalidUserInfoAlg", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].UserinfoSigningAlgorithm = "rs256" + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, "rs256", have.Clients[0].UserinfoSigningAlgorithm) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + []string{ + "identity_providers: oidc: client 'test': option 'userinfo_signing_algorithm' must be one of 'none' or 'RS256' but it's configured as 'rs256'", + }, + }, + { + "ShouldSetDefaultConsentMode", + nil, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, "explicit", have.Clients[0].ConsentMode) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldSetDefaultConsentModeAuto", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].ConsentMode = auto + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, "explicit", have.Clients[0].ConsentMode) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldSetDefaultConsentModePreConfigured", + func(have *schema.OpenIDConnectConfiguration) { + d := time.Minute + + have.Clients[0].ConsentMode = "" + have.Clients[0].ConsentPreConfiguredDuration = &d + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, "pre-configured", have.Clients[0].ConsentMode) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldSetDefaultConsentModeAutoPreConfigured", + func(have *schema.OpenIDConnectConfiguration) { + d := time.Minute + + have.Clients[0].ConsentMode = auto + have.Clients[0].ConsentPreConfiguredDuration = &d + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, "pre-configured", have.Clients[0].ConsentMode) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldNotOverrideConsentMode", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].ConsentMode = "implicit" + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, "implicit", have.Clients[0].ConsentMode) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldSentConsentPreConfiguredDefaultDuration", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].ConsentMode = "pre-configured" + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, "pre-configured", have.Clients[0].ConsentMode) + assert.Equal(t, schema.DefaultOpenIDConnectClientConfiguration.ConsentPreConfiguredDuration, have.Clients[0].ConsentPreConfiguredDuration) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + } + + errDeprecatedFunc := func() {} + + for _, tc := range testCasses { + t.Run(tc.name, func(t *testing.T) { + have := &schema.OpenIDConnectConfiguration{ + Clients: []schema.OpenIDConnectClientConfiguration{ + { + ID: "test", + Secret: MustDecodeSecret("$pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng"), + Scopes: tc.have.Scopes, + ResponseModes: tc.have.ResponseModes, + ResponseTypes: tc.have.ResponseTypes, + GrantTypes: tc.have.GrantTypes, + }, + }, + } + + if tc.setup != nil { + tc.setup(have) + } + + val := schema.NewStructValidator() + + validateOIDCClient(0, have, val, errDeprecatedFunc) + + t.Run("General", func(t *testing.T) { + assert.Equal(t, tc.expected.Scopes, have.Clients[0].Scopes) + assert.Equal(t, tc.expected.ResponseTypes, have.Clients[0].ResponseTypes) + assert.Equal(t, tc.expected.ResponseModes, have.Clients[0].ResponseModes) + assert.Equal(t, tc.expected.GrantTypes, have.Clients[0].GrantTypes) + + if tc.validate != nil { + tc.validate(t, have) + } + }) + + t.Run("Warnings", func(t *testing.T) { + require.Len(t, val.Warnings(), len(tc.serrs)) + for i, err := range tc.serrs { + assert.EqualError(t, val.Warnings()[i], err) + } + }) + + t.Run("Errors", func(t *testing.T) { + require.Len(t, val.Errors(), len(tc.errs)) + for i, err := range tc.errs { + assert.EqualError(t, val.Errors()[i], err) + } + }) + }) + } +} + +func TestValidateOIDCClientTokenEndpointAuthMethod(t *testing.T) { + testCasses := []struct { + name string + have string + public bool + expected string + errs []string + }{ + {"ShouldSetDefaultValueConfidential", "", false, "", nil}, + {"ShouldSetDefaultValuePublic", "", true, oidc.ClientAuthMethodNone, nil}, + {"ShouldErrorOnInvalidValue", "abc", false, "abc", + []string{ + "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be one of 'none', 'client_secret_post', or 'client_secret_basic' but it's configured as 'abc'", + }, + }, + {"ShouldErrorOnInvalidValueForPublicClient", "client_secret_post", true, "client_secret_post", + []string{ + "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be 'none' when configured as the public client type but it's configured as 'client_secret_post'", + }, + }, + {"ShouldErrorOnInvalidValueForConfidentialClient", "none", false, "none", + []string{ + "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be one of 'client_secret_post' or 'client_secret_basic' when configured as the confidential client type unless it only includes implicit flow response types such as 'id_token', 'token', and 'id_token token' but it's configured as 'none'", + }, + }, + } + + for _, tc := range testCasses { + t.Run(tc.name, func(t *testing.T) { + have := &schema.OpenIDConnectConfiguration{ + Clients: []schema.OpenIDConnectClientConfiguration{ + { + ID: "test", + Public: tc.public, + TokenEndpointAuthMethod: tc.have, + }, + }, + } + + val := schema.NewStructValidator() + + validateOIDCClientTokenEndpointAuthMethod(0, have, val) + + assert.Equal(t, tc.expected, have.Clients[0].TokenEndpointAuthMethod) + assert.Len(t, val.Warnings(), 0) + require.Len(t, val.Errors(), len(tc.errs)) + + if tc.errs != nil { + for i, err := range tc.errs { + assert.EqualError(t, val.Errors()[i], err) + } + } + }) + } +} + func MustDecodeSecret(value string) *schema.PasswordDigest { if secret, err := schema.DecodePasswordDigest(value); err != nil { panic(err) diff --git a/internal/configuration/validator/keys.go b/internal/configuration/validator/keys.go index 67b9d964d..863d2030e 100644 --- a/internal/configuration/validator/keys.go +++ b/internal/configuration/validator/keys.go @@ -100,7 +100,7 @@ func NewKeyMapPattern(key string) (pattern *regexp.Regexp, err error) { } if i < n { - buf.WriteString("\\.[a-z0-9]([a-z0-9-_]+)?[a-z0-9]") + buf.WriteString("\\.[a-z0-9](([a-z0-9-_]+)?[a-z0-9])?") } } diff --git a/internal/configuration/validator/keys_test.go b/internal/configuration/validator/keys_test.go index 989b4b815..a5123ef8c 100644 --- a/internal/configuration/validator/keys_test.go +++ b/internal/configuration/validator/keys_test.go @@ -101,6 +101,20 @@ func TestSpecificErrorKeys(t *testing.T) { assert.EqualError(t, errs[4], specificErrorKeys["authentication_backend.file.hashing.algorithm"]) } +func TestPatternKeys(t *testing.T) { + configKeys := []string{ + "server.endpoints.authz.xx.implementation", + "server.endpoints.authz.x.implementation", + } + + val := schema.NewStructValidator() + ValidateKeys(configKeys, "AUTHELIA_", val) + + errs := val.Errors() + + require.Len(t, errs, 0) +} + func TestReplacedErrors(t *testing.T) { configKeys := []string{ "authentication_backend.ldap.skip_verify", diff --git a/internal/configuration/validator/log.go b/internal/configuration/validator/log.go index 5c7a0761b..7b8c7f6ea 100644 --- a/internal/configuration/validator/log.go +++ b/internal/configuration/validator/log.go @@ -2,7 +2,6 @@ package validator import ( "fmt" - "strings" "github.com/authelia/authelia/v4/internal/configuration/schema" "github.com/authelia/authelia/v4/internal/utils" @@ -19,6 +18,6 @@ func ValidateLog(config *schema.Configuration, validator *schema.StructValidator } if !utils.IsStringInSlice(config.Log.Level, validLogLevels) { - validator.Push(fmt.Errorf(errFmtLoggingLevelInvalid, strings.Join(validLogLevels, "', '"), config.Log.Level)) + validator.Push(fmt.Errorf(errFmtLoggingLevelInvalid, strJoinOr(validLogLevels), config.Log.Level)) } } diff --git a/internal/configuration/validator/log_test.go b/internal/configuration/validator/log_test.go index 56cff19de..cf3de2736 100644 --- a/internal/configuration/validator/log_test.go +++ b/internal/configuration/validator/log_test.go @@ -40,5 +40,5 @@ func TestShouldRaiseErrorOnInvalidLoggingLevel(t *testing.T) { assert.Len(t, validator.Warnings(), 0) require.Len(t, validator.Errors(), 1) - assert.EqualError(t, validator.Errors()[0], "log: option 'level' must be one of 'trace', 'debug', 'info', 'warn', 'error' but it is configured as 'TRACE'") + assert.EqualError(t, validator.Errors()[0], "log: option 'level' must be one of 'trace', 'debug', 'info', 'warn', or 'error' but it's configured as 'TRACE'") } diff --git a/internal/configuration/validator/notifier_test.go b/internal/configuration/validator/notifier_test.go index c41776a29..17819993f 100644 --- a/internal/configuration/validator/notifier_test.go +++ b/internal/configuration/validator/notifier_test.go @@ -4,8 +4,10 @@ import ( "crypto/tls" "fmt" "net/mail" + "path/filepath" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" "github.com/authelia/authelia/v4/internal/configuration/schema" @@ -187,6 +189,32 @@ func (suite *NotifierSuite) TestSMTPShouldEnsureSenderIsProvided() { suite.Assert().EqualError(suite.validator.Errors()[0], fmt.Sprintf(errFmtNotifierSMTPNotConfigured, "sender")) } +func (suite *NotifierSuite) TestTemplatesEmptyDir() { + dir := suite.T().TempDir() + + suite.config.TemplatePath = dir + + ValidateNotifier(&suite.config, suite.validator) + + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 0) +} + +func (suite *NotifierSuite) TestTemplatesEmptyDirNoExist() { + dir := suite.T().TempDir() + + p := filepath.Join(dir, "notexist") + + suite.config.TemplatePath = p + + ValidateNotifier(&suite.config, suite.validator) + + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 1) + + assert.EqualError(suite.T(), suite.validator.Errors()[0], fmt.Sprintf("notifier: option 'template_path' refers to location '%s' which does not exist", p)) +} + /* File Tests. */ diff --git a/internal/configuration/validator/ntp_test.go b/internal/configuration/validator/ntp_test.go index 9780245f9..0bae5b830 100644 --- a/internal/configuration/validator/ntp_test.go +++ b/internal/configuration/validator/ntp_test.go @@ -49,5 +49,5 @@ func TestShouldRaiseErrorOnInvalidNTPVersion(t *testing.T) { require.Len(t, validator.Errors(), 1) - assert.EqualError(t, validator.Errors()[0], "ntp: option 'version' must be either 3 or 4 but it is configured as '1'") + assert.EqualError(t, validator.Errors()[0], "ntp: option 'version' must be either 3 or 4 but it's configured as '1'") } diff --git a/internal/configuration/validator/password_policy_test.go b/internal/configuration/validator/password_policy_test.go index 3d27f08d6..5ce417c01 100644 --- a/internal/configuration/validator/password_policy_test.go +++ b/internal/configuration/validator/password_policy_test.go @@ -39,7 +39,7 @@ func TestValidatePasswordPolicy(t *testing.T) { }, expectedErrs: []string{ "password_policy: only a single password policy mechanism can be specified", - "password_policy: standard: option 'min_length' must be greater than 0 but is configured as -1", + "password_policy: standard: option 'min_length' must be greater than 0 but it's configured as -1", }, }, { diff --git a/internal/configuration/validator/server.go b/internal/configuration/validator/server.go index 66a12d150..c38850634 100644 --- a/internal/configuration/validator/server.go +++ b/internal/configuration/validator/server.go @@ -155,13 +155,13 @@ func validateServerEndpointsAuthzEndpoint(config *schema.Configuration, name str config.Server.Endpoints.Authz[name] = endpoint default: if !utils.IsStringInSlice(endpoint.Implementation, validAuthzImplementations) { - validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzImplementation, name, strings.Join(validAuthzImplementations, "', '"), endpoint.Implementation)) + validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzImplementation, name, strJoinOr(validAuthzImplementations), endpoint.Implementation)) } else { validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzLegacyInvalidImplementation, name)) } } } else if !utils.IsStringInSlice(endpoint.Implementation, validAuthzImplementations) { - validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzImplementation, name, strings.Join(validAuthzImplementations, "', '"), endpoint.Implementation)) + validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzImplementation, name, strJoinOr(validAuthzImplementations), endpoint.Implementation)) } if !reAuthzEndpointName.MatchString(name) { @@ -180,7 +180,7 @@ func validateServerEndpointsAuthzStrategies(name string, strategies []schema.Ser names = append(names, strategy.Name) if !utils.IsStringInSlice(strategy.Name, validAuthzAuthnStrategies) { - validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzStrategy, name, strings.Join(validAuthzAuthnStrategies, "', '"), strategy.Name)) + validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzStrategy, name, strJoinOr(validAuthzAuthnStrategies), strategy.Name)) } } } diff --git a/internal/configuration/validator/server_test.go b/internal/configuration/validator/server_test.go index c70e7124e..cf330d393 100644 --- a/internal/configuration/validator/server_test.go +++ b/internal/configuration/validator/server_test.go @@ -314,14 +314,18 @@ func TestServerAuthzEndpointErrors(t *testing.T) { map[string]schema.ServerAuthzEndpoint{ "example": {Implementation: "zero"}, }, - []string{"server: endpoints: authz: example: option 'implementation' must be one of 'AuthRequest', 'ForwardAuth', 'ExtAuthz', 'Legacy' but is configured as 'zero'"}, + []string{ + "server: endpoints: authz: example: option 'implementation' must be one of 'AuthRequest', 'ForwardAuth', 'ExtAuthz', or 'Legacy' but it's configured as 'zero'", + }, }, { "ShouldErrorOnInvalidEndpointImplementationLegacy", map[string]schema.ServerAuthzEndpoint{ "legacy": {Implementation: "zero"}, }, - []string{"server: endpoints: authz: legacy: option 'implementation' must be one of 'AuthRequest', 'ForwardAuth', 'ExtAuthz', 'Legacy' but is configured as 'zero'"}, + []string{ + "server: endpoints: authz: legacy: option 'implementation' must be one of 'AuthRequest', 'ForwardAuth', 'ExtAuthz', or 'Legacy' but it's configured as 'zero'", + }, }, { "ShouldErrorOnInvalidEndpointLegacyImplementation", @@ -335,7 +339,9 @@ func TestServerAuthzEndpointErrors(t *testing.T) { map[string]schema.ServerAuthzEndpoint{ "example": {Implementation: "ExtAuthz", AuthnStrategies: []schema.ServerAuthzEndpointAuthnStrategy{{Name: "bad-name"}}}, }, - []string{"server: endpoints: authz: example: authn_strategies: option 'name' must be one of 'CookieSession', 'HeaderAuthorization', 'HeaderProxyAuthorization', 'HeaderAuthRequestProxyAuthorization', 'HeaderLegacy' but is configured as 'bad-name'"}, + []string{ + "server: endpoints: authz: example: authn_strategies: option 'name' must be one of 'CookieSession', 'HeaderAuthorization', 'HeaderProxyAuthorization', 'HeaderAuthRequestProxyAuthorization', or 'HeaderLegacy' but it's configured as 'bad-name'", + }, }, { "ShouldErrorOnDuplicateName", diff --git a/internal/configuration/validator/session.go b/internal/configuration/validator/session.go index f63d24ded..1de078ef8 100644 --- a/internal/configuration/validator/session.go +++ b/internal/configuration/validator/session.go @@ -45,7 +45,7 @@ func validateSession(config *schema.SessionConfiguration, validator *schema.Stru if config.SameSite == "" { config.SameSite = schema.DefaultSessionConfiguration.SameSite } else if !utils.IsStringInSlice(config.SameSite, validSessionSameSiteValues) { - validator.Push(fmt.Errorf(errFmtSessionSameSite, strings.Join(validSessionSameSiteValues, "', '"), config.SameSite)) + validator.Push(fmt.Errorf(errFmtSessionSameSite, strJoinOr(validSessionSameSiteValues), config.SameSite)) } cookies := len(config.Cookies) @@ -73,7 +73,7 @@ func validateSession(config *schema.SessionConfiguration, validator *schema.Stru func validateSessionCookieDomains(config *schema.SessionConfiguration, validator *schema.StructValidator) { if len(config.Cookies) == 0 { - validator.Push(fmt.Errorf(errFmtSessionOptionRequired, "domain")) + validator.Push(fmt.Errorf(errFmtSessionOptionRequired, "cookies")) } domains := make([]string, 0) @@ -182,7 +182,7 @@ func validateSessionSameSite(i int, config *schema.SessionConfiguration, validat config.Cookies[i].SameSite = schema.DefaultSessionConfiguration.SameSite } } else if !utils.IsStringInSlice(config.Cookies[i].SameSite, validSessionSameSiteValues) { - validator.Push(fmt.Errorf(errFmtSessionDomainSameSite, sessionDomainDescriptor(i, config.Cookies[i]), strings.Join(validSessionSameSiteValues, "', '"), config.Cookies[i].SameSite)) + validator.Push(fmt.Errorf(errFmtSessionDomainSameSite, sessionDomainDescriptor(i, config.Cookies[i]), strJoinOr(validSessionSameSiteValues), config.Cookies[i].SameSite)) } } diff --git a/internal/configuration/validator/session_test.go b/internal/configuration/validator/session_test.go index f8db62b5b..1f18eaea4 100644 --- a/internal/configuration/validator/session_test.go +++ b/internal/configuration/validator/session_test.go @@ -95,7 +95,7 @@ func TestShouldSetDefaultSessionDomainsValues(t *testing.T) { }, }, []string{ - "session: option 'same_site' must be one of 'none', 'lax', 'strict' but is configured as 'BAD VALUE'", + "session: option 'same_site' must be one of 'none', 'lax', or 'strict' but it's configured as 'BAD VALUE'", }, }, { @@ -140,6 +140,24 @@ func TestShouldSetDefaultSessionDomainsValues(t *testing.T) { }, nil, }, + { + "ShouldErrorOnEmptyConfig", + schema.SessionConfiguration{ + SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{ + Name: "", SameSite: "", Domain: "", + }, + Cookies: []schema.SessionCookieConfiguration{}, + }, + schema.SessionConfiguration{ + SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{ + Name: "authelia_session", SameSite: "lax", Expiration: time.Hour, Inactivity: time.Minute * 5, RememberMe: time.Hour * 24 * 30, + }, + Cookies: []schema.SessionCookieConfiguration{}, + }, + []string{ + "session: option 'cookies' is required", + }, + }, } validator := schema.NewStructValidator() @@ -302,7 +320,7 @@ func TestShouldRaiseErrorWhenRedisHasHostnameButNoPort(t *testing.T) { assert.False(t, validator.HasWarnings()) assert.Len(t, validator.Errors(), 1) - assert.EqualError(t, validator.Errors()[0], "session: redis: option 'port' must be between 1 and 65535 but is configured as '0'") + assert.EqualError(t, validator.Errors()[0], "session: redis: option 'port' must be between 1 and 65535 but it's configured as '0'") } func TestShouldRaiseOneErrorWhenRedisHighAvailabilityHasNodesWithNoHost(t *testing.T) { @@ -646,7 +664,7 @@ func TestShouldRaiseErrorWhenDomainIsInvalid(t *testing.T) { {"ShouldRaiseErrorOnPublicDomainDuckDNS", "duckdns.org", nil, []string{"session: domain config #1 (domain 'duckdns.org'): option 'domain' is not a valid cookie domain: the domain is part of the special public suffix list"}}, {"ShouldNotRaiseErrorOnSuffixOfPublicDomainDuckDNS", "example.duckdns.org", nil, nil}, {"ShouldRaiseWarningOnDomainWithLeadingDot", ".example.com", []string{"session: domain config #1 (domain '.example.com'): option 'domain' has a prefix of '.' which is not supported or intended behaviour: you can use this at your own risk but we recommend removing it"}, nil}, - {"ShouldRaiseErrorOnDomainWithLeadingStarDot", "*.example.com", nil, []string{"session: domain config #1 (domain '*.example.com'): option 'domain' must be the domain you wish to protect not a wildcard domain but it is configured as '*.example.com'"}}, + {"ShouldRaiseErrorOnDomainWithLeadingStarDot", "*.example.com", nil, []string{"session: domain config #1 (domain '*.example.com'): option 'domain' must be the domain you wish to protect not a wildcard domain but it's configured as '*.example.com'"}}, {"ShouldRaiseErrorOnDomainNotSet", "", nil, []string{"session: domain config #1 (domain ''): option 'domain' is required"}}, } @@ -726,8 +744,8 @@ func TestShouldRaiseErrorWhenSameSiteSetIncorrectly(t *testing.T) { assert.False(t, validator.HasWarnings()) require.Len(t, validator.Errors(), 2) - assert.EqualError(t, validator.Errors()[0], "session: option 'same_site' must be one of 'none', 'lax', 'strict' but is configured as 'NOne'") - assert.EqualError(t, validator.Errors()[1], "session: domain config #1 (domain 'example.com'): option 'same_site' must be one of 'none', 'lax', 'strict' but is configured as 'NOne'") + assert.EqualError(t, validator.Errors()[0], "session: option 'same_site' must be one of 'none', 'lax', or 'strict' but it's configured as 'NOne'") + assert.EqualError(t, validator.Errors()[1], "session: domain config #1 (domain 'example.com'): option 'same_site' must be one of 'none', 'lax', or 'strict' but it's configured as 'NOne'") } func TestShouldNotRaiseErrorWhenSameSiteSetCorrectly(t *testing.T) { diff --git a/internal/configuration/validator/storage.go b/internal/configuration/validator/storage.go index 7172383a7..035ac62e8 100644 --- a/internal/configuration/validator/storage.go +++ b/internal/configuration/validator/storage.go @@ -3,7 +3,6 @@ package validator import ( "errors" "fmt" - "strings" "github.com/authelia/authelia/v4/internal/configuration/schema" "github.com/authelia/authelia/v4/internal/utils" @@ -92,7 +91,7 @@ func validatePostgreSQLConfiguration(config *schema.PostgreSQLStorageConfigurati case config.SSL.Mode == "": config.SSL.Mode = schema.DefaultPostgreSQLStorageConfiguration.SSL.Mode case !utils.IsStringInSlice(config.SSL.Mode, validStoragePostgreSQLSSLModes): - validator.Push(fmt.Errorf(errFmtStoragePostgreSQLInvalidSSLMode, strings.Join(validStoragePostgreSQLSSLModes, "', '"), config.SSL.Mode)) + validator.Push(fmt.Errorf(errFmtStoragePostgreSQLInvalidSSLMode, strJoinOr(validStoragePostgreSQLSSLModes), config.SSL.Mode)) } } } diff --git a/internal/configuration/validator/storage_test.go b/internal/configuration/validator/storage_test.go index 8ac7d9dbb..f69bffab1 100644 --- a/internal/configuration/validator/storage_test.go +++ b/internal/configuration/validator/storage_test.go @@ -360,7 +360,7 @@ func (suite *StorageSuite) TestShouldValidatePostgresSSLModeMustBeValid() { suite.Assert().Len(suite.validator.Warnings(), 1) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "storage: postgres: ssl: option 'mode' must be one of 'disable', 'require', 'verify-ca', 'verify-full' but it is configured as 'unknown'") + suite.Assert().EqualError(suite.validator.Errors()[0], "storage: postgres: ssl: option 'mode' must be one of 'disable', 'require', 'verify-ca', or 'verify-full' but it's configured as 'unknown'") } func (suite *StorageSuite) TestShouldRaiseErrorOnNoEncryptionKey() { diff --git a/internal/configuration/validator/telemetry_test.go b/internal/configuration/validator/telemetry_test.go index aa3e59683..1643a5c40 100644 --- a/internal/configuration/validator/telemetry_test.go +++ b/internal/configuration/validator/telemetry_test.go @@ -58,7 +58,7 @@ func TestValidateTelemetry(t *testing.T) { &schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("udp://0.0.0.0")}}}, &schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("udp://0.0.0.0:9959")}}}, nil, - []string{"telemetry: metrics: option 'address' must have a scheme 'tcp://' but it is configured as 'udp'"}, + []string{"telemetry: metrics: option 'address' must have a scheme 'tcp://' but it's configured as 'udp'"}, }, } diff --git a/internal/configuration/validator/theme.go b/internal/configuration/validator/theme.go index f6c1a68d7..ccb8b7f52 100644 --- a/internal/configuration/validator/theme.go +++ b/internal/configuration/validator/theme.go @@ -2,7 +2,6 @@ package validator import ( "fmt" - "strings" "github.com/authelia/authelia/v4/internal/configuration/schema" "github.com/authelia/authelia/v4/internal/utils" @@ -15,6 +14,6 @@ func ValidateTheme(config *schema.Configuration, validator *schema.StructValidat } if !utils.IsStringInSlice(config.Theme, validThemeNames) { - validator.Push(fmt.Errorf(errFmtThemeName, strings.Join(validThemeNames, "', '"), config.Theme)) + validator.Push(fmt.Errorf(errFmtThemeName, strJoinOr(validThemeNames), config.Theme)) } } diff --git a/internal/configuration/validator/theme_test.go b/internal/configuration/validator/theme_test.go index abe796611..b1c8b89bc 100644 --- a/internal/configuration/validator/theme_test.go +++ b/internal/configuration/validator/theme_test.go @@ -36,7 +36,7 @@ func (suite *Theme) TestShouldRaiseErrorWhenInvalidThemeProvided() { suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "option 'theme' must be one of 'light', 'dark', 'grey', 'auto' but it is configured as 'invalid'") + suite.Assert().EqualError(suite.validator.Errors()[0], "option 'theme' must be one of 'light', 'dark', 'grey', or 'auto' but it's configured as 'invalid'") } func TestThemes(t *testing.T) { diff --git a/internal/configuration/validator/totp.go b/internal/configuration/validator/totp.go index 0a379a067..01a763f88 100644 --- a/internal/configuration/validator/totp.go +++ b/internal/configuration/validator/totp.go @@ -24,7 +24,7 @@ func ValidateTOTP(config *schema.Configuration, validator *schema.StructValidato config.TOTP.Algorithm = strings.ToUpper(config.TOTP.Algorithm) if !utils.IsStringInSlice(config.TOTP.Algorithm, schema.TOTPPossibleAlgorithms) { - validator.Push(fmt.Errorf(errFmtTOTPInvalidAlgorithm, strings.Join(schema.TOTPPossibleAlgorithms, "', '"), config.TOTP.Algorithm)) + validator.Push(fmt.Errorf(errFmtTOTPInvalidAlgorithm, strJoinOr(schema.TOTPPossibleAlgorithms), config.TOTP.Algorithm)) } } diff --git a/internal/configuration/validator/totp_test.go b/internal/configuration/validator/totp_test.go index 956f074c4..b94f7f00b 100644 --- a/internal/configuration/validator/totp_test.go +++ b/internal/configuration/validator/totp_test.go @@ -56,7 +56,9 @@ func TestValidateTOTP(t *testing.T) { Skew: schema.DefaultTOTPConfiguration.Skew, Issuer: "abc", }, - errs: []string{"totp: option 'algorithm' must be one of 'SHA1', 'SHA256', 'SHA512' but it is configured as 'SHA3'"}, + errs: []string{ + "totp: option 'algorithm' must be one of 'SHA1', 'SHA256', or 'SHA512' but it's configured as 'SHA3'", + }, }, { desc: "ShouldRaiseErrorWhenInvalidTOTPValue", @@ -69,10 +71,10 @@ func TestValidateTOTP(t *testing.T) { Issuer: "abc", }, errs: []string{ - "totp: option 'algorithm' must be one of 'SHA1', 'SHA256', 'SHA512' but it is configured as 'SHA3'", - "totp: option 'period' option must be 15 or more but it is configured as '5'", - "totp: option 'digits' must be 6 or 8 but it is configured as '20'", - "totp: option 'secret_size' must be 20 or higher but it is configured as '10'", + "totp: option 'algorithm' must be one of 'SHA1', 'SHA256', or 'SHA512' but it's configured as 'SHA3'", + "totp: option 'period' option must be 15 or more but it's configured as '5'", + "totp: option 'digits' must be 6 or 8 but it's configured as '20'", + "totp: option 'secret_size' must be 20 or higher but it's configured as '10'", }, }, } diff --git a/internal/configuration/validator/util.go b/internal/configuration/validator/util.go index b68bfee7a..59b9411b1 100644 --- a/internal/configuration/validator/util.go +++ b/internal/configuration/validator/util.go @@ -4,6 +4,8 @@ import ( "strings" "golang.org/x/net/publicsuffix" + + "github.com/authelia/authelia/v4/internal/utils" ) func isCookieDomainAPublicSuffix(domain string) (valid bool) { @@ -13,3 +15,95 @@ func isCookieDomainAPublicSuffix(domain string) (valid bool) { return len(strings.TrimLeft(domain, ".")) == len(suffix) } + +func strJoinOr(items []string) string { + return strJoinComma("or", items) +} + +func strJoinAnd(items []string) string { + return strJoinComma("and", items) +} + +func strJoinComma(word string, items []string) string { + if word == "" { + return buildJoinedString(",", "", "'", items) + } + + return buildJoinedString(",", word, "'", items) +} + +func buildJoinedString(sep, sepFinal, quote string, items []string) string { + n := len(items) + + if n == 0 { + return "" + } + + b := &strings.Builder{} + + for i := 0; i < n; i++ { + if quote != "" { + b.WriteString(quote) + } + + b.WriteString(items[i]) + + if quote != "" { + b.WriteString(quote) + } + + if i == (n - 1) { + continue + } + + if sep != "" { + if sepFinal == "" || n != 2 { + b.WriteString(sep) + } + + b.WriteString(" ") + } + + if sepFinal != "" && i == (n-2) { + b.WriteString(strings.Trim(sepFinal, " ")) + b.WriteString(" ") + } + } + + return b.String() +} + +func validateList(values, valid []string, chkDuplicate bool) (invalid, duplicates []string) { //nolint:unparam + chkValid := len(valid) != 0 + + for i, value := range values { + if chkValid { + if !utils.IsStringInSlice(value, valid) { + invalid = append(invalid, value) + + // Skip checking duplicates for invalid values. + continue + } + } + + if chkDuplicate { + for j, valueAlt := range values { + if i == j { + continue + } + + if value != valueAlt { + continue + } + + if utils.IsStringInSlice(value, duplicates) { + continue + } + + duplicates = append(duplicates, value) + } + } + } + + return +} diff --git a/internal/configuration/validator/webauthn.go b/internal/configuration/validator/webauthn.go index f139bc0f8..9a482e22c 100644 --- a/internal/configuration/validator/webauthn.go +++ b/internal/configuration/validator/webauthn.go @@ -2,7 +2,6 @@ package validator import ( "fmt" - "strings" "github.com/authelia/authelia/v4/internal/configuration/schema" "github.com/authelia/authelia/v4/internal/utils" @@ -22,13 +21,13 @@ func ValidateWebAuthn(config *schema.Configuration, validator *schema.StructVali case config.WebAuthn.ConveyancePreference == "": config.WebAuthn.ConveyancePreference = schema.DefaultWebAuthnConfiguration.ConveyancePreference case !utils.IsStringInSlice(string(config.WebAuthn.ConveyancePreference), validWebAuthnConveyancePreferences): - validator.Push(fmt.Errorf(errFmtWebAuthnConveyancePreference, strings.Join(validWebAuthnConveyancePreferences, "', '"), config.WebAuthn.ConveyancePreference)) + validator.Push(fmt.Errorf(errFmtWebAuthnConveyancePreference, strJoinOr(validWebAuthnConveyancePreferences), config.WebAuthn.ConveyancePreference)) } switch { case config.WebAuthn.UserVerification == "": config.WebAuthn.UserVerification = schema.DefaultWebAuthnConfiguration.UserVerification case !utils.IsStringInSlice(string(config.WebAuthn.UserVerification), validWebAuthnUserVerificationRequirement): - validator.Push(fmt.Errorf(errFmtWebAuthnUserVerification, config.WebAuthn.UserVerification)) + validator.Push(fmt.Errorf(errFmtWebAuthnUserVerification, strJoinOr(validWebAuthnConveyancePreferences), config.WebAuthn.UserVerification)) } } diff --git a/internal/configuration/validator/webauthn_test.go b/internal/configuration/validator/webauthn_test.go index 2d78ea16f..ea93f0382 100644 --- a/internal/configuration/validator/webauthn_test.go +++ b/internal/configuration/validator/webauthn_test.go @@ -93,6 +93,6 @@ func TestWebAuthnShouldRaiseErrorsOnInvalidOptions(t *testing.T) { require.Len(t, validator.Errors(), 2) - assert.EqualError(t, validator.Errors()[0], "webauthn: option 'attestation_conveyance_preference' must be one of 'none', 'indirect', 'direct' but it is configured as 'no'") - assert.EqualError(t, validator.Errors()[1], "webauthn: option 'user_verification' must be one of 'discouraged', 'preferred', 'required' but it is configured as 'yes'") + assert.EqualError(t, validator.Errors()[0], "webauthn: option 'attestation_conveyance_preference' must be one of 'none', 'indirect', or 'direct' but it's configured as 'no'") + assert.EqualError(t, validator.Errors()[1], "webauthn: option 'user_verification' must be one of 'none', 'indirect', or 'direct' but it's configured as 'yes'") } diff --git a/internal/handlers/handler_oidc_authorization.go b/internal/handlers/handler_oidc_authorization.go index 5cb193920..9924c5ec1 100644 --- a/internal/handlers/handler_oidc_authorization.go +++ b/internal/handlers/handler_oidc_authorization.go @@ -21,7 +21,7 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr var ( requester fosite.AuthorizeRequester responder fosite.AuthorizeResponder - client *oidc.Client + client oidc.Client authTime time.Time issuer *url.URL err error @@ -117,7 +117,7 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr extraClaims := oidcGrantRequests(requester, consent, &userSession) - if authTime, err = userSession.AuthenticatedTime(client.Policy); err != nil { + if authTime, err = userSession.AuthenticatedTime(client.GetAuthorizationPolicy()); err != nil { ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' could not be processed: error occurred checking authentication time: %+v", requester.GetID(), client.GetID(), err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, fosite.ErrServerError.WithHint("Could not obtain the authentication time.")) @@ -178,7 +178,7 @@ func OpenIDConnectPushedAuthorizationRequest(ctx *middlewares.AutheliaCtx, rw ht return } - var client *oidc.Client + var client oidc.Client clientID := requester.GetClient().GetID() diff --git a/internal/handlers/handler_oidc_authorization_consent.go b/internal/handlers/handler_oidc_authorization_consent.go index fef764701..1a9c400f8 100644 --- a/internal/handlers/handler_oidc_authorization_consent.go +++ b/internal/handlers/handler_oidc_authorization_consent.go @@ -18,7 +18,7 @@ import ( "github.com/authelia/authelia/v4/internal/utils" ) -func handleOIDCAuthorizationConsent(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client, +func handleOIDCAuthorizationConsent(ctx *middlewares.AutheliaCtx, issuer *url.URL, client oidc.Client, userSession session.UserSession, rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) { var ( @@ -33,14 +33,14 @@ func handleOIDCAuthorizationConsent(ctx *middlewares.AutheliaCtx, issuer *url.UR handler = handleOIDCAuthorizationConsentNotAuthenticated case client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel): if subject, err = ctx.Providers.OpenIDConnect.GetSubject(ctx, client.GetSectorIdentifier(), userSession.Username); err != nil { - ctx.Logger.Errorf(logFmtErrConsentCantGetSubject, requester.GetID(), client.GetID(), client.Consent, userSession.Username, client.GetSectorIdentifier(), err) + ctx.Logger.Errorf(logFmtErrConsentCantGetSubject, requester.GetID(), client.GetID(), client.GetConsentPolicy(), userSession.Username, client.GetSectorIdentifier(), err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrSubjectCouldNotLookup) return nil, true } - switch client.Consent.Mode { + switch client.GetConsentPolicy().Mode { case oidc.ClientConsentModeExplicit: handler = handleOIDCAuthorizationConsentModeExplicit case oidc.ClientConsentModeImplicit: @@ -56,7 +56,7 @@ func handleOIDCAuthorizationConsent(ctx *middlewares.AutheliaCtx, issuer *url.UR } default: if subject, err = ctx.Providers.OpenIDConnect.GetSubject(ctx, client.GetSectorIdentifier(), userSession.Username); err != nil { - ctx.Logger.Errorf(logFmtErrConsentCantGetSubject, requester.GetID(), client.GetID(), client.Consent, userSession.Username, client.GetSectorIdentifier(), err) + ctx.Logger.Errorf(logFmtErrConsentCantGetSubject, requester.GetID(), client.GetID(), client.GetConsentPolicy(), userSession.Username, client.GetSectorIdentifier(), err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrSubjectCouldNotLookup) @@ -69,7 +69,7 @@ func handleOIDCAuthorizationConsent(ctx *middlewares.AutheliaCtx, issuer *url.UR return handler(ctx, issuer, client, userSession, subject, rw, r, requester) } -func handleOIDCAuthorizationConsentNotAuthenticated(_ *middlewares.AutheliaCtx, issuer *url.URL, _ *oidc.Client, +func handleOIDCAuthorizationConsentNotAuthenticated(_ *middlewares.AutheliaCtx, issuer *url.URL, _ oidc.Client, _ session.UserSession, _ uuid.UUID, rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) { redirectionURL := handleOIDCAuthorizationConsentGetRedirectionURL(issuer, nil, requester) @@ -79,17 +79,17 @@ func handleOIDCAuthorizationConsentNotAuthenticated(_ *middlewares.AutheliaCtx, return nil, true } -func handleOIDCAuthorizationConsentGenerate(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client, +func handleOIDCAuthorizationConsentGenerate(ctx *middlewares.AutheliaCtx, issuer *url.URL, client oidc.Client, userSession session.UserSession, subject uuid.UUID, rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) { var ( err error ) - ctx.Logger.Debugf(logFmtDbgConsentGenerate, requester.GetID(), client.GetID(), client.Consent) + ctx.Logger.Debugf(logFmtDbgConsentGenerate, requester.GetID(), client.GetID(), client.GetConsentPolicy()) if len(ctx.QueryArgs().PeekBytes(qryArgConsentID)) != 0 { - ctx.Logger.Errorf(logFmtErrConsentGenerateError, requester.GetID(), client.GetID(), client.Consent, "generating", errors.New("consent id value was present when it should be absent")) + ctx.Logger.Errorf(logFmtErrConsentGenerateError, requester.GetID(), client.GetID(), client.GetConsentPolicy(), "generating", errors.New("consent id value was present when it should be absent")) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotGenerate) @@ -97,7 +97,7 @@ func handleOIDCAuthorizationConsentGenerate(ctx *middlewares.AutheliaCtx, issuer } if consent, err = model.NewOAuth2ConsentSession(subject, requester); err != nil { - ctx.Logger.Errorf(logFmtErrConsentGenerateError, requester.GetID(), client.GetID(), client.Consent, "generating", err) + ctx.Logger.Errorf(logFmtErrConsentGenerateError, requester.GetID(), client.GetID(), client.GetConsentPolicy(), "generating", err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotGenerate) @@ -105,7 +105,7 @@ func handleOIDCAuthorizationConsentGenerate(ctx *middlewares.AutheliaCtx, issuer } if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSession(ctx, *consent); err != nil { - ctx.Logger.Errorf(logFmtErrConsentGenerateError, requester.GetID(), client.GetID(), client.Consent, "saving", err) + ctx.Logger.Errorf(logFmtErrConsentGenerateError, requester.GetID(), client.GetID(), client.GetConsentPolicy(), "saving", err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave) @@ -117,7 +117,7 @@ func handleOIDCAuthorizationConsentGenerate(ctx *middlewares.AutheliaCtx, issuer return consent, true } -func handleOIDCAuthorizationConsentRedirect(ctx *middlewares.AutheliaCtx, issuer *url.URL, consent *model.OAuth2ConsentSession, client *oidc.Client, +func handleOIDCAuthorizationConsentRedirect(ctx *middlewares.AutheliaCtx, issuer *url.URL, consent *model.OAuth2ConsentSession, client oidc.Client, userSession session.UserSession, rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) { var location *url.URL @@ -130,14 +130,14 @@ func handleOIDCAuthorizationConsentRedirect(ctx *middlewares.AutheliaCtx, issuer location.RawQuery = query.Encode() - ctx.Logger.Debugf(logFmtDbgConsentAuthenticationSufficiency, requester.GetID(), client.GetID(), client.Consent, userSession.AuthenticationLevel.String(), "sufficient", client.Policy) + ctx.Logger.Debugf(logFmtDbgConsentAuthenticationSufficiency, requester.GetID(), client.GetID(), client.GetConsentPolicy(), userSession.AuthenticationLevel.String(), "sufficient", client.GetAuthorizationPolicy()) } else { location = handleOIDCAuthorizationConsentGetRedirectionURL(issuer, consent, requester) - ctx.Logger.Debugf(logFmtDbgConsentAuthenticationSufficiency, requester.GetID(), client.GetID(), client.Consent, userSession.AuthenticationLevel.String(), "insufficient", client.Policy) + ctx.Logger.Debugf(logFmtDbgConsentAuthenticationSufficiency, requester.GetID(), client.GetID(), client.GetConsentPolicy(), userSession.AuthenticationLevel.String(), "insufficient", client.GetAuthorizationPolicy()) } - ctx.Logger.Debugf(logFmtDbgConsentRedirect, requester.GetID(), client.GetID(), client.Consent, location) + ctx.Logger.Debugf(logFmtDbgConsentRedirect, requester.GetID(), client.GetID(), client.GetConsentPolicy(), location) http.Redirect(rw, r, location.String(), http.StatusFound) } @@ -170,7 +170,7 @@ func handleOIDCAuthorizationConsentGetRedirectionURL(issuer *url.URL, consent *m return redirectURL } -func verifyOIDCUserAuthorizedForConsent(ctx *middlewares.AutheliaCtx, client *oidc.Client, userSession session.UserSession, consent *model.OAuth2ConsentSession, subject uuid.UUID) (err error) { +func verifyOIDCUserAuthorizedForConsent(ctx *middlewares.AutheliaCtx, client oidc.Client, userSession session.UserSession, consent *model.OAuth2ConsentSession, subject uuid.UUID) (err error) { var sid uint32 if client == nil { diff --git a/internal/handlers/handler_oidc_authorization_consent_explicit.go b/internal/handlers/handler_oidc_authorization_consent_explicit.go index 9388c74d8..901806c9c 100644 --- a/internal/handlers/handler_oidc_authorization_consent_explicit.go +++ b/internal/handlers/handler_oidc_authorization_consent_explicit.go @@ -13,7 +13,7 @@ import ( "github.com/authelia/authelia/v4/internal/session" ) -func handleOIDCAuthorizationConsentModeExplicit(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client, +func handleOIDCAuthorizationConsentModeExplicit(ctx *middlewares.AutheliaCtx, issuer *url.URL, client oidc.Client, userSession session.UserSession, subject uuid.UUID, rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) { var ( @@ -28,7 +28,7 @@ func handleOIDCAuthorizationConsentModeExplicit(ctx *middlewares.AutheliaCtx, is return handleOIDCAuthorizationConsentGenerate(ctx, issuer, client, userSession, subject, rw, r, requester) default: if consentID, err = uuid.ParseBytes(bytesConsentID); err != nil { - ctx.Logger.Errorf(logFmtErrConsentParseChallengeID, requester.GetID(), client.GetID(), client.Consent, bytesConsentID, err) + ctx.Logger.Errorf(logFmtErrConsentParseChallengeID, requester.GetID(), client.GetID(), client.GetConsentPolicy(), bytesConsentID, err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentMalformedChallengeID) @@ -39,7 +39,7 @@ func handleOIDCAuthorizationConsentModeExplicit(ctx *middlewares.AutheliaCtx, is } } -func handleOIDCAuthorizationConsentModeExplicitWithID(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client, +func handleOIDCAuthorizationConsentModeExplicitWithID(ctx *middlewares.AutheliaCtx, issuer *url.URL, client oidc.Client, userSession session.UserSession, subject uuid.UUID, consentID uuid.UUID, rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) { var ( @@ -47,7 +47,7 @@ func handleOIDCAuthorizationConsentModeExplicitWithID(ctx *middlewares.AutheliaC ) if consentID.ID() == 0 { - ctx.Logger.Errorf(logFmtErrConsentZeroID, requester.GetID(), client.GetID(), client.Consent) + ctx.Logger.Errorf(logFmtErrConsentZeroID, requester.GetID(), client.GetID(), client.GetConsentPolicy()) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup) @@ -55,7 +55,7 @@ func handleOIDCAuthorizationConsentModeExplicitWithID(ctx *middlewares.AutheliaC } if consent, err = ctx.Providers.StorageProvider.LoadOAuth2ConsentSessionByChallengeID(ctx, consentID); err != nil { - ctx.Logger.Errorf(logFmtErrConsentLookupLoadingSession, requester.GetID(), client.GetID(), client.Consent, consentID, err) + ctx.Logger.Errorf(logFmtErrConsentLookupLoadingSession, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consentID, err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup) @@ -63,7 +63,7 @@ func handleOIDCAuthorizationConsentModeExplicitWithID(ctx *middlewares.AutheliaC } if subject.ID() != consent.Subject.UUID.ID() { - ctx.Logger.Errorf(logFmtErrConsentSessionSubjectNotAuthorized, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, userSession.Username, subject, consent.Subject.UUID) + ctx.Logger.Errorf(logFmtErrConsentSessionSubjectNotAuthorized, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, userSession.Username, subject, consent.Subject.UUID) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup) @@ -71,7 +71,7 @@ func handleOIDCAuthorizationConsentModeExplicitWithID(ctx *middlewares.AutheliaC } if !consent.CanGrant() { - ctx.Logger.Errorf(logFmtErrConsentCantGrant, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, "explicit") + ctx.Logger.Errorf(logFmtErrConsentCantGrant, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, "explicit") ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotPerform) @@ -80,7 +80,7 @@ func handleOIDCAuthorizationConsentModeExplicitWithID(ctx *middlewares.AutheliaC if !consent.IsAuthorized() { if consent.Responded() { - ctx.Logger.Errorf(logFmtErrConsentCantGrantRejected, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID) + ctx.Logger.Errorf(logFmtErrConsentCantGrantRejected, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, fosite.ErrAccessDenied) diff --git a/internal/handlers/handler_oidc_authorization_consent_implicit.go b/internal/handlers/handler_oidc_authorization_consent_implicit.go index 5ec5b3ad1..b0f38c0b0 100644 --- a/internal/handlers/handler_oidc_authorization_consent_implicit.go +++ b/internal/handlers/handler_oidc_authorization_consent_implicit.go @@ -13,7 +13,7 @@ import ( "github.com/authelia/authelia/v4/internal/session" ) -func handleOIDCAuthorizationConsentModeImplicit(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client, +func handleOIDCAuthorizationConsentModeImplicit(ctx *middlewares.AutheliaCtx, issuer *url.URL, client oidc.Client, userSession session.UserSession, subject uuid.UUID, rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) { var ( @@ -26,7 +26,7 @@ func handleOIDCAuthorizationConsentModeImplicit(ctx *middlewares.AutheliaCtx, is return handleOIDCAuthorizationConsentModeImplicitWithoutID(ctx, issuer, client, userSession, subject, rw, r, requester) default: if consentID, err = uuid.ParseBytes(bytesConsentID); err != nil { - ctx.Logger.Errorf(logFmtErrConsentParseChallengeID, requester.GetID(), client.GetID(), client.Consent, bytesConsentID, err) + ctx.Logger.Errorf(logFmtErrConsentParseChallengeID, requester.GetID(), client.GetID(), client.GetConsentPolicy(), bytesConsentID, err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentMalformedChallengeID) @@ -37,7 +37,7 @@ func handleOIDCAuthorizationConsentModeImplicit(ctx *middlewares.AutheliaCtx, is } } -func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaCtx, _ *url.URL, client *oidc.Client, +func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaCtx, _ *url.URL, client oidc.Client, userSession session.UserSession, subject uuid.UUID, consentID uuid.UUID, rw http.ResponseWriter, _ *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) { var ( @@ -45,7 +45,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaC ) if consentID.ID() == 0 { - ctx.Logger.Errorf(logFmtErrConsentZeroID, requester.GetID(), client.GetID(), client.Consent) + ctx.Logger.Errorf(logFmtErrConsentZeroID, requester.GetID(), client.GetID(), client.GetConsentPolicy()) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup) @@ -53,7 +53,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaC } if consent, err = ctx.Providers.StorageProvider.LoadOAuth2ConsentSessionByChallengeID(ctx, consentID); err != nil { - ctx.Logger.Errorf(logFmtErrConsentLookupLoadingSession, requester.GetID(), client.GetID(), client.Consent, consentID, err) + ctx.Logger.Errorf(logFmtErrConsentLookupLoadingSession, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consentID, err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup) @@ -61,7 +61,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaC } if subject.ID() != consent.Subject.UUID.ID() { - ctx.Logger.Errorf(logFmtErrConsentSessionSubjectNotAuthorized, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, userSession.Username, subject, consent.Subject.UUID) + ctx.Logger.Errorf(logFmtErrConsentSessionSubjectNotAuthorized, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, userSession.Username, subject, consent.Subject.UUID) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup) @@ -69,7 +69,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaC } if !consent.CanGrant() { - ctx.Logger.Errorf(logFmtErrConsentCantGrant, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, "implicit") + ctx.Logger.Errorf(logFmtErrConsentCantGrant, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, "implicit") ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotPerform) @@ -79,7 +79,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaC consent.Grant() if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSessionResponse(ctx, *consent, false); err != nil { - ctx.Logger.Errorf(logFmtErrConsentSaveSessionResponse, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err) + ctx.Logger.Errorf(logFmtErrConsentSaveSessionResponse, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave) @@ -89,7 +89,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaC return consent, false } -func handleOIDCAuthorizationConsentModeImplicitWithoutID(ctx *middlewares.AutheliaCtx, _ *url.URL, client *oidc.Client, +func handleOIDCAuthorizationConsentModeImplicitWithoutID(ctx *middlewares.AutheliaCtx, _ *url.URL, client oidc.Client, _ session.UserSession, subject uuid.UUID, rw http.ResponseWriter, _ *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) { var ( @@ -97,7 +97,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithoutID(ctx *middlewares.Authel ) if consent, err = model.NewOAuth2ConsentSession(subject, requester); err != nil { - ctx.Logger.Errorf(logFmtErrConsentGenerate, requester.GetID(), client.GetID(), client.Consent, err) + ctx.Logger.Errorf(logFmtErrConsentGenerate, requester.GetID(), client.GetID(), client.GetConsentPolicy(), err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotGenerate) @@ -105,7 +105,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithoutID(ctx *middlewares.Authel } if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSession(ctx, *consent); err != nil { - ctx.Logger.Errorf(logFmtErrConsentSaveSession, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err) + ctx.Logger.Errorf(logFmtErrConsentSaveSession, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave) @@ -113,7 +113,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithoutID(ctx *middlewares.Authel } if consent, err = ctx.Providers.StorageProvider.LoadOAuth2ConsentSessionByChallengeID(ctx, consent.ChallengeID); err != nil { - ctx.Logger.Errorf(logFmtErrConsentSaveSession, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err) + ctx.Logger.Errorf(logFmtErrConsentSaveSession, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave) @@ -123,7 +123,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithoutID(ctx *middlewares.Authel consent.Grant() if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSessionResponse(ctx, *consent, false); err != nil { - ctx.Logger.Errorf(logFmtErrConsentSaveSessionResponse, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err) + ctx.Logger.Errorf(logFmtErrConsentSaveSessionResponse, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave) diff --git a/internal/handlers/handler_oidc_authorization_consent_pre_configured.go b/internal/handlers/handler_oidc_authorization_consent_pre_configured.go index 0b8b26229..9f9e708f8 100644 --- a/internal/handlers/handler_oidc_authorization_consent_pre_configured.go +++ b/internal/handlers/handler_oidc_authorization_consent_pre_configured.go @@ -17,7 +17,7 @@ import ( "github.com/authelia/authelia/v4/internal/storage" ) -func handleOIDCAuthorizationConsentModePreConfigured(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client, +func handleOIDCAuthorizationConsentModePreConfigured(ctx *middlewares.AutheliaCtx, issuer *url.URL, client oidc.Client, userSession session.UserSession, subject uuid.UUID, rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) { var ( @@ -32,7 +32,7 @@ func handleOIDCAuthorizationConsentModePreConfigured(ctx *middlewares.AutheliaCt return handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx, issuer, client, userSession, subject, rw, r, requester) default: if consentID, err = uuid.ParseBytes(bytesConsentID); err != nil { - ctx.Logger.Errorf(logFmtErrConsentParseChallengeID, requester.GetID(), client.GetID(), client.Consent, bytesConsentID, err) + ctx.Logger.Errorf(logFmtErrConsentParseChallengeID, requester.GetID(), client.GetID(), client.GetConsentPolicy(), bytesConsentID, err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentMalformedChallengeID) @@ -43,7 +43,7 @@ func handleOIDCAuthorizationConsentModePreConfigured(ctx *middlewares.AutheliaCt } } -func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client, +func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.AutheliaCtx, issuer *url.URL, client oidc.Client, userSession session.UserSession, subject uuid.UUID, consentID uuid.UUID, rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) { var ( @@ -52,7 +52,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.Auth ) if consentID.ID() == 0 { - ctx.Logger.Errorf(logFmtErrConsentZeroID, requester.GetID(), client.GetID(), client.Consent) + ctx.Logger.Errorf(logFmtErrConsentZeroID, requester.GetID(), client.GetID(), client.GetConsentPolicy()) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup) @@ -60,7 +60,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.Auth } if consent, err = ctx.Providers.StorageProvider.LoadOAuth2ConsentSessionByChallengeID(ctx, consentID); err != nil { - ctx.Logger.Errorf(logFmtErrConsentLookupLoadingSession, requester.GetID(), client.GetID(), client.Consent, consentID, err) + ctx.Logger.Errorf(logFmtErrConsentLookupLoadingSession, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consentID, err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup) @@ -68,7 +68,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.Auth } if subject.ID() != consent.Subject.UUID.ID() { - ctx.Logger.Errorf(logFmtErrConsentSessionSubjectNotAuthorized, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, userSession.Username, subject, consent.Subject.UUID) + ctx.Logger.Errorf(logFmtErrConsentSessionSubjectNotAuthorized, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, userSession.Username, subject, consent.Subject.UUID) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup) @@ -76,7 +76,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.Auth } if !consent.CanGrant() { - ctx.Logger.Errorf(logFmtErrConsentCantGrantPreConf, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID) + ctx.Logger.Errorf(logFmtErrConsentCantGrantPreConf, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotPerform) @@ -84,7 +84,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.Auth } if config, err = handleOIDCAuthorizationConsentModePreConfiguredGetPreConfig(ctx, client, subject, requester); err != nil { - ctx.Logger.Errorf(logFmtErrConsentPreConfLookup, requester.GetID(), client.GetID(), client.Consent, err) + ctx.Logger.Errorf(logFmtErrConsentPreConfLookup, requester.GetID(), client.GetID(), client.GetConsentPolicy(), err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup) @@ -97,7 +97,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.Auth consent.PreConfiguration = sql.NullInt64{Int64: config.ID, Valid: true} if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSessionResponse(ctx, *consent, false); err != nil { - ctx.Logger.Errorf(logFmtErrConsentSaveSessionResponse, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err) + ctx.Logger.Errorf(logFmtErrConsentSaveSessionResponse, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave) @@ -109,7 +109,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.Auth if !consent.IsAuthorized() { if consent.Responded() { - ctx.Logger.Errorf(logFmtErrConsentCantGrantRejected, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID) + ctx.Logger.Errorf(logFmtErrConsentCantGrantRejected, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, fosite.ErrAccessDenied) @@ -124,7 +124,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.Auth return consent, false } -func handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client, +func handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx *middlewares.AutheliaCtx, issuer *url.URL, client oidc.Client, userSession session.UserSession, subject uuid.UUID, rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) { var ( @@ -133,7 +133,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx *middlewares.A ) if config, err = handleOIDCAuthorizationConsentModePreConfiguredGetPreConfig(ctx, client, subject, requester); err != nil { - ctx.Logger.Errorf(logFmtErrConsentPreConfLookup, requester.GetID(), client.GetID(), client.Consent, err) + ctx.Logger.Errorf(logFmtErrConsentPreConfLookup, requester.GetID(), client.GetID(), client.GetConsentPolicy(), err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup) @@ -145,7 +145,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx *middlewares.A } if consent, err = model.NewOAuth2ConsentSession(subject, requester); err != nil { - ctx.Logger.Errorf(logFmtErrConsentGenerate, requester.GetID(), client.GetID(), client.Consent, err) + ctx.Logger.Errorf(logFmtErrConsentGenerate, requester.GetID(), client.GetID(), client.GetConsentPolicy(), err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotGenerate) @@ -153,7 +153,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx *middlewares.A } if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSession(ctx, *consent); err != nil { - ctx.Logger.Errorf(logFmtErrConsentSaveSession, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err) + ctx.Logger.Errorf(logFmtErrConsentSaveSession, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave) @@ -161,7 +161,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx *middlewares.A } if consent, err = ctx.Providers.StorageProvider.LoadOAuth2ConsentSessionByChallengeID(ctx, consent.ChallengeID); err != nil { - ctx.Logger.Errorf(logFmtErrConsentSaveSession, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err) + ctx.Logger.Errorf(logFmtErrConsentSaveSession, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave) @@ -173,7 +173,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx *middlewares.A consent.PreConfiguration = sql.NullInt64{Int64: config.ID, Valid: true} if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSessionResponse(ctx, *consent, false); err != nil { - ctx.Logger.Errorf(logFmtErrConsentSaveSessionResponse, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err) + ctx.Logger.Errorf(logFmtErrConsentSaveSessionResponse, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave) @@ -183,12 +183,12 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx *middlewares.A return consent, false } -func handleOIDCAuthorizationConsentModePreConfiguredGetPreConfig(ctx *middlewares.AutheliaCtx, client *oidc.Client, subject uuid.UUID, requester fosite.Requester) (config *model.OAuth2ConsentPreConfig, err error) { +func handleOIDCAuthorizationConsentModePreConfiguredGetPreConfig(ctx *middlewares.AutheliaCtx, client oidc.Client, subject uuid.UUID, requester fosite.Requester) (config *model.OAuth2ConsentPreConfig, err error) { var ( rows *storage.ConsentPreConfigRows ) - ctx.Logger.Debugf(logFmtDbgConsentPreConfTryingLookup, requester.GetID(), client.GetID(), client.Consent, client.GetID(), subject, strings.Join(requester.GetRequestedScopes(), " ")) + ctx.Logger.Debugf(logFmtDbgConsentPreConfTryingLookup, requester.GetID(), client.GetID(), client.GetConsentPolicy(), client.GetID(), subject, strings.Join(requester.GetRequestedScopes(), " ")) if rows, err = ctx.Providers.StorageProvider.LoadOAuth2ConsentPreConfigurations(ctx, client.GetID(), subject); err != nil { return nil, fmt.Errorf("error loading rows: %w", err) @@ -196,7 +196,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredGetPreConfig(ctx *middleware defer func() { if err := rows.Close(); err != nil { - ctx.Logger.Errorf(logFmtErrConsentPreConfRowsClose, requester.GetID(), client.GetID(), client.Consent, err) + ctx.Logger.Errorf(logFmtErrConsentPreConfRowsClose, requester.GetID(), client.GetID(), client.GetConsentPolicy(), err) } }() @@ -208,13 +208,13 @@ func handleOIDCAuthorizationConsentModePreConfiguredGetPreConfig(ctx *middleware } if config.HasExactGrants(scopes, audience) && config.CanConsent() { - ctx.Logger.Debugf(logFmtDbgConsentPreConfSuccessfulLookup, requester.GetID(), client.GetID(), client.Consent, client.GetID(), subject, strings.Join(requester.GetRequestedScopes(), " "), config.ID) + ctx.Logger.Debugf(logFmtDbgConsentPreConfSuccessfulLookup, requester.GetID(), client.GetID(), client.GetConsentPolicy(), client.GetID(), subject, strings.Join(requester.GetRequestedScopes(), " "), config.ID) return config, nil } } - ctx.Logger.Debugf(logFmtDbgConsentPreConfUnsuccessfulLookup, requester.GetID(), client.GetID(), client.Consent, client.GetID(), subject, strings.Join(requester.GetRequestedScopes(), " ")) + ctx.Logger.Debugf(logFmtDbgConsentPreConfUnsuccessfulLookup, requester.GetID(), client.GetID(), client.GetConsentPolicy(), client.GetID(), subject, strings.Join(requester.GetRequestedScopes(), " ")) return nil, nil } diff --git a/internal/handlers/handler_oidc_consent.go b/internal/handlers/handler_oidc_consent.go index bea6e9067..cc97a2889 100644 --- a/internal/handlers/handler_oidc_consent.go +++ b/internal/handlers/handler_oidc_consent.go @@ -32,7 +32,7 @@ func OpenIDConnectConsentGET(ctx *middlewares.AutheliaCtx) { var ( consent *model.OAuth2ConsentSession - client *oidc.Client + client oidc.Client handled bool ) @@ -70,7 +70,7 @@ func OpenIDConnectConsentPOST(ctx *middlewares.AutheliaCtx) { var ( userSession session.UserSession consent *model.OAuth2ConsentSession - client *oidc.Client + client oidc.Client handled bool ) @@ -90,12 +90,12 @@ func OpenIDConnectConsentPOST(ctx *middlewares.AutheliaCtx) { consent.Grant() if bodyJSON.PreConfigure { - if client.Consent.Mode == oidc.ClientConsentModePreConfigured { + if client.GetConsentPolicy().Mode == oidc.ClientConsentModePreConfigured { config := model.OAuth2ConsentPreConfig{ ClientID: consent.ClientID, Subject: consent.Subject.UUID, CreatedAt: time.Now(), - ExpiresAt: sql.NullTime{Time: time.Now().Add(client.Consent.Duration), Valid: true}, + ExpiresAt: sql.NullTime{Time: time.Now().Add(client.GetConsentPolicy().Duration), Valid: true}, Scopes: consent.GrantedScopes, Audience: consent.GrantedAudience, } @@ -151,7 +151,7 @@ func OpenIDConnectConsentPOST(ctx *middlewares.AutheliaCtx) { } } -func oidcConsentGetSessionsAndClient(ctx *middlewares.AutheliaCtx, consentID uuid.UUID) (userSession session.UserSession, consent *model.OAuth2ConsentSession, client *oidc.Client, handled bool) { +func oidcConsentGetSessionsAndClient(ctx *middlewares.AutheliaCtx, consentID uuid.UUID) (userSession session.UserSession, consent *model.OAuth2ConsentSession, client oidc.Client, handled bool) { var ( err error ) @@ -185,7 +185,7 @@ func oidcConsentGetSessionsAndClient(ctx *middlewares.AutheliaCtx, consentID uui return userSession, nil, nil, true } - switch client.Consent.Mode { + switch client.GetConsentPolicy().Mode { case oidc.ClientConsentModeImplicit: ctx.Logger.Errorf("Unable to perform OpenID Connect Consent for user '%s' and client id '%s': the client is using the implicit consent mode", userSession.Username, consent.ClientID) ctx.ReplyForbidden() diff --git a/internal/handlers/handler_oidc_userinfo.go b/internal/handlers/handler_oidc_userinfo.go index 3e1314d72..c3dc4f9e6 100644 --- a/internal/handlers/handler_oidc_userinfo.go +++ b/internal/handlers/handler_oidc_userinfo.go @@ -23,7 +23,7 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, var ( tokenType fosite.TokenType requester fosite.AccessRequester - client *oidc.Client + client oidc.Client err error ) @@ -99,7 +99,7 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, ctx.Logger.Tracef("UserInfo Response with id '%s' on client with id '%s' is being sent with the following claims: %+v", requester.GetID(), clientID, claims) - switch client.UserinfoSigningAlgorithm { + switch client.GetUserinfoSigningAlgorithm() { case oidc.SigningAlgorithmRSAWithSHA256: var jti uuid.UUID @@ -129,6 +129,6 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, case oidc.SigningAlgorithmNone, "": ctx.Providers.OpenIDConnect.Write(rw, req, claims) default: - ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(fosite.ErrServerError.WithHintf("Unsupported UserInfo signing algorithm '%s'.", client.UserinfoSigningAlgorithm))) + ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(fosite.ErrServerError.WithHintf("Unsupported UserInfo signing algorithm '%s'.", client.GetUserinfoSigningAlgorithm()))) } } diff --git a/internal/handlers/response.go b/internal/handlers/response.go index b3de77c3c..9b8bda53f 100644 --- a/internal/handlers/response.go +++ b/internal/handlers/response.go @@ -178,7 +178,7 @@ func handleOIDCWorkflowResponseWithTargetURL(ctx *middlewares.AutheliaCtx, targe func handleOIDCWorkflowResponseWithID(ctx *middlewares.AutheliaCtx, id string) { var ( workflowID uuid.UUID - client *oidc.Client + client oidc.Client consent *model.OAuth2ConsentSession err error ) @@ -210,19 +210,19 @@ func handleOIDCWorkflowResponseWithID(ctx *middlewares.AutheliaCtx, id string) { var userSession session.UserSession if userSession, err = ctx.GetSession(); err != nil { - ctx.Error(fmt.Errorf("unable to redirect for authorization/consent for client with id '%s' with consent challenge id '%s': failed to lookup session: %w", client.ID, consent.ChallengeID, err), messageAuthenticationFailed) + ctx.Error(fmt.Errorf("unable to redirect for authorization/consent for client with id '%s' with consent challenge id '%s': failed to lookup session: %w", client.GetID(), consent.ChallengeID, err), messageAuthenticationFailed) return } if userSession.IsAnonymous() { - ctx.Error(fmt.Errorf("unable to redirect for authorization/consent for client with id '%s' with consent challenge id '%s': user is anonymous", client.ID, consent.ChallengeID), messageAuthenticationFailed) + ctx.Error(fmt.Errorf("unable to redirect for authorization/consent for client with id '%s' with consent challenge id '%s': user is anonymous", client.GetID(), consent.ChallengeID), messageAuthenticationFailed) return } if !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel) { - ctx.Logger.Warnf("OpenID Connect client '%s' requires 2FA, cannot be redirected yet", client.ID) + ctx.Logger.Warnf("OpenID Connect client '%s' requires 2FA, cannot be redirected yet", client.GetID()) ctx.ReplyOK() return diff --git a/internal/handlers/types.go b/internal/handlers/types.go index e9ca5d64e..1b8102759 100644 --- a/internal/handlers/types.go +++ b/internal/handlers/types.go @@ -154,7 +154,7 @@ type PasswordPolicyBody struct { } type handlerAuthorizationConsent func( - ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client, + ctx *middlewares.AutheliaCtx, issuer *url.URL, client oidc.Client, userSession session.UserSession, subject uuid.UUID, rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) diff --git a/internal/oidc/client.go b/internal/oidc/client.go index 41d302d03..71b43d867 100644 --- a/internal/oidc/client.go +++ b/internal/oidc/client.go @@ -1,8 +1,10 @@ package oidc import ( + "github.com/go-crypt/crypt/algorithm" "github.com/ory/fosite" "github.com/ory/x/errorsx" + "gopkg.in/square/go-jose.v2" "github.com/authelia/authelia/v4/internal/authentication" "github.com/authelia/authelia/v4/internal/authorization" @@ -11,8 +13,8 @@ import ( ) // NewClient creates a new Client. -func NewClient(config schema.OpenIDConnectClientConfiguration) (client *Client) { - client = &Client{ +func NewClient(config schema.OpenIDConnectClientConfiguration) (client Client) { + base := &BaseClient{ ID: config.ID, Description: config.Description, Secret: config.Secret, @@ -40,14 +42,165 @@ func NewClient(config schema.OpenIDConnectClientConfiguration) (client *Client) } for _, mode := range config.ResponseModes { - client.ResponseModes = append(client.ResponseModes, fosite.ResponseModeType(mode)) + base.ResponseModes = append(base.ResponseModes, fosite.ResponseModeType(mode)) + } + + if config.TokenEndpointAuthMethod != "" && config.TokenEndpointAuthMethod != "auto" { + client = &FullClient{ + BaseClient: base, + TokenEndpointAuthMethod: config.TokenEndpointAuthMethod, + } + } else { + client = base } return client } +// GetID returns the ID. +func (c *BaseClient) GetID() string { + return c.ID +} + +// GetDescription returns the Description. +func (c *BaseClient) GetDescription() string { + if c.Description == "" { + c.Description = c.GetID() + } + + return c.Description +} + +// GetSecret returns the Secret. +func (c *BaseClient) GetSecret() algorithm.Digest { + return c.Secret +} + +// GetSectorIdentifier returns the SectorIdentifier for this client. +func (c *BaseClient) GetSectorIdentifier() string { + return c.SectorIdentifier +} + +// GetHashedSecret returns the Secret. +func (c *BaseClient) GetHashedSecret() (secret []byte) { + if c.Secret == nil { + return []byte(nil) + } + + return []byte(c.Secret.Encode()) +} + +// GetRedirectURIs returns the RedirectURIs. +func (c *BaseClient) GetRedirectURIs() (redirectURIs []string) { + return c.RedirectURIs +} + +// GetGrantTypes returns the GrantTypes. +func (c *BaseClient) GetGrantTypes() fosite.Arguments { + if len(c.GrantTypes) == 0 { + return fosite.Arguments{"authorization_code"} + } + + return c.GrantTypes +} + +// GetResponseTypes returns the ResponseTypes. +func (c *BaseClient) GetResponseTypes() fosite.Arguments { + if len(c.ResponseTypes) == 0 { + return fosite.Arguments{"code"} + } + + return c.ResponseTypes +} + +// GetScopes returns the Scopes. +func (c *BaseClient) GetScopes() fosite.Arguments { + return c.Scopes +} + +// GetAudience returns the Audience. +func (c *BaseClient) GetAudience() fosite.Arguments { + return c.Audience +} + +// GetResponseModes returns the valid response modes for this client. +// +// Implements the fosite.ResponseModeClient. +func (c *BaseClient) GetResponseModes() []fosite.ResponseModeType { + return c.ResponseModes +} + +// GetUserinfoSigningAlgorithm returns the UserinfoSigningAlgorithm. +func (c *BaseClient) GetUserinfoSigningAlgorithm() string { + if c.UserinfoSigningAlgorithm == "" { + c.UserinfoSigningAlgorithm = SigningAlgorithmNone + } + + return c.UserinfoSigningAlgorithm +} + +// GetPAREnforcement returns EnforcePAR. +func (c *BaseClient) GetPAREnforcement() bool { + return c.EnforcePAR +} + +// GetPKCEEnforcement returns EnforcePKCE. +func (c *BaseClient) GetPKCEEnforcement() bool { + return c.EnforcePKCE +} + +// GetPKCEChallengeMethodEnforcement returns EnforcePKCEChallengeMethod. +func (c *BaseClient) GetPKCEChallengeMethodEnforcement() bool { + return c.EnforcePKCEChallengeMethod +} + +// GetPKCEChallengeMethod returns PKCEChallengeMethod. +func (c *BaseClient) GetPKCEChallengeMethod() string { + return c.PKCEChallengeMethod +} + +// GetAuthorizationPolicy returns Policy. +func (c *BaseClient) GetAuthorizationPolicy() authorization.Level { + return c.Policy +} + +// GetConsentPolicy returns Consent. +func (c *BaseClient) GetConsentPolicy() ClientConsent { + return c.Consent +} + +// GetConsentResponseBody returns the proper consent response body for this session.OIDCWorkflowSession. +func (c *BaseClient) GetConsentResponseBody(consent *model.OAuth2ConsentSession) ConsentGetResponseBody { + body := ConsentGetResponseBody{ + ClientID: c.ID, + ClientDescription: c.Description, + PreConfiguration: c.Consent.Mode == ClientConsentModePreConfigured, + } + + if consent != nil { + body.Scopes = consent.RequestedScopes + body.Audience = consent.RequestedAudience + } + + return body +} + +// IsPublic returns the value of the Public property. +func (c *BaseClient) IsPublic() bool { + return c.Public +} + +// IsAuthenticationLevelSufficient returns if the provided authentication.Level is sufficient for the client of the AutheliaClient. +func (c *BaseClient) IsAuthenticationLevelSufficient(level authentication.Level) bool { + if level == authentication.NotAuthenticated { + return false + } + + return authorization.IsAuthLevelSufficient(level, c.Policy) +} + // ValidatePKCEPolicy is a helper function to validate PKCE policy constraints on a per-client basis. -func (c *Client) ValidatePKCEPolicy(r fosite.Requester) (err error) { +func (c *BaseClient) ValidatePKCEPolicy(r fosite.Requester) (err error) { form := r.GetRequestForm() if c.EnforcePKCE { @@ -70,7 +223,7 @@ func (c *Client) ValidatePKCEPolicy(r fosite.Requester) (err error) { } // ValidatePARPolicy is a helper function to validate additional policy constraints on a per-client basis. -func (c *Client) ValidatePARPolicy(r fosite.Requester, prefix string) (err error) { +func (c *BaseClient) ValidatePARPolicy(r fosite.Requester, prefix string) (err error) { if c.EnforcePAR { if !IsPushedAuthorizedRequest(r, prefix) { switch requestURI := r.GetRequestForm().Get(FormParameterRequestURI); requestURI { @@ -87,7 +240,7 @@ func (c *Client) ValidatePARPolicy(r fosite.Requester, prefix string) (err error // ValidateResponseModePolicy is an additional check to the response mode parameter to ensure if it's omitted that the // default response mode for the fosite.AuthorizeRequester is permitted. -func (c *Client) ValidateResponseModePolicy(r fosite.AuthorizeRequester) (err error) { +func (c *BaseClient) ValidateResponseModePolicy(r fosite.AuthorizeRequester) (err error) { if r.GetResponseMode() != fosite.ResponseModeDefault { return nil } @@ -109,91 +262,52 @@ func (c *Client) ValidateResponseModePolicy(r fosite.AuthorizeRequester) (err er return errorsx.WithStack(fosite.ErrUnsupportedResponseMode.WithHintf(`The request omitted the response_mode making the default response_mode "%s" based on the other authorization request parameters but registered OAuth 2.0 client doesn't support this response_mode`, m)) } -// IsAuthenticationLevelSufficient returns if the provided authentication.Level is sufficient for the client of the AutheliaClient. -func (c *Client) IsAuthenticationLevelSufficient(level authentication.Level) bool { - if level == authentication.NotAuthenticated { - return false +// GetRequestURIs is an array of request_uri values that are pre-registered by the RP for use at the OP. Servers MAY +// cache the contents of the files referenced by these URIs and not retrieve them at the time they are used in a request. +// OPs can require that request_uri values used be pre-registered with the require_request_uri_registration +// discovery parameter. +func (c *FullClient) GetRequestURIs() []string { + return c.RequestURIs +} + +// 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. -func (c *Client) GetSectorIdentifier() string { - return c.SectorIdentifier -} - -// GetConsentResponseBody returns the proper consent response body for this session.OIDCWorkflowSession. -func (c *Client) GetConsentResponseBody(consent *model.OAuth2ConsentSession) ConsentGetResponseBody { - body := ConsentGetResponseBody{ - ClientID: c.ID, - ClientDescription: c.Description, - PreConfiguration: c.Consent.Mode == ClientConsentModePreConfigured, +// GetTokenEndpointAuthSigningAlgorithm returns the JWS [JWS] alg algorithm [JWA] that MUST be used for signing the JWT +// [JWT] used to authenticate the Client at the Token Endpoint for the private_key_jwt and client_secret_jwt +// authentication methods. +func (c *FullClient) GetTokenEndpointAuthSigningAlgorithm() string { + if c.TokenEndpointAuthSigningAlgorithm == "" { + c.TokenEndpointAuthSigningAlgorithm = SigningAlgorithmRSAWithSHA256 } - if consent != nil { - 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 + return c.TokenEndpointAuthSigningAlgorithm } diff --git a/internal/oidc/client_test.go b/internal/oidc/client_test.go index 1546644c2..a4be63346 100644 --- a/internal/oidc/client_test.go +++ b/internal/oidc/client_test.go @@ -7,6 +7,7 @@ import ( "github.com/ory/fosite" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/square/go-jose.v2" "github.com/authelia/authelia/v4/internal/authentication" "github.com/authelia/authelia/v4/internal/authorization" @@ -15,36 +16,136 @@ import ( ) func TestNewClient(t *testing.T) { - blankConfig := schema.OpenIDConnectClientConfiguration{} - blankClient := NewClient(blankConfig) - assert.Equal(t, "", blankClient.ID) - assert.Equal(t, "", blankClient.Description) - assert.Equal(t, "", blankClient.Description) - assert.Len(t, blankClient.ResponseModes, 0) + config := schema.OpenIDConnectClientConfiguration{} + client := NewClient(config) + assert.Equal(t, "", client.GetID()) + assert.Equal(t, "", client.GetDescription()) + assert.Len(t, client.GetResponseModes(), 0) + assert.Len(t, client.GetResponseTypes(), 1) + assert.Equal(t, "", client.GetSectorIdentifier()) - exampleConfig := schema.OpenIDConnectClientConfiguration{ - ID: "myapp", - Description: "My App", - Policy: "two_factor", - Secret: MustDecodeSecret("$plaintext$abcdef"), - RedirectURIs: []string{"https://google.com/callback"}, + bclient, ok := client.(*BaseClient) + require.True(t, ok) + assert.Equal(t, "", bclient.UserinfoSigningAlgorithm) + assert.Equal(t, SigningAlgorithmNone, client.GetUserinfoSigningAlgorithm()) + + _, ok = client.(*FullClient) + assert.False(t, ok) + + config = schema.OpenIDConnectClientConfiguration{ + ID: myclient, + Description: myclientdesc, + Policy: twofactor, + Secret: MustDecodeSecret(badsecret), + RedirectURIs: []string{examplecom}, Scopes: schema.DefaultOpenIDConnectClientConfiguration.Scopes, ResponseTypes: schema.DefaultOpenIDConnectClientConfiguration.ResponseTypes, GrantTypes: schema.DefaultOpenIDConnectClientConfiguration.GrantTypes, ResponseModes: schema.DefaultOpenIDConnectClientConfiguration.ResponseModes, } - exampleClient := NewClient(exampleConfig) - assert.Equal(t, "myapp", exampleClient.ID) - require.Len(t, exampleClient.ResponseModes, 3) - assert.Equal(t, fosite.ResponseModeFormPost, exampleClient.ResponseModes[0]) - assert.Equal(t, fosite.ResponseModeQuery, exampleClient.ResponseModes[1]) - assert.Equal(t, fosite.ResponseModeFragment, exampleClient.ResponseModes[2]) - assert.Equal(t, authorization.TwoFactor, exampleClient.Policy) + client = NewClient(config) + assert.Equal(t, myclient, client.GetID()) + require.Len(t, client.GetResponseModes(), 1) + assert.Equal(t, fosite.ResponseModeFormPost, client.GetResponseModes()[0]) + assert.Equal(t, authorization.TwoFactor, client.GetAuthorizationPolicy()) + + config = schema.OpenIDConnectClientConfiguration{ + TokenEndpointAuthMethod: ClientAuthMethodClientSecretBasic, + } + + client = NewClient(config) + + fclient, ok := client.(*FullClient) + + var niljwks *jose.JSONWebKeySet + + require.True(t, ok) + assert.Equal(t, "", fclient.UserinfoSigningAlgorithm) + assert.Equal(t, ClientAuthMethodClientSecretBasic, fclient.TokenEndpointAuthMethod) + assert.Equal(t, ClientAuthMethodClientSecretBasic, fclient.GetTokenEndpointAuthMethod()) + assert.Equal(t, SigningAlgorithmNone, client.GetUserinfoSigningAlgorithm()) + assert.Equal(t, "", fclient.TokenEndpointAuthSigningAlgorithm) + assert.Equal(t, SigningAlgorithmRSAWithSHA256, fclient.GetTokenEndpointAuthSigningAlgorithm()) + assert.Equal(t, "", fclient.RequestObjectSigningAlgorithm) + assert.Equal(t, "", fclient.GetRequestObjectSigningAlgorithm()) + assert.Equal(t, "", fclient.JSONWebKeysURI) + assert.Equal(t, "", fclient.GetJSONWebKeysURI()) + assert.Equal(t, niljwks, fclient.JSONWebKeys) + assert.Equal(t, niljwks, fclient.GetJSONWebKeys()) + assert.Equal(t, []string(nil), fclient.RequestURIs) + assert.Equal(t, []string(nil), fclient.GetRequestURIs()) +} + +func TestBaseClient_ValidatePARPolicy(t *testing.T) { + testCases := []struct { + name string + client *BaseClient + have *fosite.Request + expected string + }{ + { + "ShouldNotEnforcePAR", + &BaseClient{ + EnforcePAR: false, + }, + &fosite.Request{}, + "", + }, + { + "ShouldEnforcePARAndErrorWithoutCorrectRequestURI", + &BaseClient{ + EnforcePAR: true, + }, + &fosite.Request{ + Form: map[string][]string{ + FormParameterRequestURI: {"https://google.com"}, + }, + }, + "invalid_request", + }, + { + "ShouldEnforcePARAndErrorWithEmptyRequestURI", + &BaseClient{ + EnforcePAR: true, + }, + &fosite.Request{ + Form: map[string][]string{ + FormParameterRequestURI: {""}, + }, + }, + "invalid_request", + }, + { + "ShouldEnforcePARAndNotErrorWithCorrectRequestURI", + &BaseClient{ + EnforcePAR: true, + }, + &fosite.Request{ + Form: map[string][]string{ + FormParameterRequestURI: {urnPARPrefix + "abc"}, + }, + }, + "", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.client.ValidatePARPolicy(tc.have, urnPARPrefix) + + switch tc.expected { + case "": + assert.NoError(t, err) + default: + assert.EqualError(t, err, tc.expected) + } + }) + } } func TestIsAuthenticationLevelSufficient(t *testing.T) { - c := Client{} + c := &FullClient{BaseClient: &BaseClient{}} c.Policy = authorization.Bypass assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated)) @@ -68,7 +169,7 @@ func TestIsAuthenticationLevelSufficient(t *testing.T) { } func TestClient_GetConsentResponseBody(t *testing.T) { - c := Client{} + c := &FullClient{BaseClient: &BaseClient{}} consentRequestBody := c.GetConsentResponseBody(nil) assert.Equal(t, "", consentRequestBody.ClientID) @@ -76,56 +177,56 @@ func TestClient_GetConsentResponseBody(t *testing.T) { assert.Equal(t, []string(nil), consentRequestBody.Scopes) assert.Equal(t, []string(nil), consentRequestBody.Audience) - c.ID = "myclient" - c.Description = "My Client" + c.ID = myclient + c.Description = myclientdesc consent := &model.OAuth2ConsentSession{ - RequestedAudience: []string{"https://example.com"}, - RequestedScopes: []string{"openid", "groups"}, + RequestedAudience: []string{examplecom}, + RequestedScopes: []string{ScopeOpenID, ScopeGroups}, } - expectedScopes := []string{"openid", "groups"} - expectedAudiences := []string{"https://example.com"} + expectedScopes := []string{ScopeOpenID, ScopeGroups} + expectedAudiences := []string{examplecom} consentRequestBody = c.GetConsentResponseBody(consent) - assert.Equal(t, "myclient", consentRequestBody.ClientID) - assert.Equal(t, "My Client", consentRequestBody.ClientDescription) + assert.Equal(t, myclient, consentRequestBody.ClientID) + assert.Equal(t, myclientdesc, consentRequestBody.ClientDescription) assert.Equal(t, expectedScopes, consentRequestBody.Scopes) assert.Equal(t, expectedAudiences, consentRequestBody.Audience) } func TestClient_GetAudience(t *testing.T) { - c := Client{} + c := &FullClient{BaseClient: &BaseClient{}} audience := c.GetAudience() assert.Len(t, audience, 0) - c.Audience = []string{"https://example.com"} + c.Audience = []string{examplecom} audience = c.GetAudience() require.Len(t, audience, 1) - assert.Equal(t, "https://example.com", audience[0]) + assert.Equal(t, examplecom, audience[0]) } func TestClient_GetScopes(t *testing.T) { - c := Client{} + c := &FullClient{BaseClient: &BaseClient{}} scopes := c.GetScopes() assert.Len(t, scopes, 0) - c.Scopes = []string{"openid"} + c.Scopes = []string{ScopeOpenID} scopes = c.GetScopes() require.Len(t, scopes, 1) - assert.Equal(t, "openid", scopes[0]) + assert.Equal(t, ScopeOpenID, scopes[0]) } func TestClient_GetGrantTypes(t *testing.T) { - c := Client{} + c := &FullClient{BaseClient: &BaseClient{}} grantTypes := c.GetGrantTypes() require.Len(t, grantTypes, 1) - assert.Equal(t, "authorization_code", grantTypes[0]) + assert.Equal(t, GrantTypeAuthorizationCode, grantTypes[0]) c.GrantTypes = []string{"device_code"} @@ -135,55 +236,55 @@ func TestClient_GetGrantTypes(t *testing.T) { } func TestClient_Hashing(t *testing.T) { - c := Client{} + c := &FullClient{BaseClient: &BaseClient{}} hashedSecret := c.GetHashedSecret() assert.Equal(t, []byte(nil), hashedSecret) - c.Secret = MustDecodeSecret("$plaintext$a_bad_secret") + c.Secret = MustDecodeSecret(badsecret) assert.True(t, c.Secret.MatchBytes([]byte("a_bad_secret"))) } func TestClient_GetHashedSecret(t *testing.T) { - c := Client{} + c := &FullClient{BaseClient: &BaseClient{}} hashedSecret := c.GetHashedSecret() assert.Equal(t, []byte(nil), hashedSecret) - c.Secret = MustDecodeSecret("$plaintext$a_bad_secret") + c.Secret = MustDecodeSecret(badsecret) hashedSecret = c.GetHashedSecret() - assert.Equal(t, []byte("$plaintext$a_bad_secret"), hashedSecret) + assert.Equal(t, []byte(badsecret), hashedSecret) } func TestClient_GetID(t *testing.T) { - c := Client{} + c := &FullClient{BaseClient: &BaseClient{}} id := c.GetID() assert.Equal(t, "", id) - c.ID = "myid" + c.ID = myclient id = c.GetID() - assert.Equal(t, "myid", id) + assert.Equal(t, myclient, id) } func TestClient_GetRedirectURIs(t *testing.T) { - c := Client{} + c := &FullClient{BaseClient: &BaseClient{}} redirectURIs := c.GetRedirectURIs() require.Len(t, redirectURIs, 0) - c.RedirectURIs = []string{"https://example.com/oauth2/callback"} + c.RedirectURIs = []string{examplecom} redirectURIs = c.GetRedirectURIs() require.Len(t, redirectURIs, 1) - assert.Equal(t, "https://example.com/oauth2/callback", redirectURIs[0]) + assert.Equal(t, examplecom, redirectURIs[0]) } func TestClient_GetResponseModes(t *testing.T) { - c := Client{} + c := &FullClient{BaseClient: &BaseClient{}} responseModes := c.GetResponseModes() require.Len(t, responseModes, 0) @@ -202,18 +303,18 @@ func TestClient_GetResponseModes(t *testing.T) { } func TestClient_GetResponseTypes(t *testing.T) { - c := Client{} + c := &FullClient{BaseClient: &BaseClient{}} responseTypes := c.GetResponseTypes() require.Len(t, responseTypes, 1) - assert.Equal(t, "code", responseTypes[0]) + assert.Equal(t, ResponseTypeAuthorizationCodeFlow, responseTypes[0]) - c.ResponseTypes = []string{"code", "id_token"} + c.ResponseTypes = []string{ResponseTypeAuthorizationCodeFlow, ResponseTypeImplicitFlowIDToken} responseTypes = c.GetResponseTypes() require.Len(t, responseTypes, 2) - assert.Equal(t, "code", responseTypes[0]) - assert.Equal(t, "id_token", responseTypes[1]) + assert.Equal(t, ResponseTypeAuthorizationCodeFlow, responseTypes[0]) + assert.Equal(t, ResponseTypeImplicitFlowIDToken, responseTypes[1]) } func TestNewClientPKCE(t *testing.T) { @@ -290,9 +391,9 @@ func TestNewClientPKCE(t *testing.T) { t.Run(tc.name, func(t *testing.T) { client := NewClient(tc.have) - assert.Equal(t, tc.expectedEnforcePKCE, client.EnforcePKCE) - assert.Equal(t, tc.expectedEnforcePKCEChallengeMethod, client.EnforcePKCEChallengeMethod) - assert.Equal(t, tc.expected, client.PKCEChallengeMethod) + assert.Equal(t, tc.expectedEnforcePKCE, client.GetPKCEEnforcement()) + assert.Equal(t, tc.expectedEnforcePKCEChallengeMethod, client.GetPKCEChallengeMethodEnforcement()) + assert.Equal(t, tc.expected, client.GetPKCEChallengeMethod()) if tc.r != nil { err := client.ValidatePKCEPolicy(tc.r) @@ -355,7 +456,7 @@ func TestNewClientPAR(t *testing.T) { t.Run(tc.name, func(t *testing.T) { client := NewClient(tc.have) - assert.Equal(t, tc.expected, client.EnforcePAR) + assert.Equal(t, tc.expected, client.GetPAREnforcement()) if tc.r != nil { err := client.ValidatePARPolicy(tc.r, urnPARPrefix) @@ -437,7 +538,7 @@ func TestNewClientResponseModes(t *testing.T) { } func TestClient_IsPublic(t *testing.T) { - c := Client{} + c := &FullClient{BaseClient: &BaseClient{}} assert.False(t, c.IsPublic()) diff --git a/internal/oidc/config.go b/internal/oidc/config.go index 1cc7bf098..1db4e757a 100644 --- a/internal/oidc/config.go +++ b/internal/oidc/config.go @@ -169,12 +169,6 @@ type LifespanConfig struct { RefreshToken time.Duration } -const ( - PromptNone = none - PromptLogin = "login" - PromptConsent = "consent" -) - // LoadHandlers reloads the handlers based on the current configuration. func (c *Config) LoadHandlers(store *Store, strategy jwt.Signer) { validator := openid.NewOpenIDConnectRequestValidator(strategy, c) diff --git a/internal/oidc/const.go b/internal/oidc/const.go index 54c6b2ff4..3680923c3 100644 --- a/internal/oidc/const.go +++ b/internal/oidc/const.go @@ -69,15 +69,12 @@ const ( GrantTypeImplicit = implicit GrantTypeRefreshToken = "refresh_token" GrantTypeAuthorizationCode = "authorization_code" - GrantTypePassword = "password" - GrantTypeClientCredentials = "client_credentials" ) // Client Auth Method strings. const ( ClientAuthMethodClientSecretBasic = "client_secret_basic" ClientAuthMethodClientSecretPost = "client_secret_post" - ClientAuthMethodClientSecretJWT = "client_secret_jwt" ClientAuthMethodNone = "none" ) @@ -117,6 +114,13 @@ const ( FormParameterCodeChallengeMethod = "code_challenge_method" ) +const ( + PromptNone = none + PromptLogin = "login" + PromptConsent = "consent" + // PromptCreate = "create" // This prompt value is currently unused. +) + // Endpoints. const ( EndpointAuthorization = "authorization" diff --git a/internal/oidc/const_test.go b/internal/oidc/const_test.go new file mode 100644 index 000000000..b5e3d915b --- /dev/null +++ b/internal/oidc/const_test.go @@ -0,0 +1,12 @@ +package oidc + +const ( + myclient = "myclient" + myclientdesc = "My Client" + onefactor = "one_factor" + twofactor = "two_factor" + examplecom = "https://example.com" + examplecomsid = "example.com" + badsecret = "$plaintext$a_bad_secret" + badhmac = "asbdhaaskmdlkamdklasmdlkams" +) diff --git a/internal/oidc/discovery.go b/internal/oidc/discovery.go index 996e5e3f5..e57bad146 100644 --- a/internal/oidc/discovery.go +++ b/internal/oidc/discovery.go @@ -5,70 +5,76 @@ import ( ) // NewOpenIDConnectWellKnownConfiguration generates a new OpenIDConnectWellKnownConfiguration. -func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration, clients map[string]*Client) (config OpenIDConnectWellKnownConfiguration) { +func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration) (config OpenIDConnectWellKnownConfiguration) { config = OpenIDConnectWellKnownConfiguration{ - CommonDiscoveryOptions: CommonDiscoveryOptions{ - SubjectTypesSupported: []string{ - SubjectTypePublic, + OAuth2WellKnownConfiguration: OAuth2WellKnownConfiguration{ + CommonDiscoveryOptions: CommonDiscoveryOptions{ + SubjectTypesSupported: []string{ + SubjectTypePublic, + SubjectTypePairwise, + }, + ResponseTypesSupported: []string{ + ResponseTypeAuthorizationCodeFlow, + ResponseTypeImplicitFlowIDToken, + ResponseTypeImplicitFlowToken, + ResponseTypeImplicitFlowBoth, + ResponseTypeHybridFlowIDToken, + ResponseTypeHybridFlowToken, + ResponseTypeHybridFlowBoth, + }, + GrantTypesSupported: []string{ + GrantTypeAuthorizationCode, + GrantTypeImplicit, + GrantTypeRefreshToken, + }, + ResponseModesSupported: []string{ + ResponseModeFormPost, + ResponseModeQuery, + ResponseModeFragment, + }, + ScopesSupported: []string{ + ScopeOfflineAccess, + ScopeOpenID, + ScopeProfile, + ScopeGroups, + ScopeEmail, + }, + ClaimsSupported: []string{ + ClaimAuthenticationMethodsReference, + ClaimAudience, + ClaimAuthorizedParty, + ClaimClientIdentifier, + ClaimExpirationTime, + ClaimIssuedAt, + ClaimIssuer, + ClaimJWTID, + ClaimRequestedAt, + ClaimSubject, + ClaimAuthenticationTime, + ClaimNonce, + ClaimPreferredEmail, + ClaimEmailVerified, + ClaimEmailAlts, + ClaimGroups, + ClaimPreferredUsername, + ClaimFullName, + }, + TokenEndpointAuthMethodsSupported: []string{ + ClientAuthMethodClientSecretBasic, + ClientAuthMethodClientSecretPost, + ClientAuthMethodNone, + }, }, - ResponseTypesSupported: []string{ - ResponseTypeAuthorizationCodeFlow, - ResponseTypeImplicitFlowIDToken, - ResponseTypeImplicitFlowToken, - ResponseTypeImplicitFlowBoth, - ResponseTypeHybridFlowIDToken, - ResponseTypeHybridFlowToken, - ResponseTypeHybridFlowBoth, + OAuth2DiscoveryOptions: OAuth2DiscoveryOptions{ + CodeChallengeMethodsSupported: []string{ + PKCEChallengeMethodSHA256, + }, }, - GrantTypesSupported: []string{ - GrantTypeAuthorizationCode, - GrantTypeImplicit, - GrantTypeRefreshToken, - }, - ResponseModesSupported: []string{ - ResponseModeFormPost, - ResponseModeQuery, - ResponseModeFragment, - }, - ScopesSupported: []string{ - ScopeOfflineAccess, - ScopeOpenID, - ScopeProfile, - ScopeGroups, - ScopeEmail, - }, - ClaimsSupported: []string{ - ClaimAuthenticationMethodsReference, - ClaimAudience, - ClaimAuthorizedParty, - ClaimClientIdentifier, - ClaimExpirationTime, - ClaimIssuedAt, - ClaimIssuer, - ClaimJWTID, - ClaimRequestedAt, - ClaimSubject, - ClaimAuthenticationTime, - ClaimNonce, - ClaimPreferredEmail, - ClaimEmailVerified, - ClaimEmailAlts, - ClaimGroups, - ClaimPreferredUsername, - ClaimFullName, - }, - TokenEndpointAuthMethodsSupported: []string{ - ClientAuthMethodClientSecretBasic, - ClientAuthMethodClientSecretPost, - ClientAuthMethodClientSecretJWT, - ClientAuthMethodNone, - }, - }, - OAuth2DiscoveryOptions: OAuth2DiscoveryOptions{ - CodeChallengeMethodsSupported: []string{ - PKCEChallengeMethodSHA256, + OAuth2PushedAuthorizationDiscoveryOptions: &OAuth2PushedAuthorizationDiscoveryOptions{ + RequirePushedAuthorizationRequests: c.PAR.Enforce, }, }, + OpenIDConnectDiscoveryOptions: OpenIDConnectDiscoveryOptions{ IDTokenSigningAlgValuesSupported: []string{ SigningAlgorithmRSAWithSHA256, @@ -77,30 +83,15 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration SigningAlgorithmNone, SigningAlgorithmRSAWithSHA256, }, - RequestObjectSigningAlgValuesSupported: []string{ - SigningAlgorithmNone, - SigningAlgorithmRSAWithSHA256, + }, + OpenIDConnectFrontChannelLogoutDiscoveryOptions: &OpenIDConnectFrontChannelLogoutDiscoveryOptions{}, + 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 { @@ -109,3 +100,93 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration 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 +} diff --git a/internal/oidc/discovery_test.go b/internal/oidc/discovery_test.go index 63ad18b65..34ca1f732 100644 --- a/internal/oidc/discovery_test.go +++ b/internal/oidc/discovery_test.go @@ -13,51 +13,51 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) { desc string pkcePlainChallenge bool enforcePAR bool - clients map[string]*Client + clients map[string]Client expectCodeChallengeMethodsSupported, expectSubjectTypesSupported []string }{ { desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublic", pkcePlainChallenge: false, - clients: map[string]*Client{"a": {}}, + clients: map[string]Client{"a": &BaseClient{}}, expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256}, - expectSubjectTypesSupported: []string{SubjectTypePublic}, + expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise}, }, { desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublic", pkcePlainChallenge: true, - clients: map[string]*Client{"a": {}}, + clients: map[string]Client{"a": &BaseClient{}}, expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain}, - expectSubjectTypesSupported: []string{SubjectTypePublic}, + expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise}, }, { desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublicPairwise", pkcePlainChallenge: false, - clients: map[string]*Client{"a": {SectorIdentifier: "yes"}}, + clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}}, expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256}, expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise}, }, { desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublicPairwise", pkcePlainChallenge: true, - clients: map[string]*Client{"a": {SectorIdentifier: "yes"}}, + clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}}, expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain}, expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise}, }, { desc: "ShouldHaveTokenAuthMethodsNone", pkcePlainChallenge: true, - clients: map[string]*Client{"a": {SectorIdentifier: "yes"}}, + clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}}, expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain}, expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise}, }, { desc: "ShouldHaveTokenAuthMethodsNone", pkcePlainChallenge: true, - clients: map[string]*Client{ - "a": {SectorIdentifier: "yes"}, - "b": {SectorIdentifier: "yes"}, + clients: map[string]Client{ + "a": &BaseClient{SectorIdentifier: "yes"}, + "b": &BaseClient{SectorIdentifier: "yes"}, }, expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain}, expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise}, @@ -73,7 +73,7 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) { }, } - actual := NewOpenIDConnectWellKnownConfiguration(&c, tc.clients) + actual := NewOpenIDConnectWellKnownConfiguration(&c) for _, codeChallengeMethod := range tc.expectCodeChallengeMethodsSupported { assert.Contains(t, actual.CodeChallengeMethodsSupported, codeChallengeMethod) } diff --git a/internal/oidc/provider.go b/internal/oidc/provider.go index 54bd1595d..b8333351b 100644 --- a/internal/oidc/provider.go +++ b/internal/oidc/provider.go @@ -37,17 +37,14 @@ func NewOpenIDConnectProvider(config *schema.OpenIDConnectConfiguration, store s provider.Config.LoadHandlers(provider.Store, provider.KeyManager.Strategy()) - provider.discovery = NewOpenIDConnectWellKnownConfiguration(config, provider.Store.clients) + provider.discovery = NewOpenIDConnectWellKnownConfiguration(config) return provider, nil } // GetOAuth2WellKnownConfiguration returns the discovery document for the OAuth Configuration. func (p *OpenIDConnectProvider) GetOAuth2WellKnownConfiguration(issuer string) OAuth2WellKnownConfiguration { - options := OAuth2WellKnownConfiguration{ - CommonDiscoveryOptions: p.discovery.CommonDiscoveryOptions, - OAuth2DiscoveryOptions: p.discovery.OAuth2DiscoveryOptions, - } + options := p.discovery.OAuth2WellKnownConfiguration.Copy() options.Issuer = issuer @@ -63,13 +60,7 @@ func (p *OpenIDConnectProvider) GetOAuth2WellKnownConfiguration(issuer string) O // GetOpenIDConnectWellKnownConfiguration returns the discovery document for the OpenID Configuration. func (p *OpenIDConnectProvider) GetOpenIDConnectWellKnownConfiguration(issuer string) OpenIDConnectWellKnownConfiguration { - options := OpenIDConnectWellKnownConfiguration{ - CommonDiscoveryOptions: p.discovery.CommonDiscoveryOptions, - OAuth2DiscoveryOptions: p.discovery.OAuth2DiscoveryOptions, - OpenIDConnectDiscoveryOptions: p.discovery.OpenIDConnectDiscoveryOptions, - OpenIDConnectFrontChannelLogoutDiscoveryOptions: p.discovery.OpenIDConnectFrontChannelLogoutDiscoveryOptions, - OpenIDConnectBackChannelLogoutDiscoveryOptions: p.discovery.OpenIDConnectBackChannelLogoutDiscoveryOptions, - } + options := p.discovery.Copy() options.Issuer = issuer diff --git a/internal/oidc/provider_test.go b/internal/oidc/provider_test.go index 709bab0b0..da1ebbac6 100644 --- a/internal/oidc/provider_test.go +++ b/internal/oidc/provider_test.go @@ -27,15 +27,15 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing IssuerCertificateChain: schema.X509CertificateChain{}, IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey), EnablePKCEPlainChallenge: true, - HMACSecret: "asbdhaaskmdlkamdklasmdlkams", + HMACSecret: badhmac, Clients: []schema.OpenIDConnectClientConfiguration{ { - ID: "a-client", - Secret: MustDecodeSecret("$plaintext$a-client-secret"), - SectorIdentifier: url.URL{Host: "google.com"}, - Policy: "one_factor", + ID: myclient, + Secret: MustDecodeSecret(badsecret), + SectorIdentifier: url.URL{Host: examplecomsid}, + Policy: onefactor, RedirectURIs: []string{ - "https://google.com", + examplecom, }, }, }, @@ -43,7 +43,7 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing assert.NoError(t, err) - disco := provider.GetOpenIDConnectWellKnownConfiguration("https://example.com") + disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom) assert.Len(t, disco.SubjectTypesSupported, 2) assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic) @@ -58,12 +58,12 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *tes provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{ IssuerCertificateChain: schema.X509CertificateChain{}, IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey), - HMACSecret: "asbdhaaskmdlkamdklasmdlkams", + HMACSecret: badhmac, Clients: []schema.OpenIDConnectClientConfiguration{ { ID: "a-client", Secret: MustDecodeSecret("$plaintext$a-client-secret"), - Policy: "one_factor", + Policy: onefactor, RedirectURIs: []string{ "https://google.com", }, @@ -72,7 +72,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *tes ID: "b-client", Description: "Normal DisplayName", Secret: MustDecodeSecret("$plaintext$b-client-secret"), - Policy: "two_factor", + Policy: twofactor, RedirectURIs: []string{ "https://google.com", }, @@ -103,7 +103,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow { ID: "a-client", Secret: MustDecodeSecret("$plaintext$a-client-secret"), - Policy: "one_factor", + Policy: onefactor, RedirectURIs: []string{ "https://google.com", }, @@ -113,9 +113,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow assert.NoError(t, err) - disco := provider.GetOpenIDConnectWellKnownConfiguration("https://example.com") + disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom) - assert.Equal(t, "https://example.com", disco.Issuer) + assert.Equal(t, examplecom, disco.Issuer) assert.Equal(t, "https://example.com/jwks.json", disco.JWKSURI) assert.Equal(t, "https://example.com/api/oidc/authorization", disco.AuthorizationEndpoint) assert.Equal(t, "https://example.com/api/oidc/token", disco.TokenEndpoint) @@ -139,8 +139,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow assert.Contains(t, disco.ResponseModesSupported, ResponseModeQuery) assert.Contains(t, disco.ResponseModesSupported, ResponseModeFragment) - assert.Len(t, disco.SubjectTypesSupported, 1) + assert.Len(t, disco.SubjectTypesSupported, 2) assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic) + assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePairwise) assert.Len(t, disco.ResponseTypesSupported, 7) assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeAuthorizationCodeFlow) @@ -151,10 +152,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowToken) assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowBoth) - assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 4) + assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 3) assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic) assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost) - assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT) assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone) assert.Len(t, disco.GrantTypesSupported, 3) @@ -169,9 +169,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow assert.Contains(t, disco.UserinfoSigningAlgValuesSupported, SigningAlgorithmRSAWithSHA256) assert.Contains(t, disco.UserinfoSigningAlgValuesSupported, SigningAlgorithmNone) - assert.Len(t, disco.RequestObjectSigningAlgValuesSupported, 2) - assert.Contains(t, disco.RequestObjectSigningAlgValuesSupported, SigningAlgorithmRSAWithSHA256) - assert.Contains(t, disco.RequestObjectSigningAlgValuesSupported, SigningAlgorithmNone) + assert.Len(t, disco.RequestObjectSigningAlgValuesSupported, 0) assert.Len(t, disco.ClaimsSupported, 18) assert.Contains(t, disco.ClaimsSupported, ClaimAuthenticationMethodsReference) @@ -203,7 +201,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig { ID: "a-client", Secret: MustDecodeSecret("$plaintext$a-client-secret"), - Policy: "one_factor", + Policy: onefactor, RedirectURIs: []string{ "https://google.com", }, @@ -213,9 +211,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig assert.NoError(t, err) - disco := provider.GetOAuth2WellKnownConfiguration("https://example.com") + disco := provider.GetOAuth2WellKnownConfiguration(examplecom) - assert.Equal(t, "https://example.com", disco.Issuer) + assert.Equal(t, examplecom, disco.Issuer) assert.Equal(t, "https://example.com/jwks.json", disco.JWKSURI) assert.Equal(t, "https://example.com/api/oidc/authorization", disco.AuthorizationEndpoint) assert.Equal(t, "https://example.com/api/oidc/token", disco.TokenEndpoint) @@ -238,8 +236,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig assert.Contains(t, disco.ResponseModesSupported, ResponseModeQuery) assert.Contains(t, disco.ResponseModesSupported, ResponseModeFragment) - assert.Len(t, disco.SubjectTypesSupported, 1) + assert.Len(t, disco.SubjectTypesSupported, 2) assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic) + assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePairwise) assert.Len(t, disco.ResponseTypesSupported, 7) assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeAuthorizationCodeFlow) @@ -250,10 +249,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowToken) assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowBoth) - assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 4) + assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 3) assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic) assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost) - assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT) assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone) assert.Len(t, disco.GrantTypesSupported, 3) @@ -292,7 +290,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow { ID: "a-client", Secret: MustDecodeSecret("$plaintext$a-client-secret"), - Policy: "one_factor", + Policy: onefactor, RedirectURIs: []string{ "https://google.com", }, @@ -302,7 +300,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow assert.NoError(t, err) - disco := provider.GetOpenIDConnectWellKnownConfiguration("https://example.com") + disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom) require.Len(t, disco.CodeChallengeMethodsSupported, 2) assert.Equal(t, PKCEChallengeMethodSHA256, disco.CodeChallengeMethodsSupported[0]) diff --git a/internal/oidc/store.go b/internal/oidc/store.go index a5a181c2b..2f21b368c 100644 --- a/internal/oidc/store.go +++ b/internal/oidc/store.go @@ -24,7 +24,7 @@ func NewStore(config *schema.OpenIDConnectConfiguration, provider storage.Provid store = &Store{ provider: provider, - clients: map[string]*Client{}, + clients: map[string]Client{}, } for _, client := range config.Clients { @@ -72,11 +72,11 @@ func (s *Store) GetClientPolicy(id string) (level authorization.Level) { return authorization.TwoFactor } - return client.Policy + return client.GetAuthorizationPolicy() } // GetFullClient returns a fosite.Client asserted as an Client matching the provided id. -func (s *Store) GetFullClient(id string) (client *Client, err error) { +func (s *Store) GetFullClient(id string) (client Client, err error) { client, ok := s.clients[id] if !ok { return nil, fosite.ErrInvalidClient diff --git a/internal/oidc/store_test.go b/internal/oidc/store_test.go index 580e864e4..def1d4e8e 100644 --- a/internal/oidc/store_test.go +++ b/internal/oidc/store_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/ory/fosite" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -17,23 +18,23 @@ func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) { IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey), Clients: []schema.OpenIDConnectClientConfiguration{ { - ID: "myclient", - Description: "myclient desc", - Policy: "one_factor", + ID: myclient, + Description: myclientdesc, + Policy: onefactor, Scopes: []string{ScopeOpenID, ScopeProfile}, Secret: MustDecodeSecret("$plaintext$mysecret"), }, { ID: "myotherclient", - Description: "myclient desc", - Policy: "two_factor", + Description: myclientdesc, + Policy: twofactor, Scopes: []string{ScopeOpenID, ScopeProfile}, Secret: MustDecodeSecret("$plaintext$mysecret"), }, }, }, nil) - policyOne := s.GetClientPolicy("myclient") + policyOne := s.GetClientPolicy(myclient) assert.Equal(t, authorization.OneFactor, policyOne) policyTwo := s.GetClientPolicy("myotherclient") @@ -49,9 +50,9 @@ func TestOpenIDConnectStore_GetInternalClient(t *testing.T) { IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey), Clients: []schema.OpenIDConnectClientConfiguration{ { - ID: "myclient", - Description: "myclient desc", - Policy: "one_factor", + ID: myclient, + Description: myclientdesc, + Policy: onefactor, Scopes: []string{ScopeOpenID, ScopeProfile}, Secret: MustDecodeSecret("$plaintext$mysecret"), }, @@ -62,17 +63,19 @@ func TestOpenIDConnectStore_GetInternalClient(t *testing.T) { assert.EqualError(t, err, "invalid_client") assert.Nil(t, client) - client, err = s.GetClient(context.Background(), "myclient") + client, err = s.GetClient(context.Background(), myclient) require.NoError(t, err) require.NotNil(t, client) - assert.Equal(t, "myclient", client.GetID()) + assert.Equal(t, myclient, client.GetID()) } func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) { + id := myclient + c1 := schema.OpenIDConnectClientConfiguration{ - ID: "myclient", - Description: "myclient desc", - Policy: "one_factor", + ID: id, + Description: myclientdesc, + Policy: onefactor, Scopes: []string{ScopeOpenID, ScopeProfile}, Secret: MustDecodeSecret("$plaintext$mysecret"), } @@ -83,24 +86,24 @@ func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) { Clients: []schema.OpenIDConnectClientConfiguration{c1}, }, nil) - client, err := s.GetFullClient(c1.ID) + client, err := s.GetFullClient(id) require.NoError(t, err) require.NotNil(t, client) - assert.Equal(t, client.ID, c1.ID) - assert.Equal(t, client.Description, c1.Description) - assert.Equal(t, client.Scopes, c1.Scopes) - assert.Equal(t, client.GrantTypes, c1.GrantTypes) - assert.Equal(t, client.ResponseTypes, c1.ResponseTypes) - assert.Equal(t, client.RedirectURIs, c1.RedirectURIs) - assert.Equal(t, client.Policy, authorization.OneFactor) - assert.Equal(t, client.Secret.Encode(), "$plaintext$mysecret") + assert.Equal(t, id, client.GetID()) + assert.Equal(t, myclientdesc, client.GetDescription()) + assert.Equal(t, fosite.Arguments(c1.Scopes), client.GetScopes()) + assert.Equal(t, fosite.Arguments([]string{GrantTypeAuthorizationCode}), client.GetGrantTypes()) + assert.Equal(t, fosite.Arguments([]string{ResponseTypeAuthorizationCodeFlow}), client.GetResponseTypes()) + assert.Equal(t, []string(nil), client.GetRedirectURIs()) + assert.Equal(t, authorization.OneFactor, client.GetAuthorizationPolicy()) + assert.Equal(t, "$plaintext$mysecret", client.GetSecret().Encode()) } func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) { c1 := schema.OpenIDConnectClientConfiguration{ - ID: "myclient", - Description: "myclient desc", - Policy: "one_factor", + ID: myclient, + Description: myclientdesc, + Policy: onefactor, Scopes: []string{ScopeOpenID, ScopeProfile}, Secret: MustDecodeSecret("$plaintext$mysecret"), } @@ -122,16 +125,16 @@ func TestOpenIDConnectStore_IsValidClientID(t *testing.T) { IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey), Clients: []schema.OpenIDConnectClientConfiguration{ { - ID: "myclient", - Description: "myclient desc", - Policy: "one_factor", + ID: myclient, + Description: myclientdesc, + Policy: onefactor, Scopes: []string{ScopeOpenID, ScopeProfile}, Secret: MustDecodeSecret("$plaintext$mysecret"), }, }, }, nil) - validClient := s.IsValidClientID("myclient") + validClient := s.IsValidClientID(myclient) invalidClient := s.IsValidClientID("myinvalidclient") assert.True(t, validClient) diff --git a/internal/oidc/types.go b/internal/oidc/types.go index 7403f2fed..8606fe3a3 100644 --- a/internal/oidc/types.go +++ b/internal/oidc/types.go @@ -12,6 +12,7 @@ import ( "github.com/ory/herodot" "gopkg.in/square/go-jose.v2" + "github.com/authelia/authelia/v4/internal/authentication" "github.com/authelia/authelia/v4/internal/authorization" "github.com/authelia/authelia/v4/internal/model" "github.com/authelia/authelia/v4/internal/storage" @@ -97,17 +98,19 @@ type OpenIDConnectProvider struct { // openid.OpenIDConnectRequestStorage, and partially implements rfc7523.RFC7523KeyStorage. type Store struct { provider storage.Provider - clients map[string]*Client + clients map[string]Client } -// Client represents the client internally. -type Client struct { +// BaseClient is the base for all clients. +type BaseClient struct { ID string Description string Secret algorithm.Digest SectorIdentifier string Public bool + EnforcePAR bool + EnforcePKCE bool EnforcePKCEChallengeMethod bool PKCEChallengeMethod string @@ -119,8 +122,6 @@ type Client struct { ResponseTypes []string ResponseModes []fosite.ResponseModeType - EnforcePAR bool - UserinfoSigningAlgorithm string Policy authorization.Level @@ -128,6 +129,43 @@ type Client struct { Consent ClientConsent } +// FullClient is the client with comprehensive supported features. +type FullClient struct { + *BaseClient + + RequestURIs []string + JSONWebKeys *jose.JSONWebKeySet + JSONWebKeysURI string + RequestObjectSigningAlgorithm string + TokenEndpointAuthMethod string + TokenEndpointAuthSigningAlgorithm string +} + +// Client represents the internal client definitions. +type Client interface { + fosite.Client + fosite.ResponseModeClient + + GetDescription() string + GetSecret() algorithm.Digest + GetSectorIdentifier() string + GetConsentResponseBody(consent *model.OAuth2ConsentSession) ConsentGetResponseBody + GetUserinfoSigningAlgorithm() string + + GetPAREnforcement() bool + GetPKCEEnforcement() bool + GetPKCEChallengeMethodEnforcement() bool + GetPKCEChallengeMethod() string + GetAuthorizationPolicy() authorization.Level + GetConsentPolicy() ClientConsent + + IsAuthenticationLevelSufficient(level authentication.Level) bool + + ValidatePKCEPolicy(r fosite.Requester) (err error) + ValidatePARPolicy(r fosite.Requester, prefix string) (err error) + ValidateResponseModePolicy(r fosite.AuthorizeRequester) (err error) +} + // NewClientConsent converts the schema.OpenIDConnectClientConsentConfig into a oidc.ClientConsent. func NewClientConsent(mode string, duration *time.Duration) ClientConsent { switch mode { @@ -344,6 +382,12 @@ type CommonDiscoveryOptions struct { Client if it is given. */ OPTOSURI string `json:"op_tos_uri,omitempty"` + + /* + A JWT containing metadata values about the authorization server as claims. This is a string value consisting of + the entire signed JWT. A "signed_metadata" metadata value SHOULD NOT appear as a claim in the JWT. + */ + SignedMetadata string `json:"signed_metadata,omitempty"` } // OAuth2DiscoveryOptions represents the discovery options specific to OAuth 2.0. @@ -427,6 +471,98 @@ type OAuth2DiscoveryOptions struct { CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported,omitempty"` } +type OAuth2JWTIntrospectionResponseDiscoveryOptions struct { + /* + OPTIONAL. JSON array containing a list of the JWS [RFC7515] signing algorithms ("alg" values) as defined in JWA + [RFC7518] supported by the introspection endpoint to sign the response. + */ + IntrospectionSigningAlgValuesSupported []string `json:"introspection_signing_alg_values_supported,omitempty"` + + /* + OPTIONAL. JSON array containing a list of the JWE [RFC7516] encryption algorithms ("alg" values) as defined in + JWA [RFC7518] supported by the introspection endpoint to encrypt the content encryption key for introspection + responses (content key encryption). + */ + IntrospectionEncryptionAlgValuesSupported []string `json:"introspection_encryption_alg_values_supported"` + + /* + OPTIONAL. JSON array containing a list of the JWE [RFC7516] encryption algorithms ("enc" values) as defined in + JWA [RFC7518] supported by the introspection endpoint to encrypt the response (content encryption). + */ + IntrospectionEncryptionEncValuesSupported []string `json:"introspection_encryption_enc_values_supported"` +} + +type OAuth2DeviceAuthorizationGrantDiscoveryOptions struct { + /* + OPTIONAL. URL of the authorization server's device authorization endpoint, as defined in Section 3.1. + */ + DeviceAuthorizationEndpoint string `json:"device_authorization_endpoint"` +} + +type OAuth2MutualTLSClientAuthenticationDiscoveryOptions struct { + /* + OPTIONAL. Boolean value indicating server support for mutual-TLS client certificate-bound access tokens. If + omitted, the default value is false. + */ + TLSClientCertificateBoundAccessTokens bool `json:"tls_client_certificate_bound_access_tokens"` + + /* + OPTIONAL. A JSON object containing alternative authorization server endpoints that, when present, an OAuth + client intending to do mutual TLS uses in preference to the conventional endpoints. The parameter value itself + consists of one or more endpoint parameters, such as token_endpoint, revocation_endpoint, + introspection_endpoint, etc., conventionally defined for the top level of authorization server metadata. An + OAuth client intending to do mutual TLS (for OAuth client authentication and/or to acquire or use + certificate-bound tokens) when making a request directly to the authorization server MUST use the alias URL of + the endpoint within the mtls_endpoint_aliases, when present, in preference to the endpoint URL of the same name + at the top level of metadata. When an endpoint is not present in mtls_endpoint_aliases, then the client uses the + conventional endpoint URL defined at the top level of the authorization server metadata. Metadata parameters + within mtls_endpoint_aliases that do not define endpoints to which an OAuth client makes a direct request have + no meaning and SHOULD be ignored. + */ + MutualTLSEndpointAliases struct { + AuthorizationEndpoint string `json:"authorization_endpoint,omitempty"` + TokenEndpoint string `json:"token_endpoint,omitempty"` + IntrospectionEndpoint string `json:"introspection_endpoint,omitempty"` + RevocationEndpoint string `json:"revocation_endpoint,omitempty"` + EndSessionEndpoint string `json:"end_session_endpoint,omitempty"` + UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"` + BackChannelAuthenticationEndpoint string `json:"backchannel_authentication_endpoint,omitempty"` + FederationRegistrationEndpoint string `json:"federation_registration_endpoint,omitempty"` + PushedAuthorizationRequestEndpoint string `json:"pushed_authorization_request_endpoint,omitempty"` + RegistrationEndpoint string `json:"registration_endpoint,omitempty"` + } `json:"mtls_endpoint_aliases"` +} + +type OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions struct { + /* + Indicates where authorization request needs to be protected as Request Object and provided through either + request or request_uri parameter. + */ + RequireSignedRequestObject bool `json:"require_signed_request_object"` +} + +type OAuth2IssuerIdentificationDiscoveryOptions struct { + AuthorizationResponseIssuerParameterSupported bool `json:"authorization_response_iss_parameter_supported"` +} + +// OAuth2PushedAuthorizationDiscoveryOptions represents the well known discovery document specific to the +// OAuth 2.0 Pushed Authorization Requests (RFC9126) implementation. +// +// OAuth 2.0 Pushed Authorization Requests: https://datatracker.ietf.org/doc/html/rfc9126#section-5 +type OAuth2PushedAuthorizationDiscoveryOptions struct { + /* + The URL of the pushed authorization request endpoint at which a client can post an authorization request to + exchange for a "request_uri" value usable at the authorization server. + */ + PushedAuthorizationRequestEndpoint string `json:"pushed_authorization_request_endpoint"` + + /* + Boolean parameter indicating whether the authorization server accepts authorization request data only via PAR. + If omitted, the default value is "false". + */ + RequirePushedAuthorizationRequests bool `json:"require_pushed_authorization_requests"` +} + // OpenIDConnectDiscoveryOptions represents the discovery options specific to OpenID Connect. type OpenIDConnectDiscoveryOptions struct { /* @@ -552,6 +688,12 @@ type OpenIDConnectDiscoveryOptions struct { */ ClaimLocalesSupported []string `json:"claims_locales_supported,omitempty"` + /* + OPTIONAL. Boolean value specifying whether the OP supports use of the request parameter, with true indicating + support. If omitted, the default value is false. + */ + RequestParameterSupported bool `json:"request_parameter_supported"` + /* OPTIONAL. Boolean value specifying whether the OP supports use of the request_uri parameter, with true indicating support. If omitted, the default value is true. @@ -612,39 +754,202 @@ type OpenIDConnectBackChannelLogoutDiscoveryOptions struct { BackChannelLogoutSessionSupported bool `json:"backchannel_logout_session_supported"` } -// PushedAuthorizationDiscoveryOptions represents the well known discovery document specific to the -// OAuth 2.0 Pushed Authorization Requests (RFC9126) implementation. +// OpenIDConnectSessionManagementDiscoveryOptions represents the discovery options specific to OpenID Connect 1.0 +// Session Management. // -// OAuth 2.0 Pushed Authorization Requests: https://datatracker.ietf.org/doc/html/rfc9126#section-5 -type PushedAuthorizationDiscoveryOptions struct { +// To support OpenID Connect Session Management, the RP needs to obtain the Session Management related OP metadata. This +// OP metadata is normally obtained via the OP's Discovery response, as described in OpenID Connect Discovery 1.0, or +// MAY be learned via other mechanisms. This OpenID Provider Metadata parameter MUST be included in the Server's +// discovery responses when Session Management and Discovery are supported. +// +// See Also: +// +// OpenID Connect 1.0 Session Management: https://openid.net/specs/openid-connect-session-1_0.html +type OpenIDConnectSessionManagementDiscoveryOptions struct { /* - The URL of the pushed authorization request endpoint at which a client can post an authorization request to - exchange for a "request_uri" value usable at the authorization server. + REQUIRED. URL of an OP iframe that supports cross-origin communications for session state information with the + RP Client, using the HTML5 postMessage API. This URL MUST use the https scheme and MAY contain port, path, and + query parameter components. The page is loaded from an invisible iframe embedded in an RP page so that it can + run in the OP's security context. It accepts postMessage requests from the relevant RP iframe and uses + postMessage to post back the login status of the End-User at the OP. */ - PushedAuthorizationRequestEndpoint string `json:"pushed_authorization_request_endpoint,omitempty"` + CheckSessionIFrame string `json:"check_session_iframe"` +} + +// OpenIDConnectRPInitiatedLogoutDiscoveryOptions represents the discovery options specific to +// OpenID Connect RP-Initiated Logout 1.0. +// +// To support OpenID Connect RP-Initiated Logout, the RP needs to obtain the RP-Initiated Logout related OP metadata. +// This OP metadata is normally obtained via the OP's Discovery response, as described in OpenID Connect Discovery 1.0, +// or MAY be learned via other mechanisms. This OpenID Provider Metadata parameter MUST be included in the Server's +// discovery responses when RP-Initiated Logout and Discovery are supported. +// +// See Also: +// +// OpenID Connect RP-Initiated Logout 1.0: https://openid.net/specs/openid-connect-rpinitiated-1_0.html +type OpenIDConnectRPInitiatedLogoutDiscoveryOptions struct { + /* + 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. - If omitted, the default value is "false". + REQUIRED. JSON array containing one or more of the following values: poll, ping, and push. */ - 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. type OAuth2WellKnownConfiguration struct { CommonDiscoveryOptions OAuth2DiscoveryOptions - PushedAuthorizationDiscoveryOptions + *OAuth2DeviceAuthorizationGrantDiscoveryOptions + *OAuth2MutualTLSClientAuthenticationDiscoveryOptions + *OAuth2IssuerIdentificationDiscoveryOptions + *OAuth2JWTIntrospectionResponseDiscoveryOptions + *OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions + *OAuth2PushedAuthorizationDiscoveryOptions } // OpenIDConnectWellKnownConfiguration represents the well known discovery document specific to OpenID Connect. type OpenIDConnectWellKnownConfiguration struct { - CommonDiscoveryOptions - OAuth2DiscoveryOptions - PushedAuthorizationDiscoveryOptions + OAuth2WellKnownConfiguration + OpenIDConnectDiscoveryOptions - OpenIDConnectFrontChannelLogoutDiscoveryOptions - OpenIDConnectBackChannelLogoutDiscoveryOptions + *OpenIDConnectFrontChannelLogoutDiscoveryOptions + *OpenIDConnectBackChannelLogoutDiscoveryOptions + *OpenIDConnectSessionManagementDiscoveryOptions + *OpenIDConnectRPInitiatedLogoutDiscoveryOptions + *OpenIDConnectPromptCreateDiscoveryOptions + *OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions + *OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions + *OpenIDFederationDiscoveryOptions } // OpenIDConnectContext represents the context implementation that is used by some OpenID Connect 1.0 implementations. diff --git a/internal/oidc/types_test.go b/internal/oidc/types_test.go index b84461a07..d604ad009 100644 --- a/internal/oidc/types_test.go +++ b/internal/oidc/types_test.go @@ -40,7 +40,7 @@ func TestNewSessionWithAuthorizeRequest(t *testing.T) { Request: fosite.Request{ ID: requestID.String(), Form: formValues, - Client: &Client{ID: "example"}, + Client: &BaseClient{ID: "example"}, }, } @@ -50,7 +50,7 @@ func TestNewSessionWithAuthorizeRequest(t *testing.T) { requested := time.Unix(1647332518, 0) authAt := time.Unix(1647332500, 0) - issuer := "https://example.com" + issuer := examplecom amr := []string{AMRPasswordBasedAuthentication} consent := &model.OAuth2ConsentSession{ diff --git a/internal/server/public_html/api/index.html b/internal/server/public_html/api/index.html index e69de29bb..7d41d83af 100644 --- a/internal/server/public_html/api/index.html +++ b/internal/server/public_html/api/index.html @@ -0,0 +1,60 @@ + + + + + + Swagger UI + + + + + + + +
+ + + + + + diff --git a/internal/server/public_html/api/openapi.yml b/internal/server/public_html/api/openapi.yml index e69de29bb..6bc5cfb79 100644 --- a/internal/server/public_html/api/openapi.yml +++ b/internal/server/public_html/api/openapi.yml @@ -0,0 +1,3848 @@ +--- +openapi: 3.0.3 +info: + title: Authelia API + description: > + Authelia is an open-source authentication and authorization server providing 2-factor authentication and single + sign-on (SSO) for your applications via a web portal. + contact: + name: Support + url: https://www.authelia.com/contact/ + email: team@authelia.com + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0 + version: 1.0.0 +servers: + - url: "{{ .BaseURL }}" + description: Authelia API +tags: + - name: State + description: Configuration, health and state endpoints + - name: Authentication + description: Authentication endpoints + - name: Authorization + description: Authorization endpoints + {{- if .PasswordReset }} + - name: Password Reset + description: Password reset endpoints + - name: User Information + description: User configuration endpoints + {{- end }} + {{- if (or .TOTP .Webauthn .Duo) }} + - name: Second Factor + description: TOTP, Webauthn and Duo endpoints + externalDocs: + url: https://www.authelia.com/configuration/second-factor/introduction/ + {{- end }} + {{- if .OpenIDConnect }} + - name: OpenID Connect 1.0 + description: OpenID Connect 1.0 and OAuth 2.0 Endpoints + externalDocs: + url: https://www.authelia.com/integration/openid-connect/introduction/ + {{- end }} +paths: + /api/configuration: + get: + tags: + - State + summary: Application Configuration + description: > + The configuration endpoint provides detailed information including available second factor methods, if any + second factor policies exist and the TOTP period configuration. + responses: + "200": + description: Successful Operation + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.configuration.ConfigurationBody' + "403": + description: Forbidden + security: + - authelia_auth: [] + /api/configuration/password-policy: + get: + tags: + - State + summary: Password Policy Configuration + description: > + The password policy configuration endpoint provides a password policy for resetting passwords. + responses: + "200": + description: Successful Operation + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.configuration.PasswordPolicyConfigurationBody' + /api/health: + head: + tags: + - State + summary: Application Health + description: The health check endpoint provides information about the health of Authelia. + responses: + "200": + description: Successful Operation + get: + tags: + - State + summary: Application Health + description: The health check endpoint provides information about the health of Authelia. + responses: + "200": + description: Successful Operation + content: + application/json: + schema: + $ref: '#/components/schemas/middlewares.OkResponse' + /api/state: + get: + tags: + - State + summary: User Application State + description: > + The state endpoint provides detailed information including the user, current authenticate level and Authelia's + configured default redirection URL. + responses: + "200": + description: Successful Operation + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.StateResponse' + {{- $app := "" }}{{ if .Domain }}{{ $app = printf "https://%s/" .Domain }}{{ else if .BaseURL }}{{ $app = .BaseURL }}{{ else }}{{ $app = "https://app.example.com" }}{{ end }} + {{- $redir := printf "%s?rd=%s&rm=GET" (.BaseURL | default "https://auth.example.com/") (urlquery $app) }} + {{- range $name, $config := .EndpointsAuthz }} + {{- $uri := printf "/api/authz/%s" $name }} + {{- if (eq $name "legacy") }}{{ $uri = "/api/verify" }}{{ end }} + {{ $uri }}: + {{- if (eq $config.Implementation "Legacy") }} + {{- range $method := list "get" "head" "options" "post" "put" "patch" "delete" "trace" }} + {{ $method }}: + tags: + - Authorization + summary: Authorization Verification (Legacy) + description: > + The legacy authorization verification endpoint provides the ability to verify if a user has the necessary + permissions to access a specified domain with several proxies. It's generally recommended users use a proxy + specific endpoint instead. + parameters: + - name: X-Original-URL + in: header + description: Redirection URL + required: false + style: simple + explode: true + schema: + type: string + - $ref: '#/components/parameters/forwardedMethodParam' + - name: X-Forwarded-Proto + in: header + description: Redirection URL (Scheme / Protocol) + required: false + style: simple + explode: true + example: 'https' + schema: + type: string + - name: X-Forwarded-Host + in: header + description: Redirection URL (Host) + required: false + style: simple + explode: true + example: '{{ $.Domain | default "example.com" }}' + schema: + type: string + - name: X-Forwarded-Uri + in: header + description: Redirection URL (URI) + required: false + style: simple + explode: true + example: '/path/example' + schema: + type: string + - $ref: '#/components/parameters/forwardedForParam' + - $ref: '#/components/parameters/authParam' + responses: + "200": + description: Successful Operation + headers: + remote-user: + description: Username + schema: + type: string + example: john + remote-name: + description: Name + schema: + type: string + example: John Doe + remote-email: + description: Email + schema: + type: string + example: john.doe@authelia.com + remote-groups: + description: Comma separated list of Groups + schema: + type: string + example: admin,devs + set-cookie: + description: Sets a new cookie value + schema: + type: string + "302": + description: Found + headers: + location: + description: Redirect Location for user authorization + example: '{{ $redir }}' + set-cookie: + description: Sets a new cookie value + schema: + type: string + "303": + description: See Other + headers: + location: + description: Redirect Location for user authorization + example: '{{ $redir }}' + set-cookie: + description: Sets a new cookie value + schema: + type: string + "401": + description: Unauthorized + headers: + set-cookie: + description: Sets a new cookie value + schema: + type: string + security: + - authelia_auth: [] + {{- end }} + {{- else if (eq $config.Implementation "ExtAuthz") }} + {{- range $method := list "get" "head" "options" "post" "put" "patch" "delete" "trace" }} + {{ $method }}: + tags: + - Authorization + summary: Authorization Verification (ExtAuthz) + description: > + The ExtAuthz authorization verification endpoint provides the ability to verify if a user has the necessary + permissions to access a specified resource with the Envoy proxy. + parameters: + - $ref: '#/components/parameters/forwardedMethodParam' + - $ref: '#/components/parameters/forwardedHostParam' + - $ref: '#/components/parameters/forwardedURIParam' + - $ref: '#/components/parameters/forwardedForParam' + - $ref: '#/components/parameters/autheliaURLParam' + responses: + "200": + description: Successful Operation + headers: + remote-user: + description: Username + schema: + type: string + example: john + remote-name: + description: Name + schema: + type: string + example: John Doe + remote-email: + description: Email + schema: + type: string + example: john.doe@authelia.com + remote-groups: + description: Comma separated list of Groups + schema: + type: string + example: admin,devs + set-cookie: + description: Sets a new cookie value + schema: + type: string + "302": + description: Found + headers: + location: + description: Redirect Location for user authorization + example: '{{ $redir }}' + set-cookie: + description: Sets a new cookie value + schema: + type: string + "303": + description: See Other + headers: + location: + description: Redirect Location for user authorization + example: '{{ $redir }}' + set-cookie: + description: Sets a new cookie value + schema: + type: string + "400": + description: Bad Request + "401": + description: Unauthorized + security: + - authelia_auth: [] + {{- end }} + {{- else if (eq $config.Implementation "ForwardAuth") }} + {{- range $method := list "get" "head" }} + {{ $method }}: + tags: + - Authorization + summary: Authorization Verification (ForwardAuth) + description: > + The ForwardAuth authorization verification endpoint provides the ability to verify if a user has the necessary + permissions to access a specified resource with the Traefik, Caddy, or Skipper proxies. + parameters: + - $ref: '#/components/parameters/forwardedMethodParam' + - $ref: '#/components/parameters/forwardedHostParam' + - $ref: '#/components/parameters/forwardedURIParam' + - $ref: '#/components/parameters/forwardedForParam' + responses: + "200": + description: Successful Operation + headers: + remote-user: + description: Username + schema: + type: string + example: john + remote-name: + description: Name + schema: + type: string + example: John Doe + remote-email: + description: Email + schema: + type: string + example: john.doe@authelia.com + remote-groups: + description: Comma separated list of Groups + schema: + type: string + example: admin,devs + set-cookie: + description: Sets a new cookie value + schema: + type: string + "302": + description: Found + headers: + location: + description: Redirect Location for user authorization + example: '{{ $redir }}' + set-cookie: + description: Sets a new cookie value + schema: + type: string + "303": + description: See Other + headers: + location: + description: Redirect Location for user authorization + example: '{{ $redir }}' + set-cookie: + description: Sets a new cookie value + schema: + type: string + "400": + description: Bad Request + "401": + description: Unauthorized + security: + - authelia_auth: [] + {{- end }} + {{- else if (eq $config.Implementation "AuthRequest") }} + {{- range $method := list "get" "head" }} + {{ $method }}: + tags: + - Authorization + summary: Authorization Verification (AuthRequest) + description: > + The AuthRequest authorization verification endpoint provides the ability to verify if a user has the necessary + permissions to access a specified resource with the HAPROXY, NGINX, or NGINX-based proxies. + parameters: + - $ref: '#/components/parameters/originalMethodParam' + - $ref: '#/components/parameters/originalURLParam' + responses: + "200": + description: Successful Operation + headers: + remote-user: + description: Username + schema: + type: string + example: john + remote-name: + description: Name + schema: + type: string + example: John Doe + remote-email: + description: Email + schema: + type: string + example: john.doe@authelia.com + remote-groups: + description: Comma separated list of Groups + schema: + type: string + example: admin,devs + set-cookie: + description: Sets a new cookie value + schema: + type: string + "400": + description: Bad Request + "401": + description: Unauthorized + headers: + location: + description: Redirect Location for user authorization + example: '{{ $redir }}' + set-cookie: + description: Sets a new cookie value + schema: + type: string + security: + - authelia_auth: [] + {{- end }} + {{- end }} + {{- end }} + /api/firstfactor: + post: + tags: + - Authentication + summary: Login + description: > + The firstfactor endpoint allows a user to login and generates an authentication cookie for authorization. + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.bodyFirstFactorRequest' + responses: + "200": + description: Successful Operation + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.redirectResponse' + "401": + description: Unauthorized + security: + - authelia_auth: [] + /api/checks/safe-redirection: + post: + tags: + - Authentication + summary: Check whether URI is safe to redirect to. + description: > + End users usually needs to be redirected to a target website after authentication. This endpoint aims to check + if target URL is safe to redirect to. This prevents open redirect attacks. + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.checkURIWithinDomainRequestBody' + responses: + "200": + description: Successful Operation + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.checkURIWithinDomainResponseBody' + "401": + description: Unauthorized + security: + - authelia_auth: [] + /api/logout: + post: + tags: + - Authentication + summary: Logout + description: The logout endpoint allows a user to logout and destroy a sesssion. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.logoutRequestBody' + responses: + "200": + description: Successful Operation + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.logoutResponseBody' + security: + - authelia_auth: [] + {{- if .PasswordReset }} + /api/reset-password/identity/start: + post: + tags: + - Password Reset + summary: Identity Verification Token Creation + description: > + This endpoint is step 1 of 3 in the password reset process. + + It validates the user session and sends the user an email with a token and a link to reset their password. This + step also generates a session cookie for the rest of the process. + + The same session cookie must be used for all steps in this process. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.PasswordResetStep1RequestBody' + responses: + "200": + description: Successful Operation + content: + application/json: + schema: + $ref: '#/components/schemas/middlewares.OkResponse' + security: + - authelia_auth: [] + /api/reset-password/identity/finish: + post: + tags: + - Password Reset + summary: Identity Verification Token Validation + description: > + This endpoint is step 2 of 3 in the password reset process. + + It validates the user session and reset token. + + The same session cookie must be used for all steps in this process. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/middlewares.IdentityVerificationFinishBody' + responses: + "200": + description: Successful Operation + content: + application/json: + schema: + $ref: '#/components/schemas/middlewares.OkResponse' + security: + - authelia_auth: [] + /api/reset-password: + post: + tags: + - Password Reset + summary: Password Reset + description: > + This endpoint is step 3 of 3 in the password reset process. + + It validates the user session and changes the password. + + The same session cookie must be used for all steps in this process. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.PasswordResetStep2RequestBody' + responses: + "200": + description: Successful Operation + content: + application/json: + schema: + $ref: '#/components/schemas/middlewares.OkResponse' + security: + - authelia_auth: [] + {{- end }} + /api/user/info: + get: + tags: + - User Information + summary: User Configuration + description: > + The user info endpoint provides detailed information including a users display name, preferred and registered + second factor method(s). + responses: + "200": + description: Successful Operation + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.UserInfo' + "403": + description: Forbidden + security: + - authelia_auth: [] + post: + tags: + - User Information + summary: User Configuration + description: > + The user info endpoint provides detailed information including a users display name, preferred and registered + second factor method(s). The POST method also ensures the preferred method is configured correctly. + responses: + "200": + description: Successful Operation + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.UserInfo' + "403": + description: Forbidden + security: + - authelia_auth: [] + /api/user/info/2fa_method: + post: + tags: + - User Information + summary: User Configuration + description: The user info 2fa_method endpoint sets the users preferred second factor method. + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.UserInfo.MethodBody' + responses: + "200": + description: Successful Operation + content: + application/json: + schema: + $ref: '#/components/schemas/middlewares.OkResponse' + "403": + description: Forbidden + security: + - authelia_auth: [] + {{- if .TOTP }} + /api/user/info/totp: + get: + tags: + - User Information + summary: User TOTP Configuration + description: > + The user TOTP info endpoint provides information necessary to display the TOTP component to validate their + TOTP input such as the period/frequency and number of digits. + responses: + "200": + description: Successful Operation + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.UserInfoTOTP' + "403": + description: Forbidden + security: + - authelia_auth: [] + /api/secondfactor/totp/identity/start: + post: + tags: + - Second Factor + summary: Identity Verification TOTP Token Creation + description: > + This endpoint performs identity verification to begin the TOTP device registration process. + + The session generated from this endpoint must be utilised for the subsequent step in the + `/api/secondfactor/totp/identity/finish` endpoint. + responses: + "200": + description: Successful Operation + content: + application/json: + schema: + $ref: '#/components/schemas/middlewares.OkResponse' + security: + - authelia_auth: [] + /api/secondfactor/totp/identity/finish: + post: + tags: + - Second Factor + summary: Identity Verification TOTP Token Validation and Device Creation + description: > + This endpoint performs identity and token verification, upon success also generates TOTP device secret and + registers said device. + + The session cookie generated from the `/api/secondfactor/totp/identity/start` endpoint must be utilised for the + step here. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/middlewares.IdentityVerificationFinishBody' + responses: + "200": + description: Successful Operation + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.TOTPKeyResponse' + security: + - authelia_auth: [] + /api/secondfactor/totp: + post: + tags: + - Second Factor + summary: Second Factor Authentication - TOTP + description: This endpoint performs second factor authentication with a TOTP key. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.bodySignTOTPRequest' + responses: + "200": + description: Successful Operation + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.redirectResponse' + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/middlewares.ErrorResponse' + security: + - authelia_auth: [] + {{- end }} + {{- if .Webauthn }} + /api/secondfactor/webauthn/assertion: + get: + tags: + - Second Factor + summary: Second Factor Authentication - Webauthn (Request) + description: This endpoint starts the second factor authentication process with the FIDO2 Webauthn credential. + responses: + "200": + description: Successful Operation + content: + application/json: + schema: + $ref: '#/components/schemas/webauthn.PublicKeyCredentialRequestOptions' + "401": + description: Unauthorized + security: + - authelia_auth: [] + post: + tags: + - Second Factor + summary: Second Factor Authentication - Webauthn + description: This endpoint completes the second factor authentication process with the FIDO2 Webauthn credential. + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/webauthn.CredentialAssertionResponse" + responses: + "200": + description: Successful Operation + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.redirectResponse' + "401": + description: Unauthorized + security: + - authelia_auth: [] + /api/secondfactor/webauthn/identity/start: + post: + tags: + - Second Factor + summary: Identity Verification Webauthn Credential Creation + description: > + This endpoint performs identity verification to begin the FIDO2 Webauthn credential attestation process + (registration). + + The session generated from this endpoint must be utilised for the subsequent steps in the + `/api/secondfactor/webauthn/identity/finish` and `/api/secondfactor/webauthn/attestation` endpoints. + responses: + "200": + description: Successful Operation + content: + application/json: + schema: + $ref: '#/components/schemas/middlewares.OkResponse' + security: + - authelia_auth: [] + /api/secondfactor/webauthn/identity/finish: + post: + tags: + - Second Factor + summary: Identity Verification FIDO2 Webauthn Credential Validation + description: > + This endpoint performs identity and token verification, upon success generates a FIDO2 Webauthn device + attestation challenge (registration). + + The session cookie generated from the `/api/secondfactor/webauthn/identity/start` endpoint must be utilised + for the subsequent steps here and in the `/api/secondfactor/webauthn/attestation` endpoint. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/middlewares.IdentityVerificationFinishBody' + responses: + "200": + description: Successful Operation + content: + application/json: + schema: + $ref: '#/components/schemas/webauthn.PublicKeyCredentialCreationOptions' + security: + - authelia_auth: [] + /api/secondfactor/webauthn/attestation: + post: + tags: + - Second Factor + summary: Webauthn Credential Attestation + description: This endpoint performs Webauthn credential attestation (registration). + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/webauthn.CredentialAttestationResponse' + responses: + "200": + description: Successful Operation + content: + application/json: + schema: + $ref: '#/components/schemas/middlewares.OkResponse' + security: + - authelia_auth: [] + {{- end }} + {{- if .Duo }} + /api/secondfactor/duo: + post: + tags: + - Second Factor + summary: Second Factor Authentication - Duo Mobile Push + description: This endpoint performs second factor authentication with a Duo Mobile Push. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.bodySignDuoRequest' + responses: + "200": + description: Successful Operation + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.redirectResponse' + "401": + description: Unauthorized + security: + - authelia_auth: [] + /api/secondfactor/duo_devices: + get: + tags: + - Second Factor + summary: Second Factor Authentication - Duo Mobile Push + description: This endpoint retrieves a users available devices and capabilities from Duo. + responses: + "200": + description: Successful Operation + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.DuoDevicesResponse' + "401": + description: Unauthorized + security: + - authelia_auth: [] + /api/secondfactor/duo_device: + post: + tags: + - Second Factor + summary: Second Factor Authentication - Duo Mobile Push + description: This endpoint updates the users preferred Duo device and method. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.DuoDeviceBody' + responses: + "200": + description: Successful Operation + content: + application/json: + schema: + $ref: '#/components/schemas/middlewares.OkResponse' + "401": + description: Unauthorized + security: + - authelia_auth: [] + {{- end }} + {{- if .OpenIDConnect }} + /.well-known/openid-configuration: + get: + tags: + - OpenID Connect 1.0 + summary: OpenID Connect Discovery 1.0 Document + description: > + This endpoint retrieves the OpenID Connect Discovery 1.0 document used by clients to perform discovery for + an OpenID Connect 1.0 Provider. See https://openid.net/specs/openid-connect-discovery-1_0.html. + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/openid.spec.Metadata.OpenIDConfiguration' + "400": + description: Bad Request + "500": + description: Internal Server Error + /.well-known/oauth-authorization-server: + get: + tags: + - OpenID Connect 1.0 + summary: OAuth 2.0 Authorization Server Metadata + description: > + This endpoint retrieves the OAuth 2.0 Authorization Server Metadata document (RFC8414) used by clients to + perform discovery for an OAuth 2.0 Authorization Server. See https://datatracker.ietf.org/doc/html/rfc8414. + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/openid.spec.Metadata.OAuth2AuthorizationServer' + "400": + description: Bad Request + "500": + description: Internal Server Error + /jwks.json: + get: + tags: + - OpenID Connect 1.0 + summary: OpenID Connect 1.0 JSON Web Key Set Document + description: > + This endpoint retrieves the OpenID Connect 1.0 JSON Web Key Set Document (JWKS) used by clients to validate + information from this OpenID Connect 1.0 Provider. + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/jose.spec.JWKs' + /api/oidc/authorization: + get: + tags: + - OpenID Connect 1.0 + summary: OpenID Connect 1.0 Authorization Endpoint + description: > + This endpoint performs OpenID Connect 1.0 Authorization. + parameters: + - in: query + name: id + required: false + description: The OpenID Connect 1.0 consent workflow ID. + schema: + type: string + format: uuid + pattern: '^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$' + example: '713ef767-81bc-4a27-9b83-5fe2e101b2b4' + - in: query + name: scope + description: The requested scope. + required: true + schema: + type: string + example: 'openid profile groups' + - in: query + name: response_type + description: The OAuth 2.0 response type. + required: true + schema: + $ref: '#/components/schemas/openid.spec.ResponseType' + - in: query + name: client_id + description: The OAuth 2.0 client identifier. + required: true + schema: + type: string + example: 'app' + - in: query + name: redirect_uri + description: > + Redirection URI to which the response will be sent. This URI MUST exactly match one of the Redirection URI + values for the Client pre-registered at the OpenID Provider, with the matching performed as described in + Section 6.2.1 of [RFC3986] (Simple String Comparison). When using this flow, the Redirection URI SHOULD use + the https scheme; however, it MAY use the http scheme, provided that the Client Type is confidential, as + defined in Section 2.1 of OAuth 2.0, and provided the OP allows the use of http Redirection URIs in this + case. The Redirection URI MAY use an alternate scheme, such as one that is intended to identify a callback + into a native application. + required: true + schema: + type: string + example: 'https://app.{{ .Domain | default "example.com" }}' + - in: query + name: state + description: > + Opaque value used to maintain state between the request and the callback. Typically, Cross-Site Request + Forgery (CSRF, XSRF) mitigation is done by cryptographically binding the value of this parameter with a + browser cookie. + required: false + schema: + type: string + example: 'oV84Vsy7wyCgRk2h4aZBmXZq4q3g2f' + - in: query + name: response_mode + description: > + Informs the Authorization Server of the mechanism to be used for returning parameters from the Authorization + Endpoint. This use of this parameter is NOT RECOMMENDED when the Response Mode that would be requested is + the default mode specified for the Response Type. + required: false + schema: + $ref: '#/components/schemas/openid.spec.ResponseMode' + - in: query + name: nonce + description: > + String value used to associate a Client session with an ID Token, and to mitigate replay attacks. The value + is passed through unmodified from the Authentication Request to the ID Token. Sufficient entropy MUST be + present in the nonce values used to prevent attackers from guessing values. For implementation notes, see + Section 15.5.2. + required: false + schema: + type: string + example: 'TRMLqchoKGQNcooXvBvUy9PtmLdJGf' + - in: query + name: display + description: > + Not Supported: ASCII string value that specifies how the Authorization Server displays the authentication + and consent user interface pages to the End-User. + required: false + schema: + $ref: '#/components/schemas/openid.spec.DisplayType' + - in: query + name: prompt + description: > + Not Supported: Space delimited, case sensitive list of ASCII string values that specifies whether the + Authorization Server prompts the End-User for reauthentication and consent. + required: false + schema: + type: string + - in: query + name: max_age + description: > + Maximum Authentication Age. Specifies the allowable elapsed time in seconds since the last time the End-User + was actively authenticated by the OP. If the elapsed time is greater than this value, the OP MUST attempt to + actively re-authenticate the End-User. (The max_age request parameter corresponds to the OpenID 2.0 PAPE + [OpenID.PAPE] max_auth_age request parameter.) When max_age is used, the ID Token returned MUST include an + auth_time Claim Value. + required: false + schema: + type: integer + example: 3600 + - in: query + name: ui_locales + description: > + Not Supported: End-User's preferred languages and scripts for the user interface, represented as a + space-separated list of BCP47 [RFC5646] language tag values, ordered by preference. For instance, the value + "fr-CA fr en" represents a preference for French as spoken in Canada, then French (without a region + designation), followed by English (without a region designation). An error SHOULD NOT result if some or all + of the requested locales are not supported by the OpenID Provider. + required: false + schema: + type: string + example: 'en-US' + - in: query + name: claims_locales + description: > + Not Supported: End-User's preferred languages and scripts for Claims being returned, represented as a + space-separated list of BCP47 [RFC5646] language tag values, ordered by preference. An error SHOULD NOT + result if some or all of the requested locales are not supported by the OpenID Provider. + required: false + schema: + type: string + example: 'en-US' + - in: query + name: id_token_hint + required: false + description: > + Not Supported: ID Token previously issued by the Authorization Server being passed as a hint about the + End-User's current or past authenticated session with the Client. If the End-User identified by the ID Token + is logged in or is logged in by the request, then the Authorization Server returns a positive response; + otherwise, it SHOULD return an error, such as login_required. When possible, an id_token_hint SHOULD be + present when prompt=none is used and an invalid_request error MAY be returned if it is not; however, the + server SHOULD respond successfully when possible, even if it is not present. The Authorization Server need + not be listed as an audience of the ID Token when it is used as an id_token_hint value. If the ID Token + received by the RP from the OP is encrypted, to use it as an id_token_hint, the Client MUST decrypt the + signed ID Token contained within the encrypted ID Token. The Client MAY re-encrypt the signed ID token to + the Authentication Server using a key that enables the server to decrypt the ID Token, and use the + re-encrypted ID token as the id_token_hint value. + schema: + type: string + - in: query + name: login_hint + description: > + Not Supported: Hint to the Authorization Server about the login identifier the End-User might use to log in + (if necessary). This hint can be used by an RP if it first asks the End-User for their e-mail address + (or other identifier) and then wants to pass that value as a hint to the discovered authorization service. + It is RECOMMENDED that the hint value match the value used for discovery. This value MAY also be a phone + number in the format specified for the phone_number Claim. The use of this parameter is left to the OP's + discretion. + required: false + schema: + type: string + - in: query + name: acr_values + description: > + Not Supported: Requested Authentication Context Class Reference values. Space-separated string that + specifies the acr values that the Authorization Server is being requested to use for processing this + Authentication Request, with the values appearing in order of preference. The Authentication Context Class + satisfied by the authentication performed is returned as the acr Claim Value, as specified in Section 2. + The acr Claim is requested as a Voluntary Claim by this parameter. + required: false + schema: + type: string + - in: query + name: claims + description: > + Not Supported: The claims parameter value, as specified in Section 5.5. + required: false + schema: + type: string + - in: query + name: registration + description: > + Not Supported: This parameter is used by the Client to provide information about itself to a Self-Issued OP + that would normally be provided to an OP during Dynamic Client Registration, as specified in Section 7.2.1. + required: false + schema: + type: string + - in: query + name: request + description: > + Not Supported: Request Object value, as specified in Section 6.1. The Request Object MAY be encrypted to + the Self-Issued OP by the Client. In this case, the sub (subject) of a previously issued ID Token for this + Client MUST be sent as the kid (Key ID) of the JWE. Encrypting content to Self-Issued OPs is currently only + supported when the OP's JWK key type is RSA and the encryption algorithm used is RSA1_5. + required: false + schema: + type: string + - in: query + name: code_challenge + description: > + RFC7636 Code Challenge. + required: false + schema: + type: string + - in: query + name: code_challenge_method + required: false + description: > + RFC7636 Code Challenge Method. defaults to "plain" if not present in the request. + Code verifier transformation method is "S256" or "plain". + schema: + $ref: '#/components/schemas/openid.spec.CodeChallengeMethod' + responses: + "200": + description: OK + content: + text/html: + schema: + type: string + description: The Form Post Response Mode content. + "303": + description: See Other + headers: + Location: + schema: + type: string + description: > + Redirection location for the consent flow, or the authorization response callback location when using + the Query or Fragment Response Modes. + "400": + description: Bad Request + "500": + description: Internal Server Error + post: + tags: + - OpenID Connect 1.0 + summary: OpenID Connect 1.0 Authorization Endpoint + description: > + This endpoint performs OpenID Connect 1.0 Authorization. + requestBody: + description: Authorize Request Parameters. + required: true + content: + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/openid.spec.AuthorizeRequest' + responses: + "200": + description: OK + content: + text/html: + schema: + type: string + description: The Form Post Response Mode content. + "303": + description: See Other + headers: + Location: + schema: + type: string + description: > + Redirection location for the consent flow, or the authorization response callback location when using + the Query or Fragment Response Modes. + "400": + description: Bad Request + "500": + description: Internal Server Error + security: + - authelia_auth: [] + /api/oidc/token: + post: + tags: + - OpenID Connect 1.0 + summary: OpenID Connect 1.0 Token Endpoint + description: > + This endpoint performs OpenID Connect 1.0 Token Access Requests. + requestBody: + description: Access Request Parameters. + required: true + content: + application/x-www-form-urlencoded: + schema: + oneOf: + - $ref: '#/components/schemas/openid.spec.AccessRequest.AuthorizationCodeFlow' + - $ref: '#/components/schemas/openid.spec.AccessRequest.RefreshTokenFlow' + - $ref: '#/components/schemas/openid.spec.AccessRequest.DeviceCodeFlow' + responses: + "200": + description: OK + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/openid.spec.AccessResponse' + "401": + description: Forbidden + "403": + description: Unauthorized + "500": + description: Internal Server Error + security: + - openid: [] + /api/oidc/revocation: + post: + tags: + - OpenID Connect 1.0 + summary: OAuth 2.0 Token Revocation Endpoint + description: > + This endpoint performs OAuth 2.0 Token Revocation Requests. + requestBody: + description: Required OAuth 2.0 revocation parameters. + required: true + content: + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/openid.spec.IntrospectionRequest' + responses: + "200": + description: OK + "401": + description: Forbidden + "403": + description: Unauthorized + "500": + description: Internal Server Error + security: + - openid: [] + /api/oidc/introspection: + post: + tags: + - OpenID Connect 1.0 + summary: OAuth 2.0 Token Introspection Endpoint + description: > + This endpoint performs OAuth 2.0 Token Introspection Requests. + requestBody: + description: Required OAuth 2.0 introspection parameters. + required: true + content: + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/openid.spec.IntrospectionRequest' + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/openid.implementation.Claims.Object' + "401": + description: Forbidden + "403": + description: Unauthorized + "500": + description: Internal Server Error + security: + - openid: [] + /api/oidc/userinfo: + get: + tags: + - OpenID Connect 1.0 + summary: OpenID Connect 1.0 UserInfo Endpoint + description: > + This endpoint performs OpenID Connect 1.0 UserInfo Access Requests. + parameters: + - in: query + name: access_token + description: The OAuth 2.0 Access Token issued by this OpenID Connect 1.0 Provider. + schema: + type: string + example: 'authelia_at_cr4i4EtTn2F4k6mX4XzxbsBewkxCGn' + responses: + "200": + description: OK + content: + application/jwt: {} + application/json: + schema: + $ref: '#/components/schemas/openid.implementation.Claims.Object' + "401": + description: Forbidden + "403": + description: Unauthorized + "500": + description: Internal Server Error + security: + - openid: [] + post: + tags: + - OpenID Connect 1.0 + summary: OpenID Connect 1.0 UserInfo Endpoint + description: > + This endpoint performs OpenID Connect 1.0 UserInfo Access Requests. + parameters: + - in: query + name: access_token + description: The OAuth 2.0 Access Token issued by this OpenID Connect 1.0 Provider. + schema: + type: string + example: 'authelia_at_cr4i4EtTn2F4k6mX4XzxbsBewkxCGn' + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + access_token: + description: The OAuth 2.0 Access Token issued by this OpenID Connect 1.0 Provider. + type: string + example: 'authelia_at_cr4i4EtTn2F4k6mX4XzxbsBewkxCGn' + responses: + "200": + description: OK + content: + application/jwt: {} + application/json: + schema: + $ref: '#/components/schemas/openid.implementation.Claims.Object' + "401": + description: Forbidden + "403": + description: Unauthorized + "500": + description: Internal Server Error + security: + - openid: [] + /api/oidc/consent: + get: + tags: + - OpenID Connect 1.0 + summary: OpenID Connect 1.0 Consent Information + description: > + This endpoint retrieves the consent information about a specific consent ID during the consent workflow. + parameters: + - $ref: '#/components/parameters/idRequiredParam' + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/openid.request.consent' + "403": + description: Forbidden + security: + - authelia_auth: [] + post: + tags: + - OpenID Connect 1.0 + summary: OpenID Connect 1.0 Consent Response + description: > + This endpoint retrieves the consent response for a specific consent ID during the consent workflow. + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/openid.response.consent' + "403": + description: Forbidden + security: + - authelia_auth: [] + {{- end }} +components: + parameters: + originalMethodParam: + name: X-Original-Method + in: header + description: Request Method + required: true + style: simple + explode: true + schema: + type: string + enum: + - "GET" + - "HEAD" + - "POST" + - "PUT" + - "PATCH" + - "DELETE" + - "TRACE" + - "CONNECT" + - "OPTIONS" + - "COPY" + - "LOCK" + - "MKCOL" + - "MOVE" + - "PROPFIND" + - "PROPPATCH" + - "UNLOCK" + originalURLParam: + name: X-Original-URL + in: header + description: Redirection URL + required: true + style: simple + explode: true + schema: + type: string + forwardedMethodParam: + name: X-Forwarded-Method + in: header + description: Request Method + required: false + style: simple + explode: true + schema: + type: string + enum: + - "GET" + - "HEAD" + - "POST" + - "PUT" + - "PATCH" + - "DELETE" + - "TRACE" + - "CONNECT" + - "OPTIONS" + - "COPY" + - "LOCK" + - "MKCOL" + - "MOVE" + - "PROPFIND" + - "PROPPATCH" + - "UNLOCK" + forwardedProtoParam: + name: X-Forwarded-Proto + in: header + description: Redirection URL (Scheme / Protocol) + required: true + style: simple + explode: true + example: 'https' + schema: + type: string + forwardedHostParam: + name: X-Forwarded-Host + in: header + description: Redirection URL (Host) + required: true + style: simple + explode: true + example: '{{ .Domain | default "example.com" }}' + schema: + type: string + forwardedURIParam: + name: X-Forwarded-Uri + in: header + description: Redirection URL (URI) + required: true + style: simple + explode: true + example: '/path/example' + schema: + type: string + forwardedForParam: + name: X-Forwarded-For + in: header + description: Clients IP address or IP address chain + required: false + style: simple + explode: true + example: '192.168.0.55,192.168.0.20' + schema: + type: string + autheliaURLParam: + name: X-Authelia-URL + in: header + description: Authelia Portal URL + required: false + style: simple + explode: true + example: '{{ .BaseURL | default "https://auth.example.com" }}' + schema: + type: string + authParam: + name: auth + in: query + description: Switch authorization header and prompt for basic auth + required: false + schema: + type: string + enum: ["basic"] + idRequiredParam: + name: id + in: query + description: The ID of what is being requested + required: true + schema: + type: string + schemas: + handlers.checkURIWithinDomainRequestBody: + type: object + properties: + uri: + type: string + example: 'https://secure.{{ .Domain | default "example.com" }}' + handlers.checkURIWithinDomainResponseBody: + type: object + properties: + ok: + type: boolean + example: true + description: If redirection URL is safe. + handlers.configuration.ConfigurationBody: + type: object + properties: + status: + type: string + example: OK + data: + type: object + properties: + available_methods: + type: array + description: List of available 2FA methods. If no methods exist 2FA is disabled. + items: + enum: + - "totp" + - "webauthn" + - "mobile_push" + example: [totp, webauthn, mobile_push] + handlers.configuration.PasswordPolicyConfigurationBody: + type: object + properties: + status: + type: string + example: OK + data: + type: object + properties: + mode: + type: string + description: The password policy mode. + enum: + - "disabled" + - "standard" + - "zxcvbn" + min_length: + type: integer + description: The minimum password length when using the standard mode. + max_length: + type: integer + description: The maximum password length when using the standard mode. + min_score: + type: integer + description: The minimum password score when using the zxcvbn mode. + require_uppercase: + type: boolean + description: If uppercase characters are required when using the standard mode. + require_lowercase: + type: boolean + description: If uppercase characters are required when using the standard mode. + require_number: + type: boolean + description: If numeric characters are required when using the standard mode. + require_special: + type: boolean + description: If special characters are required when using the standard mode. + handlers.DuoDeviceBody: + required: + - device + - method + type: object + properties: + device: + type: string + example: ABCDE123456789FGHIJK + method: + type: string + example: push + handlers.DuoDevicesResponse: + type: object + properties: + status: + type: string + example: OK + data: + type: object + properties: + result: + type: string + example: auth + devices: + type: array + items: + type: object + properties: + device: + type: string + example: ABCDE123456789FGHIJK + display_name: + type: string + example: iOS (+XX XXX XXX 123) + capabilities: + type: array + items: + type: string + example: push + handlers.bodyFirstFactorRequest: + required: + - username + - password + type: object + properties: + username: + type: string + example: john + password: + type: string + example: password + targetURL: + type: string + example: 'https://home.{{ .Domain | default "example.com" }}' + workflow: + type: string + example: openid_connect + workflowID: + type: string + format: uuid + pattern: '^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$' + example: '3ebcfbc5-b0fd-4ee0-9d3c-080ae1e7298c' + requestMethod: + type: string + example: GET + keepMeLoggedIn: + type: boolean + example: true + handlers.logoutRequestBody: + type: object + properties: + targetURL: + type: string + example: 'https://redirect.{{ .Domain | default "example.com" }}' + handlers.logoutResponseBody: + type: object + properties: + status: + type: string + example: OK + data: + type: object + properties: + safeTargetURL: + type: boolean + example: true + handlers.redirectResponse: + type: object + properties: + status: + type: string + example: OK + data: + type: object + properties: + redirect: + type: string + example: 'https://home.{{ .Domain | default "example.com" }}' + {{- if .PasswordReset }} + handlers.PasswordResetStep1RequestBody: + required: + - username + type: object + properties: + username: + type: string + example: john + handlers.PasswordResetStep2RequestBody: + required: + - password + type: object + properties: + password: + type: string + example: password + {{- end }} + {{- if .Duo }} + handlers.bodySignDuoRequest: + type: object + properties: + targetURL: + type: string + example: 'https://secure.{{ .Domain | default "example.com" }}' + passcode: + type: string + workflow: + type: string + example: openid_connect + workflowID: + type: string + format: uuid + pattern: '^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$' + example: '3ebcfbc5-b0fd-4ee0-9d3c-080ae1e7298c' + {{- end }} + handlers.StateResponse: + type: object + properties: + status: + type: string + example: OK + data: + type: object + properties: + username: + type: string + example: john + authentication_level: + type: integer + example: 1 + default_redirection_url: + type: string + example: 'https://home.{{ .Domain | default "example.com" }}' + middlewares.ErrorResponse: + type: object + properties: + status: + type: string + example: KO + message: + type: string + example: Authentication failed, please retry later. + middlewares.IdentityVerificationFinishBody: + required: + - token + type: object + properties: + token: + type: string + example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MDc5MjU1OTYsImlzcyI6IkF1dGhlbGlhIiwiYWN0aW9uIjoiUmVzZXRQYXNzd29yZCIsInVzZXJuYW1lIjoiQW1pciJ9.636yqRrUCGCe4jsMCsonleX5CYWHncYqZum-YYb6VaY + middlewares.OkResponse: + type: object + properties: + status: + type: string + example: OK + data: + type: object + handlers.UserInfo: + type: object + properties: + status: + type: string + example: OK + data: + type: object + properties: + display_name: + type: string + example: John Doe + method: + type: string + enum: + - "totp" + - "webauthn" + - "mobile_push" + example: totp + has_webauthn: + type: boolean + example: false + has_totp: + type: boolean + example: true + has_duo: + type: boolean + example: true + handlers.UserInfo.MethodBody: + required: + - method + type: object + properties: + method: + type: string + enum: + - "totp" + - "webauthn" + - "mobile_push" + example: totp + {{- if .TOTP }} + handlers.UserInfoTOTP: + type: object + properties: + status: + type: string + example: OK + data: + type: object + properties: + period: + default: 30 + description: The period defined in the users TOTP configuration + type: integer + example: 30 + digits: + default: 6 + description: The number of digits defined in the users TOTP configuration + type: integer + example: 6 + handlers.bodySignTOTPRequest: + type: object + properties: + token: + type: string + example: '123456' + targetURL: + type: string + example: 'https://secure.{{ .Domain | default "example.com" }}' + workflow: + type: string + example: openid_connect + workflowID: + type: string + format: uuid + pattern: '^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$' + example: '3ebcfbc5-b0fd-4ee0-9d3c-080ae1e7298c' + handlers.TOTPKeyResponse: + type: object + properties: + status: + type: string + example: OK + data: + type: object + properties: + base32_secret: + type: string + example: 5ZH7Y5CTFWOXN7EOLGBMMXADRNQFHVUDZSYKCN5HMFAIRSLAWY3Q + otpauth_url: + type: string + example: 'otpauth://totp/{{ .Domain | default "example.com" }}:john?algorithm=SHA1&digits=6&issuer=auth.{{ .Domain | default "example.com" }}&period=30&secret=5ZH7Y5CTFWOXN7EOLGBMMXADRNQFHVUDZSYKCN5HMFAIRSLAWY3Q' + {{- end }} + {{- if .Webauthn }} + webauthn.PublicKeyCredential: + type: object + properties: + rawId: + type: string + format: byte + id: + type: string + type: + type: string + webauthn.AuthenticatorResponse: + type: object + properties: + clientDataJSON: + type: string + format: byte + webauthn.CredentialAttestationResponse: + allOf: + - $ref: '#/components/schemas/webauthn.PublicKeyCredential' + - type: object + properties: + clientExtensionResults: + type: object + properties: + appidExclude: + type: boolean + response: + allOf: + - $ref: '#/components/schemas/webauthn.AuthenticatorResponse' + - type: object + properties: + attestationObject: + type: string + format: byte + webauthn.CredentialAssertionResponse: + allOf: + - $ref: '#/components/schemas/webauthn.PublicKeyCredential' + - type: object + properties: + response: + allOf: + - $ref: '#/components/schemas/webauthn.AuthenticatorResponse' + - type: object + required: [authenticatorData, clientDataJSON, signature] + properties: + authenticatorData: + type: string + format: byte + clientDataJSON: + type: string + format: byte + clientExtensionResults: + type: object + properties: + appid: + type: boolean + example: false + signature: + type: string + format: byte + userHandle: + type: string + format: byte + workflow: + type: string + example: openid_connect + workflowID: + type: string + format: uuid + pattern: '^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$' + example: '3ebcfbc5-b0fd-4ee0-9d3c-080ae1e7298c' + webauthn.PublicKeyCredentialCreationOptions: + type: object + properties: + status: + type: string + example: OK + data: + type: object + properties: + publicKey: + allOf: + - $ref: '#/components/schemas/webauthn.AttestationType' + - $ref: '#/components/schemas/webauthn.AuthenticatorSelectionCriteria' + - $ref: '#/components/schemas/webauthn.CredentialUserEntity' + - $ref: '#/components/schemas/webauthn.CredentialRPEntity' + - type: object + required: + - "challenge" + - "pubKeyCredParams" + properties: + challenge: + type: string + format: byte + pubKeyCredParams: + type: array + items: + type: object + required: + - "alg" + - "type" + properties: + alg: + type: integer + type: + type: string + example: public-key + enum: + - "public-key" + timeout: + type: integer + example: 60000 + excludeCredentials: + type: array + items: + allOf: + - $ref: '#/components/schemas/webauthn.CredentialDescriptor' + extensions: + type: object + properties: + appidExclude: + type: string + example: '{{ .BaseURL }}' + webauthn.PublicKeyCredentialRequestOptions: + type: object + properties: + status: + type: string + example: OK + data: + type: object + properties: + publicKey: + allOf: + - $ref: '#/components/schemas/webauthn.UserVerification' + - type: object + required: + - "challenge" + properties: + challenge: + type: string + timeout: + type: integer + example: 60000 + rpId: + type: string + example: 'auth.{{ .Domain | default "example.com" }}' + allowCredentials: + type: array + items: + allOf: + - $ref: '#/components/schemas/webauthn.CredentialDescriptor' + extensions: + type: object + properties: + appid: + type: string + example: '{{ .BaseURL }}' + webauthn.Transports: + type: object + properties: + transports: + type: array + items: + type: string + example: + - "usb" + - "nfc" + enum: + - "usb" + - "nfc" + - "ble" + - "internal" + webauthn.UserVerification: + type: object + properties: + userVerification: + type: string + example: preferred + enum: + - "required" + - "preferred" + - "discouraged" + webauthn.AttestationType: + type: object + properties: + attestation: + type: string + example: direct + enum: + - "none" + - "indirect" + - "direct" + webauthn.AuthenticatorSelectionCriteria: + type: object + properties: + authenticatorSelection: + type: object + properties: + authenticatorAttachment: + type: string + example: cross-platform + enum: + - "platform" + - "cross-platform" + residentKey: + type: string + example: discouraged + enum: + - "discouraged" + - "preferred" + - "required" + requireResidentKey: + type: boolean + webauthn.CredentialDescriptor: + allOf: + - $ref: '#/components/schemas/webauthn.Transports' + - type: object + required: + - "id" + - "type" + properties: + id: + type: string + format: byte + type: + type: string + example: public-key + enum: + - "public-key" + webauthn.CredentialEntity: + type: object + required: + - "id" + - "name" + properties: + id: + type: string + name: + type: string + icon: + type: string + webauthn.CredentialRPEntity: + type: object + required: + - "rp" + properties: + rp: + allOf: + - $ref: '#/components/schemas/webauthn.CredentialEntity' + webauthn.CredentialUserEntity: + type: object + required: + - "user" + properties: + user: + allOf: + - $ref: '#/components/schemas/webauthn.CredentialEntity' + - type: object + required: + - "displayName" + properties: + displayName: + type: string + webauthn.AuthenticationExtensionsClientOutputs: + type: object + properties: + clientExtensionResults: + type: object + properties: + appid: + type: boolean + example: true + appidExclude: + type: boolean + example: false + uvm: + type: array + items: + type: string + format: byte + credProps: + type: object + properties: + rk: + type: boolean + example: false + largeBlob: + type: object + properties: + supported: + type: boolean + example: false + blob: + type: string + written: + type: boolean + example: false + {{- end }} + {{- if .OpenIDConnect }} + openid.request.consent: + type: object + properties: + status: + type: string + example: OK + data: + type: object + properties: + client_id: + type: string + description: The identifier of the client for the user to provide consent for. + example: 'app' + client_description: + description: The descriptive name of the client for the user to provide consent for. + type: string + example: 'App Platform' + scopes: + description: The list of the requested scopes for the user to provide consent for. + type: array + items: + type: string + enum: + - "openid" + - "offline_access" + - "groups" + - "email" + - "profile" + audience: + description: The list of the requested audiences for the user to provide consent for. + type: array + items: + type: string + pre_configuration: + description: Indicates if this client supports pre-configuration. + type: boolean + example: true + openid.response.consent: + type: object + properties: + status: + type: string + example: OK + data: + type: object + properties: + id: + description: The identifier of the consent session. + type: string + format: uuid + pattern: '^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$' + example: '713ef767-81bc-4a27-9b83-5fe2e101b2b4' + client_id: + description: The identifier of the client for the user to provide consent for. + type: string + example: 'app' + consent: + description: Indicates if the user consented to the consent request. + type: boolean + example: true + pre_configure: + description: Indicates if the user consented to pre-configuration. + type: boolean + example: true + openid.spec.Metadata.OAuth2AuthorizationServer: + type: object + required: + - issuer + - authorization_endpoint + - subject_types_supported + - response_types_supported + - require_pushed_authorization_requests + properties: + authorization_endpoint: + description: > + URL of the OP''s OAuth 2.0 Authorization Endpoint [OpenID.Core]. + See Also: OpenID.Core: https://openid.net/specs/openid-connect-core-1_0.html + type: string + example: '{{ .BaseURL }}api/oidc/authorization' + claims_supported: + description: > + JSON array containing a list of the Claim Names of the Claims that the OpenID Provider MAY be able to supply + values for. Note that for privacy or other reasons, this might not be an exhaustive list. + type: array + example: + - "amr" + - "aud" + - "azp" + - "client_id" + - "exp" + - "iat" + - "iss" + - "jti" + - "rat" + - "sub" + - "auth_time" + - "nonce" + - "email" + - "email_verified" + - "alt_emails" + - "groups" + - "preferred_username" + - "name" + items: + $ref: '#/components/schemas/openid.implementation.Claims.Array' + code_challenge_methods_supported: + description: > + JSON array containing a list of PKCE [RFC7636] code challenge methods supported by this authorization + server. Code challenge method values are used in the "code_challenge_method" parameter defined in Section + 4.3 of [RFC7636]. The valid code challenge method values are those registered in the IANA "PKCE Code + Challenge Methods" registry [IANA.OAuth.Parameters]. If omitted, the authorization server does not support + PKCE. See Also: PKCE: https://datatracker.ietf.org/doc/html/rfc7636 IANA.OAuth.Parameters: https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml + type: array + example: ["S256", "none"] + items: + $ref: '#/components/schemas/openid.spec.CodeChallengeMethod' + grant_types_supported: + type: array + description: > + JSON array containing a list of the OAuth 2.0 Grant Type values that this OP supports. Dynamic OpenID + Providers MUST support the authorization_code and implicit Grant Type values and MAY support other Grant + Types. If omitted, the default value is ["authorization_code", "implicit"]. + example: ["authorization_code", "implicit"] + items: + $ref: '#/components/schemas/openid.spec.GrantType' + introspection_endpoint: + description: > + URL of the authorization server''s OAuth 2.0 introspection endpoint [RFC7662]. See Also: OAuth 2.0 Token + Introspection: https://datatracker.ietf.org/doc/html/rfc7662 + type: string + example: '{{ .BaseURL }}api/oidc/introspection' + introspection_endpoint_auth_methods_supported: + description: > + JSON array containing a list of client authentication methods supported by this introspection endpoint. The + valid client authentication method values are those registered in the IANA "OAuth Token Endpoint + Authentication Methods" registry [IANA.OAuth.Parameters] or those registered in the IANA "OAuth Access Token + Types" registry [IANA.OAuth.Parameters]. (These values are and will remain distinct, due to Section 7.2.) If + omitted, the set of supported authentication methods MUST be determined by other means. See Also: + IANA.OAuth.Parameters: https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml + OAuth 2.0 Authorization Server Metadata - Updated Registration Instructions: + https://datatracker.ietf.org/doc/html/draft-ietf-oauth-discovery-10#section-7.2 + type: array + example: ["client_secret_post"] + items: + $ref: '#/components/schemas/openid.spec.ClientAuthMethod' + introspection_endpoint_auth_signing_alg_values_supported: + description: > + JSON array containing a list of the JWS signing algorithms ("alg" values) supported by the introspection + endpoint for the signature on the JWT [JWT] used to authenticate the client at the introspection endpoint + for the "private_key_jwt" and "client_secret_jwt" authentication methods. This metadata entry MUST be + present if either of these authentication methods are specified in the + "introspection_endpoint_auth_methods_supported" entry. No default algorithms are implied if this entry is + omitted. The value "none" MUST NOT be used. See Also: JWT: https://datatracker.ietf.org/doc/html/rfc7519 + type: array + example: ["RS256"] + items: + $ref: '#/components/schemas/jose.spec.jws' + issuer: + description: + URL using the https scheme with no query or fragment component that the OP asserts as its Issuer Identifier. + If Issuer discovery is supported (see Section 2), this value MUST be identical to the issuer value returned + by WebFinger. This also MUST be identical to the iss Claim value in ID Tokens issued from this Issuer. + type: string + example: '{{ .BaseURL }}' + jwks_uri: + description: > + URL of the OP's JSON Web Key Set [JWK] document. This contains the signing key(s) the RP uses to validate + signatures from the OP. The JWK Set MAY also contain the Server's encryption key(s), which are used by RPs + to encrypt requests to the Server. When both signing and encryption keys are made available, a use (Key Use) + parameter value is REQUIRED for all keys in the referenced JWK Set to indicate each key's intended usage. + Although some algorithms allow the same key to be used for both signatures and encryption, doing so is NOT + RECOMMENDED, as it is less secure. The JWK x5c parameter MAY be used to provide X.509 representations of + keys provided. When used, the bare key values MUST still be present and MUST match those in the certificate. + type: string + example: '{{ .BaseURL }}jwks.json' + op_policy_uri: + description: + URL that the OpenID Provider provides to the person registering the Client to read about the OP's + requirements on how the Relying Party can use the data provided by the OP. The registration process SHOULD + display this URL to the person registering the Client if it is given. + type: string + op_tos_uri: + description: > + URL that the OpenID Provider provides to the person registering the Client to read about OpenID Provider's + terms of service. The registration process SHOULD display this URL to the person registering the Client if + it is given. + type: string + pushed_authorization_request_endpoint: + description: > + 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. + type: string + example: '{{ .BaseURL }}api/oidc/par' + registration_endpoint: + description: > + URL of the authorization server''s OAuth 2.0 Dynamic Client Registration endpoint [RFC7591]. See Also: + OAuth 2.0 Dynamic Client Registration Protocol: https://datatracker.ietf.org/doc/html/rfc7591 + type: string + example: '{{ .BaseURL }}api/oidc/registration' + require_pushed_authorization_requests: + description: > + Boolean parameter indicating whether the authorization server accepts authorization request data only via + PAR. If omitted, the default value is "false". + type: boolean + example: false + response_modes_supported: + description: > + JSON array containing a list of the OAuth 2.0 response_mode values that this OP supports, as specified in + OAuth 2.0 Multiple Response Type Encoding Practices [OAuth.Responses]. If omitted, the default for Dynamic + OpenID Providers is ["query", "fragment"]. + type: array + example: ["query", "fragment"] + items: + $ref: '#/components/schemas/openid.spec.ResponseMode' + response_types_supported: + description: > + JSON array containing a list of the OAuth 2.0 response_type values that this OP supports. + Dynamic OpenID Providers MUST support the code, id_token, and the token id_token Response Type values. + type: array + example: ["code", "id_token", "token id_token"] + items: + $ref: '#/components/schemas/openid.spec.ResponseType' + revocation_endpoint: + description: > + URL of the authorization server''s OAuth 2.0 revocation endpoint [RFC7009]. + See Also: OAuth 2.0 Token Revocation: https://datatracker.ietf.org/doc/html/rfc7009 + type: string + example: '{{ .BaseURL }}api/oidc/revocation' + revocation_endpoint_auth_methods_supported: + description: > + JSON array containing a list of client authentication methods supported by this revocation endpoint. The + valid client authentication method values are those registered in the IANA "OAuth Token Endpoint + Authentication Methods" registry [IANA.OAuth.Parameters]. If omitted, the default is "client_secret_basic" + -- the HTTP Basic Authentication Scheme specified in Section 2.3.1 of OAuth 2.0 [RFC6749]. See Also: + IANA.OAuth.Parameters: https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml + OAuth 2.0 - Client Password: https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1 + type: array + example: ["client_secret_post"] + items: + $ref: '#/components/schemas/openid.spec.ClientAuthMethod' + revocation_endpoint_auth_signing_alg_values_supported: + description: > + JSON array containing a list of the JWS signing algorithms ("alg" values) supported by the revocation + endpoint for the signature on the JWT [JWT] used to authenticate the client at the revocation endpoint for + the "private_key_jwt" and "client_secret_jwt" authentication methods. This metadata entry MUST be present if + either of these authentication methods are specified in the "revocation_endpoint_auth_methods_supported" + entry. No default algorithms are implied if this entry is omitted. The value "none" MUST NOT be used. + See Also: JWT: https://datatracker.ietf.org/doc/html/rfc7519 + type: array + example: ["RS256"] + items: + $ref: '#/components/schemas/jose.spec.jws' + scopes_supported: + description: > + JSON array containing a list of the OAuth 2.0 [RFC6749] scope values that this server supports. The server + MUST support the openid scope value. Servers MAY choose not to advertise some supported scope values even + when this parameter is used, although those defined in [OpenID.Core] SHOULD be listed, if supported. + See Also: OAuth 2.0: https://datatracker.ietf.org/doc/html/rfc6749 OpenID.Core: https://openid.net/specs/openid-connect-core-1_0.html + type: array + example: + - "openid" + - "offline_access" + - "profile" + - "email" + - "groups" + items: + $ref: '#/components/schemas/openid.implementation.Scopes.Object' + service_documentation: + description: > + URL of a page containing human-readable information that developers might want or need to know when using + the OpenID Provider. In particular, if the OpenID Provider does not support Dynamic Client Registration, + then information on how to register Clients needs to be provided in this documentation. + type: string + example: 'https://authelia.com' + subject_types_supported: + description: > + JSON array containing a list of the Subject Identifier types that this OP supports. + Valid types include pairwise and public. + type: array + example: ["public", "pairwise"] + items: + $ref: '#/components/schemas/openid.spec.SubjectIdentifier' + token_endpoint: + description: > + URL of the OP''s OAuth 2.0 Token Endpoint [OpenID.Core]. This is REQUIRED unless only the Implicit Flow is + used. See Also: OpenID.Core: https://openid.net/specs/openid-connect-core-1_0.html + type: string + example: '{{ .BaseURL }}api/oidc/token' + token_endpoint_auth_methods_supported: + description: > + JSON array containing a list of Client Authentication methods supported by this Token Endpoint. The options + are client_secret_post, client_secret_basic, client_secret_jwt, and private_key_jwt, as described in Section + 9 of OpenID Connect Core 1.0 [OpenID.Core]. Other authentication methods MAY be defined by extensions. If + omitted, the default is client_secret_basic -- the HTTP Basic Authentication Scheme specified in Section + 2.3.1 of OAuth 2.0 [RFC6749]. See Also: OAuth 2.0: https://datatracker.ietf.org/doc/html/rfc6749 + OpenID.Core Section 9: https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication + type: array + example: ["client_secret_post"] + items: + $ref: '#/components/schemas/openid.spec.ClientAuthMethod' + token_endpoint_auth_signing_alg_values_supported: + description: > + JSON array containing a list of the JWS signing algorithms (alg values) supported by the Token Endpoint for + the signature on the JWT [JWT] used to authenticate the Client at the Token Endpoint for the private_key_jwt + and client_secret_jwt authentication methods. Servers SHOULD support RS256. The value none MUST NOT be used. + See Also: JWT: https://datatracker.ietf.org/doc/html/rfc7519' + type: array + example: ["RS256"] + items: + $ref: '#/components/schemas/jose.spec.jws' + ui_locales_supported: + type: array + description: > + Languages and scripts supported for the user interface, represented as a JSON array of BCP47 [RFC5646] + language tag values. See Also: BCP47: https://datatracker.ietf.org/doc/html/rfc5646 + example: ["en-US"] + items: + type: string + openid.spec.Metadata.OpenIDConfiguration: + type: object + required: + - "issuer" + - "authorization_endpoint" + - "subject_types_supported" + - "response_types_supported" + - "require_pushed_authorization_requests" + - "request_uri_parameter_supported" + - "require_request_uri_registration" + - "claims_parameter_supported" + - "frontchannel_logout_supported" + - "frontchannel_logout_session_supported" + - "backchannel_logout_supported" + - "backchannel_logout_session_supported" + properties: + acr_values_supported: + description: + JSON array containing a list of the Authentication Context Class References that this OP supports. + type: array + items: + type: string + authorization_endpoint: + description: > + URL of the OP''s OAuth 2.0 Authorization Endpoint [OpenID.Core]. + See Also: OpenID.Core: https://openid.net/specs/openid-connect-core-1_0.html + type: string + example: '{{ .BaseURL }}api/oidc/authorization' + backchannel_logout_session_supported: + description: > + Boolean value specifying whether the OP can pass a sid (session ID) Claim in the Logout Token to identify + the RP session with the OP. If supported, the sid Claim is also included in ID Tokens issued by the OP. + If omitted, the default value is false. + type: boolean + example: false + backchannel_logout_supported: + description: > + Boolean value specifying whether the OP supports back-channel logout, with true indicating support. If + omitted, the default value is false. + type: boolean + example: false + claim_types_supported: + description: > + JSON array containing a list of the Claim Types that the OpenID Provider supports. These Claim Types are + described in Section 5.6 of OpenID Connect Core 1.0 [OpenID.Core]. Values defined by this specification are + normal, aggregated, and distributed. If omitted, the implementation supports only normal Claims. See Also: + OpenID.Core Section 5.6: https://openid.net/specs/openid-connect-core-1_0.html#ClaimTypes + type: array + example: ["normal"] + items: + $ref: '#/components/schemas/openid.spec.ClaimType' + claims_locales_supported: + description: > + Languages and scripts supported for values in Claims being returned, represented as a JSON array of BCP47 + [RFC5646] language tag values. Not all languages and scripts are necessarily supported for all Claim values. + See Also: BCP47: https://datatracker.ietf.org/doc/html/rfc5646 + type: array + example: ["en-US"] + items: + type: string + claims_parameter_supported: + description: > + Boolean value specifying whether the OP supports use of the claims parameter, with true indicating support. + If omitted, the default value is false. + type: boolean + example: false + claims_supported: + description: > + JSON array containing a list of the Claim Names of the Claims that the OpenID Provider MAY be able to supply + values for. Note that for privacy or other reasons, this might not be an exhaustive list. + type: array + example: + - "amr" + - "aud" + - "azp" + - "client_id" + - "exp" + - "iat" + - "iss" + - "jti" + - "rat" + - "sub" + - "auth_time" + - "nonce" + - "email" + - "email_verified" + - "alt_emails" + - "groups" + - "preferred_username" + - "name" + items: + $ref: '#/components/schemas/openid.implementation.Claims.Array' + code_challenge_methods_supported: + description: > + JSON array containing a list of PKCE [RFC7636] code challenge methods supported by this authorization + server. Code challenge method values are used in the "code_challenge_method" parameter defined in Section + 4.3 of [RFC7636]. The valid code challenge method values are those registered in the IANA "PKCE Code + Challenge Methods" registry [IANA.OAuth.Parameters]. If omitted, the authorization server does not support + PKCE. See Also: PKCE: https://datatracker.ietf.org/doc/html/rfc7636 IANA.OAuth.Parameters: + https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml + type: array + example: ["S256", "plain"] + items: + $ref: '#/components/schemas/openid.spec.CodeChallengeMethod' + display_values_supported: + description: > + JSON array containing a list of the display parameter values that the OpenID Provider supports. These values + are described in Section 3.1.2.1 of OpenID Connect Core 1.0 [OpenID.Core]. See Also: OpenID.Core Section + 3.1.2.1: https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest + type: array + example: ["page"] + items: + $ref: '#/components/schemas/openid.spec.DisplayType' + frontchannel_logout_session_supported: + description: > + Boolean value specifying whether the OP can pass iss (issuer) and sid (session ID) query parameters to + identify the RP session with the OP when the frontchannel_logout_uri is used. If supported, the sid Claim is + also included in ID Tokens issued by the OP. If omitted, the default value is false. + type: boolean + example: false + frontchannel_logout_supported: + description: > + Boolean value specifying whether the OP supports HTTP-based logout, with true indicating support. If + omitted, the default value is false. + type: boolean + example: false + grant_types_supported: + description: > + JSON array containing a list of the OAuth 2.0 Grant Type values that this OP supports. Dynamic OpenID + Providers MUST support the authorization_code and implicit Grant Type values and MAY support other Grant + Types. If omitted, the default value is ["authorization_code", "implicit"]. + type: array + example: ["authorization_code", "implicit"] + items: + $ref: '#/components/schemas/openid.spec.GrantType' + id_token_encryption_alg_values_supported: + description: > + JSON array containing a list of the JWE encryption algorithms (alg values) supported by the OP for the ID + Token to encode the Claims in a JWT [JWT]. See Also: JWE: https://datatracker.ietf.org/doc/html/rfc7516 JWT: + https://datatracker.ietf.org/doc/html/rfc7519 + type: array + example: ["A256GCMKW"] + items: + $ref: '#/components/schemas/jose.spec.JWE.alg' + id_token_encryption_enc_values_supported: + description: > + JSON array containing a list of the JWE encryption algorithms (enc values) supported by the OP for the ID + Token to encode the Claims in a JWT [JWT]. See Also: JWE: https://datatracker.ietf.org/doc/html/rfc7516 + JWT: https://datatracker.ietf.org/doc/html/rfc7519 + type: array + example: ["A256GCM"] + items: + $ref: '#/components/schemas/jose.spec.JWE.enc' + id_token_signing_alg_values_supported: + description: > + JSON array containing a list of the JWS signing algorithms (alg values) supported by the OP for the ID Token + to encode the Claims in a JWT [JWT]. The algorithm RS256 MUST be included. The value none MAY be supported, + but MUST NOT be used unless the Response Type used returns no ID Token from the Authorization Endpoint + (such as when using the Authorization Code Flow). + See Also: JWT: https://datatracker.ietf.org/doc/html/rfc7519 + type: array + example: ["RS256"] + items: + $ref: '#/components/schemas/jose.spec.JWS.None' + introspection_endpoint: + description: > + URL of the authorization server''s OAuth 2.0 introspection endpoint [RFC7662]. See Also: OAuth 2.0 + Token Introspection: https://datatracker.ietf.org/doc/html/rfc7662' + type: string + example: '{{ .BaseURL }}api/oidc/introspection' + introspection_endpoint_auth_methods_supported: + description: > + JSON array containing a list of client authentication methods supported by this introspection endpoint. The + valid client authentication method values are those registered in the IANA "OAuth Token Endpoint + Authentication Methods" registry [IANA.OAuth.Parameters] or those registered in the IANA "OAuth Access + Token Types" registry [IANA.OAuth.Parameters]. (These values are and will remain distinct, due to Section + 7.2.) If omitted, the set of supported authentication methods MUST be determined by other means. See Also: + IANA.OAuth.Parameters: https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml + OAuth 2.0 Authorization Server Metadata - Updated Registration Instructions: + https://datatracker.ietf.org/doc/html/draft-ietf-oauth-discovery-10#section-7.2 + type: array + example: ["client_secret_post"] + items: + $ref: '#/components/schemas/openid.spec.ClientAuthMethod' + introspection_endpoint_auth_signing_alg_values_supported: + description: > + JSON array containing a list of the JWS signing algorithms ("alg" values) supported by the introspection + endpoint for the signature on the JWT [JWT] used to authenticate the client at the introspection endpoint + for the "private_key_jwt" and "client_secret_jwt" authentication methods. This metadata entry MUST be + present if either of these authentication methods are specified in the + "introspection_endpoint_auth_methods_supported" entry. No default algorithms are implied if this entry is + omitted. The value "none" MUST NOT be used. See Also: JWT: https://datatracker.ietf.org/doc/html/rfc7519 + type: array + example: ["RS256"] + items: + $ref: '#/components/schemas/jose.spec.jws' + issuer: + description: > + URL using the https scheme with no query or fragment component that the OP asserts as its Issuer Identifier. + If Issuer discovery is supported (see Section 2), this value MUST be identical to the issuer value returned + by WebFinger. This also MUST be identical to the iss Claim value in ID Tokens issued from this Issuer. + type: string + example: '{{ .BaseURL }}' + jwks_uri: + description: > + URL of the OP's JSON Web Key Set [JWK] document. This contains the signing key(s) the RP uses to validate + signatures from the OP. The JWK Set MAY also contain the Server's encryption key(s), which are used by RPs + to encrypt requests to the Server. When both signing and encryption keys are made available, a use (Key Use) + parameter value is REQUIRED for all keys in the referenced JWK Set to indicate each key's intended usage. + Although some algorithms allow the same key to be used for both signatures and encryption, doing so is NOT + RECOMMENDED, as it is less secure. The JWK x5c parameter MAY be used to provide X.509 representations of + keys provided. When used, the bare key values MUST still be present and MUST match those in the certificate. + type: string + example: '{{ .BaseURL }}jwks.json' + op_policy_uri: + description: > + URL that the OpenID Provider provides to the person registering the Client to read about the OP's + requirements on how the Relying Party can use the data provided by the OP. The registration process SHOULD + display this URL to the person registering the Client if it is given. + type: string + op_tos_uri: + description: > + URL that the OpenID Provider provides to the person registering the Client to read about OpenID Provider's + terms of service. The registration process SHOULD display this URL to the person registering the Client + if it is given. + type: string + pushed_authorization_request_endpoint: + description: > + 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. + type: string + example: '{{ .BaseURL }}api/oidc/par' + registration_endpoint: + description: > + URL of the authorization server''s OAuth 2.0 Dynamic Client Registration endpoint [RFC7591]. See Also: + OAuth 2.0 Dynamic Client Registration Protocol: https://datatracker.ietf.org/doc/html/rfc7591 + type: string + example: '{{ .BaseURL }}api/oidc/registration' + request_object_encryption_alg_values_supported: + description: > + JSON array containing a list of the JWE encryption algorithms (alg values) supported by the OP for Request + Objects. These algorithms are used both when the Request Object is passed by value and when it is passed by + reference. See Also: JWE: https://datatracker.ietf.org/doc/html/rfc7516 + type: array + example: ["A256GCMKW"] + items: + $ref: '#/components/schemas/jose.spec.JWE.alg' + request_object_encryption_enc_values_supported: + description: > + JSON array containing a list of the JWE encryption algorithms (enc values) supported by the OP for Request + Objects. These algorithms are used both when the Request Object is passed by value and when it is passed by + reference. See Also: JWE: https://datatracker.ietf.org/doc/html/rfc7516 + JWT: https://datatracker.ietf.org/doc/html/rfc7519 + type: array + example: ["A256GCM"] + items: + $ref: '#/components/schemas/jose.spec.JWE.enc' + request_object_signing_alg_values_supported: + description: > + JSON array containing a list of the JWS signing algorithms (alg values) supported by the OP for Request + Objects, which are described in Section 6.1 of OpenID Connect Core 1.0 [OpenID.Core]. These algorithms are + used both when the Request Object is passed by value (using the request parameter) and when it is passed by + reference (using the request_uri parameter). Servers SHOULD support none and RS256. + type: array + example: ["RS256"] + items: + $ref: '#/components/schemas/jose.spec.JWS.None' + request_uri_parameter_supported: + description: > + Boolean value specifying whether the OP supports use of the request_uri parameter, with true indicating + support. If omitted, the default value is true. + type: boolean + example: true + require_pushed_authorization_requests: + description: > + Boolean parameter indicating whether the authorization server accepts authorization request data only via + PAR. If omitted, the default value is "false". + type: boolean + example: false + require_request_uri_registration: + description: > + Boolean value specifying whether the OP requires any request_uri values used to be pre-registered using the + request_uris registration parameter. Pre-registration is REQUIRED when the value is true. If omitted, the + default value is false. + type: boolean + example: false + response_modes_supported: + description: > + JSON array containing a list of the OAuth 2.0 response_mode values that this OP supports, as specified in + OAuth 2.0 Multiple Response Type Encoding Practices [OAuth.Responses]. If omitted, the default for Dynamic + OpenID Providers is ["query", "fragment"]. + type: array + example: ["query", "fragment"] + items: + $ref: '#/components/schemas/openid.spec.ResponseMode' + response_types_supported: + description: > + JSON array containing a list of the OAuth 2.0 response_type values that this OP supports. Dynamic OpenID + Providers MUST support the code, id_token, and the token id_token Response Type values. + type: array + example: ["code", "id_token", "token id_token"] + items: + $ref: '#/components/schemas/openid.spec.ResponseType' + revocation_endpoint: + description: > + URL of the authorization server''s OAuth 2.0 revocation endpoint [RFC7009]. See Also: + OAuth 2.0 Token Revocation: https://datatracker.ietf.org/doc/html/rfc7009 + type: string + example: '{{ .BaseURL }}api/oidc/revocation' + revocation_endpoint_auth_methods_supported: + description: > + JSON array containing a list of client authentication methods supported by this revocation endpoint. The + valid client authentication method values are those registered in the IANA "OAuth Token Endpoint + Authentication Methods" registry [IANA.OAuth.Parameters]. If omitted, the default is "client_secret_basic" + -- the HTTP Basic Authentication Scheme specified in Section 2.3.1 of OAuth 2.0 [RFC6749]. + See Also: IANA.OAuth.Parameters: https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml + OAuth 2.0 - Client Password: https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1 + type: array + example: ["client_secret_basic"] + items: + $ref: '#/components/schemas/openid.spec.ClientAuthMethod' + revocation_endpoint_auth_signing_alg_values_supported: + description: > + JSON array containing a list of the JWS signing algorithms ("alg" values) supported by the revocation + endpoint for the signature on the JWT [JWT] used to authenticate the client at the revocation endpoint for + the "private_key_jwt" and "client_secret_jwt" authentication methods. This metadata entry MUST be present if + either of these authentication methods are specified in the "revocation_endpoint_auth_methods_supported" + entry. No default algorithms are implied if this entry is omitted. The value "none" MUST NOT be used. + See Also: JWT: https://datatracker.ietf.org/doc/html/rfc7519 + type: array + example: ["RS256"] + items: + $ref: '#/components/schemas/jose.spec.jws' + scopes_supported: + description: > + JSON array containing a list of the OAuth 2.0 [RFC6749] scope values that this server supports. + The server MUST support the openid scope value. Servers MAY choose not to advertise some supported scope + values even when this parameter is used, although those defined in [OpenID.Core] SHOULD be listed, if + supported. See Also: OAuth 2.0: https://datatracker.ietf.org/doc/html/rfc6749 OpenID.Core: + https://openid.net/specs/openid-connect-core-1_0.html + type: array + example: + - "openid" + - "offline_access" + - "profile" + - "email" + - "groups" + items: + $ref: '#/components/schemas/openid.implementation.Scopes.Object' + service_documentation: + description: > + URL of a page containing human-readable information that developers might want or need to know when using + the OpenID Provider. In particular, if the OpenID Provider does not support Dynamic Client Registration, + then information on how to register Clients needs to be provided in this documentation. + type: string + example: 'https://www.authelia.com' + subject_types_supported: + description: > + JSON array containing a list of the Subject Identifier types that this OP supports. Valid types include + pairwise and public. + type: array + example: ["public", "pairwise"] + items: + $ref: '#/components/schemas/openid.spec.SubjectIdentifier' + token_endpoint: + description: > + URL of the OP''s OAuth 2.0 Token Endpoint [OpenID.Core]. This is REQUIRED unless only the Implicit Flow is + used. See Also: OpenID.Core: https://openid.net/specs/openid-connect-core-1_0.html + type: string + example: '{{ .BaseURL }}api/oidc/token' + token_endpoint_auth_methods_supported: + description: > + JSON array containing a list of Client Authentication methods supported by this Token Endpoint. The options + are client_secret_post, client_secret_basic, client_secret_jwt, and private_key_jwt, as described in Section + 9 of OpenID Connect Core 1.0 [OpenID.Core]. Other authentication methods MAY be defined by extensions. If + omitted, the default is client_secret_basic -- the HTTP Basic Authentication Scheme specified in Section + 2.3.1 of OAuth 2.0 [RFC6749]. See Also: OAuth 2.0: https://datatracker.ietf.org/doc/html/rfc6749 + OpenID.Core Section 9: https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication + type: array + example: ["client_secret_post"] + items: + $ref: '#/components/schemas/openid.spec.ClientAuthMethod' + token_endpoint_auth_signing_alg_values_supported: + description: > + JSON array containing a list of the JWS signing algorithms (alg values) supported by the Token Endpoint + for the signature on the JWT [JWT] used to authenticate the Client at the Token Endpoint for the + private_key_jwt and client_secret_jwt authentication methods. Servers SHOULD support RS256. + The value none MUST NOT be used. See Also: JWT: https://datatracker.ietf.org/doc/html/rfc7519 + type: array + example: ["RS256"] + items: + $ref: '#/components/schemas/jose.spec.jws' + ui_locales_supported: + description: > + Languages and scripts supported for the user interface, represented as a JSON array of BCP47 + [RFC5646] language tag values. See Also: BCP47: https://datatracker.ietf.org/doc/html/rfc5646 + type: array + example: ["en-US"] + items: + type: string + userinfo_encryption_alg_values_supported: + description: > + JSON array containing a list of the JWE [JWE] encryption algorithms (alg values) [JWA] supported by the + UserInfo Endpoint to encode the Claims in a JWT [JWT]. See Also: JWE: + https://datatracker.ietf.org/doc/html/rfc7516 JWA: https://datatracker.ietf.org/doc/html/rfc7518 + JWT: https://datatracker.ietf.org/doc/html/rfc7519 + type: array + example: ["A256GCMKW"] + items: + $ref: '#/components/schemas/jose.spec.JWE.alg' + userinfo_encryption_enc_values_supported: + description: > + JSON array containing a list of the JWE encryption algorithms (enc values) [JWA] supported by the UserInfo + Endpoint to encode the Claims in a JWT [JWT]. See Also: JWE: https://datatracker.ietf.org/doc/html/rfc7516 + JWA: https://datatracker.ietf.org/doc/html/rfc7518 JWT: https://datatracker.ietf.org/doc/html/rfc7519 + type: array + example: ["A256GCM"] + items: + $ref: '#/components/schemas/jose.spec.JWE.enc' + userinfo_endpoint: + description: > + URL of the OP''s UserInfo Endpoint [OpenID.Core]. This URL MUST use the https scheme and MAY contain port, + path, and query parameter components. + See Also: OpenID.Core: https://openid.net/specs/openid-connect-core-1_0.html + type: string + example: '{{ .BaseURL }}api/oidc/userinfo' + userinfo_signing_alg_values_supported: + description: > + JSON array containing a list of the JWS [JWS] signing algorithms (alg values) [JWA] supported by the + UserInfo Endpoint to encode the Claims in a JWT [JWT]. The value none MAY be included. See Also: + JWS: https://datatracker.ietf.org/doc/html/rfc7515 JWA: https://datatracker.ietf.org/doc/html/rfc7518 + JWT: https://datatracker.ietf.org/doc/html/rfc7519 + type: array + example: ["none", "RS256"] + items: + $ref: '#/components/schemas/jose.spec.JWS.None' + openid.implementation.Claims.Array: + type: array + items: + type: string + enum: + - "amr" + - "aud" + - "azp" + - "client_id" + - "exp" + - "iat" + - "iss" + - "jti" + - "rat" + - "sub" + - "auth_time" + - "nonce" + - "email" + - "email_verified" + - "alt_emails" + - "groups" + - "preferred_username" + - "name" + openid.implementation.Claims.Object: + description: OpenID Connect 1.0 User Claims. + type: object + properties: + amr: + type: array + items: + type: string + enum: + - "mfa" + - "mca" + - "user" + - "pin" + - "pwd" + - "otp" + - "hwk" + - "sms" + aud: + type: array + items: + type: string + azp: + type: string + client_id: + type: string + scope: + type: string + scp: + type: array + items: + type: string + exp: + type: integer + iat: + type: integer + iss: + type: string + jti: + type: string + rat: + type: integer + sub: + type: string + auth_time: + type: integer + nonce: + type: string + email: + type: string + email_verified: + type: boolean + alt_emails: + type: array + items: + type: string + groups: + type: array + items: + type: string + preferred_username: + type: string + name: + type: string + openid.implementation.Scopes.Object: + description: The scope. + type: string + oneOf: + - $ref: '#/components/schemas/openid.spec.Scopes' + - type: string + enum: + - "groups" + openid.spec.Scopes: + type: string + enum: + - "openid" + - "offline_access" + - "profile" + - "email" + - "address" + - "phone" + openid.spec.IntrospectionRequest: + required: + - "token" + type: object + properties: + token: + description: > + The string value of the token. For access tokens, this + is the "access_token" value returned from the token endpoint + defined in OAuth 2.0 [RFC6749], Section 5.1. For refresh tokens, + this is the "refresh_token" value returned from the token endpoint + as defined in OAuth 2.0 [RFC6749], Section 5.1. Other token types + are outside the scope of this specification. + example: 'authelia_at_cr4i4EtTn2F4k6mX4XzxbsBewkxCGn' + type: string + token_type_hint: + description: > + A hint about the type of the token submitted for + introspection. The protected resource MAY pass this parameter to + help the authorization server optimize the token lookup. If the + server is unable to locate the token using the given hint, it MUST + extend its search across all of its supported token types. An + authorization server MAY ignore this parameter, particularly if it + is able to detect the token type automatically. Values for this + field are defined in the "OAuth Token Type Hints" registry defined + in OAuth Token Revocation [RFC7009]. + enum: + - "access_token" + - "refresh_token" + example: 'access_token' + type: string + openid.spec.AccessRequest.ClientAuth: + oneOf: + - $ref: '#/components/schemas/openid.spec.AccessRequest.ClientAuth.Base' + - $ref: '#/components/schemas/openid.spec.AccessRequest.ClientAuth.Secret' + - $ref: '#/components/schemas/openid.spec.AccessRequest.ClientAuth.JWT' + openid.spec.AccessRequest.ClientAuth.Base: + required: + - "client_id" + type: object + properties: + client_id: + description: > + REQUIRED if the client is not authenticating with the authorization server as described in + Section 3.2.1. of [RFC6749]. The client identifier as described in Section 2.2 of [RFC6749]. + example: 'my_client' + type: string + openid.spec.AccessRequest.ClientAuth.Secret: + required: + - "client_secret" + type: object + properties: + client_secret: + description: > + REQUIRED. The client secret. The client MAY omit the + parameter if the client secret is an empty string. + format: password + type: string + openid.spec.AccessRequest.ClientAuth.JWT: + allOf: + - $ref: '#/components/schemas/openid.spec.AccessRequest.ClientAuth.Base' + - type: object + required: + - "client_assertion" + - "client_assertion_type" + properties: + client_assertion: + description: > + The value of the client_assertion_type parameter MUST be + "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" + enum: + - "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" + example: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' + type: string + client_assertion_type: + description: > + A JWT signed with HS256 using the client secret value or RS256 using a registered public key. + Theoretically a properly formed JWT signed using HS256 with the client secret as the HMAC key should + work but this has not been tested. + format: password + type: string + openid.spec.AccessRequest.AuthorizationCodeFlow: + allOf: + - $ref: '#/components/schemas/openid.spec.AccessRequest.ClientAuth' + - type: object + required: + - "code" + - "grant_type" + properties: + grant_type: + description: Value MUST be set to "code". + enum: + - "authorization_code" + type: string + code: + description: The Authorization Code. + example: 'authelia_ac_1j2kn3knj12n3kj12n' + type: string + code_verifier: + description: The Authorization Code Verifier (PKCE). + example: '88a25754f7c0b3b3b88cf6cd4e29e8356b160524fdc1cb329a94471825628fd3' + type: string + redirect_uri: + description: The original Redirect URI used in the Authorization Request. + example: 'https://app.{{ .Domain | default "example.com" }}/oidc/callback' + type: string + openid.spec.AccessRequest.DeviceCodeFlow: + allOf: + - $ref: '#/components/schemas/openid.spec.AccessRequest.ClientAuth' + - type: object + required: + - "grant_type" + - "device_code" + properties: + grant_type: + description: Value MUST be set to "urn:ietf:params:oauth:grant-type:device_code". + enum: + - "urn:ietf:params:oauth:grant-type:device_code" + type: string + device_code: + description: The Device Authorization Code. + example: 'authelia_dc_mn123kjn12kj3123njk' + type: string + openid.spec.AccessRequest.RefreshTokenFlow: + allOf: + - $ref: '#/components/schemas/openid.spec.AccessRequest.ClientAuth' + - type: object + required: + - "grant_type" + - "device_code" + properties: + grant_type: + description: Value MUST be set to "refresh_token". + enum: + - "refresh_token" + type: string + refresh_token: + description: The Refresh Token. + example: 'authelia_rt_1n2j3kihn12kj3n12k' + type: string + scope: + description: > + The scope of the access request as described by + Section 3.3. The requested scope MUST NOT include any scope + not originally granted by the resource owner, and if omitted is + treated as equal to the scope originally granted by the + resource owner. + example: 'openid profile groups' + type: string + openid.spec.AccessResponse: + type: object + required: + - "access_token" + - "token_type" + - "expires_in" + properties: + access_token: + description: The access token issued by the authorization server. + example: 'authelia_at_cr4i4EtTn2F4k6mX4XzxbsBewkxCGn' + type: string + id_token: + description: The id token issued by the authorization server. + example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' + type: string + refresh_token: + description: > + The refresh token, which can be used to obtain new access tokens using the + same authorization grant as described in Section 6. + example: 'authelia_rt_kGBoSMbfVGP2RR6Kvujv3Xg7uXV2i' + type: string + token_type: + description: > + The access token type provides the client with the information + required to successfully utilize the access token to make a protected + resource request (along with type-specific attributes). The client + MUST NOT use an access token if it does not understand the token + type. + enum: + - "bearer" + example: 'bearer' + type: string + expires_in: + description: > + The lifetime in seconds of the access token. For + example, the value "3600" denotes that the access token will + expire in one hour from the time the response was generated. + If omitted, the authorization server SHOULD provide the + expiration time via other means or document the default value. + example: 3600 + type: integer + state: + description: Exactly the state value passed in the authorization request if present. + example: '5dVZhNfri5XZS6wadskuzUk4MHYCvEcUgidjMeBjsktAhY7EKB' + type: string + scope: + description: > + The scope of the access token as described by Section 3.3 if it differs from the requested scope. + example: 'openid profile groups' + type: string + openid.spec.AuthorizeRequest: + type: object + required: + - "scope" + - "response_type" + - "client_id" + - "redirect_uri" + properties: + scope: + description: The requested scope. + example: 'openid profile groups' + type: string + response_type: + $ref: '#/components/schemas/openid.spec.ResponseType' + client_id: + description: The OAuth 2.0 client identifier. + example: 'app' + type: string + redirect_uri: + description: > + Redirection URI to which the response will be sent. This URI MUST exactly match one of the + Redirection URI values for the Client pre-registered at the OpenID Provider, with the matching + performed as described in Section 6.2.1 of [RFC3986] (Simple String Comparison). When using this + flow, the Redirection URI SHOULD use the https scheme; however, it MAY use the http scheme, provided + that the Client Type is confidential, as defined in Section 2.1 of OAuth 2.0, and provided the OP + allows the use of http Redirection URIs in this case. The Redirection URI MAY use an alternate + scheme, such as one that is intended to identify a callback into a native application. + example: 'https://app.{{ .Domain | default "example.com" }}' + type: string + state: + description: > + Opaque value used to maintain state between the request and the callback. Typically, Cross-Site + Request Forgery (CSRF, XSRF) mitigation is done by cryptographically binding the value of this + parameter with a browser cookie. + example: 'oV84Vsy7wyCgRk2h4aZBmXZq4q3g2f' + type: string + response_mode: + $ref: '#/components/schemas/openid.spec.ResponseMode' + nonce: + description: > + String value used to associate a Client session with an ID Token, and to mitigate replay attacks. + The value is passed through unmodified from the Authentication Request to the ID Token. Sufficient + entropy MUST be present in the nonce values used to prevent attackers from guessing values. For + implementation notes, see Section 15.5.2. + example: 'TRMLqchoKGQNcooXvBvUy9PtmLdJGf' + type: string + display: + $ref: '#/components/schemas/openid.spec.DisplayType' + prompt: + description: > + Not Supported: Space delimited, case sensitive list of ASCII string values that specifies whether + the Authorization Server prompts the End-User for reauthentication and consent. + enum: + - "none" + - "login" + - "consent" + - "select_account" + - "login consent" + - "login select_account" + - "consent select_account" + example: 'consent' + type: string + max_age: + description: > + Maximum Authentication Age. Specifies the allowable elapsed time in seconds since the last time the + End-User was actively authenticated by the OP. If the elapsed time is greater than this value, the + OP MUST attempt to actively re-authenticate the End-User. (The max_age request parameter corresponds + to the OpenID 2.0 PAPE [OpenID.PAPE] max_auth_age request parameter.) When max_age is used, the ID + Token returned MUST include an auth_time Claim Value. + type: integer + ui_locales: + description: > + Not Supported: End-User's preferred languages and scripts for the user interface, represented as a + space-separated list of BCP47 [RFC5646] language tag values, ordered by preference. For instance, + the value "fr-CA fr en" represents a preference for French as spoken in Canada, then French (without + a region designation), followed by English (without a region designation). An error SHOULD NOT + result if some or all of the requested locales are not supported by the OpenID Provider. + type: string + claims_locales: + description: > + Not Supported: End-User's preferred languages and scripts for Claims being returned, represented as + a space-separated list of BCP47 [RFC5646] language tag values, ordered by preference. An error + SHOULD NOT result if some or all of the requested locales are not supported by the OpenID Provider. + type: string + id_token_hint: + description: > + Not Supported: ID Token previously issued by the Authorization Server being passed as a hint about + the End-User's current or past authenticated session with the Client. If the End-User identified by + the ID Token is logged in or is logged in by the request, then the Authorization Server returns a + positive response; otherwise, it SHOULD return an error, such as login_required. When possible, an + id_token_hint SHOULD be present when prompt=none is used and an invalid_request error MAY be + returned if it is not; however, the server SHOULD respond successfully when possible, even if it is + not present. The Authorization Server need not be listed as an audience of the ID Token when it is + used as an id_token_hint value. If the ID Token received by the RP from the OP is encrypted, to use + it as an id_token_hint, the Client MUST decrypt the signed ID Token contained within the encrypted + ID Token. The Client MAY re-encrypt the signed ID token to the Authentication Server using a key + that enables the server to decrypt the ID Token, and use the re-encrypted ID token as the + id_token_hint value. + type: string + login_hint: + description: > + Not Supported: Hint to the Authorization Server about the login identifier the End-User might use to + log in (if necessary). This hint can be used by an RP if it first asks the End-User for their e-mail + address (or other identifier) and then wants to pass that value as a hint to the discovered + authorization service. It is RECOMMENDED that the hint value match the value used for discovery. + This value MAY also be a phone number in the format specified for the phone_number Claim. The use + of this parameter is left to the OP's discretion. + type: string + acr_values: + description: > + Not Supported: Requested Authentication Context Class Reference values. Space-separated string that + specifies the acr values that the Authorization Server is being requested to use for processing this + Authentication Request, with the values appearing in order of preference. The Authentication Context + Class satisfied by the authentication performed is returned as the acr Claim Value, as specified in + Section 2. The acr Claim is requested as a Voluntary Claim by this parameter. + type: string + claims: + description: > + Not Supported: The claims parameter value, as specified in Section 5.5. + type: string + registration: + description: > + Not Supported: This parameter is used by the Client to provide information about itself to a + Self-Issued OP that would normally be provided to an OP during Dynamic Client Registration, as + specified in Section 7.2.1. + type: string + request: + description: > + Not Supported: Request Object value, as specified in Section 6.1. The Request Object MAY be + encrypted to the Self-Issued OP by the Client. In this case, the sub (subject) of a previously + issued ID Token for this Client MUST be sent as the kid (Key ID) of the JWE. Encrypting content to + Self-Issued OPs is currently only supported when the OP's JWK key type is RSA and the encryption + algorithm used is RSA1_5. + type: string + openid.spec.SubjectIdentifier: + description: > + A Subject Identifier is a locally unique and never reassigned identifier within the Issuer for the + End-User, which is intended to be consumed by the Client. + enum: + - "public" + - "pairwise" + type: string + openid.spec.ClientAuthMethod: + description: The OAuth 2.0 / OpenID Connect 1.0 Client Authentication Method. + enum: + - "client_secret_basic" + - "client_secret_post" + - "client_secret_jwt" + - "private_key_jwt" + - "none" + type: string + openid.spec.DisplayType: + description: > + ASCII string value that specifies how the Authorization Server displays the authentication and consent user + interface pages to the End-User. + enum: + - "page" + - "popup" + - "touch" + - "wap" + example: 'page' + type: string + openid.spec.ResponseType: + description: The OAuth 2.0 / OpenID Connect 1.0 Response Type. + enum: + - "code" + - "id_token" + - "token" + - "code token" + - "code id_token" + - "token id_token" + - "code id_token token" + - "none" + example: 'code' + type: string + openid.spec.ResponseMode: + description: > + Informs the Authorization Server of the mechanism to be used for returning parameters from the Authorization + Endpoint. This use of this parameter is NOT RECOMMENDED when the Response Mode that would be requested is + the default mode specified for the Response Type. + enum: + - "query" + - "fragment" + - "form_post" + example: 'query' + type: string + openid.spec.GrantType: + description: The OAuth 2.0 / OpenID Connect 1.0 Grant Type. + enum: + - "authorization_code" + - "refresh_token" + - "implicit" + - "password" + - "client_credentials" + - "urn:ietf:params:oauth:grant-type:device_code" + example: 'authorization_code' + type: string + openid.spec.CodeChallengeMethod: + description: The RFC7636 Code Challenge Verifier Method. + enum: + - "plain" + - "S256" + example: 'S256' + type: string + openid.spec.ClaimType: + description: The representation of claims. + enum: + - "normal" + - "aggregated" + - "distributed" + example: 'normal' + type: string + jose.spec.None: + description: The JSON Web Signature Algorithm + type: string + enum: + - "none" + jose.spec.JWS.None: + description: The JSON Web Signature Algorithm + oneOf: + - $ref: '#/components/schemas/jose.spec.None' + - $ref: '#/components/schemas/jose.spec.jws' + type: string + jose.spec.jws: + description: The JSON Web Signature Algorithm + enum: + - "HS256" + - "HS384" + - "HS512" + - "RS256" + - "RS384" + - "RS512" + - "ES256" + - "ES384" + - "ES512" + - "PS256" + - "PS384" + - "PS512" + type: string + jose.spec.JWE.alg: + description: The JSON Web Encryption Algorithm (CEK) + enum: + - "RSA1_5" + - "RSA-OAEP" + - "RSA-OAEP-256" + - "A128KW" + - "A192KW" + - "A256KW" + - "dir" + - "ECDH-ES" + - "ECDH-ES+A128KW" + - "ECDH-ES+A192KW" + - "ECDH-ES+A256KW" + - "A128GCMKW" + - "A192GCMKW" + - "A256GCMKW" + - "PBES2-HS256+A128KW" + - "PBES2-HS384+A192KW" + - "PBES2-HS512+A256KW" + type: string + jose.spec.JWE.enc: + description: The JSON Web Encryption Algorithm (Claims) + enum: + - "A128CBC-HS256" + - "A192CBC-HS384" + - "A256CBC-HS512" + - "A128CBC" + - "A256CBC" + - "A128GCM" + - "A256GCM" + type: string + jose.spec.JWK.base: + type: object + properties: + use: + description: > + The "use" (public key use) parameter identifies the intended use of + the public key. The "use" parameter is employed to indicate whether + a public key is used for encrypting data or verifying the signature + on data. + enum: + - "sig" + - "enc" + example: 'sig' + type: string + key_ops: + description: > + The "key_ops" (key operations) parameter identifies the operation(s) + for which the key is intended to be used. The "key_ops" parameter is + intended for use cases in which public, private, or symmetric keys + may be present. + example: ["sign"] + type: array + items: + enum: + - "sign" + - "verify" + - "encrypt" + - "decrypt" + - "wrapKey" + - "unwrapKey" + - "deriveKey" + - "deriveBits" + type: string + kid: + description: > + The "kid" (key ID) parameter is used to match a specific key. This + is used, for instance, to choose among a set of keys within a JWK Set + during key rollover. The structure of the "kid" value is + unspecified. When "kid" values are used within a JWK Set, different + keys within the JWK Set SHOULD use distinct "kid" values. (One + example in which different keys might use the same "kid" value is if + they have different "kty" (key type) values but are considered to be + equivalent alternatives by the application using them.) The "kid" + value is a case-sensitive string. Use of this member is OPTIONAL. + When used with JWS or JWE, the "kid" value is used to match a JWS or + JWE "kid" Header Parameter value. + type: string + x5u: + description: > + The "x5u" (X.509 URL) parameter is a URI [RFC3986] that refers to a + resource for an X.509 public key certificate or certificate chain + [RFC5280]. The identified resource MUST provide a representation of + the certificate or certificate chain that conforms to RFC 5280 + [RFC5280] in PEM-encoded form, with each certificate delimited as + specified in Section 6.1 of RFC 4945 [RFC4945]. The key in the first + certificate MUST match the public key represented by other members of + the JWK. The protocol used to acquire the resource MUST provide + integrity protection; an HTTP GET request to retrieve the certificate + MUST use TLS [RFC2818] [RFC5246]; the identity of the server MUST be + validated, as per Section 6 of RFC 6125 [RFC6125]. Use of this + member is OPTIONAL. + type: string + x5c: + description: > + The "x5c" (X.509 certificate chain) parameter contains a chain of one + or more PKIX certificates [RFC5280]. The certificate chain is + represented as a JSON array of certificate value strings. Each + string in the array is a base64-encoded (Section 4 of [RFC4648] -- + not base64url-encoded) DER [ITU.X690.1994] PKIX certificate value. + The PKIX certificate containing the key value MUST be the first + certificate. This MAY be followed by additional certificates, with + each subsequent certificate being the one used to certify the + previous one. The key in the first certificate MUST match the public + key represented by other members of the JWK. Use of this member is + OPTIONAL. + type: array + items: + format: byte + type: string + x5t: + description: > + The "x5t" (X.509 certificate SHA-1 thumbprint) parameter is a + base64url-encoded SHA-1 thumbprint (a.k.a. digest) of the DER + encoding of an X.509 certificate [RFC5280]. Note that certificate + thumbprints are also sometimes known as certificate fingerprints. + The key in the certificate MUST match the public key represented by + other members of the JWK. Use of this member is OPTIONAL. + format: byte + type: string + x5t#S256: + description: > + The "x5t#S256" (X.509 certificate SHA-256 thumbprint) parameter is a + base64url-encoded SHA-256 thumbprint (a.k.a. digest) of the DER + encoding of an X.509 certificate [RFC5280]. Note that certificate + thumbprints are also sometimes known as certificate fingerprints. + The key in the certificate MUST match the public key represented by + other members of the JWK. Use of this member is OPTIONAL. + format: byte + type: string + jose.spec.JWK.RSA: + description: RSA Public Key in JSON Web Key format as defined by RFC7517 and RFC7518. + allOf: + - $ref: '#/components/schemas/jose.spec.JWK.base' + - required: + - "kty" + - "n" + - "e" + type: object + properties: + kty: + description: > + The "kty" (key type) parameter identifies the cryptographic algorithm + family used with the key. + type: string + example: 'RSA' + enum: + - "RSA" + alg: + description: The JSON Web Signature Algorithm + type: string + example: 'RS256' + enum: + - "RS256" + - "RS384" + - "RS512" + - "PS256" + - "PS384" + - "PS512" + n: + description: > + RSA Public Key: The "n" (modulus) parameter contains the modulus value for the RSA public key. It is + represented as a Base64urlUInt-encoded value. + type: string + format: byte + e: + description: > + RSA Public Key: The "e" (exponent) parameter contains the exponent value for the RSA public key. + It is represented as a Base64urlUInt-encoded value. + type: string + format: byte + jose.spec.JWK.RSA.Private: + description: RSA Private Key in JSON Web Key format as defined by RFC7517 and RFC7518. + allOf: + - $ref: '#/components/schemas/jose.spec.JWK.base' + - $ref: '#/components/schemas/jose.spec.JWK.RSA' + - type: object + required: + - "d" + properties: + d: + description: > + RSA Private Key: The "d" (private exponent) parameter contains the private exponent value for the RSA + private key. It is represented as a Base64urlUInt-encoded value. + type: string + format: byte + p: + description: > + RSA Private Key: The "p" (first prime factor) parameter contains the first prime factor. + It is represented as a Base64urlUInt-encoded value. + type: string + format: byte + q: + description: > + RSA Private Key: The "q" (second prime factor) parameter contains the second prime factor. It is + represented as a Base64urlUInt-encoded value. + type: string + format: byte + dp: + description: > + RSA Private Key: The "dp" (first factor CRT exponent) parameter contains the Chinese Remainder Theorem + (CRT) exponent of the first factor. It is represented as a Base64urlUInt-encoded value. + type: string + dq: + description: > + RSA Private Key: The "dq" (second factor CRT exponent) parameter contains the CRT exponent of the + second factor. It is represented as a Base64urlUInt-encoded value. + type: string + qi: + description: > + RSA Private Key: The "qi" (first CRT coefficient) parameter contains the CRT coefficient of the second + factor. It is represented as a Base64urlUInt-encoded value. + type: string + format: byte + oth: + description: > + The "oth" (other primes info) parameter contains an array of + information about any third and subsequent primes, should they exist. + type: array + items: + type: object + required: + - "r" + - "d" + - "t" + properties: + r: + description: > + The "r" (prime factor) parameter within an "oth" array member + represents the value of a subsequent prime factor. It is represented + as a Base64urlUInt-encoded value. + type: string + format: byte + d: + description: > + The "d" (factor CRT exponent) parameter within an "oth" array member + represents the CRT exponent of the corresponding prime factor. It is + represented as a Base64urlUInt-encoded value. + type: string + format: byte + t: + description: > + The "t" (factor CRT coefficient) parameter within an "oth" array + member represents the CRT coefficient of the corresponding prime + factor. It is represented as a Base64urlUInt-encoded value. + type: string + format: byte + jose.spec.JWK.EC: + description: Elliptic Curve Public Key in JSON Web Key format as defined by RFC7517 and RFC7518. + allOf: + - $ref: '#/components/schemas/jose.spec.JWK.base' + - type: object + required: + - "kty" + - "crv" + - "x" + properties: + kty: + description: > + The "kty" (key type) parameter identifies the cryptographic algorithm + family used with the key. + type: string + example: 'EC' + enum: + - "EC" + alg: + description: The JSON Web Signature Algorithm + type: string + example: 'ES256' + enum: + - "ES256" + - "ES384" + - "ES512" + x: + description: > + EC Public Key: The x coordinate parameter contains the x coordinate for the Elliptic Curve point. + It is represented as the base64url encoding of the octet string representation of the coordinate, as + defined in Section 2.3.5 of SEC1 [SEC1]. + type: string + format: byte + y: + description: > + EC Public Key: The y coordinate parameter contains the y coordinate for the Elliptic Curve point. + It is represented as the base64url encoding of the octet string representation of the coordinate, as + defined in Section 2.3.5 of SEC1 [SEC1]. + type: string + format: byte + crv: + description: > + The curve parameter identifies the cryptographic curve used with the key. Curve + values from [DSS] used by this specification. + type: string + example: 'P-521' + enum: + - "P-256" + - "P-384" + - "P-521" + - "Ed25519" + - "Ed448" + - "X25519" + - "X448" + - "secp256k1" + jose.spec.JWK.EC.Private: + description: Elliptic Curve Private Key in JSON Web Key format as defined by RFC7517 and RFC7518. + allOf: + - $ref: '#/components/schemas/jose.spec.JWK.base' + - $ref: '#/components/schemas/jose.spec.JWK.EC' + - type: object + required: + - "d" + properties: + d: + description: > + ECC Private Key: The "d" (ECC private key) parameter contains the Elliptic Curve private key value. It + is represented as the base64url encoding of the octet string representation of the private key value, + as defined in Section 2.3.7 of SEC1 [SEC1]. The length of this octet string MUST be + ceiling(log-base-2(n)/8) octets (where n is the order of the curve). + type: string + format: byte + jose.spec.JWK.Symmetric: + description: Symmetric Key in JSON Web Key format as defined by RFC7517 and RFC7518. + allOf: + - $ref: '#/components/schemas/jose.spec.JWK.base' + - type: object + required: + - "k" + properties: + kty: + description: > + The "kty" (key type) parameter identifies the cryptographic algorithm + family used with the key. + type: string + example: 'oct' + enum: + - "oct" + k: + description: > + The "k" (key value) parameter contains the value of the symmetric (or + other single-valued) key. It is represented as the base64url + encoding of the octet sequence containing the key value. + type: string + format: byte + jose.spec.JWK: + type: string + anyOf: + - $ref: '#/components/schemas/jose.spec.JWK.RSA' + - $ref: '#/components/schemas/jose.spec.JWK.RSA.Private' + - $ref: '#/components/schemas/jose.spec.JWK.EC' + - $ref: '#/components/schemas/jose.spec.JWK.EC.Private' + - $ref: '#/components/schemas/jose.spec.JWK.Symmetric' + jose.spec.JWKs: + type: object + description: The JSON Web Key Sets Document as defined by RFC7517. + properties: + keys: + description: List of JSON Wek Key's in the JSON Web Key format as defined by RFC7517. + type: array + items: + $ref: '#/components/schemas/jose.spec.JWK' + {{- end }} + securitySchemes: + authelia_auth: + type: apiKey + name: "{{ .Session }}" + in: cookie + {{- if .OpenIDConnect }} + openid: + type: openIdConnect + openIdConnectUrl: "{{ .BaseURL }}.well-known/openid-configuration" + {{- end }} +... diff --git a/internal/server/public_html/index.html b/internal/server/public_html/index.html index ca923626d..0659f1872 100644 --- a/internal/server/public_html/index.html +++ b/internal/server/public_html/index.html @@ -1,11 +1,32 @@ -{ - "Base":"{{ .Base }}", - "DuoSelfEnrollment":"{{ .DuoSelfEnrollment }}", - "LogoOverride":"{{ .LogoOverride }}", - "RememberMe":"{{ .RememberMe }}", - "ResetPassword":"{{ .ResetPassword }}", - "ResetPasswordCustomURL":"{{ .ResetPasswordCustomURL }}", - "PrivacyPolicyURL":"{{ .PrivacyPolicyURL }}", - "PrivacyPolicyAccept":"{{ .PrivacyPolicyAccept }}", - "Theme":"{{ .Theme }}" -} + + + + + + + + + + + + Login - Authelia + + + + + + +
+ + + diff --git a/internal/suites/example/compose/envoy/docker-compose.yml b/internal/suites/example/compose/envoy/docker-compose.yml index c17497ace..231a2cbe6 100644 --- a/internal/suites/example/compose/envoy/docker-compose.yml +++ b/internal/suites/example/compose/envoy/docker-compose.yml @@ -2,7 +2,7 @@ version: '3' services: envoy: - image: envoyproxy/envoy:v1.25.4 + image: envoyproxy/envoy:v1.25.5 volumes: - ./example/compose/envoy/envoy.yaml:/etc/envoy/envoy.yaml - ./common/pki:/pki diff --git a/internal/utils/strings.go b/internal/utils/strings.go index 3e9dc12cd..1afdfc89d 100644 --- a/internal/utils/strings.go +++ b/internal/utils/strings.go @@ -200,8 +200,8 @@ func URLsFromStringSlice(urls []string) []url.URL { } // OriginFromURL returns an origin url.URL given another url.URL. -func OriginFromURL(u url.URL) (origin url.URL) { - return url.URL{ +func OriginFromURL(u *url.URL) (origin *url.URL) { + return &url.URL{ Scheme: u.Scheme, Host: u.Host, } diff --git a/internal/utils/strings_test.go b/internal/utils/strings_test.go index fe7c6742e..c12978739 100644 --- a/internal/utils/strings_test.go +++ b/internal/utils/strings_test.go @@ -242,7 +242,7 @@ func TestOriginFromURL(t *testing.T) { google, err := url.Parse("https://google.com/abc?a=123#five") assert.NoError(t, err) - origin := OriginFromURL(*google) + origin := OriginFromURL(google) assert.Equal(t, "https://google.com", origin.String()) } diff --git a/web/package.json b/web/package.json index 9c3d4b975..c6235393f 100644 --- a/web/package.json +++ b/web/package.json @@ -72,13 +72,13 @@ ] }, "devDependencies": { - "@commitlint/cli": "17.5.1", - "@commitlint/config-conventional": "17.4.4", + "@commitlint/cli": "17.6.0", + "@commitlint/config-conventional": "17.6.0", "@limegrass/eslint-plugin-import-alias": "1.0.6", "@testing-library/jest-dom": "5.16.5", "@testing-library/react": "14.0.0", "@types/node": "18.15.11", - "@types/react": "18.0.34", + "@types/react": "18.0.35", "@types/react-dom": "18.0.11", "@types/testing-library__jest-dom": "5.14.5", "@types/zxcvbn": "4.4.1", @@ -97,7 +97,7 @@ "eslint-plugin-prettier": "4.2.1", "eslint-plugin-react": "7.32.2", "eslint-plugin-react-hooks": "4.6.0", - "happy-dom": "9.2.1", + "happy-dom": "9.5.0", "husky": "8.0.3", "prettier": "2.8.7", "react-test-renderer": "18.2.0", @@ -106,7 +106,7 @@ "vite-plugin-eslint": "1.8.1", "vite-plugin-istanbul": "4.0.1", "vite-plugin-svgr": "2.4.0", - "vite-tsconfig-paths": "4.1.0", + "vite-tsconfig-paths": "4.2.0", "vitest": "0.30.1", "vitest-preview": "0.0.1" } diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index f2b4aff73..1ced3cda0 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -6,10 +6,10 @@ dependencies: version: 11.10.7 '@emotion/react': specifier: 11.10.6 - version: 11.10.6(@types/react@18.0.34)(react@18.2.0) + version: 11.10.6(@types/react@18.0.35)(react@18.2.0) '@emotion/styled': specifier: 11.10.6 - version: 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.34)(react@18.2.0) + version: 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.35)(react@18.2.0) '@fortawesome/fontawesome-svg-core': specifier: 6.4.0 version: 6.4.0 @@ -24,13 +24,16 @@ dependencies: version: 0.2.0(@fortawesome/fontawesome-svg-core@6.4.0)(react@18.2.0) '@mui/icons-material': specifier: 5.11.16 - version: 5.11.16(@mui/material@5.12.0)(@types/react@18.0.34)(react@18.2.0) + version: 5.11.16(@mui/material@5.12.0)(@types/react@18.0.35)(react@18.2.0) '@mui/material': specifier: 5.12.0 - version: 5.12.0(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.34)(react-dom@18.2.0)(react@18.2.0) + version: 5.12.0(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.35)(react-dom@18.2.0)(react@18.2.0) '@mui/styles': specifier: 5.12.0 - version: 5.12.0(@types/react@18.0.34)(react@18.2.0) + version: 5.12.0(@types/react@18.0.35)(react@18.2.0) + '@simplewebauthn/browser': + specifier: 7.2.0 + version: 7.2.0 '@simplewebauthn/typescript-types': specifier: 7.0.0 version: 7.0.0 @@ -79,11 +82,11 @@ dependencies: devDependencies: '@commitlint/cli': - specifier: 17.5.1 - version: 17.5.1 + specifier: 17.6.0 + version: 17.6.0 '@commitlint/config-conventional': - specifier: 17.4.4 - version: 17.4.4 + specifier: 17.6.0 + version: 17.6.0 '@limegrass/eslint-plugin-import-alias': specifier: 1.0.6 version: 1.0.6(eslint@8.38.0) @@ -97,8 +100,8 @@ devDependencies: specifier: 18.15.11 version: 18.15.11 '@types/react': - specifier: 18.0.34 - version: 18.0.34 + specifier: 18.0.35 + version: 18.0.35 '@types/react-dom': specifier: 18.0.11 version: 18.0.11 @@ -154,8 +157,8 @@ devDependencies: specifier: 4.6.0 version: 4.6.0(eslint@8.38.0) happy-dom: - specifier: 9.2.1 - version: 9.2.1 + specifier: 9.5.0 + version: 9.5.0 husky: specifier: 8.0.3 version: 8.0.3 @@ -181,11 +184,11 @@ devDependencies: specifier: 2.4.0 version: 2.4.0(vite@4.2.1) vite-tsconfig-paths: - specifier: 4.1.0 - version: 4.1.0(typescript@5.0.4)(vite@4.2.1) + specifier: 4.2.0 + version: 4.2.0(typescript@5.0.4)(vite@4.2.1) vitest: specifier: 0.30.1 - version: 0.30.1(happy-dom@9.2.1) + version: 0.30.1(happy-dom@9.5.0) vitest-preview: specifier: 0.0.1 version: 0.0.1 @@ -1551,12 +1554,12 @@ packages: '@babel/helper-validator-identifier': 7.19.1 to-fast-properties: 2.0.0 - /@commitlint/cli@17.5.1: - resolution: {integrity: sha512-pRRgGSzdHQHehxZbGA3qF6wVPyl+EEQgTe/t321rtMLFbuJ7nRj2waS17s/v5oEbyZtiY5S8PGB6XtEIm0I+Sg==} + /@commitlint/cli@17.6.0: + resolution: {integrity: sha512-JaZeZ1p6kfkSiZlDoQjK09AuiI9zYQMiIUJzTOM8qNRHFOXOPmiTM56nI67yzeUSNTFu6M/DRqjmdjtA5q3hEg==} engines: {node: '>=v14'} dependencies: '@commitlint/format': 17.4.4 - '@commitlint/lint': 17.4.4 + '@commitlint/lint': 17.6.0 '@commitlint/load': 17.5.0 '@commitlint/read': 17.5.1 '@commitlint/types': 17.4.4 @@ -1570,8 +1573,8 @@ packages: - '@swc/wasm' dev: true - /@commitlint/config-conventional@17.4.4: - resolution: {integrity: sha512-u6ztvxqzi6NuhrcEDR7a+z0yrh11elY66nRrQIpqsqW6sZmpxYkDLtpRH8jRML+mmxYQ8s4qqF06Q/IQx5aJeQ==} + /@commitlint/config-conventional@17.6.0: + resolution: {integrity: sha512-2Y9M7MN942bTK5h70fJGknhXA02+OtWCkKeIzTSwsdwz1V7y6bxYv24x052E9XHKtZHJfvM3iLuTOsjRvLqWtA==} engines: {node: '>=v14'} dependencies: conventional-changelog-conventionalcommits: 5.0.0 @@ -1618,13 +1621,13 @@ packages: semver: 7.3.8 dev: true - /@commitlint/lint@17.4.4: - resolution: {integrity: sha512-qgkCRRFjyhbMDWsti/5jRYVJkgYZj4r+ZmweZObnbYqPUl5UKLWMf9a/ZZisOI4JfiPmRktYRZ2JmqlSvg+ccw==} + /@commitlint/lint@17.6.0: + resolution: {integrity: sha512-6cEXxpxZd7fbtYMxeosOum/Nnwu3VdSuZcrFSqP9lWNsrHRv4ijVsnLeomvo6WHPchGOeEWAazAI7Q6Ap22fJw==} engines: {node: '>=v14'} dependencies: '@commitlint/is-ignored': 17.4.4 '@commitlint/parse': 17.4.4 - '@commitlint/rules': 17.4.4 + '@commitlint/rules': 17.6.0 '@commitlint/types': 17.4.4 dev: true @@ -1688,8 +1691,8 @@ packages: resolve-global: 1.0.0 dev: true - /@commitlint/rules@17.4.4: - resolution: {integrity: sha512-0tgvXnHi/mVcyR8Y8mjTFZIa/FEQXA4uEutXS/imH2v1UNkYDSEMsK/68wiXRpfW1euSgEdwRkvE1z23+yhNrQ==} + /@commitlint/rules@17.6.0: + resolution: {integrity: sha512-Ka7AsRFvkKMYYE7itgo7hddRGCiV+0BgbTIAq4PWmnkHAECxYpdqMVzW5jaATmXZfwfRRTB57e7KZWj6EPmK1A==} engines: {node: '>=v14'} dependencies: '@commitlint/ensure': 17.4.4 @@ -1765,7 +1768,7 @@ packages: resolution: {integrity: sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==} dev: false - /@emotion/react@11.10.6(@types/react@18.0.34)(react@18.2.0): + /@emotion/react@11.10.6(@types/react@18.0.35)(react@18.2.0): resolution: {integrity: sha512-6HT8jBmcSkfzO7mc+N1L9uwvOnlcGoix8Zn7srt+9ga0MjREo6lRpuVX0kzo6Jp6oTqDhREOFsygN6Ew4fEQbw==} peerDependencies: '@types/react': '*' @@ -1781,7 +1784,7 @@ packages: '@emotion/use-insertion-effect-with-fallbacks': 1.0.0(react@18.2.0) '@emotion/utils': 1.2.0 '@emotion/weak-memoize': 0.3.0 - '@types/react': 18.0.34 + '@types/react': 18.0.35 hoist-non-react-statics: 3.3.2 react: 18.2.0 dev: false @@ -1800,7 +1803,7 @@ packages: resolution: {integrity: sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==} dev: false - /@emotion/styled@11.10.6(@emotion/react@11.10.6)(@types/react@18.0.34)(react@18.2.0): + /@emotion/styled@11.10.6(@emotion/react@11.10.6)(@types/react@18.0.35)(react@18.2.0): resolution: {integrity: sha512-OXtBzOmDSJo5Q0AFemHCfl+bUueT8BIcPSxu0EGTpGk6DmI5dnhSzQANm1e1ze0YZL7TDyAyy6s/b/zmGOS3Og==} peerDependencies: '@emotion/react': ^11.0.0-rc.0 @@ -1813,11 +1816,11 @@ packages: '@babel/runtime': 7.21.0 '@emotion/babel-plugin': 11.10.6 '@emotion/is-prop-valid': 1.2.0 - '@emotion/react': 11.10.6(@types/react@18.0.34)(react@18.2.0) + '@emotion/react': 11.10.6(@types/react@18.0.35)(react@18.2.0) '@emotion/serialize': 1.1.1 '@emotion/use-insertion-effect-with-fallbacks': 1.0.0(react@18.2.0) '@emotion/utils': 1.2.0 - '@types/react': 18.0.34 + '@types/react': 18.0.35 react: 18.2.0 dev: false @@ -2256,7 +2259,7 @@ packages: tsconfig-paths: 3.14.2 dev: true - /@mui/base@5.0.0-alpha.125(@types/react@18.0.34)(react-dom@18.2.0)(react@18.2.0): + /@mui/base@5.0.0-alpha.125(@types/react@18.0.35)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-hAHJJ97SATu6SrkLH/HsAayK1zMZt89lrWyKuAInBKVyn363H78d1MnwyZwre9vDK5MrPoDL/NnZxtAXhwTnBA==} engines: {node: '>=12.0.0'} peerDependencies: @@ -2269,10 +2272,10 @@ packages: dependencies: '@babel/runtime': 7.21.0 '@emotion/is-prop-valid': 1.2.0 - '@mui/types': 7.2.4(@types/react@18.0.34) + '@mui/types': 7.2.4(@types/react@18.0.35) '@mui/utils': 5.12.0(react@18.2.0) '@popperjs/core': 2.11.7 - '@types/react': 18.0.34 + '@types/react': 18.0.35 clsx: 1.2.1 prop-types: 15.8.1 react: 18.2.0 @@ -2284,7 +2287,7 @@ packages: resolution: {integrity: sha512-1hoFIdlLI0sG+mkJgm70FjgIVpfLcE1vxPtNolg1tLFXrvbXGUYp9NHy3d6c41nDkg2OajuVS+Mn6A8UirFuMw==} dev: false - /@mui/icons-material@5.11.16(@mui/material@5.12.0)(@types/react@18.0.34)(react@18.2.0): + /@mui/icons-material@5.11.16(@mui/material@5.12.0)(@types/react@18.0.35)(react@18.2.0): resolution: {integrity: sha512-oKkx9z9Kwg40NtcIajF9uOXhxiyTZrrm9nmIJ4UjkU2IdHpd4QVLbCc/5hZN/y0C6qzi2Zlxyr9TGddQx2vx2A==} engines: {node: '>=12.0.0'} peerDependencies: @@ -2296,12 +2299,12 @@ packages: optional: true dependencies: '@babel/runtime': 7.21.0 - '@mui/material': 5.12.0(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.34)(react-dom@18.2.0)(react@18.2.0) - '@types/react': 18.0.34 + '@mui/material': 5.12.0(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.35)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.0.35 react: 18.2.0 dev: false - /@mui/material@5.12.0(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.34)(react-dom@18.2.0)(react@18.2.0): + /@mui/material@5.12.0(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.35)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-IMellv153zJ6+xfhLWgXpAm/9hsX8qE6gP66xWcW/Pf2B8ubyVhmkTXsp8pAJxk81D6p/EyYcnAjo5DiDVkj9g==} engines: {node: '>=12.0.0'} peerDependencies: @@ -2319,14 +2322,14 @@ packages: optional: true dependencies: '@babel/runtime': 7.21.0 - '@emotion/react': 11.10.6(@types/react@18.0.34)(react@18.2.0) - '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.34)(react@18.2.0) - '@mui/base': 5.0.0-alpha.125(@types/react@18.0.34)(react-dom@18.2.0)(react@18.2.0) + '@emotion/react': 11.10.6(@types/react@18.0.35)(react@18.2.0) + '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.35)(react@18.2.0) + '@mui/base': 5.0.0-alpha.125(@types/react@18.0.35)(react-dom@18.2.0)(react@18.2.0) '@mui/core-downloads-tracker': 5.12.0 - '@mui/system': 5.12.0(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.34)(react@18.2.0) - '@mui/types': 7.2.4(@types/react@18.0.34) + '@mui/system': 5.12.0(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.35)(react@18.2.0) + '@mui/types': 7.2.4(@types/react@18.0.35) '@mui/utils': 5.12.0(react@18.2.0) - '@types/react': 18.0.34 + '@types/react': 18.0.35 '@types/react-transition-group': 4.4.5 clsx: 1.2.1 csstype: 3.1.2 @@ -2337,7 +2340,7 @@ packages: react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) dev: false - /@mui/private-theming@5.12.0(@types/react@18.0.34)(react@18.2.0): + /@mui/private-theming@5.12.0(@types/react@18.0.35)(react@18.2.0): resolution: {integrity: sha512-w5dwMen1CUm1puAtubqxY9BIzrBxbOThsg2iWMvRJmWyJAPdf3Z583fPXpqeA2lhTW79uH2jajk5Ka4FuGlTPg==} engines: {node: '>=12.0.0'} peerDependencies: @@ -2349,7 +2352,7 @@ packages: dependencies: '@babel/runtime': 7.21.0 '@mui/utils': 5.12.0(react@18.2.0) - '@types/react': 18.0.34 + '@types/react': 18.0.35 prop-types: 15.8.1 react: 18.2.0 dev: false @@ -2369,14 +2372,14 @@ packages: dependencies: '@babel/runtime': 7.21.0 '@emotion/cache': 11.10.7 - '@emotion/react': 11.10.6(@types/react@18.0.34)(react@18.2.0) - '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.34)(react@18.2.0) + '@emotion/react': 11.10.6(@types/react@18.0.35)(react@18.2.0) + '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.35)(react@18.2.0) csstype: 3.1.2 prop-types: 15.8.1 react: 18.2.0 dev: false - /@mui/styles@5.12.0(@types/react@18.0.34)(react@18.2.0): + /@mui/styles@5.12.0(@types/react@18.0.35)(react@18.2.0): resolution: {integrity: sha512-X7obkgZTd9X+7igqwKKe8pEncyXYdUCNmyJfHruV9TSc6LThoI29OYs6hkN6n+7ueNli+YDKdZ+TCoC1GpJuOw==} engines: {node: '>=12.0.0'} peerDependencies: @@ -2388,10 +2391,10 @@ packages: dependencies: '@babel/runtime': 7.21.0 '@emotion/hash': 0.9.0 - '@mui/private-theming': 5.12.0(@types/react@18.0.34)(react@18.2.0) - '@mui/types': 7.2.4(@types/react@18.0.34) + '@mui/private-theming': 5.12.0(@types/react@18.0.35)(react@18.2.0) + '@mui/types': 7.2.4(@types/react@18.0.35) '@mui/utils': 5.12.0(react@18.2.0) - '@types/react': 18.0.34 + '@types/react': 18.0.35 clsx: 1.2.1 csstype: 3.1.2 hoist-non-react-statics: 3.3.2 @@ -2407,7 +2410,7 @@ packages: react: 18.2.0 dev: false - /@mui/system@5.12.0(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.34)(react@18.2.0): + /@mui/system@5.12.0(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.35)(react@18.2.0): resolution: {integrity: sha512-Zi+WHuiJfK1ya+9+oeJQ1rLIBdY8CGDYT5oVlQg/6kIuyiCaE6SnN9PVzxBxfY77wHuOPwz4kxcPe9srdZc12Q==} engines: {node: '>=12.0.0'} peerDependencies: @@ -2424,20 +2427,20 @@ packages: optional: true dependencies: '@babel/runtime': 7.21.0 - '@emotion/react': 11.10.6(@types/react@18.0.34)(react@18.2.0) - '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.34)(react@18.2.0) - '@mui/private-theming': 5.12.0(@types/react@18.0.34)(react@18.2.0) + '@emotion/react': 11.10.6(@types/react@18.0.35)(react@18.2.0) + '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.35)(react@18.2.0) + '@mui/private-theming': 5.12.0(@types/react@18.0.35)(react@18.2.0) '@mui/styled-engine': 5.12.0(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(react@18.2.0) - '@mui/types': 7.2.4(@types/react@18.0.34) + '@mui/types': 7.2.4(@types/react@18.0.35) '@mui/utils': 5.12.0(react@18.2.0) - '@types/react': 18.0.34 + '@types/react': 18.0.35 clsx: 1.2.1 csstype: 3.1.2 prop-types: 15.8.1 react: 18.2.0 dev: false - /@mui/types@7.2.4(@types/react@18.0.34): + /@mui/types@7.2.4(@types/react@18.0.35): resolution: {integrity: sha512-LBcwa8rN84bKF+f5sDyku42w1NTxaPgPyYKODsh01U1fVstTClbUoSA96oyRBnSNyEiAVjKm6Gwx9vjR+xyqHA==} peerDependencies: '@types/react': '*' @@ -2445,7 +2448,7 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 18.0.34 + '@types/react': 18.0.35 dev: false /@mui/utils@5.12.0(react@18.2.0): @@ -2853,23 +2856,23 @@ packages: /@types/react-dom@18.0.11: resolution: {integrity: sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==} dependencies: - '@types/react': 18.0.34 + '@types/react': 18.0.35 dev: true /@types/react-is@17.0.3: resolution: {integrity: sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==} dependencies: - '@types/react': 18.0.34 + '@types/react': 18.0.35 dev: false /@types/react-transition-group@4.4.5: resolution: {integrity: sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==} dependencies: - '@types/react': 18.0.34 + '@types/react': 18.0.35 dev: false - /@types/react@18.0.34: - resolution: {integrity: sha512-NO1UO8941541CJl1BeOXi8a9dNKFK09Gnru5ZJqkm4Q3/WoQJtHvmwt0VX0SB9YCEwe7TfSSxDuaNmx6H2BAIQ==} + /@types/react@18.0.35: + resolution: {integrity: sha512-6Laome31HpetaIUGFWl1VQ3mdSImwxtFZ39rh059a1MNnKGqBpC88J6NJ8n/Is3Qx7CefDGLgf/KhN/sYCf7ag==} dependencies: '@types/prop-types': 15.7.5 '@types/scheduler': 0.16.3 @@ -3089,7 +3092,7 @@ packages: istanbul-lib-source-maps: 4.0.1 istanbul-reports: 3.1.5 test-exclude: 6.0.0 - vitest: 0.30.1(happy-dom@9.2.1) + vitest: 0.30.1(happy-dom@9.5.0) transitivePeerDependencies: - supports-color dev: true @@ -5092,8 +5095,8 @@ packages: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} dev: true - /happy-dom@9.2.1: - resolution: {integrity: sha512-6L1p1XalEVw/yzb+HVpCpfR0R3HDTAbTaTIDquJCzFPVVUC7q8+jHENH1W+4P2HsI9klCsPyeKm2/Ttfz/B5ag==} + /happy-dom@9.5.0: + resolution: {integrity: sha512-pNdHSZRWIckzg8aDQRbBgaivr2Ef+uSTpCCRGnxIETyewHA6841T8EPE+cmfhPjGi5jQN6c+oloXGGYB5SrpcA==} dependencies: css.escape: 1.5.1 he: 1.2.0 @@ -7379,8 +7382,8 @@ packages: - supports-color dev: true - /vite-tsconfig-paths@4.1.0(typescript@5.0.4)(vite@4.2.1): - resolution: {integrity: sha512-Ps275He1fF6Wpm/3tvyokIfXd3lcmk4KsdCG4yduSTRVt+htaxIoEor88M1h3soOeXg5fn77ZiupVRwGeB/XNQ==} + /vite-tsconfig-paths@4.2.0(typescript@5.0.4)(vite@4.2.1): + resolution: {integrity: sha512-jGpus0eUy5qbbMVGiTxCL1iB9ZGN6Bd37VGLJU39kTDD6ZfULTTb1bcc5IeTWqWJKiWV5YihCaibeASPiGi8kw==} peerDependencies: vite: '*' peerDependenciesMeta: @@ -7479,7 +7482,7 @@ packages: - terser dev: true - /vitest@0.30.1(happy-dom@9.2.1): + /vitest@0.30.1(happy-dom@9.5.0): resolution: {integrity: sha512-y35WTrSTlTxfMLttgQk4rHcaDkbHQwDP++SNwPb+7H8yb13Q3cu2EixrtHzF27iZ8v0XCciSsLg00RkPAzB/aA==} engines: {node: '>=v14.18.0'} peerDependencies: @@ -7523,7 +7526,7 @@ packages: chai: 4.3.7 concordance: 5.0.4 debug: 4.3.4 - happy-dom: 9.2.1 + happy-dom: 9.5.0 local-pkg: 0.4.3 magic-string: 0.30.0 pathe: 1.1.0