feat(oidc): client authentication modes (#5150)
This adds a feature to OpenID Connect 1.0 where clients can be restricted to a specific client authentication mode, as well as implements some backend requirements for the private_key_jwt client authentication mode (and potentially the tls_client_auth / self_signed_tls_client_auth client authentication modes). It also adds some improvements to configuration defaults and validations which will for now be warnings but likely be made into errors. Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>pull/4779/head^2
parent
db130dad48
commit
3d2da0b070
|
@ -1480,12 +1480,6 @@ notifier:
|
|||
# - email
|
||||
# - 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 == "" {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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{}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ const (
|
|||
)
|
||||
|
||||
const (
|
||||
errSuffixMustBeOneOf = "is configured as '%s' but must be one of the following values: '%s'"
|
||||
errSuffixMustBeOneOf = "must be one of %s but it's configured as '%s'"
|
||||
)
|
||||
|
||||
// Authentication Backend Error constants.
|
||||
|
@ -105,19 +105,19 @@ const (
|
|||
errFmtLDAPAuthBackendURLNotParsable = "authentication_backend: ldap: option " +
|
||||
"'url' could not be parsed: %w"
|
||||
errFmtLDAPAuthBackendURLInvalidScheme = "authentication_backend: ldap: option " +
|
||||
"'url' must have either the 'ldap' or 'ldaps' scheme but it is configured as '%s'"
|
||||
"'url' must have either the 'ldap' or 'ldaps' scheme but it's configured as '%s'"
|
||||
errFmtLDAPAuthBackendFilterEnclosingParenthesis = "authentication_backend: ldap: option " +
|
||||
"'%s' must contain enclosing parenthesis: '%s' should probably be '(%s)'"
|
||||
errFmtLDAPAuthBackendFilterMissingPlaceholder = "authentication_backend: ldap: option " +
|
||||
"'%s' must contain the placeholder '{%s}' but it is required"
|
||||
"'%s' must contain the placeholder '{%s}' but it's absent"
|
||||
)
|
||||
|
||||
// TOTP Error constants.
|
||||
const (
|
||||
errFmtTOTPInvalidAlgorithm = "totp: option 'algorithm' must be one of '%s' but it is configured as '%s'"
|
||||
errFmtTOTPInvalidPeriod = "totp: option 'period' option must be 15 or more but it is configured as '%d'"
|
||||
errFmtTOTPInvalidDigits = "totp: option 'digits' must be 6 or 8 but it is configured as '%d'"
|
||||
errFmtTOTPInvalidSecretSize = "totp: option 'secret_size' must be %d or higher but it is configured as '%d'" //nolint:gosec
|
||||
errFmtTOTPInvalidAlgorithm = "totp: option 'algorithm' must be one of %s but it's configured as '%s'"
|
||||
errFmtTOTPInvalidPeriod = "totp: option 'period' option must be 15 or more but it's configured as '%d'"
|
||||
errFmtTOTPInvalidDigits = "totp: option 'digits' must be 6 or 8 but it's configured as '%d'"
|
||||
errFmtTOTPInvalidSecretSize = "totp: option 'secret_size' must be %d or higher but it's configured as '%d'" //nolint:gosec
|
||||
)
|
||||
|
||||
// Storage Error constants.
|
||||
|
@ -128,14 +128,14 @@ const (
|
|||
errFmtStorageUserPassMustBeProvided = "storage: %s: option 'username' and 'password' are required" //nolint:gosec
|
||||
errFmtStorageOptionMustBeProvided = "storage: %s: option '%s' is required"
|
||||
errFmtStorageTLSConfigInvalid = "storage: %s: tls: %w"
|
||||
errFmtStoragePostgreSQLInvalidSSLMode = "storage: postgres: ssl: option 'mode' must be one of '%s' but it is configured as '%s'"
|
||||
errFmtStoragePostgreSQLInvalidSSLMode = "storage: postgres: ssl: option 'mode' must be one of %s but it's configured as '%s'"
|
||||
errFmtStoragePostgreSQLInvalidSSLAndTLSConfig = "storage: postgres: can't define both 'tls' and 'ssl' configuration options"
|
||||
warnFmtStoragePostgreSQLInvalidSSLDeprecated = "storage: postgres: ssl: the ssl configuration options are deprecated and we recommend the tls options instead"
|
||||
)
|
||||
|
||||
// Telemetry Error constants.
|
||||
const (
|
||||
errFmtTelemetryMetricsScheme = "telemetry: metrics: option 'address' must have a scheme 'tcp://' but it is configured as '%s'"
|
||||
errFmtTelemetryMetricsScheme = "telemetry: metrics: option 'address' must have a scheme 'tcp://' but it's configured as '%s'"
|
||||
)
|
||||
|
||||
// OpenID Error constants.
|
||||
|
@ -148,17 +148,16 @@ const (
|
|||
errFmtOIDCCertificateMismatch = "identity_providers: oidc: option 'issuer_private_key' does not appear to be the private key the certificate provided by option 'issuer_certificate_chain'"
|
||||
errFmtOIDCCertificateChain = "identity_providers: oidc: option 'issuer_certificate_chain' produced an error during validation of the chain: %w"
|
||||
errFmtOIDCEnforcePKCEInvalidValue = "identity_providers: oidc: option 'enforce_pkce' must be 'never', " +
|
||||
"'public_clients_only' or 'always', but it is configured as '%s'"
|
||||
"'public_clients_only' or 'always', but it's configured as '%s'"
|
||||
|
||||
errFmtOIDCCORSInvalidOrigin = "identity_providers: oidc: cors: option 'allowed_origins' contains an invalid value '%s' as it has a %s: origins must only be scheme, hostname, and an optional port"
|
||||
errFmtOIDCCORSInvalidOriginWildcard = "identity_providers: oidc: cors: option 'allowed_origins' contains the wildcard origin '*' with more than one origin but the wildcard origin must be defined by itself"
|
||||
errFmtOIDCCORSInvalidOriginWildcardWithClients = "identity_providers: oidc: cors: option 'allowed_origins' contains the wildcard origin '*' cannot be specified with option 'allowed_origins_from_client_redirect_uris' enabled"
|
||||
errFmtOIDCCORSInvalidEndpoint = "identity_providers: oidc: cors: option 'endpoints' contains an invalid value '%s': must be one of '%s'"
|
||||
errFmtOIDCCORSInvalidEndpoint = "identity_providers: oidc: cors: option 'endpoints' contains an invalid value '%s': must be one of %s"
|
||||
|
||||
errFmtOIDCClientsDuplicateID = "identity_providers: oidc: one or more clients have the same id but all client" +
|
||||
"id's must be unique"
|
||||
errFmtOIDCClientsWithEmptyID = "identity_providers: oidc: one or more clients have been configured with " +
|
||||
"an empty id"
|
||||
errFmtOIDCClientsDuplicateID = "identity_providers: oidc: clients: option 'id' must be unique for every client but one or more clients share the following 'id' values %s"
|
||||
errFmtOIDCClientsWithEmptyID = "identity_providers: oidc: clients: option 'id' is required but was absent on the clients in positions %s"
|
||||
errFmtOIDCClientsDeprecated = "identity_providers: oidc: clients: warnings for clients above indicate deprecated functionality and it's strongly suggested these issues are checked and fixed if they're legitimate issues or reported if they are not as in a future version these warnings will become errors"
|
||||
|
||||
errFmtOIDCClientInvalidSecret = "identity_providers: oidc: client '%s': option 'secret' is required"
|
||||
errFmtOIDCClientInvalidSecretPlainText = "identity_providers: oidc: client '%s': option 'secret' is plaintext but it should be a hashed value as plaintext values are deprecated and will be removed when oidc becomes stable"
|
||||
|
@ -170,36 +169,43 @@ const (
|
|||
"redirect uri '%s' when option 'public' is false but this is invalid as this uri is not valid " +
|
||||
"for the openid connect confidential client type"
|
||||
errFmtOIDCClientRedirectURIAbsolute = "identity_providers: oidc: client '%s': option 'redirect_uris' has an " +
|
||||
"invalid value: redirect uri '%s' must have the scheme but it is absent"
|
||||
errFmtOIDCClientInvalidPolicy = "identity_providers: oidc: client '%s': option 'policy' must be 'one_factor' " +
|
||||
"or 'two_factor' but it is configured as '%s'"
|
||||
errFmtOIDCClientInvalidPKCEChallengeMethod = "identity_providers: oidc: client '%s': option 'pkce_challenge_method' must be 'plain' " +
|
||||
"or 'S256' but it is configured as '%s'"
|
||||
"invalid value: redirect uri '%s' must have a scheme but it's absent"
|
||||
errFmtOIDCClientInvalidConsentMode = "identity_providers: oidc: client '%s': consent: option 'mode' must be one of " +
|
||||
"'%s' but it is configured as '%s'"
|
||||
errFmtOIDCClientInvalidEntry = "identity_providers: oidc: client '%s': option '%s' must only have the values " +
|
||||
"'%s' but one option is configured as '%s'"
|
||||
errFmtOIDCClientInvalidUserinfoAlgorithm = "identity_providers: oidc: client '%s': option " +
|
||||
"'userinfo_signing_algorithm' must be one of '%s' but it is configured as '%s'"
|
||||
"%s but it's configured as '%s'"
|
||||
errFmtOIDCClientInvalidEntries = "identity_providers: oidc: client '%s': option '%s' must only have the values " +
|
||||
"%s but the values %s are present"
|
||||
errFmtOIDCClientInvalidEntryDuplicates = "identity_providers: oidc: client '%s': option '%s' must have unique values but the values %s are duplicated"
|
||||
errFmtOIDCClientInvalidValue = "identity_providers: oidc: client '%s': option " +
|
||||
"'%s' must be one of %s but it's configured as '%s'"
|
||||
errFmtOIDCClientInvalidTokenEndpointAuthMethod = "identity_providers: oidc: client '%s': option " +
|
||||
"'token_endpoint_auth_method' must be one of %s when configured as the confidential client type unless it only includes implicit flow response types such as %s but it's configured as '%s'"
|
||||
errFmtOIDCClientInvalidTokenEndpointAuthMethodPublic = "identity_providers: oidc: client '%s': option " +
|
||||
"'token_endpoint_auth_method' must be 'none' when configured as the public client type but it's configured as '%s'"
|
||||
errFmtOIDCClientInvalidSectorIdentifier = "identity_providers: oidc: client '%s': option " +
|
||||
"'sector_identifier' with value '%s': must be a URL with only the host component for example '%s' but it has a %s with the value '%s'"
|
||||
errFmtOIDCClientInvalidSectorIdentifierWithoutValue = "identity_providers: oidc: client '%s': option " +
|
||||
"'sector_identifier' with value '%s': must be a URL with only the host component for example '%s' but it has a %s"
|
||||
errFmtOIDCClientInvalidSectorIdentifierHost = "identity_providers: oidc: client '%s': option " +
|
||||
"'sector_identifier' with value '%s': must be a URL with only the host component but appears to be invalid"
|
||||
errFmtOIDCClientInvalidGrantTypeMatch = "identity_providers: oidc: client '%s': option " +
|
||||
"'grant_types' should only have grant type values which are valid with the configured 'response_types' for the client but '%s' expects a response type %s such as %s but the response types are %s"
|
||||
errFmtOIDCClientInvalidGrantTypeRefresh = "identity_providers: oidc: client '%s': option " +
|
||||
"'grant_types' should only have the 'refresh_token' value if the client is also configured with the 'offline_access' scope"
|
||||
errFmtOIDCClientInvalidRefreshTokenOptionWithoutCodeResponseType = "identity_providers: oidc: client '%s': option " +
|
||||
"'%s' should only have the values %s if the client is also configured with a 'response_type' such as %s which respond with authorization codes"
|
||||
errFmtOIDCServerInsecureParameterEntropy = "openid connect provider: SECURITY ISSUE - minimum parameter entropy is " +
|
||||
"configured to an unsafe value, it should be above 8 but it's configured to %d"
|
||||
)
|
||||
|
||||
// Webauthn Error constants.
|
||||
const (
|
||||
errFmtWebauthnConveyancePreference = "webauthn: option 'attestation_conveyance_preference' must be one of '%s' but it is configured as '%s'"
|
||||
errFmtWebauthnUserVerification = "webauthn: option 'user_verification' must be one of 'discouraged', 'preferred', 'required' but it is configured as '%s'"
|
||||
errFmtWebauthnConveyancePreference = "webauthn: option 'attestation_conveyance_preference' must be one of %s but it's configured as '%s'"
|
||||
errFmtWebauthnUserVerification = "webauthn: option 'user_verification' must be one of %s but it's configured as '%s'"
|
||||
)
|
||||
|
||||
// Access Control error constants.
|
||||
const (
|
||||
errFmtAccessControlDefaultPolicyValue = "access control: option 'default_policy' must be one of '%s' but it is " +
|
||||
errFmtAccessControlDefaultPolicyValue = "access control: option 'default_policy' must be one of %s but it's " +
|
||||
"configured as '%s'"
|
||||
errFmtAccessControlDefaultPolicyWithoutRules = "access control: 'default_policy' option '%s' is invalid: when " +
|
||||
"no rules are specified it must be 'two_factor' or 'one_factor'"
|
||||
|
@ -207,10 +213,9 @@ const (
|
|||
"network '%s' is not a valid IP or CIDR notation"
|
||||
errFmtAccessControlWarnNoRulesDefaultPolicy = "access control: no rules have been specified so the " +
|
||||
"'default_policy' of '%s' is going to be applied to all requests"
|
||||
errFmtAccessControlRuleNoDomains = "access control: rule %s: rule is invalid: must have the option " +
|
||||
"'domain' or 'domain_regex' configured"
|
||||
errFmtAccessControlRuleInvalidPolicy = "access control: rule %s: rule 'policy' option '%s' " +
|
||||
"is invalid: must be one of 'deny', 'two_factor', 'one_factor' or 'bypass'"
|
||||
errFmtAccessControlRuleNoDomains = "access control: rule %s: option 'domain' or 'domain_regex' must be present but are both absent"
|
||||
errFmtAccessControlRuleNoPolicy = "access control: rule %s: option 'policy' must be present but it's absent"
|
||||
errFmtAccessControlRuleInvalidPolicy = "access control: rule %s: option 'policy' must be one of %s but it's configured as '%s'"
|
||||
errAccessControlRuleBypassPolicyInvalidWithSubjects = "access control: rule %s: 'policy' option 'bypass' is " +
|
||||
"not supported when 'subject' option is configured: see " +
|
||||
"https://www.authelia.com/c/acl#bypass"
|
||||
|
@ -221,39 +226,35 @@ const (
|
|||
"valid Group Name, IP, or CIDR notation"
|
||||
errFmtAccessControlRuleSubjectInvalid = "access control: rule %s: 'subject' option '%s' is " +
|
||||
"invalid: must start with 'user:' or 'group:'"
|
||||
errFmtAccessControlRuleMethodInvalid = "access control: rule %s: 'methods' option '%s' is " +
|
||||
"invalid: must be one of '%s'"
|
||||
errFmtAccessControlRuleQueryInvalid = "access control: rule %s: 'query' option 'operator' with value '%s' is " +
|
||||
"invalid: must be one of '%s'"
|
||||
errFmtAccessControlRuleQueryInvalidNoValue = "access control: rule %s: 'query' option '%s' is " +
|
||||
"invalid: must have a value"
|
||||
errFmtAccessControlRuleQueryInvalidNoValueOperator = "access control: rule %s: 'query' option '%s' is " +
|
||||
"invalid: must have a value when the operator is '%s'"
|
||||
errFmtAccessControlRuleQueryInvalidValue = "access control: rule %s: 'query' option '%s' is " +
|
||||
"invalid: must not have a value when the operator is '%s'"
|
||||
errFmtAccessControlRuleQueryInvalidValueParse = "access control: rule %s: 'query' option '%s' is " +
|
||||
errFmtAccessControlRuleInvalidEntries = "access control: rule %s: option '%s' must only have the values %s but the values %s are present"
|
||||
errFmtAccessControlRuleInvalidDuplicates = "access control: rule %s: option '%s' must have unique values but the values %s are duplicated"
|
||||
errFmtAccessControlRuleQueryInvalid = "access control: rule %s: query: option 'operator' must be one of %s but it's configured as '%s'"
|
||||
errFmtAccessControlRuleQueryInvalidNoValue = "access control: rule %s: query: option '%s' is required but it's absent"
|
||||
errFmtAccessControlRuleQueryInvalidNoValueOperator = "access control: rule %s: query: option '%s' must be present when the option 'operator' is '%s' but it's absent"
|
||||
errFmtAccessControlRuleQueryInvalidValue = "access control: rule %s: query: option '%s' must not be present when the option 'operator' is '%s' but it's present"
|
||||
errFmtAccessControlRuleQueryInvalidValueParse = "access control: rule %s: query: option '%s' is " +
|
||||
"invalid: %w"
|
||||
errFmtAccessControlRuleQueryInvalidValueType = "access control: rule %s: 'query' option 'value' is " +
|
||||
errFmtAccessControlRuleQueryInvalidValueType = "access control: rule %s: query: option 'value' is " +
|
||||
"invalid: expected type was string but got %T"
|
||||
)
|
||||
|
||||
// Theme Error constants.
|
||||
const (
|
||||
errFmtThemeName = "option 'theme' must be one of '%s' but it is configured as '%s'"
|
||||
errFmtThemeName = "option 'theme' must be one of %s but it's configured as '%s'"
|
||||
)
|
||||
|
||||
// NTP Error constants.
|
||||
const (
|
||||
errFmtNTPVersion = "ntp: option 'version' must be either 3 or 4 but it is configured as '%d'"
|
||||
errFmtNTPVersion = "ntp: option 'version' must be either 3 or 4 but it's configured as '%d'"
|
||||
)
|
||||
|
||||
// Session error constants.
|
||||
const (
|
||||
errFmtSessionOptionRequired = "session: option '%s' is required"
|
||||
errFmtSessionLegacyAndWarning = "session: option 'domain' and option 'cookies' can't be specified at the same time"
|
||||
errFmtSessionSameSite = "session: option 'same_site' must be one of '%s' but is configured as '%s'"
|
||||
errFmtSessionSameSite = "session: option 'same_site' must be one of %s but it's configured as '%s'"
|
||||
errFmtSessionSecretRequired = "session: option 'secret' is required when using the '%s' provider"
|
||||
errFmtSessionRedisPortRange = "session: redis: option 'port' must be between 1 and 65535 but is configured as '%d'"
|
||||
errFmtSessionRedisPortRange = "session: redis: option 'port' must be between 1 and 65535 but it's configured as '%d'"
|
||||
errFmtSessionRedisHostRequired = "session: redis: option 'host' is required"
|
||||
errFmtSessionRedisHostOrNodesRequired = "session: redis: option 'host' or the 'high_availability' option 'nodes' is required"
|
||||
errFmtSessionRedisTLSConfigInvalid = "session: redis: tls: %w"
|
||||
|
@ -261,8 +262,8 @@ const (
|
|||
errFmtSessionRedisSentinelMissingName = "session: redis: high_availability: option 'sentinel_name' is required"
|
||||
errFmtSessionRedisSentinelNodeHostMissing = "session: redis: high_availability: option 'nodes': option 'host' is required for each node but one or more nodes are missing this"
|
||||
|
||||
errFmtSessionDomainMustBeRoot = "session: domain config %s: option 'domain' must be the domain you wish to protect not a wildcard domain but it is configured as '%s'"
|
||||
errFmtSessionDomainSameSite = "session: domain config %s: option 'same_site' must be one of '%s' but is configured as '%s'"
|
||||
errFmtSessionDomainMustBeRoot = "session: domain config %s: option 'domain' must be the domain you wish to protect not a wildcard domain but it's configured as '%s'"
|
||||
errFmtSessionDomainSameSite = "session: domain config %s: option 'same_site' must be one of %s but it's configured as '%s'"
|
||||
errFmtSessionDomainRequired = "session: domain config %s: option 'domain' is required"
|
||||
errFmtSessionDomainHasPeriodPrefix = "session: domain config %s: option 'domain' has a prefix of '.' which is not supported or intended behaviour: you can use this at your own risk but we recommend removing it"
|
||||
errFmtSessionDomainDuplicate = "session: domain config %s: option 'domain' is a duplicate value for another configured session domain"
|
||||
|
@ -291,8 +292,8 @@ const (
|
|||
errFmtServerPathNoForwardSlashes = "server: option 'path' must not contain any forward slashes"
|
||||
errFmtServerPathAlphaNum = "server: option 'path' must only contain alpha numeric characters"
|
||||
|
||||
errFmtServerEndpointsAuthzImplementation = "server: endpoints: authz: %s: option 'implementation' must be one of '%s' but is configured as '%s'"
|
||||
errFmtServerEndpointsAuthzStrategy = "server: endpoints: authz: %s: authn_strategies: option 'name' must be one of '%s' but is configured as '%s'"
|
||||
errFmtServerEndpointsAuthzImplementation = "server: endpoints: authz: %s: option 'implementation' must be one of %s but it's configured as '%s'"
|
||||
errFmtServerEndpointsAuthzStrategy = "server: endpoints: authz: %s: authn_strategies: option 'name' must be one of %s but it's configured as '%s'"
|
||||
errFmtServerEndpointsAuthzStrategyDuplicate = "server: endpoints: authz: %s: authn_strategies: duplicate strategy name detected with name '%s'"
|
||||
errFmtServerEndpointsAuthzPrefixDuplicate = "server: endpoints: authz: %s: endpoint starts with the same prefix as the '%s' endpoint with the '%s' implementation which accepts prefixes as part of its implementation"
|
||||
errFmtServerEndpointsAuthzInvalidName = "server: endpoints: authz: %s: contains invalid characters"
|
||||
|
@ -302,7 +303,7 @@ const (
|
|||
|
||||
const (
|
||||
errPasswordPolicyMultipleDefined = "password_policy: only a single password policy mechanism can be specified"
|
||||
errFmtPasswordPolicyStandardMinLengthNotGreaterThanZero = "password_policy: standard: option 'min_length' must be greater than 0 but is configured as %d"
|
||||
errFmtPasswordPolicyStandardMinLengthNotGreaterThanZero = "password_policy: standard: option 'min_length' must be greater than 0 but it's configured as %d"
|
||||
errFmtPasswordPolicyZXCVBNMinScoreInvalid = "password_policy: zxcvbn: option 'min_score' is invalid: must be between 1 and 4 but it's configured as %d"
|
||||
)
|
||||
|
||||
|
@ -312,19 +313,17 @@ const (
|
|||
)
|
||||
|
||||
const (
|
||||
errFmtDuoMissingOption = "duo_api: option '%s' is required when duo is enabled but it is missing"
|
||||
errFmtDuoMissingOption = "duo_api: option '%s' is required when duo is enabled but it's absent"
|
||||
)
|
||||
|
||||
// Error constants.
|
||||
const (
|
||||
errFmtInvalidDefault2FAMethod = "option 'default_2fa_method' is configured as '%s' but must be one of " +
|
||||
"the following values: '%s'"
|
||||
errFmtInvalidDefault2FAMethodDisabled = "option 'default_2fa_method' is configured as '%s' " +
|
||||
"but must be one of the following enabled method values: '%s'"
|
||||
errFmtInvalidDefault2FAMethod = "option 'default_2fa_method' must be one of %s but it's configured as '%s'"
|
||||
errFmtInvalidDefault2FAMethodDisabled = "option 'default_2fa_method' must be one of the enabled options %s but it's configured as '%s'"
|
||||
|
||||
errFmtReplacedConfigurationKey = "invalid configuration key '%s' was replaced by '%s'"
|
||||
|
||||
errFmtLoggingLevelInvalid = "log: option 'level' must be one of '%s' but it is configured as '%s'"
|
||||
errFmtLoggingLevelInvalid = "log: option 'level' must be one of %s but it's configured as '%s'"
|
||||
|
||||
errFileHashing = "config key incorrect: authentication_backend.file.hashing should be authentication_backend.file.password"
|
||||
errFilePHashing = "config key incorrect: authentication_backend.file.password_hashing should be authentication_backend.file.password"
|
||||
|
@ -357,6 +356,10 @@ const (
|
|||
authzImplementationExtAuthz = "ExtAuthz"
|
||||
)
|
||||
|
||||
const (
|
||||
auto = "auto"
|
||||
)
|
||||
|
||||
var (
|
||||
validAuthzImplementations = []string{"AuthRequest", "ForwardAuth", authzImplementationExtAuthz, authzImplementationLegacy}
|
||||
validAuthzAuthnStrategies = []string{"CookieSession", "HeaderAuthorization", "HeaderProxyAuthorization", "HeaderAuthRequestProxyAuthorization", "HeaderLegacy"}
|
||||
|
@ -372,7 +375,7 @@ var (
|
|||
|
||||
var (
|
||||
validStoragePostgreSQLSSLModes = []string{"disable", "require", "verify-ca", "verify-full"}
|
||||
validThemeNames = []string{"light", "dark", "grey", "auto"}
|
||||
validThemeNames = []string{"light", "dark", "grey", auto}
|
||||
validSessionSameSiteValues = []string{"none", "lax", "strict"}
|
||||
validLogLevels = []string{"trace", "debug", "info", "warn", "error"}
|
||||
validWebauthnConveyancePreferences = []string{string(protocol.PreferNoAttestation), string(protocol.PreferIndirectAttestation), string(protocol.PreferDirectAttestation)}
|
||||
|
@ -389,19 +392,38 @@ var (
|
|||
|
||||
var validDefault2FAMethods = []string{"totp", "webauthn", "mobile_push"}
|
||||
|
||||
const (
|
||||
attrOIDCScopes = "scopes"
|
||||
attrOIDCResponseTypes = "response_types"
|
||||
attrOIDCResponseModes = "response_modes"
|
||||
attrOIDCGrantTypes = "grant_types"
|
||||
attrOIDCRedirectURIs = "redirect_uris"
|
||||
attrOIDCTokenAuthMethod = "token_endpoint_auth_method"
|
||||
attrOIDCUsrSigAlg = "userinfo_signing_algorithm"
|
||||
attrOIDCPKCEChallengeMethod = "pkce_challenge_method"
|
||||
)
|
||||
|
||||
var (
|
||||
validOIDCScopes = []string{oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeProfile, oidc.ScopeGroups, oidc.ScopeOfflineAccess}
|
||||
validOIDCGrantTypes = []string{oidc.GrantTypeImplicit, oidc.GrantTypeRefreshToken, oidc.GrantTypeAuthorizationCode, oidc.GrantTypePassword, oidc.GrantTypeClientCredentials}
|
||||
validOIDCResponseModes = []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment}
|
||||
validOIDCUserinfoAlgorithms = []string{oidc.SigningAlgorithmNone, oidc.SigningAlgorithmRSAWithSHA256}
|
||||
validOIDCCORSEndpoints = []string{oidc.EndpointAuthorization, oidc.EndpointPushedAuthorizationRequest, oidc.EndpointToken, oidc.EndpointIntrospection, oidc.EndpointRevocation, oidc.EndpointUserinfo}
|
||||
validOIDCClientConsentModes = []string{"auto", oidc.ClientConsentModeImplicit.String(), oidc.ClientConsentModeExplicit.String(), oidc.ClientConsentModePreConfigured.String()}
|
||||
validOIDCCORSEndpoints = []string{oidc.EndpointAuthorization, oidc.EndpointPushedAuthorizationRequest, oidc.EndpointToken, oidc.EndpointIntrospection, oidc.EndpointRevocation, oidc.EndpointUserinfo}
|
||||
|
||||
validOIDCClientScopes = []string{oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeProfile, oidc.ScopeGroups, oidc.ScopeOfflineAccess}
|
||||
validOIDCClientUserinfoAlgorithms = []string{oidc.SigningAlgorithmNone, oidc.SigningAlgorithmRSAWithSHA256}
|
||||
validOIDCClientConsentModes = []string{auto, oidc.ClientConsentModeImplicit.String(), oidc.ClientConsentModeExplicit.String(), oidc.ClientConsentModePreConfigured.String()}
|
||||
validOIDCClientResponseModes = []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment}
|
||||
validOIDCClientResponseTypes = []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowIDToken, oidc.ResponseTypeImplicitFlowToken, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeHybridFlowIDToken, oidc.ResponseTypeHybridFlowToken, oidc.ResponseTypeHybridFlowBoth}
|
||||
validOIDCClientResponseTypesImplicitFlow = []string{oidc.ResponseTypeImplicitFlowIDToken, oidc.ResponseTypeImplicitFlowToken, oidc.ResponseTypeImplicitFlowBoth}
|
||||
validOIDCClientResponseTypesHybridFlow = []string{oidc.ResponseTypeHybridFlowIDToken, oidc.ResponseTypeHybridFlowToken, oidc.ResponseTypeHybridFlowBoth}
|
||||
validOIDCClientResponseTypesRefreshToken = []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeHybridFlowIDToken, oidc.ResponseTypeHybridFlowToken, oidc.ResponseTypeHybridFlowBoth}
|
||||
validOIDCClientGrantTypes = []string{oidc.GrantTypeImplicit, oidc.GrantTypeRefreshToken, oidc.GrantTypeAuthorizationCode}
|
||||
|
||||
validOIDCClientTokenEndpointAuthMethods = []string{oidc.ClientAuthMethodNone, oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic}
|
||||
validOIDCClientTokenEndpointAuthMethodsConfidential = []string{oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic}
|
||||
)
|
||||
|
||||
var (
|
||||
reKeyReplacer = regexp.MustCompile(`\[\d+]`)
|
||||
reDomainCharacters = regexp.MustCompile(`^[a-z0-9-]+(\.[a-z0-9-]+)+[a-z0-9]$`)
|
||||
reAuthzEndpointName = regexp.MustCompile(`^[a-zA-Z](([a-zA-Z0-9/\._-]*)([a-zA-Z]))?$`)
|
||||
reAuthzEndpointName = regexp.MustCompile(`^[a-zA-Z](([a-zA-Z0-9/._-]*)([a-zA-Z]))?$`)
|
||||
)
|
||||
|
||||
var replacedKeys = map[string]string{
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -100,7 +100,7 @@ func NewKeyMapPattern(key string) (pattern *regexp.Regexp, err error) {
|
|||
}
|
||||
|
||||
if i < n {
|
||||
buf.WriteString("\\.[a-z0-9]([a-z0-9-_]+)?[a-z0-9]")
|
||||
buf.WriteString("\\.[a-z0-9](([a-z0-9-_]+)?[a-z0-9])?")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -101,6 +101,20 @@ func TestSpecificErrorKeys(t *testing.T) {
|
|||
assert.EqualError(t, errs[4], specificErrorKeys["authentication_backend.file.hashing.algorithm"])
|
||||
}
|
||||
|
||||
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",
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'")
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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'")
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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'"},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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'",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'")
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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())))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -143,7 +143,7 @@ type PasswordPolicyBody struct {
|
|||
}
|
||||
|
||||
type handlerAuthorizationConsent func(
|
||||
ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client,
|
||||
ctx *middlewares.AutheliaCtx, issuer *url.URL, client oidc.Client,
|
||||
userSession session.UserSession, subject uuid.UUID,
|
||||
rw http.ResponseWriter, r *http.Request,
|
||||
requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package oidc
|
||||
|
||||
const (
|
||||
myclient = "myclient"
|
||||
myclientdesc = "My Client"
|
||||
onefactor = "one_factor"
|
||||
twofactor = "two_factor"
|
||||
examplecom = "https://example.com"
|
||||
examplecomsid = "example.com"
|
||||
badsecret = "$plaintext$a_bad_secret"
|
||||
badhmac = "asbdhaaskmdlkamdklasmdlkams"
|
||||
)
|
|
@ -5,70 +5,76 @@ import (
|
|||
)
|
||||
|
||||
// NewOpenIDConnectWellKnownConfiguration generates a new OpenIDConnectWellKnownConfiguration.
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -27,15 +27,15 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing
|
|||
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
||||
EnablePKCEPlainChallenge: true,
|
||||
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
||||
HMACSecret: badhmac,
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||
{
|
||||
ID: "a-client",
|
||||
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
|
||||
SectorIdentifier: url.URL{Host: "google.com"},
|
||||
Policy: "one_factor",
|
||||
ID: myclient,
|
||||
Secret: MustDecodeSecret(badsecret),
|
||||
SectorIdentifier: url.URL{Host: examplecomsid},
|
||||
Policy: onefactor,
|
||||
RedirectURIs: []string{
|
||||
"https://google.com",
|
||||
examplecom,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -43,7 +43,7 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing
|
|||
|
||||
assert.NoError(t, err)
|
||||
|
||||
disco := provider.GetOpenIDConnectWellKnownConfiguration("https://example.com")
|
||||
disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom)
|
||||
|
||||
assert.Len(t, disco.SubjectTypesSupported, 2)
|
||||
assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic)
|
||||
|
@ -58,12 +58,12 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *tes
|
|||
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
||||
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
||||
HMACSecret: badhmac,
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||
{
|
||||
ID: "a-client",
|
||||
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
|
||||
Policy: "one_factor",
|
||||
Policy: onefactor,
|
||||
RedirectURIs: []string{
|
||||
"https://google.com",
|
||||
},
|
||||
|
@ -72,7 +72,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *tes
|
|||
ID: "b-client",
|
||||
Description: "Normal Description",
|
||||
Secret: MustDecodeSecret("$plaintext$b-client-secret"),
|
||||
Policy: "two_factor",
|
||||
Policy: twofactor,
|
||||
RedirectURIs: []string{
|
||||
"https://google.com",
|
||||
},
|
||||
|
@ -103,7 +103,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
|
|||
{
|
||||
ID: "a-client",
|
||||
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
|
||||
Policy: "one_factor",
|
||||
Policy: onefactor,
|
||||
RedirectURIs: []string{
|
||||
"https://google.com",
|
||||
},
|
||||
|
@ -113,9 +113,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
|
|||
|
||||
assert.NoError(t, err)
|
||||
|
||||
disco := provider.GetOpenIDConnectWellKnownConfiguration("https://example.com")
|
||||
disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom)
|
||||
|
||||
assert.Equal(t, "https://example.com", disco.Issuer)
|
||||
assert.Equal(t, examplecom, disco.Issuer)
|
||||
assert.Equal(t, "https://example.com/jwks.json", disco.JWKSURI)
|
||||
assert.Equal(t, "https://example.com/api/oidc/authorization", disco.AuthorizationEndpoint)
|
||||
assert.Equal(t, "https://example.com/api/oidc/token", disco.TokenEndpoint)
|
||||
|
@ -139,8 +139,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
|
|||
assert.Contains(t, disco.ResponseModesSupported, ResponseModeQuery)
|
||||
assert.Contains(t, disco.ResponseModesSupported, ResponseModeFragment)
|
||||
|
||||
assert.Len(t, disco.SubjectTypesSupported, 1)
|
||||
assert.Len(t, disco.SubjectTypesSupported, 2)
|
||||
assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic)
|
||||
assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePairwise)
|
||||
|
||||
assert.Len(t, disco.ResponseTypesSupported, 7)
|
||||
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeAuthorizationCodeFlow)
|
||||
|
@ -151,10 +152,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
|
|||
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowToken)
|
||||
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowBoth)
|
||||
|
||||
assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 4)
|
||||
assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 3)
|
||||
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic)
|
||||
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost)
|
||||
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT)
|
||||
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone)
|
||||
|
||||
assert.Len(t, disco.GrantTypesSupported, 3)
|
||||
|
@ -169,9 +169,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
|
|||
assert.Contains(t, disco.UserinfoSigningAlgValuesSupported, SigningAlgorithmRSAWithSHA256)
|
||||
assert.Contains(t, disco.UserinfoSigningAlgValuesSupported, SigningAlgorithmNone)
|
||||
|
||||
assert.Len(t, disco.RequestObjectSigningAlgValuesSupported, 2)
|
||||
assert.Contains(t, disco.RequestObjectSigningAlgValuesSupported, SigningAlgorithmRSAWithSHA256)
|
||||
assert.Contains(t, disco.RequestObjectSigningAlgValuesSupported, SigningAlgorithmNone)
|
||||
assert.Len(t, disco.RequestObjectSigningAlgValuesSupported, 0)
|
||||
|
||||
assert.Len(t, disco.ClaimsSupported, 18)
|
||||
assert.Contains(t, disco.ClaimsSupported, ClaimAuthenticationMethodsReference)
|
||||
|
@ -203,7 +201,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
|
|||
{
|
||||
ID: "a-client",
|
||||
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
|
||||
Policy: "one_factor",
|
||||
Policy: onefactor,
|
||||
RedirectURIs: []string{
|
||||
"https://google.com",
|
||||
},
|
||||
|
@ -213,9 +211,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
|
|||
|
||||
assert.NoError(t, err)
|
||||
|
||||
disco := provider.GetOAuth2WellKnownConfiguration("https://example.com")
|
||||
disco := provider.GetOAuth2WellKnownConfiguration(examplecom)
|
||||
|
||||
assert.Equal(t, "https://example.com", disco.Issuer)
|
||||
assert.Equal(t, examplecom, disco.Issuer)
|
||||
assert.Equal(t, "https://example.com/jwks.json", disco.JWKSURI)
|
||||
assert.Equal(t, "https://example.com/api/oidc/authorization", disco.AuthorizationEndpoint)
|
||||
assert.Equal(t, "https://example.com/api/oidc/token", disco.TokenEndpoint)
|
||||
|
@ -238,8 +236,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
|
|||
assert.Contains(t, disco.ResponseModesSupported, ResponseModeQuery)
|
||||
assert.Contains(t, disco.ResponseModesSupported, ResponseModeFragment)
|
||||
|
||||
assert.Len(t, disco.SubjectTypesSupported, 1)
|
||||
assert.Len(t, disco.SubjectTypesSupported, 2)
|
||||
assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic)
|
||||
assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePairwise)
|
||||
|
||||
assert.Len(t, disco.ResponseTypesSupported, 7)
|
||||
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeAuthorizationCodeFlow)
|
||||
|
@ -250,10 +249,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
|
|||
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowToken)
|
||||
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowBoth)
|
||||
|
||||
assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 4)
|
||||
assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 3)
|
||||
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic)
|
||||
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost)
|
||||
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT)
|
||||
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone)
|
||||
|
||||
assert.Len(t, disco.GrantTypesSupported, 3)
|
||||
|
@ -292,7 +290,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
|
|||
{
|
||||
ID: "a-client",
|
||||
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
|
||||
Policy: "one_factor",
|
||||
Policy: onefactor,
|
||||
RedirectURIs: []string{
|
||||
"https://google.com",
|
||||
},
|
||||
|
@ -302,7 +300,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
|
|||
|
||||
assert.NoError(t, err)
|
||||
|
||||
disco := provider.GetOpenIDConnectWellKnownConfiguration("https://example.com")
|
||||
disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom)
|
||||
|
||||
require.Len(t, disco.CodeChallengeMethodsSupported, 2)
|
||||
assert.Equal(t, PKCEChallengeMethodSHA256, disco.CodeChallengeMethodsSupported[0])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue