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
James Elliott 2023-04-13 20:58:18 +10:00 committed by GitHub
parent db130dad48
commit 3d2da0b070
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 3007 additions and 971 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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",

View File

@ -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",

View File

@ -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 == "" {

View File

@ -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) {

View File

@ -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{}

View File

@ -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() {

View File

@ -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))
}
}

View File

@ -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'",
},
},
}

View File

@ -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{

View File

@ -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",
},
},
}

View File

@ -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

View File

@ -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])?")
}
}

View File

@ -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",

View File

@ -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))
}
}

View File

@ -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'")
}

View File

@ -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.
*/

View File

@ -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'")
}

View File

@ -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",
},
},
{

View File

@ -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))
}
}
}

View File

@ -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",

View File

@ -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))
}
}

View File

@ -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) {

View File

@ -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))
}
}
}

View File

@ -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() {

View File

@ -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'"},
},
}

View File

@ -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))
}
}

View File

@ -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) {

View File

@ -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))
}
}

View File

@ -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'",
},
},
}

View File

@ -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
}

View File

@ -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))
}
}

View File

@ -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'")
}

View File

@ -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()

View File

@ -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 {

View File

@ -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)

View File

@ -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)

View File

@ -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
}

View File

@ -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()

View File

@ -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())))
}
}

View File

@ -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

View File

@ -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)

View File

@ -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
}

View File

@ -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())

View File

@ -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)

View File

@ -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"

View File

@ -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"
)

View File

@ -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
}

View File

@ -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)
}

View File

@ -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

View File

@ -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])

View File

@ -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

View File

@ -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)

View File

@ -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.

View File

@ -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{

View File

@ -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,
}

View File

@ -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())
}