Merge remote-tracking branch 'origin/master' into feat-settings-ui
# Conflicts: # web/package.json # web/pnpm-lock.yamlfeat-settings-ui
commit
53d3cdb271
|
@ -2,7 +2,7 @@
|
||||||
title: "OpenID Connect 1.0"
|
title: "OpenID Connect 1.0"
|
||||||
description: ""
|
description: ""
|
||||||
lead: ""
|
lead: ""
|
||||||
date: 2023-05-08T13:38:08+10:00
|
date: 2023-05-15T10:32:10+10:00
|
||||||
lastmod: 2022-01-18T20:07:56+01:00
|
lastmod: 2022-01-18T20:07:56+01:00
|
||||||
draft: false
|
draft: false
|
||||||
images: []
|
images: []
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
title: "OpenID Connect 1.0 Clients"
|
title: "OpenID Connect 1.0 Clients"
|
||||||
description: "OpenID Connect 1.0 Registered Clients Configuration"
|
description: "OpenID Connect 1.0 Registered Clients Configuration"
|
||||||
lead: "Authelia can operate as an OpenID Connect 1.0 Provider. This section describes how to configure the registered clients."
|
lead: "Authelia can operate as an OpenID Connect 1.0 Provider. This section describes how to configure the registered clients."
|
||||||
date: 2023-05-08T13:38:08+10:00
|
date: 2023-05-15T10:32:10+10:00
|
||||||
draft: false
|
draft: false
|
||||||
images: []
|
images: []
|
||||||
menu:
|
menu:
|
||||||
|
@ -28,39 +28,41 @@ intended for production use it's used to provide context and an indentation exam
|
||||||
identity_providers:
|
identity_providers:
|
||||||
oidc:
|
oidc:
|
||||||
clients:
|
clients:
|
||||||
- id: myapp
|
- id: 'myapp'
|
||||||
description: My Application
|
description: 'My Application'
|
||||||
secret: '$pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng' # The digest of 'insecure_secret'.
|
secret: '$pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng' # The digest of 'insecure_secret'.
|
||||||
sector_identifier: ''
|
sector_identifier: ''
|
||||||
public: false
|
public: false
|
||||||
redirect_uris:
|
redirect_uris:
|
||||||
- https://oidc.example.com:8080/oauth2/callback
|
- 'https://oidc.example.com:8080/oauth2/callback'
|
||||||
audience: []
|
audience: []
|
||||||
scopes:
|
scopes:
|
||||||
- openid
|
- 'openid'
|
||||||
- groups
|
- 'groups'
|
||||||
- email
|
- 'email'
|
||||||
- profile
|
- 'profile'
|
||||||
grant_types:
|
grant_types:
|
||||||
- refresh_token
|
- 'refresh_token'
|
||||||
- authorization_code
|
- 'authorization_code'
|
||||||
response_types:
|
response_types:
|
||||||
- code
|
- 'code'
|
||||||
response_modes:
|
response_modes:
|
||||||
- form_post
|
- 'form_post'
|
||||||
- query
|
- 'query'
|
||||||
- fragment
|
- 'fragment'
|
||||||
authorization_policy: two_factor
|
authorization_policy: 'two_factor'
|
||||||
consent_mode: explicit
|
consent_mode: 'explicit'
|
||||||
pre_configured_consent_duration: 1w
|
pre_configured_consent_duration: '1 week'
|
||||||
enforce_par: false
|
enforce_par: false
|
||||||
enforce_pkce: false
|
enforce_pkce: false
|
||||||
pkce_challenge_method: S256
|
pkce_challenge_method: 'S256'
|
||||||
|
id_token_signing_alg: 'RS256'
|
||||||
|
id_token_signing_key_id: ''
|
||||||
|
userinfo_signing_alg: 'none'
|
||||||
|
userinfo_signing_key_id: ''
|
||||||
|
request_object_signing_alg: 'RS256'
|
||||||
|
token_endpoint_auth_signing_alg: 'RS256'
|
||||||
token_endpoint_auth_method: ''
|
token_endpoint_auth_method: ''
|
||||||
token_endpoint_auth_signing_alg: RS256
|
|
||||||
id_token_signing_alg: RS256
|
|
||||||
request_object_signing_alg: RS256
|
|
||||||
userinfo_signing_alg: none
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Options
|
## Options
|
||||||
|
@ -270,6 +272,65 @@ effectively enables the [enforce_pkce](#enforcepkce) option for this client.
|
||||||
Valid values are an empty string, `plain`, or `S256`. It should be noted that `S256` is strongly recommended if the
|
Valid values are an empty string, `plain`, or `S256`. It should be noted that `S256` is strongly recommended if the
|
||||||
relying party supports it.
|
relying party supports it.
|
||||||
|
|
||||||
|
### id_token_signing_alg
|
||||||
|
|
||||||
|
{{< confkey type="string" default="RS256" required="no" >}}
|
||||||
|
|
||||||
|
The algorithm used to sign the ID Tokens in the token responses.
|
||||||
|
|
||||||
|
See the response object section of the
|
||||||
|
[integration guide](../../../integration/openid-connect/introduction.md#response-object) for more information including
|
||||||
|
the algorithm column for supported values. In addition to the values listed we also support `none` as a value for this
|
||||||
|
endpoint.
|
||||||
|
|
||||||
|
The algorithm chosen must have a key configured in the [issuer_private_keys](provider.md#issuerprivatekeys) section to
|
||||||
|
be considered valid.
|
||||||
|
|
||||||
|
This option has no effect if the [id_token_signing_key_id](#idtokensigningkid) is specified as the algorithm is
|
||||||
|
automatically assumed by the configured key.
|
||||||
|
|
||||||
|
### id_token_signing_key_id
|
||||||
|
|
||||||
|
{{< confkey type="string" required="no" >}}
|
||||||
|
|
||||||
|
The key id of the JWK used to sign the ID Tokens in the token responses. This option takes precedence over
|
||||||
|
[id_token_signing_alg](#idtokensigningalg). The value of this must one of those provided or calculated in the
|
||||||
|
[issuer_private_keys](provider.md#issuerprivatekeys).
|
||||||
|
|
||||||
|
### userinfo_signing_alg
|
||||||
|
|
||||||
|
{{< confkey type="string" default="none" required="no" >}}
|
||||||
|
|
||||||
|
The algorithm used to sign the userinfo endpoint responses.
|
||||||
|
|
||||||
|
See the response object section of the [integration guide](../../../integration/openid-connect/introduction.md#response-object)
|
||||||
|
for more information including the algorithm column for supported values. In addition to the values listed we also
|
||||||
|
support `none` as a value for this endpoint.
|
||||||
|
|
||||||
|
The algorithm chosen must have a key configured in the [issuer_private_keys](provider.md#issuerprivatekeys) section to
|
||||||
|
be considered valid.
|
||||||
|
|
||||||
|
This option has no effect if the [userinfo_signing_key_id](#userinfosigningkeyid) is specified as the algorithm is
|
||||||
|
automatically assumed by the configured key.
|
||||||
|
|
||||||
|
### userinfo_signing_key_id
|
||||||
|
|
||||||
|
{{< confkey type="string" required="no" >}}
|
||||||
|
|
||||||
|
The key id of the JWK used to sign the userinfo endpoint responses in the token responses. This option takes precedence
|
||||||
|
over [userinfo_signing_alg](#userinfosigningalg). The value of this must one of those provided or calculated in the
|
||||||
|
[issuer_private_keys](provider.md#issuerprivatekeys).
|
||||||
|
|
||||||
|
### request_object_signing_alg
|
||||||
|
|
||||||
|
{{< confkey type="string" default="RSA256" required="no" >}}
|
||||||
|
|
||||||
|
The JWT signing algorithm accepted for request objects.
|
||||||
|
|
||||||
|
See the request object section of the
|
||||||
|
[integration guide](../../../integration/openid-connect/introduction.md#request-object) for more information including
|
||||||
|
the algorithm column for supported values.
|
||||||
|
|
||||||
### token_endpoint_auth_method
|
### token_endpoint_auth_method
|
||||||
|
|
||||||
{{< confkey type="string" default="" required="no" >}}
|
{{< confkey type="string" default="" required="no" >}}
|
||||||
|
@ -300,35 +361,6 @@ otherwise we assume the default value:
|
||||||
| [token_endpoint_auth_method](#tokenendpointauthsigningalg) | `private_key_jwt` | `RS256` |
|
| [token_endpoint_auth_method](#tokenendpointauthsigningalg) | `private_key_jwt` | `RS256` |
|
||||||
| [token_endpoint_auth_method](#tokenendpointauthsigningalg) | `client_secret_jwt` | `HS256` |
|
| [token_endpoint_auth_method](#tokenendpointauthsigningalg) | `client_secret_jwt` | `HS256` |
|
||||||
|
|
||||||
### request_object_signing_alg
|
|
||||||
|
|
||||||
{{< confkey type="string" default="RSA256" required="no" >}}
|
|
||||||
|
|
||||||
The JWT signing algorithm accepted for request objects.
|
|
||||||
|
|
||||||
See the request object section of the [integration guide](../../../integration/openid-connect/introduction.md#request-object)
|
|
||||||
for more information including the algorithm column for supported values.
|
|
||||||
|
|
||||||
### id_token_signing_alg
|
|
||||||
|
|
||||||
{{< confkey type="string" default="RS256" required="no" >}}
|
|
||||||
|
|
||||||
The algorithm used to sign the ID Tokens in the token responses.
|
|
||||||
|
|
||||||
See the response object section of the [integration guide](../../../integration/openid-connect/introduction.md#response-object)
|
|
||||||
for more information including the algorithm column for supported values. In addition to the values listed we also
|
|
||||||
support `none` as a value for this endpoint.
|
|
||||||
|
|
||||||
### userinfo_signing_alg
|
|
||||||
|
|
||||||
{{< confkey type="string" default="none" required="no" >}}
|
|
||||||
|
|
||||||
The algorithm used to sign the userinfo endpoint responses.
|
|
||||||
|
|
||||||
See the response object section of the [integration guide](../../../integration/openid-connect/introduction.md#response-object)
|
|
||||||
for more information including the algorithm column for supported values. In addition to the values listed we also
|
|
||||||
support `none` as a value for this endpoint.
|
|
||||||
|
|
||||||
### public_keys
|
### public_keys
|
||||||
|
|
||||||
This section configures the trusted JSON Web Keys or JWKS for this registered client. This can either be static values
|
This section configures the trusted JSON Web Keys or JWKS for this registered client. This can either be static values
|
||||||
|
@ -368,11 +400,29 @@ A list of static keys.
|
||||||
|
|
||||||
The Key ID used to match the request object's JWT header `kid` value against.
|
The Key ID used to match the request object's JWT header `kid` value against.
|
||||||
|
|
||||||
|
##### use
|
||||||
|
|
||||||
|
{{< confkey type="string" default="sig" required="no" >}}
|
||||||
|
|
||||||
|
The key usage. Defaults to `sig` which is the only available option at this time.
|
||||||
|
|
||||||
|
##### algorithm
|
||||||
|
|
||||||
|
{{< confkey type="string" default="RS256" required="situational" >}}
|
||||||
|
|
||||||
|
The algorithm for this key. This value typically optional as it can be automatically detected based on the type of key
|
||||||
|
in some situations. It is however strongly recommended this is set.
|
||||||
|
|
||||||
|
See the request object table in the [integration guide](../../../integration/openid-connect/introduction.md#request-object)
|
||||||
|
for more information. The `Algorithm` column lists supported values, the `Key` column references the required
|
||||||
|
[key](#key) type constraints that exist for the algorithm, and the `JWK Default Conditions` column briefly explains the
|
||||||
|
conditions under which it's the default algorithm.
|
||||||
|
|
||||||
##### key
|
##### key
|
||||||
|
|
||||||
{{< confkey type="string" required="yes" >}}
|
{{< confkey type="string" required="yes" >}}
|
||||||
|
|
||||||
The public key portion of the JSON Web Key
|
The public key portion of the JSON Web Key.
|
||||||
|
|
||||||
The public key the clients use to sign/encrypt the [OpenID Connect 1.0] asserted [JWT]'s. The key is generated by the
|
The public key the clients use to sign/encrypt the [OpenID Connect 1.0] asserted [JWT]'s. The key is generated by the
|
||||||
client application or the administrator of the client application.
|
client application or the administrator of the client application.
|
||||||
|
@ -388,9 +438,15 @@ The key *__MUST__*:
|
||||||
* A P-384 elliptical curve.
|
* A P-384 elliptical curve.
|
||||||
* A P-512 elliptical curve.
|
* A P-512 elliptical curve.
|
||||||
|
|
||||||
If the [issuer_certificate_chain](#issuercertificatechain) is provided the private key must include matching public
|
If the [certificate_chain](#certificatechain) is provided the private key must include matching public
|
||||||
key data for the first certificate in the chain.
|
key data for the first certificate in the chain.
|
||||||
|
|
||||||
|
##### certificate_chain
|
||||||
|
|
||||||
|
{{< confkey type="string" required="no" >}}
|
||||||
|
|
||||||
|
The certificate chain/bundle to be used with the [key](#key) DER base64 ([RFC4648])
|
||||||
|
encoded PEM format used to sign/encrypt the [OpenID Connect 1.0] [JWT]'s.
|
||||||
|
|
||||||
## Integration
|
## Integration
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
title: "OpenID Connect 1.0 Provider"
|
title: "OpenID Connect 1.0 Provider"
|
||||||
description: "OpenID Connect 1.0 Provider Configuration"
|
description: "OpenID Connect 1.0 Provider Configuration"
|
||||||
lead: "Authelia can operate as an OpenID Connect 1.0 Provider. This section describes how to configure this."
|
lead: "Authelia can operate as an OpenID Connect 1.0 Provider. This section describes how to configure this."
|
||||||
date: 2023-05-08T13:38:08+10:00
|
date: 2023-05-15T10:32:10+10:00
|
||||||
draft: false
|
draft: false
|
||||||
images: []
|
images: []
|
||||||
menu:
|
menu:
|
||||||
|
@ -135,41 +135,31 @@ with 64 or more characters.
|
||||||
|
|
||||||
### issuer_private_keys
|
### issuer_private_keys
|
||||||
|
|
||||||
The key *__MUST__*:
|
|
||||||
|
|
||||||
* Be a PEM block encoded in the DER base64 format ([RFC4648]).
|
|
||||||
* Be either:
|
|
||||||
* An RSA public key:
|
|
||||||
* With a key size of at least 2048 bits.
|
|
||||||
* An ECDSA public key with one of:
|
|
||||||
* A P-256 elliptical curve.
|
|
||||||
* A P-384 elliptical curve.
|
|
||||||
* A P-512 elliptical curve.
|
|
||||||
|
|
||||||
### issuer_private_keys
|
|
||||||
|
|
||||||
{{< confkey type="list(object" required="no" >}}
|
{{< confkey type="list(object" required="no" >}}
|
||||||
|
|
||||||
The list of JWKS instead of or in addition to the [issuer_private_key](#issuerprivatekey) and
|
The list of JWKS instead of or in addition to the [issuer_private_key](#issuerprivatekey) and
|
||||||
[issuer_certificate_chain](#issuercertificatechain). Can also accept ECDSA Private Key's and Certificates.
|
[issuer_certificate_chain](#issuercertificatechain). Can also accept ECDSA Private Key's and Certificates.
|
||||||
|
|
||||||
|
The default key for each algorithm is is decided based on the order of this list. The first key for each algorithm is
|
||||||
|
considered the default if a client is not configured to use a specific key id. For example if a client has
|
||||||
|
[id_token_signing_alg](clients.md#idtokensigningalg) `ES256` and [id_token_signing_key_id](clients.md#idtokensigningkid) is
|
||||||
|
not specified then the first `ES256` key in this list is used.
|
||||||
|
|
||||||
#### key_id
|
#### key_id
|
||||||
|
|
||||||
{{< confkey type="string" default="<thumbprint of public key>" required="no" >}}
|
{{< confkey type="string" default="<thumbprint of public key>" required="no" >}}
|
||||||
|
|
||||||
Completely optional, and generally discouraged unless there is a collision between the automatically generated key id's.
|
Completely optional, and generally discouraged unless there is a collision between the automatically generated key id's.
|
||||||
If provided must be a unique string with 7 or less alphanumeric characters.
|
If provided must be a unique string with 100 or less characters, with a recommendation to use a length less
|
||||||
|
than 10. In addition it must meet the following rules:
|
||||||
|
|
||||||
This value is the first 7 characters of the public key thumbprint (SHA1) encoded into hexadecimal.
|
- Match the regular expression `^[a-zA-Z0-9](([a-zA-Z0-9._~-]*)([a-zA-Z0-9]))?$` which should enforce the following rules:
|
||||||
|
- Start with an alphanumeric character.
|
||||||
|
- End with an alphanumeric character.
|
||||||
|
- Only contain the [RFC3986 Unreserved Characters](https://datatracker.ietf.org/doc/html/rfc3986#section-2.3).
|
||||||
|
|
||||||
#### algorithm
|
The default if this value is omitted is the first 7 characters of the public key SHA256 thumbprint encoded into
|
||||||
|
hexadecimal.
|
||||||
{{< confkey type="string" default="RS256" required="no" >}}
|
|
||||||
|
|
||||||
The algorithm for this key. This value must be unique. It's automatically detected based on the type of key.
|
|
||||||
|
|
||||||
See the response object table in the [integration guide](../../../integration/openid-connect/introduction.md#response-object)
|
|
||||||
including the algorithm column for the supported values and the key type column for the default algorithm value.
|
|
||||||
|
|
||||||
#### use
|
#### use
|
||||||
|
|
||||||
|
@ -177,14 +167,28 @@ including the algorithm column for the supported values and the key type column
|
||||||
|
|
||||||
The key usage. Defaults to `sig` which is the only available option at this time.
|
The key usage. Defaults to `sig` which is the only available option at this time.
|
||||||
|
|
||||||
|
#### algorithm
|
||||||
|
|
||||||
|
{{< confkey type="string" default="RS256" required="situational" >}}
|
||||||
|
|
||||||
|
The algorithm for this key. This value typically optional as it can be automatically detected based on the type of key
|
||||||
|
in some situations.
|
||||||
|
|
||||||
|
See the response object table in the [integration guide](../../../integration/openid-connect/introduction.md#response-object)
|
||||||
|
for more information. The `Algorithm` column lists supported values, the `Key` column references the required
|
||||||
|
[key](#key) type constraints that exist for the algorithm, and the `JWK Default Conditions` column briefly explains the
|
||||||
|
conditions under which it's the default algorithm.
|
||||||
|
|
||||||
|
At least one `RSA256` key must be provided.
|
||||||
|
|
||||||
#### key
|
#### key
|
||||||
|
|
||||||
{{< confkey type="string" required="yes" >}}
|
{{< confkey type="string" required="yes" >}}
|
||||||
|
|
||||||
The private key associated with this key entry.
|
The private key associated with this key entry.
|
||||||
|
|
||||||
The private key used to sign/encrypt the [OpenID Connect 1.0] issued [JWT]'s. The key must be generated by the administrator
|
The private key used to sign/encrypt the [OpenID Connect 1.0] issued [JWT]'s. The key must be generated by the
|
||||||
and can be done by following the
|
administrator and can be done by following the
|
||||||
[Generating an RSA Keypair](../../../reference/guides/generating-secure-values.md#generating-an-rsa-keypair) guide.
|
[Generating an RSA Keypair](../../../reference/guides/generating-secure-values.md#generating-an-rsa-keypair) guide.
|
||||||
|
|
||||||
The private key *__MUST__*:
|
The private key *__MUST__*:
|
||||||
|
@ -217,15 +221,13 @@ it if present.
|
||||||
|
|
||||||
{{< confkey type="string" required="yes" >}}
|
{{< confkey type="string" required="yes" >}}
|
||||||
|
|
||||||
*__Important Note:__ This can also be defined using a [secret](../../methods/secrets.md) which is __strongly recommended__
|
The private key used to sign/encrypt the [OpenID Connect 1.0] issued [JWT]'s. The key must be generated by the
|
||||||
especially for containerized deployments.*
|
administrator and can be done by following the
|
||||||
|
|
||||||
The private key used to sign/encrypt the [OpenID Connect 1.0] issued [JWT]'s. The key must be generated by the administrator
|
|
||||||
and can be done by following the
|
|
||||||
[Generating an RSA Keypair](../../../reference/guides/generating-secure-values.md#generating-an-rsa-keypair) guide.
|
[Generating an RSA Keypair](../../../reference/guides/generating-secure-values.md#generating-an-rsa-keypair) guide.
|
||||||
|
|
||||||
This private key is automatically appended to the [issuer_private_keys](#issuerprivatekeys) and assumed to be for the
|
This private key is automatically appended to the [issuer_private_keys](#issuerprivatekeys) and assumed to be for the
|
||||||
RS256 algorithm. As such no other key in this list should be RS256 if this is configured.
|
`RS256` algorithm. If provided it is always the first key in this list. As such this key is assumed to be the default
|
||||||
|
for `RS256` if provided.
|
||||||
|
|
||||||
The issuer private key *__MUST__*:
|
The issuer private key *__MUST__*:
|
||||||
|
|
||||||
|
@ -240,7 +242,7 @@ key data for the first certificate in the chain.
|
||||||
|
|
||||||
{{< confkey type="string" required="no" >}}
|
{{< confkey type="string" required="no" >}}
|
||||||
|
|
||||||
The certificate chain/bundle to be used with the [issuer_private_key](#issuer_private_key) DER base64 ([RFC4648])
|
The certificate chain/bundle to be used with the [issuer_private_key](#issuerprivatekey) DER base64 ([RFC4648])
|
||||||
encoded PEM format used to sign/encrypt the [OpenID Connect 1.0] [JWT]'s. When configured it enables the [x5c] and [x5t]
|
encoded PEM format used to sign/encrypt the [OpenID Connect 1.0] [JWT]'s. When configured it enables the [x5c] and [x5t]
|
||||||
JSON key's in the JWKs [Discoverable Endpoint](../../../integration/openid-connect/introduction.md#discoverable-endpoints)
|
JSON key's in the JWKs [Discoverable Endpoint](../../../integration/openid-connect/introduction.md#discoverable-endpoints)
|
||||||
as per [RFC7517].
|
as per [RFC7517].
|
||||||
|
@ -303,6 +305,8 @@ make certain scenarios less secure. It is highly encouraged that if your OpenID
|
||||||
these parameters or sends parameters with a lower length than the default that they implement a change rather than
|
these parameters or sends parameters with a lower length than the default that they implement a change rather than
|
||||||
changing this value.
|
changing this value.
|
||||||
|
|
||||||
|
This restriction can also be disabled entirely when set to `-1`.
|
||||||
|
|
||||||
### enforce_pkce
|
### enforce_pkce
|
||||||
|
|
||||||
{{< confkey type="string" default="public_clients_only" required="no" >}}
|
{{< confkey type="string" default="public_clients_only" required="no" >}}
|
||||||
|
@ -411,7 +415,7 @@ See the [OpenID Connect 1.0 Registered Clients](clients.md) documentation for co
|
||||||
## Integration
|
## Integration
|
||||||
|
|
||||||
To integrate Authelia's [OpenID Connect 1.0] implementation with a relying party please see the
|
To integrate Authelia's [OpenID Connect 1.0] implementation with a relying party please see the
|
||||||
[integration docs](../../integration/openid-connect/introduction.md).
|
[integration docs](../../../integration/openid-connect/introduction.md).
|
||||||
|
|
||||||
[token lifespan]: https://docs.apigee.com/api-platform/antipatterns/oauth-long-expiration
|
[token lifespan]: https://docs.apigee.com/api-platform/antipatterns/oauth-long-expiration
|
||||||
[OpenID Connect 1.0]: https://openid.net/connect/
|
[OpenID Connect 1.0]: https://openid.net/connect/
|
||||||
|
|
|
@ -15,6 +15,11 @@ aliases:
|
||||||
- /docs/configuration/access-control.html
|
- /docs/configuration/access-control.html
|
||||||
---
|
---
|
||||||
|
|
||||||
|
*__Important Note:__ This section does not apply to OpenID Connect 1.0. See the [Frequently Asked Questions] for more
|
||||||
|
information.*
|
||||||
|
|
||||||
|
[Frequently Asked Questions]: ../../integration/openid-connect/frequently-asked-questions.md#why-doesnt-the-access-control-configuration-work-with-openid-connect-10
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
{{< config-alert-example >}}
|
{{< config-alert-example >}}
|
||||||
|
|
|
@ -32,6 +32,7 @@ storage:
|
||||||
schema: 'public'
|
schema: 'public'
|
||||||
username: 'authelia'
|
username: 'authelia'
|
||||||
password: 'mypassword'
|
password: 'mypassword'
|
||||||
|
timeout: '5s'
|
||||||
tls:
|
tls:
|
||||||
server_name: 'postgres.example.com'
|
server_name: 'postgres.example.com'
|
||||||
skip_verify: false
|
skip_verify: false
|
||||||
|
|
|
@ -88,6 +88,26 @@ If you've configured Authelia alongside a proxy and are making a request directl
|
||||||
request via the proxy. If you're avoiding the proxy due to a DNS limitation see
|
request via the proxy. If you're avoiding the proxy due to a DNS limitation see
|
||||||
[Solution: Configure DNS Appropriately](#configure-dns-appropriately) section.
|
[Solution: Configure DNS Appropriately](#configure-dns-appropriately) section.
|
||||||
|
|
||||||
|
### Why doesn't the access control configuration work with OpenID Connect 1.0?
|
||||||
|
|
||||||
|
The [access control](../../configuration/security/access-control.md) configuration contains several elements which are
|
||||||
|
not very compatible with OpenID Connect 1.0. They were designed with per-request authorizations in mind. In particular
|
||||||
|
the [resources](../../configuration/security/access-control.md#resources),
|
||||||
|
[query](../../configuration/security/access-control.md#query),
|
||||||
|
[methods](../../configuration/security/access-control.md#methods), and
|
||||||
|
[networks](../../configuration/security/access-control.md#networks) criteria are very specific to each request and to
|
||||||
|
some degree so are the [domain](../../configuration/security/access-control.md#domain) and
|
||||||
|
[domain regex](../../configuration/security/access-control.md#domainregex) criteria as the token is issued to the client
|
||||||
|
not a specific domain.
|
||||||
|
|
||||||
|
For these reasons we implemented the
|
||||||
|
[authorization policy](../../configuration/identity-providers/openid-connect/clients.md#authorizationpolicy) as a direct
|
||||||
|
option in the client. It's likely in the future that we'll expand this option to encompass the features that work well
|
||||||
|
with OpenID Connect 1.0 such as the [subject](../../configuration/security/access-control.md#subject) criteria which
|
||||||
|
reasonably be matched to an individual authorization policy. Because the other criteria are mostly geared towards
|
||||||
|
per-request authorization these criteria types are fairly unlikely to become part of OpenID Connect 1.0 as there are no
|
||||||
|
ways to apply these criteria except during the initial authorization request.
|
||||||
|
|
||||||
## Solutions
|
## Solutions
|
||||||
|
|
||||||
The following section details solutions for multiple of the questions above.
|
The following section details solutions for multiple of the questions above.
|
||||||
|
|
|
@ -36,7 +36,7 @@ In addition to the `https` scheme requirement for Authelia itself:
|
||||||
1. Due to the fact a cookie is used, it's an intentional design decision that *__ALL__* applications/domains protected via
|
1. Due to the fact a cookie is used, it's an intentional design decision that *__ALL__* applications/domains protected via
|
||||||
this method *__MUST__* use secure schemes (`https` and `wss`) for all of their communication.
|
this method *__MUST__* use secure schemes (`https` and `wss`) for all of their communication.
|
||||||
|
|
||||||
### OpenID Connect
|
### OpenID Connect 1.0
|
||||||
|
|
||||||
No additional requirements other than the use of the `https` scheme for Authelia itself exist excluding those mandated
|
No additional requirements other than the use of the `https` scheme for Authelia itself exist excluding those mandated
|
||||||
by the relevant specifications.
|
by the relevant specifications.
|
||||||
|
@ -93,6 +93,11 @@ recommended that you read the relevant [Proxy Integration Documentation](../prox
|
||||||
recommend viewing the dedicated [Kubernetes Documentation](../kubernetes/introduction.md) prior to viewing the
|
recommend viewing the dedicated [Kubernetes Documentation](../kubernetes/introduction.md) prior to viewing the
|
||||||
[Proxy Integration Documentation](../proxies/introduction.md).*
|
[Proxy Integration Documentation](../proxies/introduction.md).*
|
||||||
|
|
||||||
|
## Additional Useful Links
|
||||||
|
|
||||||
|
See the [Frequently Asked Questions](../../reference/guides/frequently-asked-questions.md) for helpful sections of the
|
||||||
|
documentation which may answer specific questions.
|
||||||
|
|
||||||
## Moving to Production
|
## Moving to Production
|
||||||
|
|
||||||
We consider it important to do several things in moving to a production environment.
|
We consider it important to do several things in moving to a production environment.
|
||||||
|
|
8
go.mod
8
go.mod
|
@ -11,9 +11,9 @@ require (
|
||||||
github.com/fasthttp/session/v2 v2.5.0
|
github.com/fasthttp/session/v2 v2.5.0
|
||||||
github.com/fsnotify/fsnotify v1.6.0
|
github.com/fsnotify/fsnotify v1.6.0
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.4
|
github.com/go-asn1-ber/asn1-ber v1.5.4
|
||||||
github.com/go-crypt/crypt v0.2.7
|
github.com/go-crypt/crypt v0.2.9
|
||||||
github.com/go-ldap/ldap/v3 v3.4.5-0.20230506142018-039466e6b835
|
github.com/go-ldap/ldap/v3 v3.4.5-0.20230521105649-cdb0754f6668
|
||||||
github.com/go-rod/rod v0.113.0
|
github.com/go-rod/rod v0.113.1
|
||||||
github.com/go-sql-driver/mysql v1.7.1
|
github.com/go-sql-driver/mysql v1.7.1
|
||||||
github.com/go-webauthn/webauthn v0.8.2
|
github.com/go-webauthn/webauthn v0.8.2
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||||
|
@ -70,7 +70,7 @@ require (
|
||||||
github.com/ecordell/optgen v0.0.6 // indirect
|
github.com/ecordell/optgen v0.0.6 // indirect
|
||||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
|
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
|
||||||
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
|
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
|
||||||
github.com/go-crypt/x v0.2.0 // indirect
|
github.com/go-crypt/x v0.2.1 // indirect
|
||||||
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
||||||
github.com/go-webauthn/revoke v0.1.9 // indirect
|
github.com/go-webauthn/revoke v0.1.9 // indirect
|
||||||
github.com/golang/glog v1.0.0 // indirect
|
github.com/golang/glog v1.0.0 // indirect
|
||||||
|
|
16
go.sum
16
go.sum
|
@ -130,22 +130,22 @@ github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrt
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A=
|
github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A=
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||||
github.com/go-crypt/crypt v0.2.7 h1:Ir6E59c1wrskJhpJXMqaynHA2xAxpGN7nQXlLkbpzR0=
|
github.com/go-crypt/crypt v0.2.9 h1:5gWWTId2Qyqs9ROIsegt5pnqo9wUSRLbhpkR6JSftjg=
|
||||||
github.com/go-crypt/crypt v0.2.7/go.mod h1:ulieouNs4qwFCq4wF61oyTQYXAXSoOv995EU4hcHwMU=
|
github.com/go-crypt/crypt v0.2.9/go.mod h1:JjzdTYE2mArb6nBoIvvpF7o46/rK/1pfmlArCRMTFUk=
|
||||||
github.com/go-crypt/x v0.2.0 h1:rHMiKRAu6kFc+xAnQywDb3iHGpvrFbIGXnP3IfCZ+2U=
|
github.com/go-crypt/x v0.2.1 h1:OGw78Bswme9lffCOX6tyuC280ouU5391glsvThMtM5U=
|
||||||
github.com/go-crypt/x v0.2.0/go.mod h1:uLo5o+Cc8nvahDASQpntR1g3ZMUoq2LM/859PkhykC4=
|
github.com/go-crypt/x v0.2.1/go.mod h1:Q/y9rms7yw4/1CavBlNGn0Itg4HqwNpe1N9FX0TxXrc=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-ldap/ldap/v3 v3.4.5-0.20230506142018-039466e6b835 h1:XgBmN9yZXIh9vJGzs2qYPb5ee8/VnOWLHHYcKXGXKME=
|
github.com/go-ldap/ldap/v3 v3.4.5-0.20230521105649-cdb0754f6668 h1:qbWHOCDBT8m2I1tDGP7S58dgi/xaDDCKuR5dbarLGOU=
|
||||||
github.com/go-ldap/ldap/v3 v3.4.5-0.20230506142018-039466e6b835/go.mod h1:bMGIq3AGbytbaMwf8wdv5Phdxz0FWHTIYMSzyrYgnQs=
|
github.com/go-ldap/ldap/v3 v3.4.5-0.20230521105649-cdb0754f6668/go.mod h1:bMGIq3AGbytbaMwf8wdv5Phdxz0FWHTIYMSzyrYgnQs=
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||||
github.com/go-rod/rod v0.113.0 h1:E7+GLjYVZnScewIB2u8+66joQLaDGbOLzSOT4orNHms=
|
github.com/go-rod/rod v0.113.1 h1:+Qb4K/vkR7BOhW6FhfhtLzUD3l11+0XlF4do+27sOQk=
|
||||||
github.com/go-rod/rod v0.113.0/go.mod h1:aiedSEFg5DwG/fnNbUOTPMTTWX3MRj6vIs/a684Mthw=
|
github.com/go-rod/rod v0.113.1/go.mod h1:aiedSEFg5DwG/fnNbUOTPMTTWX3MRj6vIs/a684Mthw=
|
||||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
|
|
|
@ -2,20 +2,8 @@ package authentication
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
)
|
|
||||||
|
|
||||||
// Level is the type representing a level of authentication.
|
"golang.org/x/text/encoding/unicode"
|
||||||
type Level int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// NotAuthenticated if the user is not authenticated yet.
|
|
||||||
NotAuthenticated Level = iota
|
|
||||||
|
|
||||||
// OneFactor if the user has passed first factor only.
|
|
||||||
OneFactor
|
|
||||||
|
|
||||||
// TwoFactor if the user has passed two factors.
|
|
||||||
TwoFactor
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -109,3 +97,7 @@ const fileAuthenticationMode = 0600
|
||||||
// OWASP recommends to escape some special characters.
|
// OWASP recommends to escape some special characters.
|
||||||
// https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/LDAP_Injection_Prevention_Cheat_Sheet.md
|
// https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/LDAP_Injection_Prevention_Cheat_Sheet.md
|
||||||
const specialLDAPRunes = ",#+<>;\"="
|
const specialLDAPRunes = ",#+<>;\"="
|
||||||
|
|
||||||
|
var (
|
||||||
|
encodingUTF16LittleEndian = unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)
|
||||||
|
)
|
||||||
|
|
|
@ -23,7 +23,7 @@ import (
|
||||||
type FileUserProvider struct {
|
type FileUserProvider struct {
|
||||||
config *schema.FileAuthenticationBackend
|
config *schema.FileAuthenticationBackend
|
||||||
hash algorithm.Hash
|
hash algorithm.Hash
|
||||||
database *FileUserDatabase
|
database FileUserDatabase
|
||||||
mutex *sync.Mutex
|
mutex *sync.Mutex
|
||||||
timeoutReload time.Time
|
timeoutReload time.Time
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,7 @@ func NewFileUserProvider(config *schema.FileAuthenticationBackend) (provider *Fi
|
||||||
config: config,
|
config: config,
|
||||||
mutex: &sync.Mutex{},
|
mutex: &sync.Mutex{},
|
||||||
timeoutReload: time.Now().Add(-1 * time.Second),
|
timeoutReload: time.Now().Add(-1 * time.Second),
|
||||||
|
database: NewYAMLUserDatabase(config.Path, config.Search.Email, config.Search.CaseInsensitive),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +137,9 @@ func (p *FileUserProvider) StartupCheck() (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
p.database = NewFileUserDatabase(p.config.Path, p.config.Search.Email, p.config.Search.CaseInsensitive)
|
if p.database == nil {
|
||||||
|
p.database = NewYAMLUserDatabase(p.config.Path, p.config.Search.Email, p.config.Search.CaseInsensitive)
|
||||||
|
}
|
||||||
|
|
||||||
if err = p.database.Load(); err != nil {
|
if err = p.database.Load(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -194,10 +197,6 @@ func NewFileCryptoHashFromConfig(config schema.Password) (hash algorithm.Hash, e
|
||||||
return nil, fmt.Errorf("failed to initialize hash settings: %w", err)
|
return nil, fmt.Errorf("failed to initialize hash settings: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = hash.Validate(); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to validate hash settings: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return hash, nil
|
return hash, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,9 +12,16 @@ import (
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewFileUserDatabase creates a new FileUserDatabase.
|
type FileUserDatabase interface {
|
||||||
func NewFileUserDatabase(filePath string, searchEmail, searchCI bool) (database *FileUserDatabase) {
|
Save() (err error)
|
||||||
return &FileUserDatabase{
|
Load() (err error)
|
||||||
|
GetUserDetails(username string) (user DatabaseUserDetails, err error)
|
||||||
|
SetUserDetails(username string, details *DatabaseUserDetails)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewYAMLUserDatabase creates a new YAMLUserDatabase.
|
||||||
|
func NewYAMLUserDatabase(filePath string, searchEmail, searchCI bool) (database *YAMLUserDatabase) {
|
||||||
|
return &YAMLUserDatabase{
|
||||||
RWMutex: &sync.RWMutex{},
|
RWMutex: &sync.RWMutex{},
|
||||||
Path: filePath,
|
Path: filePath,
|
||||||
Users: map[string]DatabaseUserDetails{},
|
Users: map[string]DatabaseUserDetails{},
|
||||||
|
@ -25,8 +32,8 @@ func NewFileUserDatabase(filePath string, searchEmail, searchCI bool) (database
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileUserDatabase is a user details database that is concurrency safe database and can be reloaded.
|
// YAMLUserDatabase is a user details database that is concurrency safe database and can be reloaded.
|
||||||
type FileUserDatabase struct {
|
type YAMLUserDatabase struct {
|
||||||
*sync.RWMutex
|
*sync.RWMutex
|
||||||
|
|
||||||
Path string
|
Path string
|
||||||
|
@ -39,7 +46,7 @@ type FileUserDatabase struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the database to disk.
|
// Save the database to disk.
|
||||||
func (m *FileUserDatabase) Save() (err error) {
|
func (m *YAMLUserDatabase) Save() (err error) {
|
||||||
m.RLock()
|
m.RLock()
|
||||||
|
|
||||||
defer m.RUnlock()
|
defer m.RUnlock()
|
||||||
|
@ -52,7 +59,7 @@ func (m *FileUserDatabase) Save() (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the database from disk.
|
// Load the database from disk.
|
||||||
func (m *FileUserDatabase) Load() (err error) {
|
func (m *YAMLUserDatabase) Load() (err error) {
|
||||||
yml := &DatabaseModel{Users: map[string]UserDetailsModel{}}
|
yml := &DatabaseModel{Users: map[string]UserDetailsModel{}}
|
||||||
|
|
||||||
if err = yml.Read(m.Path); err != nil {
|
if err = yml.Read(m.Path); err != nil {
|
||||||
|
@ -71,7 +78,7 @@ func (m *FileUserDatabase) Load() (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadAliases performs the loading of alias information from the database.
|
// LoadAliases performs the loading of alias information from the database.
|
||||||
func (m *FileUserDatabase) LoadAliases() (err error) {
|
func (m *YAMLUserDatabase) LoadAliases() (err error) {
|
||||||
if m.SearchEmail || m.SearchCI {
|
if m.SearchEmail || m.SearchCI {
|
||||||
for k, user := range m.Users {
|
for k, user := range m.Users {
|
||||||
if m.SearchEmail && user.Email != "" {
|
if m.SearchEmail && user.Email != "" {
|
||||||
|
@ -91,7 +98,7 @@ func (m *FileUserDatabase) LoadAliases() (err error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *FileUserDatabase) loadAlias(k string) (err error) {
|
func (m *YAMLUserDatabase) loadAlias(k string) (err error) {
|
||||||
u := strings.ToLower(k)
|
u := strings.ToLower(k)
|
||||||
|
|
||||||
if u != k {
|
if u != k {
|
||||||
|
@ -113,7 +120,7 @@ func (m *FileUserDatabase) loadAlias(k string) (err error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *FileUserDatabase) loadAliasEmail(k string, user DatabaseUserDetails) (err error) {
|
func (m *YAMLUserDatabase) loadAliasEmail(k string, user DatabaseUserDetails) (err error) {
|
||||||
e := strings.ToLower(user.Email)
|
e := strings.ToLower(user.Email)
|
||||||
|
|
||||||
var duplicates []string
|
var duplicates []string
|
||||||
|
@ -145,7 +152,7 @@ func (m *FileUserDatabase) loadAliasEmail(k string, user DatabaseUserDetails) (e
|
||||||
|
|
||||||
// GetUserDetails get a DatabaseUserDetails given a username as a value type where the username must be the users actual
|
// GetUserDetails get a DatabaseUserDetails given a username as a value type where the username must be the users actual
|
||||||
// username.
|
// username.
|
||||||
func (m *FileUserDatabase) GetUserDetails(username string) (user DatabaseUserDetails, err error) {
|
func (m *YAMLUserDatabase) GetUserDetails(username string) (user DatabaseUserDetails, err error) {
|
||||||
m.RLock()
|
m.RLock()
|
||||||
|
|
||||||
defer m.RUnlock()
|
defer m.RUnlock()
|
||||||
|
@ -172,7 +179,7 @@ func (m *FileUserDatabase) GetUserDetails(username string) (user DatabaseUserDet
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetUserDetails sets the DatabaseUserDetails for a given user.
|
// SetUserDetails sets the DatabaseUserDetails for a given user.
|
||||||
func (m *FileUserDatabase) SetUserDetails(username string, details *DatabaseUserDetails) {
|
func (m *YAMLUserDatabase) SetUserDetails(username string, details *DatabaseUserDetails) {
|
||||||
if details == nil {
|
if details == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -184,8 +191,8 @@ func (m *FileUserDatabase) SetUserDetails(username string, details *DatabaseUser
|
||||||
m.Unlock()
|
m.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToDatabaseModel converts the FileUserDatabase into the DatabaseModel for saving.
|
// ToDatabaseModel converts the YAMLUserDatabase into the DatabaseModel for saving.
|
||||||
func (m *FileUserDatabase) ToDatabaseModel() (model *DatabaseModel) {
|
func (m *YAMLUserDatabase) ToDatabaseModel() (model *DatabaseModel) {
|
||||||
model = &DatabaseModel{
|
model = &DatabaseModel{
|
||||||
Users: map[string]UserDetailsModel{},
|
Users: map[string]UserDetailsModel{},
|
||||||
}
|
}
|
||||||
|
@ -236,8 +243,8 @@ type DatabaseModel struct {
|
||||||
Users map[string]UserDetailsModel `yaml:"users" valid:"required"`
|
Users map[string]UserDetailsModel `yaml:"users" valid:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadToFileUserDatabase reads the DatabaseModel into a FileUserDatabase.
|
// ReadToFileUserDatabase reads the DatabaseModel into a YAMLUserDatabase.
|
||||||
func (m *DatabaseModel) ReadToFileUserDatabase(db *FileUserDatabase) (err error) {
|
func (m *DatabaseModel) ReadToFileUserDatabase(db *YAMLUserDatabase) (err error) {
|
||||||
users := map[string]DatabaseUserDetails{}
|
users := map[string]DatabaseUserDetails{}
|
||||||
|
|
||||||
var udm *DatabaseUserDetails
|
var udm *DatabaseUserDetails
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: github.com/authelia/authelia/v4/internal/authentication (interfaces: FileUserDatabase)
|
||||||
|
|
||||||
|
// Package authentication is a generated GoMock package.
|
||||||
|
package authentication
|
||||||
|
|
||||||
|
import (
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockFileUserDatabase is a mock of FileUserDatabase interface.
|
||||||
|
type MockFileUserDatabase struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockFileUserDatabaseMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockFileUserDatabaseMockRecorder is the mock recorder for MockFileUserDatabase.
|
||||||
|
type MockFileUserDatabaseMockRecorder struct {
|
||||||
|
mock *MockFileUserDatabase
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockFileUserDatabase creates a new mock instance.
|
||||||
|
func NewMockFileUserDatabase(ctrl *gomock.Controller) *MockFileUserDatabase {
|
||||||
|
mock := &MockFileUserDatabase{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockFileUserDatabaseMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockFileUserDatabase) EXPECT() *MockFileUserDatabaseMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserDetails mocks base method.
|
||||||
|
func (m *MockFileUserDatabase) GetUserDetails(arg0 string) (DatabaseUserDetails, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetUserDetails", arg0)
|
||||||
|
ret0, _ := ret[0].(DatabaseUserDetails)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserDetails indicates an expected call of GetUserDetails.
|
||||||
|
func (mr *MockFileUserDatabaseMockRecorder) GetUserDetails(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserDetails", reflect.TypeOf((*MockFileUserDatabase)(nil).GetUserDetails), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load mocks base method.
|
||||||
|
func (m *MockFileUserDatabase) Load() error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Load")
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load indicates an expected call of Load.
|
||||||
|
func (mr *MockFileUserDatabaseMockRecorder) Load() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Load", reflect.TypeOf((*MockFileUserDatabase)(nil).Load))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save mocks base method.
|
||||||
|
func (m *MockFileUserDatabase) Save() error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Save")
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save indicates an expected call of Save.
|
||||||
|
func (mr *MockFileUserDatabaseMockRecorder) Save() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Save", reflect.TypeOf((*MockFileUserDatabase)(nil).Save))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserDetails mocks base method.
|
||||||
|
func (m *MockFileUserDatabase) SetUserDetails(arg0 string, arg1 *DatabaseUserDetails) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "SetUserDetails", arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserDetails indicates an expected call of SetUserDetails.
|
||||||
|
func (mr *MockFileUserDatabaseMockRecorder) SetUserDetails(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetUserDetails", reflect.TypeOf((*MockFileUserDatabase)(nil).SetUserDetails), arg0, arg1)
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package authentication
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDatabaseModel_Read(t *testing.T) {
|
||||||
|
model := &DatabaseModel{}
|
||||||
|
|
||||||
|
dir := t.TempDir()
|
||||||
|
|
||||||
|
_, err := os.Create(filepath.Join(dir, "users_database.yml"))
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.EqualError(t, model.Read(filepath.Join(dir, "users_database.yml")), "no file content")
|
||||||
|
|
||||||
|
assert.NoError(t, os.Mkdir(filepath.Join(dir, "x"), 0000))
|
||||||
|
|
||||||
|
f := filepath.Join(dir, "x", "users_database.yml")
|
||||||
|
|
||||||
|
assert.EqualError(t, model.Read(f), fmt.Sprintf("failed to read the '%s' file: open %s: permission denied", f, f))
|
||||||
|
|
||||||
|
f = filepath.Join(dir, "schema.yml")
|
||||||
|
|
||||||
|
file, err := os.Create(f)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = file.WriteString("users:\n\tjohn: {}")
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.EqualError(t, model.Read(f), "could not parse the YAML database: yaml: line 2: found character that cannot start any token")
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: github.com/go-crypt/crypt/algorithm (interfaces: Hash)
|
||||||
|
|
||||||
|
// Package authentication is a generated GoMock package.
|
||||||
|
package authentication
|
||||||
|
|
||||||
|
import (
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
|
algorithm "github.com/go-crypt/crypt/algorithm"
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockHash is a mock of Hash interface.
|
||||||
|
type MockHash struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockHashMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockHashMockRecorder is the mock recorder for MockHash.
|
||||||
|
type MockHashMockRecorder struct {
|
||||||
|
mock *MockHash
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockHash creates a new mock instance.
|
||||||
|
func NewMockHash(ctrl *gomock.Controller) *MockHash {
|
||||||
|
mock := &MockHash{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockHashMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockHash) EXPECT() *MockHashMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash mocks base method.
|
||||||
|
func (m *MockHash) Hash(arg0 string) (algorithm.Digest, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Hash", arg0)
|
||||||
|
ret0, _ := ret[0].(algorithm.Digest)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash indicates an expected call of Hash.
|
||||||
|
func (mr *MockHashMockRecorder) Hash(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Hash", reflect.TypeOf((*MockHash)(nil).Hash), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HashWithSalt mocks base method.
|
||||||
|
func (m *MockHash) HashWithSalt(arg0 string, arg1 []byte) (algorithm.Digest, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "HashWithSalt", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(algorithm.Digest)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// HashWithSalt indicates an expected call of HashWithSalt.
|
||||||
|
func (mr *MockHashMockRecorder) HashWithSalt(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HashWithSalt", reflect.TypeOf((*MockHash)(nil).HashWithSalt), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustHash mocks base method.
|
||||||
|
func (m *MockHash) MustHash(arg0 string) algorithm.Digest {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "MustHash", arg0)
|
||||||
|
ret0, _ := ret[0].(algorithm.Digest)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustHash indicates an expected call of MustHash.
|
||||||
|
func (mr *MockHashMockRecorder) MustHash(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MustHash", reflect.TypeOf((*MockHash)(nil).MustHash), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate mocks base method.
|
||||||
|
func (m *MockHash) Validate() error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Validate")
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate indicates an expected call of Validate.
|
||||||
|
func (mr *MockHashMockRecorder) Validate() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockHash)(nil).Validate))
|
||||||
|
}
|
|
@ -1,59 +1,167 @@
|
||||||
package authentication
|
package authentication
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-crypt/crypt/algorithm/bcrypt"
|
||||||
|
"github.com/go-crypt/crypt/algorithm/pbkdf2"
|
||||||
|
"github.com/go-crypt/crypt/algorithm/scrypt"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
func WithDatabase(content []byte, f func(path string)) {
|
|
||||||
tmpfile, err := os.CreateTemp("", "users_database.*.yaml")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer os.Remove(tmpfile.Name()) // Clean up.
|
|
||||||
|
|
||||||
if _, err := tmpfile.Write(content); err != nil {
|
|
||||||
tmpfile.Close()
|
|
||||||
log.Panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f(tmpfile.Name())
|
|
||||||
|
|
||||||
if err := tmpfile.Close(); err != nil {
|
|
||||||
log.Panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShouldErrorPermissionsOnLocalFS(t *testing.T) {
|
func TestShouldErrorPermissionsOnLocalFS(t *testing.T) {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
t.Skip("skipping test due to being on windows")
|
t.Skip("skipping test due to being on windows")
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = os.Mkdir("/tmp/noperms/", 0000)
|
dir := t.TempDir()
|
||||||
err := checkDatabase("/tmp/noperms/users_database.yml")
|
|
||||||
|
|
||||||
require.EqualError(t, err, "error checking user authentication database file: stat /tmp/noperms/users_database.yml: permission denied")
|
_ = os.Mkdir(filepath.Join(dir, "noperms"), 0000)
|
||||||
|
|
||||||
|
f := filepath.Join(dir, "noperms", "users_database.yml")
|
||||||
|
require.EqualError(t, checkDatabase(f), fmt.Sprintf("error checking user authentication database file: stat %s: permission denied", f))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldErrorAndGenerateUserDB(t *testing.T) {
|
func TestShouldErrorAndGenerateUserDB(t *testing.T) {
|
||||||
err := checkDatabase("./nonexistent.yml")
|
dir := t.TempDir()
|
||||||
_ = os.Remove("./nonexistent.yml")
|
|
||||||
|
|
||||||
require.EqualError(t, err, "user authentication database file doesn't exist at path './nonexistent.yml' and has been generated")
|
f := filepath.Join(dir, "users_database.yml")
|
||||||
|
|
||||||
|
require.EqualError(t, checkDatabase(f), fmt.Sprintf("user authentication database file doesn't exist at path '%s' and has been generated", f))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldErrorFailCreateDB(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
|
||||||
|
assert.NoError(t, os.Mkdir(filepath.Join(dir, "x"), 0000))
|
||||||
|
|
||||||
|
f := filepath.Join(dir, "x", "users.yml")
|
||||||
|
|
||||||
|
provider := NewFileUserProvider(&schema.FileAuthenticationBackend{Path: f, Password: schema.DefaultPasswordConfig})
|
||||||
|
|
||||||
|
require.NotNil(t, provider)
|
||||||
|
|
||||||
|
assert.EqualError(t, provider.StartupCheck(), "one or more errors occurred checking the authentication database")
|
||||||
|
|
||||||
|
assert.NotNil(t, provider.database)
|
||||||
|
|
||||||
|
reloaded, err := provider.Reload()
|
||||||
|
|
||||||
|
assert.False(t, reloaded)
|
||||||
|
assert.EqualError(t, err, fmt.Sprintf("failed to reload: error reading the authentication database: failed to read the '%s' file: open %s: permission denied", f, f))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldErrorBadPasswordConfig(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
|
||||||
|
f := filepath.Join(dir, "users.yml")
|
||||||
|
|
||||||
|
require.NoError(t, os.WriteFile(f, UserDatabaseContent, 0600))
|
||||||
|
|
||||||
|
provider := NewFileUserProvider(&schema.FileAuthenticationBackend{Path: f})
|
||||||
|
|
||||||
|
require.NotNil(t, provider)
|
||||||
|
|
||||||
|
assert.EqualError(t, provider.StartupCheck(), "failed to initialize hash settings: argon2 validation error: parameter is invalid: parameter 't' must be between 1 and 2147483647 but is set to '0'")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldNotPanicOnNilDB(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
|
||||||
|
f := filepath.Join(dir, "users.yml")
|
||||||
|
|
||||||
|
assert.NoError(t, os.WriteFile(f, UserDatabaseContent, 0600))
|
||||||
|
|
||||||
|
provider := &FileUserProvider{
|
||||||
|
config: &schema.FileAuthenticationBackend{Path: f, Password: schema.DefaultPasswordConfig},
|
||||||
|
mutex: &sync.Mutex{},
|
||||||
|
timeoutReload: time.Now().Add(-1 * time.Second),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NoError(t, provider.StartupCheck())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldReloadDatabase(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
path := filepath.Join(dir, "users.yml")
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
setup func(t *testing.T, provider *FileUserProvider)
|
||||||
|
expected bool
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ShouldSkipReloadRecentlyReloaded",
|
||||||
|
func(t *testing.T, provider *FileUserProvider) {
|
||||||
|
provider.timeoutReload = time.Now().Add(time.Minute)
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldReloadWithoutError",
|
||||||
|
func(t *testing.T, provider *FileUserProvider) {
|
||||||
|
provider.timeoutReload = time.Now().Add(time.Minute * -1)
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldNotReloadWithNoContent",
|
||||||
|
func(t *testing.T, provider *FileUserProvider) {
|
||||||
|
p := filepath.Join(dir, "empty.yml")
|
||||||
|
|
||||||
|
_, _ = os.Create(p)
|
||||||
|
|
||||||
|
provider.timeoutReload = time.Now().Add(time.Minute * -1)
|
||||||
|
|
||||||
|
provider.config.Path = p
|
||||||
|
|
||||||
|
provider.database = NewYAMLUserDatabase(p, provider.config.Search.Email, provider.config.Search.CaseInsensitive)
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, os.WriteFile(path, UserDatabaseContent, 0600))
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
provider := NewFileUserProvider(&schema.FileAuthenticationBackend{
|
||||||
|
Path: path,
|
||||||
|
Password: schema.DefaultPasswordConfig,
|
||||||
|
})
|
||||||
|
|
||||||
|
tc.setup(t, provider)
|
||||||
|
|
||||||
|
actual, theError := provider.Reload()
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expected, actual)
|
||||||
|
if tc.err == "" {
|
||||||
|
assert.NoError(t, theError)
|
||||||
|
} else {
|
||||||
|
assert.EqualError(t, theError, tc.err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldCheckUserArgon2idPasswordIsCorrect(t *testing.T) {
|
func TestShouldCheckUserArgon2idPasswordIsCorrect(t *testing.T) {
|
||||||
WithDatabase(UserDatabaseContent, func(path string) {
|
WithDatabase(t, UserDatabaseContent, func(path string) {
|
||||||
config := DefaultFileAuthenticationBackendConfiguration
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
config.Path = path
|
config.Path = path
|
||||||
provider := NewFileUserProvider(&config)
|
provider := NewFileUserProvider(&config)
|
||||||
|
@ -68,7 +176,7 @@ func TestShouldCheckUserArgon2idPasswordIsCorrect(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldCheckUserSHA512PasswordIsCorrect(t *testing.T) {
|
func TestShouldCheckUserSHA512PasswordIsCorrect(t *testing.T) {
|
||||||
WithDatabase(UserDatabaseContent, func(path string) {
|
WithDatabase(t, UserDatabaseContent, func(path string) {
|
||||||
config := DefaultFileAuthenticationBackendConfiguration
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
config.Path = path
|
config.Path = path
|
||||||
|
|
||||||
|
@ -84,7 +192,7 @@ func TestShouldCheckUserSHA512PasswordIsCorrect(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldCheckUserPasswordIsWrong(t *testing.T) {
|
func TestShouldCheckUserPasswordIsWrong(t *testing.T) {
|
||||||
WithDatabase(UserDatabaseContent, func(path string) {
|
WithDatabase(t, UserDatabaseContent, func(path string) {
|
||||||
config := DefaultFileAuthenticationBackendConfiguration
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
config.Path = path
|
config.Path = path
|
||||||
|
|
||||||
|
@ -100,7 +208,7 @@ func TestShouldCheckUserPasswordIsWrong(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldCheckUserPasswordIsWrongForEnumerationCompare(t *testing.T) {
|
func TestShouldCheckUserPasswordIsWrongForEnumerationCompare(t *testing.T) {
|
||||||
WithDatabase(UserDatabaseContent, func(path string) {
|
WithDatabase(t, UserDatabaseContent, func(path string) {
|
||||||
config := DefaultFileAuthenticationBackendConfiguration
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
config.Path = path
|
config.Path = path
|
||||||
|
|
||||||
|
@ -115,7 +223,7 @@ func TestShouldCheckUserPasswordIsWrongForEnumerationCompare(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldCheckUserPasswordOfUserThatDoesNotExist(t *testing.T) {
|
func TestShouldCheckUserPasswordOfUserThatDoesNotExist(t *testing.T) {
|
||||||
WithDatabase(UserDatabaseContent, func(path string) {
|
WithDatabase(t, UserDatabaseContent, func(path string) {
|
||||||
config := DefaultFileAuthenticationBackendConfiguration
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
config.Path = path
|
config.Path = path
|
||||||
|
|
||||||
|
@ -131,7 +239,7 @@ func TestShouldCheckUserPasswordOfUserThatDoesNotExist(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldRetrieveUserDetails(t *testing.T) {
|
func TestShouldRetrieveUserDetails(t *testing.T) {
|
||||||
WithDatabase(UserDatabaseContent, func(path string) {
|
WithDatabase(t, UserDatabaseContent, func(path string) {
|
||||||
config := DefaultFileAuthenticationBackendConfiguration
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
config.Path = path
|
config.Path = path
|
||||||
|
|
||||||
|
@ -147,8 +255,27 @@ func TestShouldRetrieveUserDetails(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestShouldErrOnUserDetailsNoUser(t *testing.T) {
|
||||||
|
WithDatabase(t, UserDatabaseContent, func(path string) {
|
||||||
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
|
config.Path = path
|
||||||
|
|
||||||
|
provider := NewFileUserProvider(&config)
|
||||||
|
|
||||||
|
assert.NoError(t, provider.StartupCheck())
|
||||||
|
|
||||||
|
details, err := provider.GetDetails("nouser")
|
||||||
|
assert.Nil(t, details)
|
||||||
|
assert.Equal(t, err, ErrUserNotFound)
|
||||||
|
|
||||||
|
details, err = provider.GetDetails("dis")
|
||||||
|
assert.Nil(t, details)
|
||||||
|
assert.Equal(t, err, ErrUserNotFound)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestShouldUpdatePassword(t *testing.T) {
|
func TestShouldUpdatePassword(t *testing.T) {
|
||||||
WithDatabase(UserDatabaseContent, func(path string) {
|
WithDatabase(t, UserDatabaseContent, func(path string) {
|
||||||
config := DefaultFileAuthenticationBackendConfiguration
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
config.Path = path
|
config.Path = path
|
||||||
|
|
||||||
|
@ -172,7 +299,7 @@ func TestShouldUpdatePassword(t *testing.T) {
|
||||||
|
|
||||||
// Checks both that the hashing algo changes and that it removes {CRYPT} from the start.
|
// Checks both that the hashing algo changes and that it removes {CRYPT} from the start.
|
||||||
func TestShouldUpdatePasswordHashingAlgorithmToArgon2id(t *testing.T) {
|
func TestShouldUpdatePasswordHashingAlgorithmToArgon2id(t *testing.T) {
|
||||||
WithDatabase(UserDatabaseContent, func(path string) {
|
WithDatabase(t, UserDatabaseContent, func(path string) {
|
||||||
config := DefaultFileAuthenticationBackendConfiguration
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
config.Path = path
|
config.Path = path
|
||||||
|
|
||||||
|
@ -180,7 +307,10 @@ func TestShouldUpdatePasswordHashingAlgorithmToArgon2id(t *testing.T) {
|
||||||
|
|
||||||
assert.NoError(t, provider.StartupCheck())
|
assert.NoError(t, provider.StartupCheck())
|
||||||
|
|
||||||
assert.True(t, strings.HasPrefix(provider.database.Users["harry"].Digest.Encode(), "$6$"))
|
db, ok := provider.database.(*YAMLUserDatabase)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
assert.True(t, strings.HasPrefix(db.Users["harry"].Digest.Encode(), "$6$"))
|
||||||
err := provider.UpdatePassword("harry", "newpassword")
|
err := provider.UpdatePassword("harry", "newpassword")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
@ -189,15 +319,15 @@ func TestShouldUpdatePasswordHashingAlgorithmToArgon2id(t *testing.T) {
|
||||||
|
|
||||||
assert.NoError(t, provider.StartupCheck())
|
assert.NoError(t, provider.StartupCheck())
|
||||||
|
|
||||||
ok, err := provider.CheckUserPassword("harry", "newpassword")
|
ok, err = provider.CheckUserPassword("harry", "newpassword")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
assert.True(t, strings.HasPrefix(provider.database.Users["harry"].Digest.Encode(), "$argon2id$"))
|
assert.True(t, strings.HasPrefix(db.Users["harry"].Digest.Encode(), "$argon2id$"))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldUpdatePasswordHashingAlgorithmToSHA512(t *testing.T) {
|
func TestShouldUpdatePasswordHashingAlgorithmToSHA512(t *testing.T) {
|
||||||
WithDatabase(UserDatabaseContent, func(path string) {
|
WithDatabase(t, UserDatabaseContent, func(path string) {
|
||||||
config := DefaultFileAuthenticationBackendConfiguration
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
config.Path = path
|
config.Path = path
|
||||||
config.Password.Algorithm = "sha2crypt"
|
config.Password.Algorithm = "sha2crypt"
|
||||||
|
@ -207,7 +337,10 @@ func TestShouldUpdatePasswordHashingAlgorithmToSHA512(t *testing.T) {
|
||||||
|
|
||||||
assert.NoError(t, provider.StartupCheck())
|
assert.NoError(t, provider.StartupCheck())
|
||||||
|
|
||||||
assert.True(t, strings.HasPrefix(provider.database.Users["john"].Digest.Encode(), "$argon2id$"))
|
db, ok := provider.database.(*YAMLUserDatabase)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
assert.True(t, strings.HasPrefix(db.Users["john"].Digest.Encode(), "$argon2id$"))
|
||||||
err := provider.UpdatePassword("john", "newpassword")
|
err := provider.UpdatePassword("john", "newpassword")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
@ -216,15 +349,29 @@ func TestShouldUpdatePasswordHashingAlgorithmToSHA512(t *testing.T) {
|
||||||
|
|
||||||
assert.NoError(t, provider.StartupCheck())
|
assert.NoError(t, provider.StartupCheck())
|
||||||
|
|
||||||
ok, err := provider.CheckUserPassword("john", "newpassword")
|
ok, err = provider.CheckUserPassword("john", "newpassword")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
assert.True(t, strings.HasPrefix(provider.database.Users["john"].Digest.Encode(), "$6$"))
|
assert.True(t, strings.HasPrefix(db.Users["john"].Digest.Encode(), "$6$"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldErrOnUpdatePasswordNoUser(t *testing.T) {
|
||||||
|
WithDatabase(t, UserDatabaseContent, func(path string) {
|
||||||
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
|
config.Path = path
|
||||||
|
|
||||||
|
provider := NewFileUserProvider(&config)
|
||||||
|
|
||||||
|
assert.NoError(t, provider.StartupCheck())
|
||||||
|
|
||||||
|
assert.Equal(t, provider.UpdatePassword("nousers", "newpassword"), ErrUserNotFound)
|
||||||
|
assert.Equal(t, provider.UpdatePassword("dis", "example"), ErrUserNotFound)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldRaiseWhenLoadingMalformedDatabaseForFirstTime(t *testing.T) {
|
func TestShouldRaiseWhenLoadingMalformedDatabaseForFirstTime(t *testing.T) {
|
||||||
WithDatabase(MalformedUserDatabaseContent, func(path string) {
|
WithDatabase(t, MalformedUserDatabaseContent, func(path string) {
|
||||||
config := DefaultFileAuthenticationBackendConfiguration
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
config.Path = path
|
config.Path = path
|
||||||
|
|
||||||
|
@ -235,7 +382,7 @@ func TestShouldRaiseWhenLoadingMalformedDatabaseForFirstTime(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldRaiseWhenLoadingDatabaseWithBadSchemaForFirstTime(t *testing.T) {
|
func TestShouldRaiseWhenLoadingDatabaseWithBadSchemaForFirstTime(t *testing.T) {
|
||||||
WithDatabase(BadSchemaUserDatabaseContent, func(path string) {
|
WithDatabase(t, BadSchemaUserDatabaseContent, func(path string) {
|
||||||
config := DefaultFileAuthenticationBackendConfiguration
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
config.Path = path
|
config.Path = path
|
||||||
|
|
||||||
|
@ -246,7 +393,7 @@ func TestShouldRaiseWhenLoadingDatabaseWithBadSchemaForFirstTime(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldRaiseWhenLoadingDatabaseWithBadSHA512HashesForTheFirstTime(t *testing.T) {
|
func TestShouldRaiseWhenLoadingDatabaseWithBadSHA512HashesForTheFirstTime(t *testing.T) {
|
||||||
WithDatabase(BadSHA512HashContent, func(path string) {
|
WithDatabase(t, BadSHA512HashContent, func(path string) {
|
||||||
config := DefaultFileAuthenticationBackendConfiguration
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
config.Path = path
|
config.Path = path
|
||||||
|
|
||||||
|
@ -257,7 +404,7 @@ func TestShouldRaiseWhenLoadingDatabaseWithBadSHA512HashesForTheFirstTime(t *tes
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashSettingsForTheFirstTime(t *testing.T) {
|
func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashSettingsForTheFirstTime(t *testing.T) {
|
||||||
WithDatabase(BadArgon2idHashSettingsContent, func(path string) {
|
WithDatabase(t, BadArgon2idHashSettingsContent, func(path string) {
|
||||||
config := DefaultFileAuthenticationBackendConfiguration
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
config.Path = path
|
config.Path = path
|
||||||
|
|
||||||
|
@ -268,7 +415,7 @@ func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashSettingsForTheFirstTim
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashKeyForTheFirstTime(t *testing.T) {
|
func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashKeyForTheFirstTime(t *testing.T) {
|
||||||
WithDatabase(BadArgon2idHashKeyContent, func(path string) {
|
WithDatabase(t, BadArgon2idHashKeyContent, func(path string) {
|
||||||
config := DefaultFileAuthenticationBackendConfiguration
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
config.Path = path
|
config.Path = path
|
||||||
|
|
||||||
|
@ -279,7 +426,7 @@ func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashKeyForTheFirstTime(t *
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashSaltForTheFirstTime(t *testing.T) {
|
func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashSaltForTheFirstTime(t *testing.T) {
|
||||||
WithDatabase(BadArgon2idHashSaltContent, func(path string) {
|
WithDatabase(t, BadArgon2idHashSaltContent, func(path string) {
|
||||||
config := DefaultFileAuthenticationBackendConfiguration
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
config.Path = path
|
config.Path = path
|
||||||
|
|
||||||
|
@ -290,7 +437,7 @@ func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashSaltForTheFirstTime(t
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldSupportHashPasswordWithoutCRYPT(t *testing.T) {
|
func TestShouldSupportHashPasswordWithoutCRYPT(t *testing.T) {
|
||||||
WithDatabase(UserDatabaseWithoutCryptContent, func(path string) {
|
WithDatabase(t, UserDatabaseWithoutCryptContent, func(path string) {
|
||||||
config := DefaultFileAuthenticationBackendConfiguration
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
config.Path = path
|
config.Path = path
|
||||||
|
|
||||||
|
@ -306,7 +453,7 @@ func TestShouldSupportHashPasswordWithoutCRYPT(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldNotAllowLoginOfDisabledUsers(t *testing.T) {
|
func TestShouldNotAllowLoginOfDisabledUsers(t *testing.T) {
|
||||||
WithDatabase(UserDatabaseContent, func(path string) {
|
WithDatabase(t, UserDatabaseContent, func(path string) {
|
||||||
config := DefaultFileAuthenticationBackendConfiguration
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
config.Path = path
|
config.Path = path
|
||||||
|
|
||||||
|
@ -322,7 +469,7 @@ func TestShouldNotAllowLoginOfDisabledUsers(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldErrorOnInvalidCaseSensitiveFile(t *testing.T) {
|
func TestShouldErrorOnInvalidCaseSensitiveFile(t *testing.T) {
|
||||||
WithDatabase(UserDatabaseContentInvalidSearchCaseInsenstive, func(path string) {
|
WithDatabase(t, UserDatabaseContentInvalidSearchCaseInsenstive, func(path string) {
|
||||||
config := DefaultFileAuthenticationBackendConfiguration
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
config.Path = path
|
config.Path = path
|
||||||
config.Search.Email = false
|
config.Search.Email = false
|
||||||
|
@ -335,7 +482,7 @@ func TestShouldErrorOnInvalidCaseSensitiveFile(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldErrorOnDuplicateEmail(t *testing.T) {
|
func TestShouldErrorOnDuplicateEmail(t *testing.T) {
|
||||||
WithDatabase(UserDatabaseContentInvalidSearchEmail, func(path string) {
|
WithDatabase(t, UserDatabaseContentInvalidSearchEmail, func(path string) {
|
||||||
config := DefaultFileAuthenticationBackendConfiguration
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
config.Path = path
|
config.Path = path
|
||||||
config.Search.Email = true
|
config.Search.Email = true
|
||||||
|
@ -349,7 +496,7 @@ func TestShouldErrorOnDuplicateEmail(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldNotErrorOnEmailAsUsername(t *testing.T) {
|
func TestShouldNotErrorOnEmailAsUsername(t *testing.T) {
|
||||||
WithDatabase(UserDatabaseContentSearchEmailAsUsername, func(path string) {
|
WithDatabase(t, UserDatabaseContentSearchEmailAsUsername, func(path string) {
|
||||||
config := DefaultFileAuthenticationBackendConfiguration
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
config.Path = path
|
config.Path = path
|
||||||
config.Search.Email = true
|
config.Search.Email = true
|
||||||
|
@ -362,7 +509,7 @@ func TestShouldNotErrorOnEmailAsUsername(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldErrorOnEmailAsUsernameWithDuplicateEmail(t *testing.T) {
|
func TestShouldErrorOnEmailAsUsernameWithDuplicateEmail(t *testing.T) {
|
||||||
WithDatabase(UserDatabaseContentInvalidSearchEmailAsUsername, func(path string) {
|
WithDatabase(t, UserDatabaseContentInvalidSearchEmailAsUsername, func(path string) {
|
||||||
config := DefaultFileAuthenticationBackendConfiguration
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
config.Path = path
|
config.Path = path
|
||||||
config.Search.Email = true
|
config.Search.Email = true
|
||||||
|
@ -375,7 +522,7 @@ func TestShouldErrorOnEmailAsUsernameWithDuplicateEmail(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldErrorOnEmailAsUsernameWithDuplicateEmailCase(t *testing.T) {
|
func TestShouldErrorOnEmailAsUsernameWithDuplicateEmailCase(t *testing.T) {
|
||||||
WithDatabase(UserDatabaseContentInvalidSearchEmailAsUsernameCase, func(path string) {
|
WithDatabase(t, UserDatabaseContentInvalidSearchEmailAsUsernameCase, func(path string) {
|
||||||
config := DefaultFileAuthenticationBackendConfiguration
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
config.Path = path
|
config.Path = path
|
||||||
config.Search.Email = false
|
config.Search.Email = false
|
||||||
|
@ -388,7 +535,7 @@ func TestShouldErrorOnEmailAsUsernameWithDuplicateEmailCase(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldAllowLookupByEmail(t *testing.T) {
|
func TestShouldAllowLookupByEmail(t *testing.T) {
|
||||||
WithDatabase(UserDatabaseContent, func(path string) {
|
WithDatabase(t, UserDatabaseContent, func(path string) {
|
||||||
config := DefaultFileAuthenticationBackendConfiguration
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
config.Path = path
|
config.Path = path
|
||||||
config.Search.Email = true
|
config.Search.Email = true
|
||||||
|
@ -415,7 +562,7 @@ func TestShouldAllowLookupByEmail(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldAllowLookupCI(t *testing.T) {
|
func TestShouldAllowLookupCI(t *testing.T) {
|
||||||
WithDatabase(UserDatabaseContent, func(path string) {
|
WithDatabase(t, UserDatabaseContent, func(path string) {
|
||||||
config := DefaultFileAuthenticationBackendConfiguration
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
config.Path = path
|
config.Path = path
|
||||||
config.Search.CaseInsensitive = true
|
config.Search.CaseInsensitive = true
|
||||||
|
@ -436,6 +583,139 @@ func TestShouldAllowLookupCI(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewFileCryptoHashFromConfig(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
have schema.Password
|
||||||
|
expected any
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ShouldCreatePBKDF2",
|
||||||
|
schema.Password{
|
||||||
|
Algorithm: "pbkdf2",
|
||||||
|
PBKDF2: schema.PBKDF2Password{
|
||||||
|
Variant: "sha256",
|
||||||
|
Iterations: 100000,
|
||||||
|
SaltLength: 16,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&pbkdf2.Hasher{},
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldCreateSCrypt",
|
||||||
|
schema.Password{
|
||||||
|
Algorithm: "scrypt",
|
||||||
|
SCrypt: schema.SCryptPassword{
|
||||||
|
Iterations: 12,
|
||||||
|
SaltLength: 16,
|
||||||
|
Parallelism: 1,
|
||||||
|
BlockSize: 1,
|
||||||
|
KeyLength: 32,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&scrypt.Hasher{},
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldCreateBCrypt",
|
||||||
|
schema.Password{
|
||||||
|
Algorithm: "bcrypt",
|
||||||
|
BCrypt: schema.BCryptPassword{
|
||||||
|
Variant: "standard",
|
||||||
|
Cost: 12,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&bcrypt.Hasher{},
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldFailToCreateSCryptInvalidParameter",
|
||||||
|
schema.Password{
|
||||||
|
Algorithm: "scrypt",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
"failed to initialize hash settings: scrypt validation error: parameter is invalid: parameter 'iterations' must be between 1 and 58 but is set to '0'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldFailUnknown",
|
||||||
|
schema.Password{
|
||||||
|
Algorithm: "unknown",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
"algorithm 'unknown' is unknown",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
actual, theError := NewFileCryptoHashFromConfig(tc.have)
|
||||||
|
|
||||||
|
if tc.err == "" {
|
||||||
|
assert.NoError(t, theError)
|
||||||
|
require.NotNil(t, actual)
|
||||||
|
assert.IsType(t, tc.expected, actual)
|
||||||
|
} else {
|
||||||
|
assert.EqualError(t, theError, tc.err)
|
||||||
|
assert.Nil(t, actual)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHashError(t *testing.T) {
|
||||||
|
WithDatabase(t, UserDatabaseContent, func(path string) {
|
||||||
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
|
config.Search.CaseInsensitive = true
|
||||||
|
config.Path = path
|
||||||
|
|
||||||
|
provider := NewFileUserProvider(&config)
|
||||||
|
|
||||||
|
assert.NoError(t, provider.StartupCheck())
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
mock := NewMockHash(ctrl)
|
||||||
|
provider.hash = mock
|
||||||
|
|
||||||
|
mock.EXPECT().Hash("apple123").Return(nil, fmt.Errorf("failed to mock hash"))
|
||||||
|
|
||||||
|
assert.EqualError(t, provider.UpdatePassword("john", "apple123"), "failed to mock hash")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDatabaseError(t *testing.T) {
|
||||||
|
WithDatabase(t, UserDatabaseContent, func(path string) {
|
||||||
|
db := NewYAMLUserDatabase(path, false, false)
|
||||||
|
assert.NoError(t, db.Load())
|
||||||
|
|
||||||
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
|
config.Search.CaseInsensitive = true
|
||||||
|
config.Path = path
|
||||||
|
|
||||||
|
provider := NewFileUserProvider(&config)
|
||||||
|
|
||||||
|
assert.NoError(t, provider.StartupCheck())
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
mock := NewMockFileUserDatabase(ctrl)
|
||||||
|
|
||||||
|
provider.database = mock
|
||||||
|
|
||||||
|
gomock.InOrder(
|
||||||
|
mock.EXPECT().GetUserDetails("john").Return(db.GetUserDetails("john")),
|
||||||
|
mock.EXPECT().SetUserDetails("john", gomock.Any()),
|
||||||
|
mock.EXPECT().Save().Return(fmt.Errorf("failed to mock save")),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.EqualError(t, provider.UpdatePassword("john", "apple123"), "failed to mock save")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
DefaultFileAuthenticationBackendConfiguration = schema.FileAuthenticationBackend{
|
DefaultFileAuthenticationBackendConfiguration = schema.FileAuthenticationBackend{
|
||||||
Path: "",
|
Path: "",
|
||||||
|
@ -657,3 +937,17 @@ users:
|
||||||
- admins
|
- admins
|
||||||
- dev
|
- dev
|
||||||
`)
|
`)
|
||||||
|
|
||||||
|
func WithDatabase(t *testing.T, content []byte, f func(path string)) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
|
||||||
|
db, err := os.CreateTemp(dir, "users_database.*.yaml")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = db.Write(content)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
f(db.Name())
|
||||||
|
|
||||||
|
require.NoError(t, db.Close())
|
||||||
|
}
|
||||||
|
|
|
@ -5,3 +5,5 @@ package authentication
|
||||||
|
|
||||||
//go:generate mockgen -package authentication -destination ldap_client_mock.go -mock_names LDAPClient=MockLDAPClient github.com/authelia/authelia/v4/internal/authentication LDAPClient
|
//go:generate mockgen -package authentication -destination ldap_client_mock.go -mock_names LDAPClient=MockLDAPClient github.com/authelia/authelia/v4/internal/authentication LDAPClient
|
||||||
//go:generate mockgen -package authentication -destination ldap_client_factory_mock.go -mock_names LDAPClientFactory=MockLDAPClientFactory github.com/authelia/authelia/v4/internal/authentication LDAPClientFactory
|
//go:generate mockgen -package authentication -destination ldap_client_factory_mock.go -mock_names LDAPClientFactory=MockLDAPClientFactory github.com/authelia/authelia/v4/internal/authentication LDAPClientFactory
|
||||||
|
//go:generate mockgen -package authentication -destination file_user_provider_database_mock_test.go -mock_names FileUserDatabase=MockFileUserDatabase github.com/authelia/authelia/v4/internal/authentication FileUserDatabase
|
||||||
|
//go:generate mockgen -package authentication -destination file_user_provider_hash_mock_test.go -mock_names Hash=MockHash github.com/go-crypt/crypt/algorithm Hash
|
||||||
|
|
|
@ -215,7 +215,7 @@ func (p *LDAPUserProvider) UpdatePassword(username, password string) (err error)
|
||||||
modifyRequest := ldap.NewModifyRequest(profile.DN, controls)
|
modifyRequest := ldap.NewModifyRequest(profile.DN, controls)
|
||||||
// The password needs to be enclosed in quotes
|
// The password needs to be enclosed in quotes
|
||||||
// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/6e803168-f140-4d23-b2d3-c3a8ab5917d2
|
// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/6e803168-f140-4d23-b2d3-c3a8ab5917d2
|
||||||
pwdEncoded, _ := utf16LittleEndian.NewEncoder().String(fmt.Sprintf("\"%s\"", password))
|
pwdEncoded, _ := encodingUTF16LittleEndian.NewEncoder().String(fmt.Sprintf("\"%s\"", password))
|
||||||
modifyRequest.Replace(ldapAttributeUnicodePwd, []string{pwdEncoded})
|
modifyRequest.Replace(ldapAttributeUnicodePwd, []string{pwdEncoded})
|
||||||
|
|
||||||
err = p.modify(client, modifyRequest)
|
err = p.modify(client, modifyRequest)
|
||||||
|
|
|
@ -1604,7 +1604,7 @@ func TestShouldUpdateUserPasswordMSAD(t *testing.T) {
|
||||||
[]ldap.Control{&controlMsftServerPolicyHints{ldapOIDControlMsftServerPolicyHints}},
|
[]ldap.Control{&controlMsftServerPolicyHints{ldapOIDControlMsftServerPolicyHints}},
|
||||||
)
|
)
|
||||||
|
|
||||||
pwdEncoded, _ := utf16LittleEndian.NewEncoder().String(fmt.Sprintf("\"%s\"", "password"))
|
pwdEncoded, _ := encodingUTF16LittleEndian.NewEncoder().String(fmt.Sprintf("\"%s\"", "password"))
|
||||||
modifyRequest.Replace(ldapAttributeUnicodePwd, []string{pwdEncoded})
|
modifyRequest.Replace(ldapAttributeUnicodePwd, []string{pwdEncoded})
|
||||||
|
|
||||||
dialURLOIDs := mockFactory.EXPECT().
|
dialURLOIDs := mockFactory.EXPECT().
|
||||||
|
@ -1715,7 +1715,7 @@ func TestShouldUpdateUserPasswordMSADWithReferrals(t *testing.T) {
|
||||||
[]ldap.Control{&controlMsftServerPolicyHints{ldapOIDControlMsftServerPolicyHints}},
|
[]ldap.Control{&controlMsftServerPolicyHints{ldapOIDControlMsftServerPolicyHints}},
|
||||||
)
|
)
|
||||||
|
|
||||||
pwdEncoded, _ := utf16LittleEndian.NewEncoder().String(fmt.Sprintf("\"%s\"", "password"))
|
pwdEncoded, _ := encodingUTF16LittleEndian.NewEncoder().String(fmt.Sprintf("\"%s\"", "password"))
|
||||||
modifyRequest.Replace(ldapAttributeUnicodePwd, []string{pwdEncoded})
|
modifyRequest.Replace(ldapAttributeUnicodePwd, []string{pwdEncoded})
|
||||||
|
|
||||||
dialURLOIDs := mockFactory.EXPECT().
|
dialURLOIDs := mockFactory.EXPECT().
|
||||||
|
@ -1843,7 +1843,7 @@ func TestShouldUpdateUserPasswordMSADWithReferralsWithReferralConnectErr(t *test
|
||||||
[]ldap.Control{&controlMsftServerPolicyHints{ldapOIDControlMsftServerPolicyHints}},
|
[]ldap.Control{&controlMsftServerPolicyHints{ldapOIDControlMsftServerPolicyHints}},
|
||||||
)
|
)
|
||||||
|
|
||||||
pwdEncoded, _ := utf16LittleEndian.NewEncoder().String(fmt.Sprintf("\"%s\"", "password"))
|
pwdEncoded, _ := encodingUTF16LittleEndian.NewEncoder().String(fmt.Sprintf("\"%s\"", "password"))
|
||||||
modifyRequest.Replace(ldapAttributeUnicodePwd, []string{pwdEncoded})
|
modifyRequest.Replace(ldapAttributeUnicodePwd, []string{pwdEncoded})
|
||||||
|
|
||||||
dialURLOIDs := mockFactory.EXPECT().
|
dialURLOIDs := mockFactory.EXPECT().
|
||||||
|
@ -1962,7 +1962,7 @@ func TestShouldUpdateUserPasswordMSADWithReferralsWithReferralModifyErr(t *testi
|
||||||
[]ldap.Control{&controlMsftServerPolicyHints{ldapOIDControlMsftServerPolicyHints}},
|
[]ldap.Control{&controlMsftServerPolicyHints{ldapOIDControlMsftServerPolicyHints}},
|
||||||
)
|
)
|
||||||
|
|
||||||
pwdEncoded, _ := utf16LittleEndian.NewEncoder().String(fmt.Sprintf("\"%s\"", "password"))
|
pwdEncoded, _ := encodingUTF16LittleEndian.NewEncoder().String(fmt.Sprintf("\"%s\"", "password"))
|
||||||
modifyRequest.Replace(ldapAttributeUnicodePwd, []string{pwdEncoded})
|
modifyRequest.Replace(ldapAttributeUnicodePwd, []string{pwdEncoded})
|
||||||
|
|
||||||
dialURLOIDs := mockFactory.EXPECT().
|
dialURLOIDs := mockFactory.EXPECT().
|
||||||
|
@ -2094,7 +2094,7 @@ func TestShouldUpdateUserPasswordMSADWithoutReferrals(t *testing.T) {
|
||||||
[]ldap.Control{&controlMsftServerPolicyHints{ldapOIDControlMsftServerPolicyHints}},
|
[]ldap.Control{&controlMsftServerPolicyHints{ldapOIDControlMsftServerPolicyHints}},
|
||||||
)
|
)
|
||||||
|
|
||||||
pwdEncoded, _ := utf16LittleEndian.NewEncoder().String(fmt.Sprintf("\"%s\"", "password"))
|
pwdEncoded, _ := encodingUTF16LittleEndian.NewEncoder().String(fmt.Sprintf("\"%s\"", "password"))
|
||||||
modifyRequest.Replace(ldapAttributeUnicodePwd, []string{pwdEncoded})
|
modifyRequest.Replace(ldapAttributeUnicodePwd, []string{pwdEncoded})
|
||||||
|
|
||||||
dialURLOIDs := mockFactory.EXPECT().
|
dialURLOIDs := mockFactory.EXPECT().
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-ldap/ldap/v3"
|
"github.com/go-ldap/ldap/v3"
|
||||||
"golang.org/x/text/encoding/unicode"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// LDAPClientFactory an interface of factory of LDAP clients.
|
// LDAPClientFactory an interface of factory of LDAP clients.
|
||||||
|
@ -103,4 +102,30 @@ type LDAPSupportedControlTypes struct {
|
||||||
MsftPwdPolHintsDeprecated bool
|
MsftPwdPolHintsDeprecated bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var utf16LittleEndian = unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)
|
// Level is the type representing a level of authentication.
|
||||||
|
type Level int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// NotAuthenticated if the user is not authenticated yet.
|
||||||
|
NotAuthenticated Level = iota
|
||||||
|
|
||||||
|
// OneFactor if the user has passed first factor only.
|
||||||
|
OneFactor
|
||||||
|
|
||||||
|
// TwoFactor if the user has passed two factors.
|
||||||
|
TwoFactor
|
||||||
|
)
|
||||||
|
|
||||||
|
// String returns a string representation of an authentication.Level.
|
||||||
|
func (l Level) String() string {
|
||||||
|
switch l {
|
||||||
|
case NotAuthenticated:
|
||||||
|
return "not_authenticated"
|
||||||
|
case OneFactor:
|
||||||
|
return "one_factor"
|
||||||
|
case TwoFactor:
|
||||||
|
return "two_factor"
|
||||||
|
default:
|
||||||
|
return "invalid"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
package authentication
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/mail"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUserDetails_Addresses(t *testing.T) {
|
||||||
|
details := &UserDetails{}
|
||||||
|
|
||||||
|
assert.Equal(t, []mail.Address(nil), details.Addresses())
|
||||||
|
|
||||||
|
details = &UserDetails{
|
||||||
|
DisplayName: "Example",
|
||||||
|
Emails: []string{"abc@123.com"},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, []mail.Address{{Name: "Example", Address: "abc@123.com"}}, details.Addresses())
|
||||||
|
|
||||||
|
details = &UserDetails{
|
||||||
|
DisplayName: "Example",
|
||||||
|
Emails: []string{"abc@123.com", "two@apple.com"},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, []mail.Address{{Name: "Example", Address: "abc@123.com"}, {Name: "Example", Address: "two@apple.com"}}, details.Addresses())
|
||||||
|
|
||||||
|
details = &UserDetails{
|
||||||
|
DisplayName: "",
|
||||||
|
Emails: []string{"abc@123.com"},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, []mail.Address{{Address: "abc@123.com"}}, details.Addresses())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLevel_String(t *testing.T) {
|
||||||
|
assert.Equal(t, "one_factor", OneFactor.String())
|
||||||
|
assert.Equal(t, "two_factor", TwoFactor.String())
|
||||||
|
assert.Equal(t, "not_authenticated", NotAuthenticated.String())
|
||||||
|
assert.Equal(t, "invalid", Level(-1).String())
|
||||||
|
}
|
|
@ -1,15 +0,0 @@
|
||||||
package authentication
|
|
||||||
|
|
||||||
// String returns a string representation of an authentication.Level.
|
|
||||||
func (l Level) String() string {
|
|
||||||
switch l {
|
|
||||||
case NotAuthenticated:
|
|
||||||
return "not_authenticated"
|
|
||||||
case OneFactor:
|
|
||||||
return "one_factor"
|
|
||||||
case TwoFactor:
|
|
||||||
return "two_factor"
|
|
||||||
default:
|
|
||||||
return "invalid"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -66,13 +66,13 @@ func (m AccessControlDomainMatcher) IsMatch(domain string, subject Subject) (mat
|
||||||
return strings.HasSuffix(domain, m.Name)
|
return strings.HasSuffix(domain, m.Name)
|
||||||
case m.UserWildcard:
|
case m.UserWildcard:
|
||||||
if subject.IsAnonymous() && strings.HasSuffix(domain, m.Name) {
|
if subject.IsAnonymous() && strings.HasSuffix(domain, m.Name) {
|
||||||
return true
|
return len(domain) > len(m.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
return domain == fmt.Sprintf("%s%s", subject.Username, m.Name)
|
return domain == fmt.Sprintf("%s%s", subject.Username, m.Name)
|
||||||
case m.GroupWildcard:
|
case m.GroupWildcard:
|
||||||
if subject.IsAnonymous() && strings.HasSuffix(domain, m.Name) {
|
if subject.IsAnonymous() && strings.HasSuffix(domain, m.Name) {
|
||||||
return true
|
return len(domain) > len(m.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
i := strings.Index(domain, ".")
|
i := strings.Index(domain, ".")
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
package authorization
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccessControlDomain_IsMatch(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
have *AccessControlDomainMatcher
|
||||||
|
domain string
|
||||||
|
subject Subject
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ShouldMatchDomainSuffixUserWildcard",
|
||||||
|
&AccessControlDomainMatcher{
|
||||||
|
Name: "-user.domain.com",
|
||||||
|
UserWildcard: true,
|
||||||
|
},
|
||||||
|
"a-user.domain.com",
|
||||||
|
Subject{},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldMatchDomainSuffixGroupWildcard",
|
||||||
|
&AccessControlDomainMatcher{
|
||||||
|
Name: "-group.domain.com",
|
||||||
|
GroupWildcard: true,
|
||||||
|
},
|
||||||
|
"a-group.domain.com",
|
||||||
|
Subject{},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldNotMatchExactDomainWithUserWildcard",
|
||||||
|
&AccessControlDomainMatcher{
|
||||||
|
Name: "-user.domain.com",
|
||||||
|
UserWildcard: true,
|
||||||
|
},
|
||||||
|
"-user.domain.com",
|
||||||
|
Subject{},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldMatchWildcard",
|
||||||
|
&AccessControlDomainMatcher{
|
||||||
|
Name: "user.domain.com",
|
||||||
|
Wildcard: true,
|
||||||
|
},
|
||||||
|
"abc.user.domain.com",
|
||||||
|
Subject{},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tc.expected, tc.have.IsMatch(tc.domain, tc.subject))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package authorization
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewAccessControlQuery(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
have [][]schema.ACLQueryRule
|
||||||
|
expected []AccessControlQuery
|
||||||
|
matches [][]Object
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ShouldSkipInvalidTypeEqual",
|
||||||
|
[][]schema.ACLQueryRule{
|
||||||
|
{
|
||||||
|
{Operator: operatorEqual, Key: "example", Value: 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]AccessControlQuery{{Rules: []ObjectMatcher(nil)}},
|
||||||
|
[][]Object{{{}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldSkipInvalidTypePattern",
|
||||||
|
[][]schema.ACLQueryRule{
|
||||||
|
{
|
||||||
|
{Operator: operatorPattern, Key: "example", Value: 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]AccessControlQuery{{Rules: []ObjectMatcher(nil)}},
|
||||||
|
[][]Object{{{}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldSkipInvalidOperator",
|
||||||
|
[][]schema.ACLQueryRule{
|
||||||
|
{
|
||||||
|
{Operator: "nop", Key: "example", Value: 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]AccessControlQuery{{Rules: []ObjectMatcher(nil)}},
|
||||||
|
[][]Object{{{}}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
actual := NewAccessControlQuery(tc.have)
|
||||||
|
assert.Equal(t, tc.expected, actual)
|
||||||
|
|
||||||
|
for i, rule := range actual {
|
||||||
|
for _, object := range tc.matches[i] {
|
||||||
|
assert.True(t, rule.IsMatch(object))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package authorization
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccessControlRule_MatchesSubjectExact(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
have *AccessControlRule
|
||||||
|
subject Subject
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ShouldNotMatchAnonymous",
|
||||||
|
&AccessControlRule{
|
||||||
|
Subjects: []AccessControlSubjects{
|
||||||
|
{[]SubjectMatcher{schemaSubjectToACLSubject("user:john")}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Subject{},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tc.expected, tc.have.MatchesSubjectExact(tc.subject))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1069,9 +1069,9 @@ func TestAuthorizerIsSecondFactorEnabledRuleWithOIDC(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
IdentityProviders: schema.IdentityProvidersConfiguration{
|
IdentityProviders: schema.IdentityProviders{
|
||||||
OIDC: &schema.OpenIDConnectConfiguration{
|
OIDC: &schema.OpenIDConnect{
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClient{
|
||||||
{
|
{
|
||||||
Policy: oneFactor,
|
Policy: oneFactor,
|
||||||
},
|
},
|
||||||
|
|
|
@ -16,7 +16,8 @@ type RegexpGroupStringSubjectMatcher struct {
|
||||||
|
|
||||||
// IsMatch returns true if the underlying pattern matches the input given the subject.
|
// IsMatch returns true if the underlying pattern matches the input given the subject.
|
||||||
func (r RegexpGroupStringSubjectMatcher) IsMatch(input string, subject Subject) (match bool) {
|
func (r RegexpGroupStringSubjectMatcher) IsMatch(input string, subject Subject) (match bool) {
|
||||||
if !r.Pattern.MatchString(input) {
|
matches := r.Pattern.FindStringSubmatch(input)
|
||||||
|
if matches == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,16 +25,11 @@ func (r RegexpGroupStringSubjectMatcher) IsMatch(input string, subject Subject)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
matches := r.Pattern.FindAllStringSubmatch(input, -1)
|
if r.SubexpNameUser != -1 && !strings.EqualFold(subject.Username, matches[r.SubexpNameUser]) {
|
||||||
if matches == nil {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.SubexpNameUser != -1 && !strings.EqualFold(subject.Username, matches[0][r.SubexpNameUser]) {
|
if r.SubexpNameGroup != -1 && !utils.IsStringInSliceFold(matches[r.SubexpNameGroup], subject.Groups) {
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.SubexpNameGroup != -1 && !utils.IsStringInSliceFold(matches[0][r.SubexpNameGroup], subject.Groups) {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
package authorization
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRegexpGroupStringSubjectMatcher_IsMatch(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
have *RegexpGroupStringSubjectMatcher
|
||||||
|
input string
|
||||||
|
subject Subject
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"Abc",
|
||||||
|
&RegexpGroupStringSubjectMatcher{
|
||||||
|
MustCompileRegexNoPtr(`^(?P<User>[a-zA-Z0-9]+)\.regex.com$`),
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
"example.com",
|
||||||
|
Subject{Username: "a-user"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tc.expected, tc.have.IsMatch(tc.input, tc.subject))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustCompileRegexNoPtr(input string) regexp.Regexp {
|
||||||
|
out := regexp.MustCompile(input)
|
||||||
|
|
||||||
|
return *out
|
||||||
|
}
|
|
@ -85,3 +85,33 @@ func TestShouldCleanURL(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRuleMatchResult_IsPotentialMatch(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
have RuleMatchResult
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ShouldNotMatch",
|
||||||
|
RuleMatchResult{},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldMatch",
|
||||||
|
RuleMatchResult{nil, true, true, true, true, true, true, true, false},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldMatchExact",
|
||||||
|
RuleMatchResult{nil, true, true, true, true, true, true, true, true},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tc.expected, tc.have.IsPotentialMatch())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package authorization
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -217,3 +218,76 @@ func TestIsAuthLevelSufficient(t *testing.T) {
|
||||||
assert.False(t, IsAuthLevelSufficient(authentication.OneFactor, TwoFactor))
|
assert.False(t, IsAuthLevelSufficient(authentication.OneFactor, TwoFactor))
|
||||||
assert.True(t, IsAuthLevelSufficient(authentication.TwoFactor, TwoFactor))
|
assert.True(t, IsAuthLevelSufficient(authentication.TwoFactor, TwoFactor))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStringSliceToRegexpSlice(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
have []string
|
||||||
|
expected []regexp.Regexp
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ShouldNotParseBadRegex",
|
||||||
|
[]string{`\q`},
|
||||||
|
[]regexp.Regexp(nil),
|
||||||
|
"error parsing regexp: invalid escape sequence: `\\q`",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
actual, theError := stringSliceToRegexpSlice(tc.have)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expected, actual)
|
||||||
|
|
||||||
|
if tc.err == "" {
|
||||||
|
assert.NoError(t, theError)
|
||||||
|
} else {
|
||||||
|
assert.EqualError(t, theError, tc.err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSchemaNetworksToACL(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
have []string
|
||||||
|
globals map[string][]*net.IPNet
|
||||||
|
cache map[string]*net.IPNet
|
||||||
|
expected []*net.IPNet
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ShouldLoadFromCache",
|
||||||
|
[]string{"192.168.0.0/24"},
|
||||||
|
nil,
|
||||||
|
map[string]*net.IPNet{"192.168.0.0/24": MustParseCIDR("192.168.0.0/24")},
|
||||||
|
[]*net.IPNet{MustParseCIDR("192.168.0.0/24")},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if tc.globals == nil {
|
||||||
|
tc.globals = map[string][]*net.IPNet{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.cache == nil {
|
||||||
|
tc.cache = map[string]*net.IPNet{}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := schemaNetworksToACL(tc.have, tc.globals, tc.cache)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expected, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustParseCIDR(input string) *net.IPNet {
|
||||||
|
_, out, err := net.ParseCIDR(input)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
|
@ -8,20 +8,20 @@ type Configuration struct {
|
||||||
DefaultRedirectionURL string `koanf:"default_redirection_url"`
|
DefaultRedirectionURL string `koanf:"default_redirection_url"`
|
||||||
Default2FAMethod string `koanf:"default_2fa_method"`
|
Default2FAMethod string `koanf:"default_2fa_method"`
|
||||||
|
|
||||||
Log LogConfiguration `koanf:"log"`
|
Log LogConfiguration `koanf:"log"`
|
||||||
IdentityProviders IdentityProvidersConfiguration `koanf:"identity_providers"`
|
IdentityProviders IdentityProviders `koanf:"identity_providers"`
|
||||||
AuthenticationBackend AuthenticationBackend `koanf:"authentication_backend"`
|
AuthenticationBackend AuthenticationBackend `koanf:"authentication_backend"`
|
||||||
Session SessionConfiguration `koanf:"session"`
|
Session SessionConfiguration `koanf:"session"`
|
||||||
TOTP TOTPConfiguration `koanf:"totp"`
|
TOTP TOTPConfiguration `koanf:"totp"`
|
||||||
DuoAPI DuoAPIConfiguration `koanf:"duo_api"`
|
DuoAPI DuoAPIConfiguration `koanf:"duo_api"`
|
||||||
AccessControl AccessControlConfiguration `koanf:"access_control"`
|
AccessControl AccessControlConfiguration `koanf:"access_control"`
|
||||||
NTP NTPConfiguration `koanf:"ntp"`
|
NTP NTPConfiguration `koanf:"ntp"`
|
||||||
Regulation RegulationConfiguration `koanf:"regulation"`
|
Regulation RegulationConfiguration `koanf:"regulation"`
|
||||||
Storage StorageConfiguration `koanf:"storage"`
|
Storage StorageConfiguration `koanf:"storage"`
|
||||||
Notifier NotifierConfiguration `koanf:"notifier"`
|
Notifier NotifierConfiguration `koanf:"notifier"`
|
||||||
Server ServerConfiguration `koanf:"server"`
|
Server ServerConfiguration `koanf:"server"`
|
||||||
Telemetry TelemetryConfig `koanf:"telemetry"`
|
Telemetry TelemetryConfig `koanf:"telemetry"`
|
||||||
WebAuthn WebAuthnConfiguration `koanf:"webauthn"`
|
WebAuthn WebAuthnConfiguration `koanf:"webauthn"`
|
||||||
PasswordPolicy PasswordPolicyConfiguration `koanf:"password_policy"`
|
PasswordPolicy PasswordPolicyConfiguration `koanf:"password_policy"`
|
||||||
PrivacyPolicy PrivacyPolicy `koanf:"privacy_policy"`
|
PrivacyPolicy PrivacyPolicy `koanf:"privacy_policy"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,13 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IdentityProvidersConfiguration represents the IdentityProviders 2.0 configuration for Authelia.
|
// IdentityProviders represents the Identity Providers configuration for Authelia.
|
||||||
type IdentityProvidersConfiguration struct {
|
type IdentityProviders struct {
|
||||||
OIDC *OpenIDConnectConfiguration `koanf:"oidc"`
|
OIDC *OpenIDConnect `koanf:"oidc"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenIDConnectConfiguration configuration for OpenID Connect.
|
// OpenIDConnect configuration for OpenID Connect 1.0.
|
||||||
type OpenIDConnectConfiguration struct {
|
type OpenIDConnect struct {
|
||||||
HMACSecret string `koanf:"hmac_secret"`
|
HMACSecret string `koanf:"hmac_secret"`
|
||||||
IssuerPrivateKeys []JWK `koanf:"issuer_private_keys"`
|
IssuerPrivateKeys []JWK `koanf:"issuer_private_keys"`
|
||||||
|
|
||||||
|
@ -30,36 +30,39 @@ type OpenIDConnectConfiguration struct {
|
||||||
EnforcePKCE string `koanf:"enforce_pkce"`
|
EnforcePKCE string `koanf:"enforce_pkce"`
|
||||||
EnablePKCEPlainChallenge bool `koanf:"enable_pkce_plain_challenge"`
|
EnablePKCEPlainChallenge bool `koanf:"enable_pkce_plain_challenge"`
|
||||||
|
|
||||||
PAR OpenIDConnectPARConfiguration `koanf:"pushed_authorizations"`
|
PAR OpenIDConnectPAR `koanf:"pushed_authorizations"`
|
||||||
CORS OpenIDConnectCORSConfiguration `koanf:"cors"`
|
CORS OpenIDConnectCORS `koanf:"cors"`
|
||||||
|
|
||||||
Clients []OpenIDConnectClientConfiguration `koanf:"clients"`
|
Clients []OpenIDConnectClient `koanf:"clients"`
|
||||||
|
|
||||||
Discovery OpenIDConnectDiscovery // MetaData value. Not configurable by users.
|
Discovery OpenIDConnectDiscovery // MetaData value. Not configurable by users.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenIDConnectDiscovery is information discovered during validation reused for the discovery handlers.
|
||||||
type OpenIDConnectDiscovery struct {
|
type OpenIDConnectDiscovery struct {
|
||||||
DefaultKeyID string
|
DefaultKeyIDs map[string]string
|
||||||
ResponseObjectSigningAlgs []string
|
DefaultKeyID string
|
||||||
RequestObjectSigningAlgs []string
|
ResponseObjectSigningKeyIDs []string
|
||||||
|
ResponseObjectSigningAlgs []string
|
||||||
|
RequestObjectSigningAlgs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenIDConnectPARConfiguration represents an OpenID Connect PAR config.
|
// OpenIDConnectPAR represents an OpenID Connect 1.0 PAR config.
|
||||||
type OpenIDConnectPARConfiguration struct {
|
type OpenIDConnectPAR struct {
|
||||||
Enforce bool `koanf:"enforce"`
|
Enforce bool `koanf:"enforce"`
|
||||||
ContextLifespan time.Duration `koanf:"context_lifespan"`
|
ContextLifespan time.Duration `koanf:"context_lifespan"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenIDConnectCORSConfiguration represents an OpenID Connect CORS config.
|
// OpenIDConnectCORS represents an OpenID Connect 1.0 CORS config.
|
||||||
type OpenIDConnectCORSConfiguration struct {
|
type OpenIDConnectCORS struct {
|
||||||
Endpoints []string `koanf:"endpoints"`
|
Endpoints []string `koanf:"endpoints"`
|
||||||
AllowedOrigins []url.URL `koanf:"allowed_origins"`
|
AllowedOrigins []url.URL `koanf:"allowed_origins"`
|
||||||
|
|
||||||
AllowedOriginsFromClientRedirectURIs bool `koanf:"allowed_origins_from_client_redirect_uris"`
|
AllowedOriginsFromClientRedirectURIs bool `koanf:"allowed_origins_from_client_redirect_uris"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenIDConnectClientConfiguration configuration for an OpenID Connect client.
|
// OpenIDConnectClient represents a configuration for an OpenID Connect 1.0 client.
|
||||||
type OpenIDConnectClientConfiguration struct {
|
type OpenIDConnectClient struct {
|
||||||
ID string `koanf:"id"`
|
ID string `koanf:"id"`
|
||||||
Description string `koanf:"description"`
|
Description string `koanf:"description"`
|
||||||
Secret *PasswordDigest `koanf:"secret"`
|
Secret *PasswordDigest `koanf:"secret"`
|
||||||
|
@ -84,25 +87,28 @@ type OpenIDConnectClientConfiguration struct {
|
||||||
|
|
||||||
PKCEChallengeMethod string `koanf:"pkce_challenge_method"`
|
PKCEChallengeMethod string `koanf:"pkce_challenge_method"`
|
||||||
|
|
||||||
TokenEndpointAuthMethod string `koanf:"token_endpoint_auth_method"`
|
|
||||||
|
|
||||||
TokenEndpointAuthSigningAlg string `koanf:"token_endpoint_auth_signing_alg"`
|
|
||||||
RequestObjectSigningAlg string `koanf:"request_object_signing_alg"`
|
|
||||||
IDTokenSigningAlg string `koanf:"id_token_signing_alg"`
|
IDTokenSigningAlg string `koanf:"id_token_signing_alg"`
|
||||||
|
IDTokenSigningKeyID string `koanf:"id_token_signing_key_id"`
|
||||||
UserinfoSigningAlg string `koanf:"userinfo_signing_alg"`
|
UserinfoSigningAlg string `koanf:"userinfo_signing_alg"`
|
||||||
|
UserinfoSigningKeyID string `koanf:"userinfo_signing_key_id"`
|
||||||
|
RequestObjectSigningAlg string `koanf:"request_object_signing_alg"`
|
||||||
|
TokenEndpointAuthSigningAlg string `koanf:"token_endpoint_auth_signing_alg"`
|
||||||
|
|
||||||
|
TokenEndpointAuthMethod string `koanf:"token_endpoint_auth_method"`
|
||||||
|
|
||||||
PublicKeys OpenIDConnectClientPublicKeys `koanf:"public_keys"`
|
PublicKeys OpenIDConnectClientPublicKeys `koanf:"public_keys"`
|
||||||
|
|
||||||
Discovery OpenIDConnectDiscovery
|
Discovery OpenIDConnectDiscovery
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenIDConnectClientPublicKeys represents the Client Public Keys configuration for an OpenID Connect 1.0 client.
|
||||||
type OpenIDConnectClientPublicKeys struct {
|
type OpenIDConnectClientPublicKeys struct {
|
||||||
URI *url.URL `koanf:"uri"`
|
URI *url.URL `koanf:"uri"`
|
||||||
Values []JWK `koanf:"values"`
|
Values []JWK `koanf:"values"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultOpenIDConnectConfiguration contains defaults for OIDC.
|
// DefaultOpenIDConnectConfiguration contains defaults for OIDC.
|
||||||
var DefaultOpenIDConnectConfiguration = OpenIDConnectConfiguration{
|
var DefaultOpenIDConnectConfiguration = OpenIDConnect{
|
||||||
AccessTokenLifespan: time.Hour,
|
AccessTokenLifespan: time.Hour,
|
||||||
AuthorizeCodeLifespan: time.Minute,
|
AuthorizeCodeLifespan: time.Minute,
|
||||||
IDTokenLifespan: time.Hour,
|
IDTokenLifespan: time.Hour,
|
||||||
|
@ -113,12 +119,11 @@ var DefaultOpenIDConnectConfiguration = OpenIDConnectConfiguration{
|
||||||
var defaultOIDCClientConsentPreConfiguredDuration = time.Hour * 24 * 7
|
var defaultOIDCClientConsentPreConfiguredDuration = time.Hour * 24 * 7
|
||||||
|
|
||||||
// DefaultOpenIDConnectClientConfiguration contains defaults for OIDC Clients.
|
// DefaultOpenIDConnectClientConfiguration contains defaults for OIDC Clients.
|
||||||
var DefaultOpenIDConnectClientConfiguration = OpenIDConnectClientConfiguration{
|
var DefaultOpenIDConnectClientConfiguration = OpenIDConnectClient{
|
||||||
Policy: "two_factor",
|
Policy: "two_factor",
|
||||||
Scopes: []string{"openid", "groups", "profile", "email"},
|
Scopes: []string{"openid", "groups", "profile", "email"},
|
||||||
ResponseTypes: []string{"code"},
|
ResponseTypes: []string{"code"},
|
||||||
ResponseModes: []string{"form_post"},
|
ResponseModes: []string{"form_post"},
|
||||||
|
|
||||||
IDTokenSigningAlg: "RS256",
|
IDTokenSigningAlg: "RS256",
|
||||||
UserinfoSigningAlg: "none",
|
UserinfoSigningAlg: "none",
|
||||||
ConsentMode: "auto",
|
ConsentMode: "auto",
|
||||||
|
|
|
@ -20,7 +20,7 @@ var Keys = []string{
|
||||||
"identity_providers.oidc.hmac_secret",
|
"identity_providers.oidc.hmac_secret",
|
||||||
"identity_providers.oidc.issuer_private_keys",
|
"identity_providers.oidc.issuer_private_keys",
|
||||||
"identity_providers.oidc.issuer_private_keys[].key_id",
|
"identity_providers.oidc.issuer_private_keys[].key_id",
|
||||||
"identity_providers.oidc.issuer_private_keys[]",
|
"identity_providers.oidc.issuer_private_keys[].use",
|
||||||
"identity_providers.oidc.issuer_private_keys[].algorithm",
|
"identity_providers.oidc.issuer_private_keys[].algorithm",
|
||||||
"identity_providers.oidc.issuer_private_keys[].key",
|
"identity_providers.oidc.issuer_private_keys[].key",
|
||||||
"identity_providers.oidc.issuer_private_keys[].certificate_chain",
|
"identity_providers.oidc.issuer_private_keys[].certificate_chain",
|
||||||
|
@ -57,15 +57,17 @@ var Keys = []string{
|
||||||
"identity_providers.oidc.clients[].enforce_par",
|
"identity_providers.oidc.clients[].enforce_par",
|
||||||
"identity_providers.oidc.clients[].enforce_pkce",
|
"identity_providers.oidc.clients[].enforce_pkce",
|
||||||
"identity_providers.oidc.clients[].pkce_challenge_method",
|
"identity_providers.oidc.clients[].pkce_challenge_method",
|
||||||
"identity_providers.oidc.clients[].token_endpoint_auth_method",
|
|
||||||
"identity_providers.oidc.clients[].token_endpoint_auth_signing_alg",
|
|
||||||
"identity_providers.oidc.clients[].request_object_signing_alg",
|
|
||||||
"identity_providers.oidc.clients[].id_token_signing_alg",
|
"identity_providers.oidc.clients[].id_token_signing_alg",
|
||||||
|
"identity_providers.oidc.clients[].id_token_signing_key_id",
|
||||||
"identity_providers.oidc.clients[].userinfo_signing_alg",
|
"identity_providers.oidc.clients[].userinfo_signing_alg",
|
||||||
|
"identity_providers.oidc.clients[].userinfo_signing_key_id",
|
||||||
|
"identity_providers.oidc.clients[].request_object_signing_alg",
|
||||||
|
"identity_providers.oidc.clients[].token_endpoint_auth_signing_alg",
|
||||||
|
"identity_providers.oidc.clients[].token_endpoint_auth_method",
|
||||||
"identity_providers.oidc.clients[].public_keys.uri",
|
"identity_providers.oidc.clients[].public_keys.uri",
|
||||||
"identity_providers.oidc.clients[].public_keys.values",
|
"identity_providers.oidc.clients[].public_keys.values",
|
||||||
"identity_providers.oidc.clients[].public_keys.values[].key_id",
|
"identity_providers.oidc.clients[].public_keys.values[].key_id",
|
||||||
"identity_providers.oidc.clients[].public_keys.values[]",
|
"identity_providers.oidc.clients[].public_keys.values[].use",
|
||||||
"identity_providers.oidc.clients[].public_keys.values[].algorithm",
|
"identity_providers.oidc.clients[].public_keys.values[].algorithm",
|
||||||
"identity_providers.oidc.clients[].public_keys.values[].key",
|
"identity_providers.oidc.clients[].public_keys.values[].key",
|
||||||
"identity_providers.oidc.clients[].public_keys.values[].certificate_chain",
|
"identity_providers.oidc.clients[].public_keys.values[].certificate_chain",
|
||||||
|
|
|
@ -37,8 +37,8 @@ type ServerBuffers struct {
|
||||||
|
|
||||||
// JWK represents a JWK.
|
// JWK represents a JWK.
|
||||||
type JWK struct {
|
type JWK struct {
|
||||||
KeyID string `koanf:"key_id"`
|
KeyID string `koanf:"key_id"`
|
||||||
Use string
|
Use string `koanf:"use"`
|
||||||
Algorithm string `koanf:"algorithm"`
|
Algorithm string `koanf:"algorithm"`
|
||||||
Key CryptographicKey `koanf:"key"`
|
Key CryptographicKey `koanf:"key"`
|
||||||
CertificateChain X509CertificateChain `koanf:"certificate_chain"`
|
CertificateChain X509CertificateChain `koanf:"certificate_chain"`
|
||||||
|
|
|
@ -147,13 +147,15 @@ const (
|
||||||
errFmtOIDCProviderNoPrivateKey = "identity_providers: oidc: option `issuer_private_keys` or 'issuer_private_key' is required"
|
errFmtOIDCProviderNoPrivateKey = "identity_providers: oidc: option `issuer_private_keys` or 'issuer_private_key' is required"
|
||||||
errFmtOIDCProviderEnforcePKCEInvalidValue = "identity_providers: oidc: option 'enforce_pkce' must be 'never', " +
|
errFmtOIDCProviderEnforcePKCEInvalidValue = "identity_providers: oidc: option 'enforce_pkce' must be 'never', " +
|
||||||
"'public_clients_only' or 'always', but it's configured as '%s'"
|
"'public_clients_only' or 'always', but it's configured as '%s'"
|
||||||
errFmtOIDCProviderInsecureParameterEntropy = "openid connect provider: SECURITY ISSUE - minimum parameter entropy is " +
|
errFmtOIDCProviderInsecureParameterEntropy = "identity_providers: oidc: option 'minimum_parameter_entropy' is " +
|
||||||
"configured to an unsafe value, it should be above 8 but it's configured to %d"
|
"configured to an unsafe and insecure value, it should at least be %d but it's configured to %d"
|
||||||
|
errFmtOIDCProviderInsecureDisabledParameterEntropy = "identity_providers: oidc: option 'minimum_parameter_entropy' is " +
|
||||||
|
"disabled which is considered unsafe and insecure"
|
||||||
errFmtOIDCProviderPrivateKeysInvalid = "identity_providers: oidc: issuer_private_keys: key #%d: option 'key' must be a valid private key but the provided data is malformed as it's missing the public key bits"
|
errFmtOIDCProviderPrivateKeysInvalid = "identity_providers: oidc: issuer_private_keys: key #%d: option 'key' must be a valid private key but the provided data is malformed as it's missing the public key bits"
|
||||||
errFmtOIDCProviderPrivateKeysCalcThumbprint = "identity_providers: oidc: issuer_private_keys: key #%d: option 'key' failed to calculate thumbprint to configure key id value: %w"
|
errFmtOIDCProviderPrivateKeysCalcThumbprint = "identity_providers: oidc: issuer_private_keys: key #%d: option 'key' failed to calculate thumbprint to configure key id value: %w"
|
||||||
errFmtOIDCProviderPrivateKeysKeyIDLength = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option `key_id`` must be 7 characters or less"
|
errFmtOIDCProviderPrivateKeysKeyIDLength = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option `key_id` must be 100 characters or less"
|
||||||
errFmtOIDCProviderPrivateKeysAttributeNotUnique = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option '%s' must be unique"
|
errFmtOIDCProviderPrivateKeysAttributeNotUnique = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option '%s' must be unique"
|
||||||
errFmtOIDCProviderPrivateKeysKeyIDNotAlphaNumeric = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option 'key_id' must only have alphanumeric characters"
|
errFmtOIDCProviderPrivateKeysKeyIDNotValid = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option 'key_id' must only contain RFC3986 unreserved characters and must only start and end with alphanumeric characters"
|
||||||
errFmtOIDCProviderPrivateKeysProperties = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option 'key' failed to get key properties: %w"
|
errFmtOIDCProviderPrivateKeysProperties = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option 'key' failed to get key properties: %w"
|
||||||
errFmtOIDCProviderPrivateKeysInvalidOptionOneOf = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option '%s' must be one of %s but it's configured as '%s'"
|
errFmtOIDCProviderPrivateKeysInvalidOptionOneOf = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option '%s' must be one of %s but it's configured as '%s'"
|
||||||
errFmtOIDCProviderPrivateKeysRSAKeyLessThan2048Bits = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option 'key' is an RSA %d bit private key but it must at minimum be a RSA 2048 bit private key"
|
errFmtOIDCProviderPrivateKeysRSAKeyLessThan2048Bits = "identity_providers: oidc: issuer_private_keys: key #%d with key id '%s': option 'key' is an RSA %d bit private key but it must at minimum be a RSA 2048 bit private key"
|
||||||
|
@ -436,7 +438,9 @@ const (
|
||||||
attrOIDCRedirectURIs = "redirect_uris"
|
attrOIDCRedirectURIs = "redirect_uris"
|
||||||
attrOIDCTokenAuthMethod = "token_endpoint_auth_method"
|
attrOIDCTokenAuthMethod = "token_endpoint_auth_method"
|
||||||
attrOIDCUsrSigAlg = "userinfo_signing_alg"
|
attrOIDCUsrSigAlg = "userinfo_signing_alg"
|
||||||
|
attrOIDCUsrSigKID = "userinfo_signing_key_id"
|
||||||
attrOIDCIDTokenSigAlg = "id_token_signing_alg"
|
attrOIDCIDTokenSigAlg = "id_token_signing_alg"
|
||||||
|
attrOIDCIDTokenSigKID = "id_token_signing_key_id"
|
||||||
attrOIDCPKCEChallengeMethod = "pkce_challenge_method"
|
attrOIDCPKCEChallengeMethod = "pkce_challenge_method"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -462,6 +466,7 @@ var (
|
||||||
reKeyReplacer = regexp.MustCompile(`\[\d+]`)
|
reKeyReplacer = regexp.MustCompile(`\[\d+]`)
|
||||||
reDomainCharacters = regexp.MustCompile(`^[a-z0-9-]+(\.[a-z0-9-]+)+[a-z0-9]$`)
|
reDomainCharacters = regexp.MustCompile(`^[a-z0-9-]+(\.[a-z0-9-]+)+[a-z0-9]$`)
|
||||||
reAuthzEndpointName = regexp.MustCompile(`^[a-zA-Z](([a-zA-Z0-9/._-]*)([a-zA-Z]))?$`)
|
reAuthzEndpointName = regexp.MustCompile(`^[a-zA-Z](([a-zA-Z0-9/._-]*)([a-zA-Z]))?$`)
|
||||||
|
reOpenIDConnectKID = regexp.MustCompile(`^([a-zA-Z0-9](([a-zA-Z0-9._~-]*)([a-zA-Z0-9]))?)?$`)
|
||||||
)
|
)
|
||||||
|
|
||||||
var replacedKeys = map[string]string{
|
var replacedKeys = map[string]string{
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
package validator
|
package validator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/ed25519"
|
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -12,7 +10,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
jose "gopkg.in/square/go-jose.v2"
|
"github.com/ory/fosite"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
"github.com/authelia/authelia/v4/internal/oidc"
|
"github.com/authelia/authelia/v4/internal/oidc"
|
||||||
|
@ -20,11 +18,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidateIdentityProviders validates and updates the IdentityProviders configuration.
|
// ValidateIdentityProviders validates and updates the IdentityProviders configuration.
|
||||||
func ValidateIdentityProviders(config *schema.IdentityProvidersConfiguration, val *schema.StructValidator) {
|
func ValidateIdentityProviders(config *schema.IdentityProviders, val *schema.StructValidator) {
|
||||||
validateOIDC(config.OIDC, val)
|
validateOIDC(config.OIDC, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDC(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
func validateOIDC(config *schema.OpenIDConnect, val *schema.StructValidator) {
|
||||||
if config == nil {
|
if config == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -35,8 +33,13 @@ func validateOIDC(config *schema.OpenIDConnectConfiguration, val *schema.StructV
|
||||||
|
|
||||||
sort.Sort(oidc.SortedSigningAlgs(config.Discovery.ResponseObjectSigningAlgs))
|
sort.Sort(oidc.SortedSigningAlgs(config.Discovery.ResponseObjectSigningAlgs))
|
||||||
|
|
||||||
if config.MinimumParameterEntropy != 0 && config.MinimumParameterEntropy < 8 {
|
switch {
|
||||||
val.PushWarning(fmt.Errorf(errFmtOIDCProviderInsecureParameterEntropy, config.MinimumParameterEntropy))
|
case config.MinimumParameterEntropy == -1:
|
||||||
|
val.PushWarning(fmt.Errorf(errFmtOIDCProviderInsecureDisabledParameterEntropy))
|
||||||
|
case config.MinimumParameterEntropy <= 0:
|
||||||
|
config.MinimumParameterEntropy = fosite.MinParameterEntropy
|
||||||
|
case config.MinimumParameterEntropy < fosite.MinParameterEntropy:
|
||||||
|
val.PushWarning(fmt.Errorf(errFmtOIDCProviderInsecureParameterEntropy, fosite.MinParameterEntropy, config.MinimumParameterEntropy))
|
||||||
}
|
}
|
||||||
|
|
||||||
switch config.EnforcePKCE {
|
switch config.EnforcePKCE {
|
||||||
|
@ -55,7 +58,7 @@ func validateOIDC(config *schema.OpenIDConnectConfiguration, val *schema.StructV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDCIssuer(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
func validateOIDCIssuer(config *schema.OpenIDConnect, val *schema.StructValidator) {
|
||||||
switch {
|
switch {
|
||||||
case config.IssuerPrivateKey != nil:
|
case config.IssuerPrivateKey != nil:
|
||||||
validateOIDCIssuerPrivateKey(config)
|
validateOIDCIssuerPrivateKey(config)
|
||||||
|
@ -68,7 +71,7 @@ func validateOIDCIssuer(config *schema.OpenIDConnectConfiguration, val *schema.S
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDCIssuerPrivateKey(config *schema.OpenIDConnectConfiguration) {
|
func validateOIDCIssuerPrivateKey(config *schema.OpenIDConnect) {
|
||||||
config.IssuerPrivateKeys = append([]schema.JWK{{
|
config.IssuerPrivateKeys = append([]schema.JWK{{
|
||||||
Algorithm: oidc.SigningAlgRSAUsingSHA256,
|
Algorithm: oidc.SigningAlgRSAUsingSHA256,
|
||||||
Use: oidc.KeyUseSignature,
|
Use: oidc.KeyUseSignature,
|
||||||
|
@ -77,34 +80,14 @@ func validateOIDCIssuerPrivateKey(config *schema.OpenIDConnectConfiguration) {
|
||||||
}}, config.IssuerPrivateKeys...)
|
}}, config.IssuerPrivateKeys...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func jwkCalculateThumbprint(key schema.CryptographicKey) (thumbprintStr string, err error) {
|
func validateOIDCIssuerPrivateKeys(config *schema.OpenIDConnect, val *schema.StructValidator) {
|
||||||
j := jose.JSONWebKey{}
|
|
||||||
|
|
||||||
switch k := key.(type) {
|
|
||||||
case schema.CryptographicPrivateKey:
|
|
||||||
j.Key = k.Public()
|
|
||||||
case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey:
|
|
||||||
j.Key = k
|
|
||||||
default:
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var thumbprint []byte
|
|
||||||
|
|
||||||
if thumbprint, err = j.Thumbprint(crypto.SHA256); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%x", thumbprint)[:6], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateOIDCIssuerPrivateKeys(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
|
||||||
var (
|
var (
|
||||||
props *JWKProperties
|
props *JWKProperties
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
kids := make([]string, len(config.IssuerPrivateKeys))
|
config.Discovery.ResponseObjectSigningKeyIDs = make([]string, len(config.IssuerPrivateKeys))
|
||||||
|
config.Discovery.DefaultKeyIDs = map[string]string{}
|
||||||
|
|
||||||
for i := 0; i < len(config.IssuerPrivateKeys); i++ {
|
for i := 0; i < len(config.IssuerPrivateKeys); i++ {
|
||||||
if key, ok := config.IssuerPrivateKeys[i].Key.(*rsa.PrivateKey); ok && key.PublicKey.N == nil {
|
if key, ok := config.IssuerPrivateKeys[i].Key.(*rsa.PrivateKey); ok && key.PublicKey.N == nil {
|
||||||
|
@ -120,18 +103,18 @@ func validateOIDCIssuerPrivateKeys(config *schema.OpenIDConnectConfiguration, va
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
case n > 7:
|
case n > 100:
|
||||||
val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysKeyIDLength, i+1, config.IssuerPrivateKeys[i].KeyID))
|
val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysKeyIDLength, i+1, config.IssuerPrivateKeys[i].KeyID))
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.IssuerPrivateKeys[i].KeyID != "" && utils.IsStringInSlice(config.IssuerPrivateKeys[i].KeyID, kids) {
|
if config.IssuerPrivateKeys[i].KeyID != "" && utils.IsStringInSlice(config.IssuerPrivateKeys[i].KeyID, config.Discovery.ResponseObjectSigningKeyIDs) {
|
||||||
val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysAttributeNotUnique, i+1, config.IssuerPrivateKeys[i].KeyID, attrOIDCKeyID))
|
val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysAttributeNotUnique, i+1, config.IssuerPrivateKeys[i].KeyID, attrOIDCKeyID))
|
||||||
}
|
}
|
||||||
|
|
||||||
kids[i] = config.IssuerPrivateKeys[i].KeyID
|
config.Discovery.ResponseObjectSigningKeyIDs[i] = config.IssuerPrivateKeys[i].KeyID
|
||||||
|
|
||||||
if !utils.IsStringAlphaNumeric(config.IssuerPrivateKeys[i].KeyID) {
|
if !reOpenIDConnectKID.MatchString(config.IssuerPrivateKeys[i].KeyID) {
|
||||||
val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysKeyIDNotAlphaNumeric, i+1, config.IssuerPrivateKeys[i].KeyID))
|
val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysKeyIDNotValid, i+1, config.IssuerPrivateKeys[i].KeyID))
|
||||||
}
|
}
|
||||||
|
|
||||||
if props, err = schemaJWKGetProperties(config.IssuerPrivateKeys[i]); err != nil {
|
if props, err = schemaJWKGetProperties(config.IssuerPrivateKeys[i]); err != nil {
|
||||||
|
@ -149,7 +132,7 @@ func validateOIDCIssuerPrivateKeys(config *schema.OpenIDConnectConfiguration, va
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDCIssuerPrivateKeysUseAlg(i int, props *JWKProperties, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
func validateOIDCIssuerPrivateKeysUseAlg(i int, props *JWKProperties, config *schema.OpenIDConnect, val *schema.StructValidator) {
|
||||||
switch config.IssuerPrivateKeys[i].Use {
|
switch config.IssuerPrivateKeys[i].Use {
|
||||||
case "":
|
case "":
|
||||||
config.IssuerPrivateKeys[i].Use = props.Use
|
config.IssuerPrivateKeys[i].Use = props.Use
|
||||||
|
@ -162,26 +145,26 @@ func validateOIDCIssuerPrivateKeysUseAlg(i int, props *JWKProperties, config *sc
|
||||||
switch {
|
switch {
|
||||||
case config.IssuerPrivateKeys[i].Algorithm == "":
|
case config.IssuerPrivateKeys[i].Algorithm == "":
|
||||||
config.IssuerPrivateKeys[i].Algorithm = props.Algorithm
|
config.IssuerPrivateKeys[i].Algorithm = props.Algorithm
|
||||||
|
|
||||||
|
fallthrough
|
||||||
case utils.IsStringInSlice(config.IssuerPrivateKeys[i].Algorithm, validOIDCIssuerJWKSigningAlgs):
|
case utils.IsStringInSlice(config.IssuerPrivateKeys[i].Algorithm, validOIDCIssuerJWKSigningAlgs):
|
||||||
break
|
if config.IssuerPrivateKeys[i].KeyID != "" && config.IssuerPrivateKeys[i].Algorithm != "" {
|
||||||
|
if _, ok := config.Discovery.DefaultKeyIDs[config.IssuerPrivateKeys[i].Algorithm]; !ok {
|
||||||
|
config.Discovery.DefaultKeyIDs[config.IssuerPrivateKeys[i].Algorithm] = config.IssuerPrivateKeys[i].KeyID
|
||||||
|
}
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysInvalidOptionOneOf, i+1, config.IssuerPrivateKeys[i].KeyID, attrOIDCAlgorithm, strJoinOr(validOIDCIssuerJWKSigningAlgs), config.IssuerPrivateKeys[i].Algorithm))
|
val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysInvalidOptionOneOf, i+1, config.IssuerPrivateKeys[i].KeyID, attrOIDCAlgorithm, strJoinOr(validOIDCIssuerJWKSigningAlgs), config.IssuerPrivateKeys[i].Algorithm))
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.IssuerPrivateKeys[i].Algorithm != "" {
|
if config.IssuerPrivateKeys[i].Algorithm != "" {
|
||||||
if utils.IsStringInSlice(config.IssuerPrivateKeys[i].Algorithm, config.Discovery.ResponseObjectSigningAlgs) {
|
if !utils.IsStringInSlice(config.IssuerPrivateKeys[i].Algorithm, config.Discovery.ResponseObjectSigningAlgs) {
|
||||||
val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysAttributeNotUnique, i+1, config.IssuerPrivateKeys[i].KeyID, attrOIDCAlgorithm))
|
|
||||||
} else {
|
|
||||||
config.Discovery.ResponseObjectSigningAlgs = append(config.Discovery.ResponseObjectSigningAlgs, config.IssuerPrivateKeys[i].Algorithm)
|
config.Discovery.ResponseObjectSigningAlgs = append(config.Discovery.ResponseObjectSigningAlgs, config.IssuerPrivateKeys[i].Algorithm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.IssuerPrivateKeys[i].Algorithm == oidc.SigningAlgRSAUsingSHA256 && config.Discovery.DefaultKeyID == "" {
|
|
||||||
config.Discovery.DefaultKeyID = config.IssuerPrivateKeys[i].KeyID
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDCIssuerPrivateKeyPair(i int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
func validateOIDCIssuerPrivateKeyPair(i int, config *schema.OpenIDConnect, val *schema.StructValidator) {
|
||||||
var (
|
var (
|
||||||
checkEqualKey bool
|
checkEqualKey bool
|
||||||
err error
|
err error
|
||||||
|
@ -213,7 +196,7 @@ func validateOIDCIssuerPrivateKeyPair(i int, config *schema.OpenIDConnectConfigu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setOIDCDefaults(config *schema.OpenIDConnectConfiguration) {
|
func setOIDCDefaults(config *schema.OpenIDConnect) {
|
||||||
if config.AccessTokenLifespan == time.Duration(0) {
|
if config.AccessTokenLifespan == time.Duration(0) {
|
||||||
config.AccessTokenLifespan = schema.DefaultOpenIDConnectConfiguration.AccessTokenLifespan
|
config.AccessTokenLifespan = schema.DefaultOpenIDConnectConfiguration.AccessTokenLifespan
|
||||||
}
|
}
|
||||||
|
@ -235,7 +218,7 @@ func setOIDCDefaults(config *schema.OpenIDConnectConfiguration) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDCOptionsCORS(config *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) {
|
func validateOIDCOptionsCORS(config *schema.OpenIDConnect, validator *schema.StructValidator) {
|
||||||
validateOIDCOptionsCORSAllowedOrigins(config, validator)
|
validateOIDCOptionsCORSAllowedOrigins(config, validator)
|
||||||
|
|
||||||
if config.CORS.AllowedOriginsFromClientRedirectURIs {
|
if config.CORS.AllowedOriginsFromClientRedirectURIs {
|
||||||
|
@ -245,7 +228,7 @@ func validateOIDCOptionsCORS(config *schema.OpenIDConnectConfiguration, validato
|
||||||
validateOIDCOptionsCORSEndpoints(config, validator)
|
validateOIDCOptionsCORSEndpoints(config, validator)
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDCOptionsCORSAllowedOrigins(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
func validateOIDCOptionsCORSAllowedOrigins(config *schema.OpenIDConnect, val *schema.StructValidator) {
|
||||||
for _, origin := range config.CORS.AllowedOrigins {
|
for _, origin := range config.CORS.AllowedOrigins {
|
||||||
if origin.String() == "*" {
|
if origin.String() == "*" {
|
||||||
if len(config.CORS.AllowedOrigins) != 1 {
|
if len(config.CORS.AllowedOrigins) != 1 {
|
||||||
|
@ -269,7 +252,7 @@ func validateOIDCOptionsCORSAllowedOrigins(config *schema.OpenIDConnectConfigura
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDCOptionsCORSAllowedOriginsFromClientRedirectURIs(config *schema.OpenIDConnectConfiguration) {
|
func validateOIDCOptionsCORSAllowedOriginsFromClientRedirectURIs(config *schema.OpenIDConnect) {
|
||||||
for _, client := range config.Clients {
|
for _, client := range config.Clients {
|
||||||
for _, redirectURI := range client.RedirectURIs {
|
for _, redirectURI := range client.RedirectURIs {
|
||||||
uri, err := url.ParseRequestURI(redirectURI)
|
uri, err := url.ParseRequestURI(redirectURI)
|
||||||
|
@ -286,7 +269,7 @@ func validateOIDCOptionsCORSAllowedOriginsFromClientRedirectURIs(config *schema.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDCOptionsCORSEndpoints(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
func validateOIDCOptionsCORSEndpoints(config *schema.OpenIDConnect, val *schema.StructValidator) {
|
||||||
for _, endpoint := range config.CORS.Endpoints {
|
for _, endpoint := range config.CORS.Endpoints {
|
||||||
if !utils.IsStringInSlice(endpoint, validOIDCCORSEndpoints) {
|
if !utils.IsStringInSlice(endpoint, validOIDCCORSEndpoints) {
|
||||||
val.Push(fmt.Errorf(errFmtOIDCCORSInvalidEndpoint, endpoint, strJoinOr(validOIDCCORSEndpoints)))
|
val.Push(fmt.Errorf(errFmtOIDCCORSInvalidEndpoint, endpoint, strJoinOr(validOIDCCORSEndpoints)))
|
||||||
|
@ -294,7 +277,7 @@ func validateOIDCOptionsCORSEndpoints(config *schema.OpenIDConnectConfiguration,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDCClients(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
func validateOIDCClients(config *schema.OpenIDConnect, val *schema.StructValidator) {
|
||||||
var (
|
var (
|
||||||
errDeprecated bool
|
errDeprecated bool
|
||||||
|
|
||||||
|
@ -336,7 +319,7 @@ func validateOIDCClients(config *schema.OpenIDConnectConfiguration, val *schema.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDCClient(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator, errDeprecatedFunc func()) {
|
func validateOIDCClient(c int, config *schema.OpenIDConnect, val *schema.StructValidator, errDeprecatedFunc func()) {
|
||||||
if config.Clients[c].Public {
|
if config.Clients[c].Public {
|
||||||
if config.Clients[c].Secret != nil {
|
if config.Clients[c].Secret != nil {
|
||||||
val.Push(fmt.Errorf(errFmtOIDCClientPublicInvalidSecret, config.Clients[c].ID))
|
val.Push(fmt.Errorf(errFmtOIDCClientPublicInvalidSecret, config.Clients[c].ID))
|
||||||
|
@ -386,7 +369,7 @@ func validateOIDCClient(c int, config *schema.OpenIDConnectConfiguration, val *s
|
||||||
validateOIDCClientTokenEndpointAuth(c, config, val)
|
validateOIDCClientTokenEndpointAuth(c, config, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDCClientPublicKeys(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
func validateOIDCClientPublicKeys(c int, config *schema.OpenIDConnect, val *schema.StructValidator) {
|
||||||
switch {
|
switch {
|
||||||
case config.Clients[c].PublicKeys.URI != nil && len(config.Clients[c].PublicKeys.Values) != 0:
|
case config.Clients[c].PublicKeys.URI != nil && len(config.Clients[c].PublicKeys.Values) != 0:
|
||||||
val.Push(fmt.Errorf(errFmtOIDCClientPublicKeysBothURIAndValuesConfigured, config.Clients[c].ID))
|
val.Push(fmt.Errorf(errFmtOIDCClientPublicKeysBothURIAndValuesConfigured, config.Clients[c].ID))
|
||||||
|
@ -399,7 +382,7 @@ func validateOIDCClientPublicKeys(c int, config *schema.OpenIDConnectConfigurati
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDCClientJSONWebKeysList(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
func validateOIDCClientJSONWebKeysList(c int, config *schema.OpenIDConnect, val *schema.StructValidator) {
|
||||||
var (
|
var (
|
||||||
props *JWKProperties
|
props *JWKProperties
|
||||||
err error
|
err error
|
||||||
|
@ -457,7 +440,7 @@ func validateOIDCClientJSONWebKeysList(c int, config *schema.OpenIDConnectConfig
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDCClientJSONWebKeysListKeyUseAlg(c, i int, props *JWKProperties, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
func validateOIDCClientJSONWebKeysListKeyUseAlg(c, i int, props *JWKProperties, config *schema.OpenIDConnect, val *schema.StructValidator) {
|
||||||
switch config.Clients[c].PublicKeys.Values[i].Use {
|
switch config.Clients[c].PublicKeys.Values[i].Use {
|
||||||
case "":
|
case "":
|
||||||
config.Clients[c].PublicKeys.Values[i].Use = props.Use
|
config.Clients[c].PublicKeys.Values[i].Use = props.Use
|
||||||
|
@ -487,7 +470,7 @@ func validateOIDCClientJSONWebKeysListKeyUseAlg(c, i int, props *JWKProperties,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDCClientSectorIdentifier(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
func validateOIDCClientSectorIdentifier(c int, config *schema.OpenIDConnect, val *schema.StructValidator) {
|
||||||
if config.Clients[c].SectorIdentifier.String() != "" {
|
if config.Clients[c].SectorIdentifier.String() != "" {
|
||||||
if utils.IsURLHostComponent(config.Clients[c].SectorIdentifier) || utils.IsURLHostComponentWithPort(config.Clients[c].SectorIdentifier) {
|
if utils.IsURLHostComponent(config.Clients[c].SectorIdentifier) || utils.IsURLHostComponentWithPort(config.Clients[c].SectorIdentifier) {
|
||||||
return
|
return
|
||||||
|
@ -523,7 +506,7 @@ func validateOIDCClientSectorIdentifier(c int, config *schema.OpenIDConnectConfi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDCClientConsentMode(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
func validateOIDCClientConsentMode(c int, config *schema.OpenIDConnect, val *schema.StructValidator) {
|
||||||
switch {
|
switch {
|
||||||
case utils.IsStringInSlice(config.Clients[c].ConsentMode, []string{"", auto}):
|
case utils.IsStringInSlice(config.Clients[c].ConsentMode, []string{"", auto}):
|
||||||
if config.Clients[c].ConsentPreConfiguredDuration != nil {
|
if config.Clients[c].ConsentPreConfiguredDuration != nil {
|
||||||
|
@ -542,7 +525,7 @@ func validateOIDCClientConsentMode(c int, config *schema.OpenIDConnectConfigurat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDCClientScopes(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator, errDeprecatedFunc func()) {
|
func validateOIDCClientScopes(c int, config *schema.OpenIDConnect, val *schema.StructValidator, errDeprecatedFunc func()) {
|
||||||
if len(config.Clients[c].Scopes) == 0 {
|
if len(config.Clients[c].Scopes) == 0 {
|
||||||
config.Clients[c].Scopes = schema.DefaultOpenIDConnectClientConfiguration.Scopes
|
config.Clients[c].Scopes = schema.DefaultOpenIDConnectClientConfiguration.Scopes
|
||||||
}
|
}
|
||||||
|
@ -575,7 +558,7 @@ func validateOIDCClientScopes(c int, config *schema.OpenIDConnectConfiguration,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDCClientResponseTypes(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator, errDeprecatedFunc func()) {
|
func validateOIDCClientResponseTypes(c int, config *schema.OpenIDConnect, val *schema.StructValidator, errDeprecatedFunc func()) {
|
||||||
if len(config.Clients[c].ResponseTypes) == 0 {
|
if len(config.Clients[c].ResponseTypes) == 0 {
|
||||||
config.Clients[c].ResponseTypes = schema.DefaultOpenIDConnectClientConfiguration.ResponseTypes
|
config.Clients[c].ResponseTypes = schema.DefaultOpenIDConnectClientConfiguration.ResponseTypes
|
||||||
}
|
}
|
||||||
|
@ -593,7 +576,7 @@ func validateOIDCClientResponseTypes(c int, config *schema.OpenIDConnectConfigur
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDCClientResponseModes(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator, errDeprecatedFunc func()) {
|
func validateOIDCClientResponseModes(c int, config *schema.OpenIDConnect, val *schema.StructValidator, errDeprecatedFunc func()) {
|
||||||
if len(config.Clients[c].ResponseModes) == 0 {
|
if len(config.Clients[c].ResponseModes) == 0 {
|
||||||
config.Clients[c].ResponseModes = schema.DefaultOpenIDConnectClientConfiguration.ResponseModes
|
config.Clients[c].ResponseModes = schema.DefaultOpenIDConnectClientConfiguration.ResponseModes
|
||||||
|
|
||||||
|
@ -625,7 +608,7 @@ func validateOIDCClientResponseModes(c int, config *schema.OpenIDConnectConfigur
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDCClientGrantTypes(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator, errDeprecatedFunc func()) {
|
func validateOIDCClientGrantTypes(c int, config *schema.OpenIDConnect, val *schema.StructValidator, errDeprecatedFunc func()) {
|
||||||
if len(config.Clients[c].GrantTypes) == 0 {
|
if len(config.Clients[c].GrantTypes) == 0 {
|
||||||
validateOIDCClientGrantTypesSetDefaults(c, config)
|
validateOIDCClientGrantTypesSetDefaults(c, config)
|
||||||
}
|
}
|
||||||
|
@ -645,7 +628,7 @@ func validateOIDCClientGrantTypes(c int, config *schema.OpenIDConnectConfigurati
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDCClientGrantTypesSetDefaults(c int, config *schema.OpenIDConnectConfiguration) {
|
func validateOIDCClientGrantTypesSetDefaults(c int, config *schema.OpenIDConnect) {
|
||||||
for _, responseType := range config.Clients[c].ResponseTypes {
|
for _, responseType := range config.Clients[c].ResponseTypes {
|
||||||
switch responseType {
|
switch responseType {
|
||||||
case oidc.ResponseTypeAuthorizationCodeFlow:
|
case oidc.ResponseTypeAuthorizationCodeFlow:
|
||||||
|
@ -668,7 +651,7 @@ func validateOIDCClientGrantTypesSetDefaults(c int, config *schema.OpenIDConnect
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDCClientGrantTypesCheckRelated(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator, errDeprecatedFunc func()) {
|
func validateOIDCClientGrantTypesCheckRelated(c int, config *schema.OpenIDConnect, val *schema.StructValidator, errDeprecatedFunc func()) {
|
||||||
for _, grantType := range config.Clients[c].GrantTypes {
|
for _, grantType := range config.Clients[c].GrantTypes {
|
||||||
switch grantType {
|
switch grantType {
|
||||||
case oidc.GrantTypeImplicit:
|
case oidc.GrantTypeImplicit:
|
||||||
|
@ -703,7 +686,7 @@ func validateOIDCClientGrantTypesCheckRelated(c int, config *schema.OpenIDConnec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDCClientRedirectURIs(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator, errDeprecatedFunc func()) {
|
func validateOIDCClientRedirectURIs(c int, config *schema.OpenIDConnect, val *schema.StructValidator, errDeprecatedFunc func()) {
|
||||||
var (
|
var (
|
||||||
parsedRedirectURI *url.URL
|
parsedRedirectURI *url.URL
|
||||||
err error
|
err error
|
||||||
|
@ -740,7 +723,7 @@ func validateOIDCClientRedirectURIs(c int, config *schema.OpenIDConnectConfigura
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDCClientTokenEndpointAuth(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
func validateOIDCClientTokenEndpointAuth(c int, config *schema.OpenIDConnect, val *schema.StructValidator) {
|
||||||
implcit := len(config.Clients[c].ResponseTypes) != 0 && utils.IsStringSliceContainsAll(config.Clients[c].ResponseTypes, validOIDCClientResponseTypesImplicitFlow)
|
implcit := len(config.Clients[c].ResponseTypes) != 0 && utils.IsStringSliceContainsAll(config.Clients[c].ResponseTypes, validOIDCClientResponseTypesImplicitFlow)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
@ -767,7 +750,7 @@ func validateOIDCClientTokenEndpointAuth(c int, config *schema.OpenIDConnectConf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDCClientTokenEndpointAuthClientSecretJWT(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
func validateOIDCClientTokenEndpointAuthClientSecretJWT(c int, config *schema.OpenIDConnect, val *schema.StructValidator) {
|
||||||
switch {
|
switch {
|
||||||
case config.Clients[c].TokenEndpointAuthSigningAlg == "":
|
case config.Clients[c].TokenEndpointAuthSigningAlg == "":
|
||||||
config.Clients[c].TokenEndpointAuthSigningAlg = oidc.SigningAlgHMACUsingSHA256
|
config.Clients[c].TokenEndpointAuthSigningAlg = oidc.SigningAlgHMACUsingSHA256
|
||||||
|
@ -776,7 +759,7 @@ func validateOIDCClientTokenEndpointAuthClientSecretJWT(c int, config *schema.Op
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDCClientTokenEndpointAuthPublicKeyJWT(config schema.OpenIDConnectClientConfiguration, val *schema.StructValidator) {
|
func validateOIDCClientTokenEndpointAuthPublicKeyJWT(config schema.OpenIDConnectClient, val *schema.StructValidator) {
|
||||||
switch {
|
switch {
|
||||||
case config.TokenEndpointAuthSigningAlg == "":
|
case config.TokenEndpointAuthSigningAlg == "":
|
||||||
val.Push(fmt.Errorf(errFmtOIDCClientInvalidTokenEndpointAuthSigAlgMissingPrivateKeyJWT, config.ID))
|
val.Push(fmt.Errorf(errFmtOIDCClientInvalidTokenEndpointAuthSigAlgMissingPrivateKeyJWT, config.ID))
|
||||||
|
@ -793,18 +776,38 @@ func validateOIDCClientTokenEndpointAuthPublicKeyJWT(config schema.OpenIDConnect
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDDClientSigningAlgs(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
func validateOIDDClientSigningAlgs(c int, config *schema.OpenIDConnect, val *schema.StructValidator) {
|
||||||
if config.Clients[c].UserinfoSigningAlg == "" {
|
switch config.Clients[c].UserinfoSigningKeyID {
|
||||||
config.Clients[c].UserinfoSigningAlg = schema.DefaultOpenIDConnectClientConfiguration.UserinfoSigningAlg
|
case "":
|
||||||
} else if config.Clients[c].UserinfoSigningAlg != oidc.SigningAlgNone && !utils.IsStringInSlice(config.Clients[c].UserinfoSigningAlg, config.Discovery.ResponseObjectSigningAlgs) {
|
if config.Clients[c].UserinfoSigningAlg == "" {
|
||||||
val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue,
|
config.Clients[c].UserinfoSigningAlg = schema.DefaultOpenIDConnectClientConfiguration.UserinfoSigningAlg
|
||||||
config.Clients[c].ID, attrOIDCUsrSigAlg, strJoinOr(append(config.Discovery.ResponseObjectSigningAlgs, oidc.SigningAlgNone)), config.Clients[c].UserinfoSigningAlg))
|
} else if config.Clients[c].UserinfoSigningAlg != oidc.SigningAlgNone && !utils.IsStringInSlice(config.Clients[c].UserinfoSigningAlg, config.Discovery.ResponseObjectSigningAlgs) {
|
||||||
|
val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue,
|
||||||
|
config.Clients[c].ID, attrOIDCUsrSigAlg, strJoinOr(append(config.Discovery.ResponseObjectSigningAlgs, oidc.SigningAlgNone)), config.Clients[c].UserinfoSigningAlg))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if !utils.IsStringInSlice(config.Clients[c].UserinfoSigningKeyID, config.Discovery.ResponseObjectSigningKeyIDs) {
|
||||||
|
val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue,
|
||||||
|
config.Clients[c].ID, attrOIDCUsrSigKID, strJoinOr(config.Discovery.ResponseObjectSigningKeyIDs), config.Clients[c].UserinfoSigningKeyID))
|
||||||
|
} else {
|
||||||
|
config.Clients[c].UserinfoSigningAlg = getResponseObjectAlgFromKID(config, config.Clients[c].UserinfoSigningKeyID, config.Clients[c].UserinfoSigningAlg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Clients[c].IDTokenSigningAlg == "" {
|
switch config.Clients[c].IDTokenSigningKeyID {
|
||||||
config.Clients[c].IDTokenSigningAlg = schema.DefaultOpenIDConnectClientConfiguration.IDTokenSigningAlg
|
case "":
|
||||||
} else if !utils.IsStringInSlice(config.Clients[c].IDTokenSigningAlg, config.Discovery.ResponseObjectSigningAlgs) {
|
if config.Clients[c].IDTokenSigningAlg == "" {
|
||||||
val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue,
|
config.Clients[c].IDTokenSigningAlg = schema.DefaultOpenIDConnectClientConfiguration.IDTokenSigningAlg
|
||||||
config.Clients[c].ID, attrOIDCIDTokenSigAlg, strJoinOr(config.Discovery.ResponseObjectSigningAlgs), config.Clients[c].IDTokenSigningAlg))
|
} else if !utils.IsStringInSlice(config.Clients[c].IDTokenSigningAlg, config.Discovery.ResponseObjectSigningAlgs) {
|
||||||
|
val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue,
|
||||||
|
config.Clients[c].ID, attrOIDCIDTokenSigAlg, strJoinOr(config.Discovery.ResponseObjectSigningAlgs), config.Clients[c].IDTokenSigningAlg))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if !utils.IsStringInSlice(config.Clients[c].IDTokenSigningKeyID, config.Discovery.ResponseObjectSigningKeyIDs) {
|
||||||
|
val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue,
|
||||||
|
config.Clients[c].ID, attrOIDCIDTokenSigKID, strJoinOr(config.Discovery.ResponseObjectSigningKeyIDs), config.Clients[c].IDTokenSigningKeyID))
|
||||||
|
} else {
|
||||||
|
config.Clients[c].IDTokenSigningAlg = getResponseObjectAlgFromKID(config, config.Clients[c].IDTokenSigningKeyID, config.Clients[c].IDTokenSigningAlg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,7 @@
|
||||||
package validator
|
package validator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
|
@ -9,6 +10,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/net/publicsuffix"
|
"golang.org/x/net/publicsuffix"
|
||||||
|
"gopkg.in/square/go-jose.v2"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
"github.com/authelia/authelia/v4/internal/oidc"
|
"github.com/authelia/authelia/v4/internal/oidc"
|
||||||
|
@ -166,3 +168,34 @@ func schemaJWKGetProperties(jwk schema.JWK) (properties *JWKProperties, err erro
|
||||||
return nil, fmt.Errorf("the key type '%T' is unknown or not valid for the configuration", key)
|
return nil, fmt.Errorf("the key type '%T' is unknown or not valid for the configuration", key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func jwkCalculateThumbprint(key schema.CryptographicKey) (thumbprintStr string, err error) {
|
||||||
|
j := jose.JSONWebKey{}
|
||||||
|
|
||||||
|
switch k := key.(type) {
|
||||||
|
case schema.CryptographicPrivateKey:
|
||||||
|
j.Key = k.Public()
|
||||||
|
case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey:
|
||||||
|
j.Key = k
|
||||||
|
default:
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var thumbprint []byte
|
||||||
|
|
||||||
|
if thumbprint, err = j.Thumbprint(crypto.SHA256); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%x", thumbprint)[:6], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getResponseObjectAlgFromKID(config *schema.OpenIDConnect, kid, alg string) string {
|
||||||
|
for _, jwk := range config.IssuerPrivateKeys {
|
||||||
|
if kid == jwk.KeyID {
|
||||||
|
return jwk.Algorithm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return alg
|
||||||
|
}
|
||||||
|
|
|
@ -79,3 +79,16 @@ func TestSchemaJWKGetPropertiesMissingTests(t *testing.T) {
|
||||||
assert.Equal(t, nil, props.Curve)
|
assert.Equal(t, nil, props.Curve)
|
||||||
assert.Equal(t, 0, props.Bits)
|
assert.Equal(t, 0, props.Bits)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetResponseObjectAlgFromKID(t *testing.T) {
|
||||||
|
c := &schema.OpenIDConnect{
|
||||||
|
IssuerPrivateKeys: []schema.JWK{
|
||||||
|
{KeyID: "abc", Algorithm: "EX256"},
|
||||||
|
{KeyID: "123", Algorithm: "EX512"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, "EX256", getResponseObjectAlgFromKID(c, "abc", "not"))
|
||||||
|
assert.Equal(t, "EX512", getResponseObjectAlgFromKID(c, "123", "not"))
|
||||||
|
assert.Equal(t, "not", getResponseObjectAlgFromKID(c, "111111", "not"))
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAuthzBuilder_WithConfig(t *testing.T) {
|
||||||
|
builder := NewAuthzBuilder()
|
||||||
|
|
||||||
|
builder.WithConfig(&schema.Configuration{
|
||||||
|
AuthenticationBackend: schema.AuthenticationBackend{
|
||||||
|
RefreshInterval: "always",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, time.Second*0, builder.config.RefreshInterval)
|
||||||
|
|
||||||
|
builder.WithConfig(&schema.Configuration{
|
||||||
|
AuthenticationBackend: schema.AuthenticationBackend{
|
||||||
|
RefreshInterval: "disable",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, time.Second*-1, builder.config.RefreshInterval)
|
||||||
|
|
||||||
|
builder.WithConfig(&schema.Configuration{
|
||||||
|
AuthenticationBackend: schema.AuthenticationBackend{
|
||||||
|
RefreshInterval: "1m",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, time.Minute, builder.config.RefreshInterval)
|
||||||
|
|
||||||
|
builder.WithConfig(nil)
|
||||||
|
|
||||||
|
assert.Equal(t, time.Minute, builder.config.RefreshInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthzBuilder_WithEndpointConfig(t *testing.T) {
|
||||||
|
builder := NewAuthzBuilder()
|
||||||
|
|
||||||
|
builder.WithEndpointConfig(schema.ServerAuthzEndpoint{
|
||||||
|
Implementation: "ExtAuthz",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, AuthzImplExtAuthz, builder.implementation)
|
||||||
|
|
||||||
|
builder.WithEndpointConfig(schema.ServerAuthzEndpoint{
|
||||||
|
Implementation: "ForwardAuth",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, AuthzImplForwardAuth, builder.implementation)
|
||||||
|
|
||||||
|
builder.WithEndpointConfig(schema.ServerAuthzEndpoint{
|
||||||
|
Implementation: "AuthRequest",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, AuthzImplAuthRequest, builder.implementation)
|
||||||
|
|
||||||
|
builder.WithEndpointConfig(schema.ServerAuthzEndpoint{
|
||||||
|
Implementation: "Legacy",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, AuthzImplLegacy, builder.implementation)
|
||||||
|
|
||||||
|
builder.WithEndpointConfig(schema.ServerAuthzEndpoint{
|
||||||
|
Implementation: "ExtAuthz",
|
||||||
|
AuthnStrategies: []schema.ServerAuthzEndpointAuthnStrategy{
|
||||||
|
{Name: "HeaderProxyAuthorization"},
|
||||||
|
{Name: "CookieSession"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Len(t, builder.strategies, 2)
|
||||||
|
|
||||||
|
builder.WithEndpointConfig(schema.ServerAuthzEndpoint{
|
||||||
|
Implementation: "ExtAuthz",
|
||||||
|
AuthnStrategies: []schema.ServerAuthzEndpointAuthnStrategy{
|
||||||
|
{Name: "HeaderAuthorization"},
|
||||||
|
{Name: "HeaderProxyAuthorization"},
|
||||||
|
{Name: "HeaderAuthRequestProxyAuthorization"},
|
||||||
|
{Name: "HeaderLegacy"},
|
||||||
|
{Name: "CookieSession"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Len(t, builder.strategies, 5)
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/valyala/fasthttp"
|
||||||
|
|
||||||
|
"github.com/authelia/authelia/v4/internal/authentication"
|
||||||
|
"github.com/authelia/authelia/v4/internal/mocks"
|
||||||
|
"github.com/authelia/authelia/v4/internal/session"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAuthzImplementation(t *testing.T) {
|
||||||
|
assert.Equal(t, "Legacy", AuthzImplLegacy.String())
|
||||||
|
assert.Equal(t, "", AuthzImplementation(-1).String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFriendlyMethod(t *testing.T) {
|
||||||
|
assert.Equal(t, "unknown", friendlyMethod(""))
|
||||||
|
assert.Equal(t, "GET", friendlyMethod(fasthttp.MethodGet))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateVerifySessionHasUpToDateProfileTraceLogs(t *testing.T) {
|
||||||
|
mock := mocks.NewMockAutheliaCtx(t)
|
||||||
|
|
||||||
|
generateVerifySessionHasUpToDateProfileTraceLogs(mock.Ctx, &session.UserSession{Username: "john", DisplayName: "example", Groups: []string{"abc"}, Emails: []string{"user@example.com", "test@example.com"}}, &authentication.UserDetails{Username: "john", Groups: []string{"123"}, DisplayName: "notexample", Emails: []string{"notuser@example.com"}})
|
||||||
|
generateVerifySessionHasUpToDateProfileTraceLogs(mock.Ctx, &session.UserSession{Username: "john", DisplayName: "example"}, &authentication.UserDetails{Username: "john", DisplayName: "example"})
|
||||||
|
generateVerifySessionHasUpToDateProfileTraceLogs(mock.Ctx, &session.UserSession{Username: "john", DisplayName: "example", Emails: []string{"abc@example.com"}}, &authentication.UserDetails{Username: "john", DisplayName: "example"})
|
||||||
|
generateVerifySessionHasUpToDateProfileTraceLogs(mock.Ctx, &session.UserSession{Username: "john", DisplayName: "example"}, &authentication.UserDetails{Username: "john", DisplayName: "example", Emails: []string{"abc@example.com"}})
|
||||||
|
}
|
|
@ -127,8 +127,7 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr
|
||||||
|
|
||||||
ctx.Logger.Debugf("Authorization Request with id '%s' on client with id '%s' was successfully processed, proceeding to build Authorization Response", requester.GetID(), clientID)
|
ctx.Logger.Debugf("Authorization Request with id '%s' on client with id '%s' was successfully processed, proceeding to build Authorization Response", requester.GetID(), clientID)
|
||||||
|
|
||||||
session := oidc.NewSessionWithAuthorizeRequest(issuer, ctx.Providers.OpenIDConnect.KeyManager.GetKeyIDFromAlg(ctx, client.GetIDTokenSigningAlg()),
|
session := oidc.NewSessionWithAuthorizeRequest(issuer, ctx.Providers.OpenIDConnect.KeyManager.GetKeyID(ctx, client.GetIDTokenSigningKeyID(), client.GetIDTokenSigningAlg()), userSession.Username, userSession.AuthenticationMethodRefs.MarshalRFC8176(), extraClaims, authTime, consent, requester)
|
||||||
userSession.Username, userSession.AuthenticationMethodRefs.MarshalRFC8176(), extraClaims, authTime, consent, requester)
|
|
||||||
|
|
||||||
ctx.Logger.Tracef("Authorization Request with id '%s' on client with id '%s' creating session for Authorization Response for subject '%s' with username '%s' with claims: %+v",
|
ctx.Logger.Tracef("Authorization Request with id '%s' on client with id '%s' creating session for Authorization Response for subject '%s' with username '%s' with claims: %+v",
|
||||||
requester.GetID(), session.ClientID, session.Subject, session.Username, session.Claims)
|
requester.GetID(), session.ClientID, session.Subject, session.Username, session.Claims)
|
||||||
|
|
|
@ -105,7 +105,7 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
|
||||||
default:
|
default:
|
||||||
var jwk *oidc.JWK
|
var jwk *oidc.JWK
|
||||||
|
|
||||||
if jwk = ctx.Providers.OpenIDConnect.KeyManager.GetByAlg(ctx, alg); jwk == nil {
|
if jwk = ctx.Providers.OpenIDConnect.KeyManager.Get(ctx, client.GetUserinfoSigningKeyID(), alg); jwk == nil {
|
||||||
ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(fosite.ErrServerError.WithHintf("Unsupported UserInfo signing algorithm '%s'.", alg)))
|
ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(fosite.ErrServerError.WithHintf("Unsupported UserInfo signing algorithm '%s'.", alg)))
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewPrometheus(t *testing.T) {
|
||||||
|
p := NewPrometheus()
|
||||||
|
|
||||||
|
assert.NotNil(t, p)
|
||||||
|
|
||||||
|
p.RecordRequest("400", "GET", time.Second)
|
||||||
|
p.RecordAuthz("400")
|
||||||
|
p.RecordAuthn(true, false, "WebAuthn")
|
||||||
|
p.RecordAuthn(true, false, "1fa")
|
||||||
|
p.RecordAuthenticationDuration(true, time.Second)
|
||||||
|
}
|
|
@ -154,6 +154,8 @@ func TestIssuerURL(t *testing.T) {
|
||||||
|
|
||||||
func TestShouldCallNextWithAutheliaCtx(t *testing.T) {
|
func TestShouldCallNextWithAutheliaCtx(t *testing.T) {
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
ctx := &fasthttp.RequestCtx{}
|
ctx := &fasthttp.RequestCtx{}
|
||||||
configuration := schema.Configuration{}
|
configuration := schema.Configuration{}
|
||||||
userProvider := mocks.NewMockUserProvider(ctrl)
|
userProvider := mocks.NewMockUserProvider(ctrl)
|
||||||
|
|
|
@ -199,12 +199,12 @@ func (s *ClientAuthenticationStrategySuite) SetupTest() {
|
||||||
|
|
||||||
secret := tOpenIDConnectPlainTextClientSecret
|
secret := tOpenIDConnectPlainTextClientSecret
|
||||||
|
|
||||||
s.provider = oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
s.provider = oidc.NewOpenIDConnectProvider(&schema.OpenIDConnect{
|
||||||
IssuerPrivateKeys: []schema.JWK{
|
IssuerPrivateKeys: []schema.JWK{
|
||||||
{Key: keyRSA2048, CertificateChain: certRSA2048, Use: oidc.KeyUseSignature, Algorithm: oidc.SigningAlgRSAUsingSHA256},
|
{Key: keyRSA2048, CertificateChain: certRSA2048, Use: oidc.KeyUseSignature, Algorithm: oidc.SigningAlgRSAUsingSHA256},
|
||||||
},
|
},
|
||||||
HMACSecret: "abc123",
|
HMACSecret: "abc123",
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClient{
|
||||||
{
|
{
|
||||||
ID: "hs256",
|
ID: "hs256",
|
||||||
Secret: secret,
|
Secret: secret,
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewClient creates a new Client.
|
// NewClient creates a new Client.
|
||||||
func NewClient(config schema.OpenIDConnectClientConfiguration) (client Client) {
|
func NewClient(config schema.OpenIDConnectClient) (client Client) {
|
||||||
base := &BaseClient{
|
base := &BaseClient{
|
||||||
ID: config.ID,
|
ID: config.ID,
|
||||||
Description: config.Description,
|
Description: config.Description,
|
||||||
|
@ -34,8 +34,10 @@ func NewClient(config schema.OpenIDConnectClientConfiguration) (client Client) {
|
||||||
|
|
||||||
EnforcePAR: config.EnforcePAR,
|
EnforcePAR: config.EnforcePAR,
|
||||||
|
|
||||||
IDTokenSigningAlg: config.IDTokenSigningAlg,
|
IDTokenSigningAlg: config.IDTokenSigningAlg,
|
||||||
UserinfoSigningAlg: config.UserinfoSigningAlg,
|
IDTokenSigningKeyID: config.IDTokenSigningKeyID,
|
||||||
|
UserinfoSigningAlg: config.UserinfoSigningAlg,
|
||||||
|
UserinfoSigningKeyID: config.UserinfoSigningKeyID,
|
||||||
|
|
||||||
Policy: authorization.NewLevel(config.Policy),
|
Policy: authorization.NewLevel(config.Policy),
|
||||||
|
|
||||||
|
@ -151,6 +153,11 @@ func (c *BaseClient) GetIDTokenSigningAlg() (alg string) {
|
||||||
return c.IDTokenSigningAlg
|
return c.IDTokenSigningAlg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetIDTokenSigningKeyID returns the IDTokenSigningKeyID.
|
||||||
|
func (c *BaseClient) GetIDTokenSigningKeyID() (alg string) {
|
||||||
|
return c.IDTokenSigningKeyID
|
||||||
|
}
|
||||||
|
|
||||||
// GetUserinfoSigningAlg returns the UserinfoSigningAlg.
|
// GetUserinfoSigningAlg returns the UserinfoSigningAlg.
|
||||||
func (c *BaseClient) GetUserinfoSigningAlg() string {
|
func (c *BaseClient) GetUserinfoSigningAlg() string {
|
||||||
if c.UserinfoSigningAlg == "" {
|
if c.UserinfoSigningAlg == "" {
|
||||||
|
@ -160,6 +167,11 @@ func (c *BaseClient) GetUserinfoSigningAlg() string {
|
||||||
return c.UserinfoSigningAlg
|
return c.UserinfoSigningAlg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUserinfoSigningKeyID returns the UserinfoSigningKeyID.
|
||||||
|
func (c *BaseClient) GetUserinfoSigningKeyID() (kid string) {
|
||||||
|
return c.UserinfoSigningKeyID
|
||||||
|
}
|
||||||
|
|
||||||
// GetPAREnforcement returns EnforcePAR.
|
// GetPAREnforcement returns EnforcePAR.
|
||||||
func (c *BaseClient) GetPAREnforcement() bool {
|
func (c *BaseClient) GetPAREnforcement() bool {
|
||||||
return c.EnforcePAR
|
return c.EnforcePAR
|
||||||
|
|
|
@ -18,7 +18,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewClient(t *testing.T) {
|
func TestNewClient(t *testing.T) {
|
||||||
config := schema.OpenIDConnectClientConfiguration{}
|
config := schema.OpenIDConnectClient{}
|
||||||
client := oidc.NewClient(config)
|
client := oidc.NewClient(config)
|
||||||
assert.Equal(t, "", client.GetID())
|
assert.Equal(t, "", client.GetID())
|
||||||
assert.Equal(t, "", client.GetDescription())
|
assert.Equal(t, "", client.GetDescription())
|
||||||
|
@ -30,11 +30,12 @@ func TestNewClient(t *testing.T) {
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Equal(t, "", bclient.UserinfoSigningAlg)
|
assert.Equal(t, "", bclient.UserinfoSigningAlg)
|
||||||
assert.Equal(t, oidc.SigningAlgNone, client.GetUserinfoSigningAlg())
|
assert.Equal(t, oidc.SigningAlgNone, client.GetUserinfoSigningAlg())
|
||||||
|
assert.Equal(t, "", client.GetUserinfoSigningKeyID())
|
||||||
|
|
||||||
_, ok = client.(*oidc.FullClient)
|
_, ok = client.(*oidc.FullClient)
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
|
|
||||||
config = schema.OpenIDConnectClientConfiguration{
|
config = schema.OpenIDConnectClient{
|
||||||
ID: myclient,
|
ID: myclient,
|
||||||
Description: myclientdesc,
|
Description: myclientdesc,
|
||||||
Policy: twofactor,
|
Policy: twofactor,
|
||||||
|
@ -52,7 +53,7 @@ func TestNewClient(t *testing.T) {
|
||||||
assert.Equal(t, fosite.ResponseModeFormPost, client.GetResponseModes()[0])
|
assert.Equal(t, fosite.ResponseModeFormPost, client.GetResponseModes()[0])
|
||||||
assert.Equal(t, authorization.TwoFactor, client.GetAuthorizationPolicy())
|
assert.Equal(t, authorization.TwoFactor, client.GetAuthorizationPolicy())
|
||||||
|
|
||||||
config = schema.OpenIDConnectClientConfiguration{
|
config = schema.OpenIDConnectClient{
|
||||||
TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretPost,
|
TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretPost,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,12 +65,32 @@ func TestNewClient(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(t, "", fclient.UserinfoSigningAlg)
|
assert.Equal(t, "", fclient.UserinfoSigningAlg)
|
||||||
assert.Equal(t, oidc.SigningAlgNone, client.GetUserinfoSigningAlg())
|
assert.Equal(t, oidc.SigningAlgNone, client.GetUserinfoSigningAlg())
|
||||||
|
assert.Equal(t, oidc.SigningAlgNone, fclient.GetUserinfoSigningAlg())
|
||||||
assert.Equal(t, oidc.SigningAlgNone, fclient.UserinfoSigningAlg)
|
assert.Equal(t, oidc.SigningAlgNone, fclient.UserinfoSigningAlg)
|
||||||
|
|
||||||
|
assert.Equal(t, "", fclient.UserinfoSigningKeyID)
|
||||||
|
assert.Equal(t, "", client.GetUserinfoSigningKeyID())
|
||||||
|
assert.Equal(t, "", fclient.GetUserinfoSigningKeyID())
|
||||||
|
|
||||||
|
fclient.UserinfoSigningKeyID = "aukeyid"
|
||||||
|
|
||||||
|
assert.Equal(t, "aukeyid", client.GetUserinfoSigningKeyID())
|
||||||
|
assert.Equal(t, "aukeyid", fclient.GetUserinfoSigningKeyID())
|
||||||
|
|
||||||
assert.Equal(t, "", fclient.IDTokenSigningAlg)
|
assert.Equal(t, "", fclient.IDTokenSigningAlg)
|
||||||
assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, client.GetIDTokenSigningAlg())
|
assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, client.GetIDTokenSigningAlg())
|
||||||
|
assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, fclient.GetIDTokenSigningAlg())
|
||||||
assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, fclient.IDTokenSigningAlg)
|
assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, fclient.IDTokenSigningAlg)
|
||||||
|
|
||||||
|
assert.Equal(t, "", fclient.IDTokenSigningKeyID)
|
||||||
|
assert.Equal(t, "", client.GetIDTokenSigningKeyID())
|
||||||
|
assert.Equal(t, "", fclient.GetIDTokenSigningKeyID())
|
||||||
|
|
||||||
|
fclient.IDTokenSigningKeyID = "akeyid"
|
||||||
|
|
||||||
|
assert.Equal(t, "akeyid", client.GetIDTokenSigningKeyID())
|
||||||
|
assert.Equal(t, "akeyid", fclient.GetIDTokenSigningKeyID())
|
||||||
|
|
||||||
assert.Equal(t, oidc.ClientAuthMethodClientSecretPost, fclient.TokenEndpointAuthMethod)
|
assert.Equal(t, oidc.ClientAuthMethodClientSecretPost, fclient.TokenEndpointAuthMethod)
|
||||||
assert.Equal(t, oidc.ClientAuthMethodClientSecretPost, fclient.GetTokenEndpointAuthMethod())
|
assert.Equal(t, oidc.ClientAuthMethodClientSecretPost, fclient.GetTokenEndpointAuthMethod())
|
||||||
|
|
||||||
|
@ -81,6 +102,7 @@ func TestNewClient(t *testing.T) {
|
||||||
assert.Equal(t, "", fclient.GetRequestObjectSigningAlgorithm())
|
assert.Equal(t, "", fclient.GetRequestObjectSigningAlgorithm())
|
||||||
|
|
||||||
fclient.RequestObjectSigningAlgorithm = oidc.SigningAlgRSAUsingSHA256
|
fclient.RequestObjectSigningAlgorithm = oidc.SigningAlgRSAUsingSHA256
|
||||||
|
|
||||||
assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, fclient.GetRequestObjectSigningAlgorithm())
|
assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, fclient.GetRequestObjectSigningAlgorithm())
|
||||||
|
|
||||||
assert.Equal(t, "", fclient.JSONWebKeysURI)
|
assert.Equal(t, "", fclient.JSONWebKeysURI)
|
||||||
|
@ -355,7 +377,7 @@ func TestClient_GetResponseTypes(t *testing.T) {
|
||||||
func TestNewClientPKCE(t *testing.T) {
|
func TestNewClientPKCE(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
have schema.OpenIDConnectClientConfiguration
|
have schema.OpenIDConnectClient
|
||||||
expectedEnforcePKCE bool
|
expectedEnforcePKCE bool
|
||||||
expectedEnforcePKCEChallengeMethod bool
|
expectedEnforcePKCEChallengeMethod bool
|
||||||
expected string
|
expected string
|
||||||
|
@ -365,7 +387,7 @@ func TestNewClientPKCE(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"ShouldNotEnforcePKCEAndNotErrorOnNonPKCERequest",
|
"ShouldNotEnforcePKCEAndNotErrorOnNonPKCERequest",
|
||||||
schema.OpenIDConnectClientConfiguration{},
|
schema.OpenIDConnectClient{},
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
"",
|
"",
|
||||||
|
@ -375,7 +397,7 @@ func TestNewClientPKCE(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ShouldEnforcePKCEAndErrorOnNonPKCERequest",
|
"ShouldEnforcePKCEAndErrorOnNonPKCERequest",
|
||||||
schema.OpenIDConnectClientConfiguration{EnforcePKCE: true},
|
schema.OpenIDConnectClient{EnforcePKCE: true},
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
"",
|
"",
|
||||||
|
@ -385,7 +407,7 @@ func TestNewClientPKCE(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ShouldEnforcePKCEAndNotErrorOnPKCERequest",
|
"ShouldEnforcePKCEAndNotErrorOnPKCERequest",
|
||||||
schema.OpenIDConnectClientConfiguration{EnforcePKCE: true},
|
schema.OpenIDConnectClient{EnforcePKCE: true},
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
"",
|
"",
|
||||||
|
@ -394,7 +416,7 @@ func TestNewClientPKCE(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
},
|
},
|
||||||
{"ShouldEnforcePKCEFromChallengeMethodAndErrorOnNonPKCERequest",
|
{"ShouldEnforcePKCEFromChallengeMethodAndErrorOnNonPKCERequest",
|
||||||
schema.OpenIDConnectClientConfiguration{PKCEChallengeMethod: "S256"},
|
schema.OpenIDConnectClient{PKCEChallengeMethod: "S256"},
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
"S256",
|
"S256",
|
||||||
|
@ -403,7 +425,7 @@ func TestNewClientPKCE(t *testing.T) {
|
||||||
"The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Clients must include a code_challenge when performing the authorize code flow, but it is missing. The server is configured in a way that enforces PKCE for this client.",
|
"The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Clients must include a code_challenge when performing the authorize code flow, but it is missing. The server is configured in a way that enforces PKCE for this client.",
|
||||||
},
|
},
|
||||||
{"ShouldEnforcePKCEFromChallengeMethodAndErrorOnInvalidChallengeMethod",
|
{"ShouldEnforcePKCEFromChallengeMethodAndErrorOnInvalidChallengeMethod",
|
||||||
schema.OpenIDConnectClientConfiguration{PKCEChallengeMethod: "S256"},
|
schema.OpenIDConnectClient{PKCEChallengeMethod: "S256"},
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
"S256",
|
"S256",
|
||||||
|
@ -412,7 +434,7 @@ func TestNewClientPKCE(t *testing.T) {
|
||||||
"The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Client must use code_challenge_method=S256, is not allowed. The server is configured in a way that enforces PKCE S256 as challenge method for this client.",
|
"The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Client must use code_challenge_method=S256, is not allowed. The server is configured in a way that enforces PKCE S256 as challenge method for this client.",
|
||||||
},
|
},
|
||||||
{"ShouldEnforcePKCEFromChallengeMethodAndNotErrorOnValidRequest",
|
{"ShouldEnforcePKCEFromChallengeMethodAndNotErrorOnValidRequest",
|
||||||
schema.OpenIDConnectClientConfiguration{PKCEChallengeMethod: "S256"},
|
schema.OpenIDConnectClient{PKCEChallengeMethod: "S256"},
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
"S256",
|
"S256",
|
||||||
|
@ -448,7 +470,7 @@ func TestNewClientPKCE(t *testing.T) {
|
||||||
func TestNewClientPAR(t *testing.T) {
|
func TestNewClientPAR(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
have schema.OpenIDConnectClientConfiguration
|
have schema.OpenIDConnectClient
|
||||||
expected bool
|
expected bool
|
||||||
r *fosite.Request
|
r *fosite.Request
|
||||||
err string
|
err string
|
||||||
|
@ -456,7 +478,7 @@ func TestNewClientPAR(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"ShouldNotEnforcEPARAndNotErrorOnNonPARRequest",
|
"ShouldNotEnforcEPARAndNotErrorOnNonPARRequest",
|
||||||
schema.OpenIDConnectClientConfiguration{},
|
schema.OpenIDConnectClient{},
|
||||||
false,
|
false,
|
||||||
&fosite.Request{},
|
&fosite.Request{},
|
||||||
"",
|
"",
|
||||||
|
@ -464,7 +486,7 @@ func TestNewClientPAR(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ShouldEnforcePARAndErrorOnNonPARRequest",
|
"ShouldEnforcePARAndErrorOnNonPARRequest",
|
||||||
schema.OpenIDConnectClientConfiguration{EnforcePAR: true},
|
schema.OpenIDConnectClient{EnforcePAR: true},
|
||||||
true,
|
true,
|
||||||
&fosite.Request{},
|
&fosite.Request{},
|
||||||
"invalid_request",
|
"invalid_request",
|
||||||
|
@ -472,14 +494,14 @@ func TestNewClientPAR(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ShouldEnforcePARAndErrorOnNonPARRequest",
|
"ShouldEnforcePARAndErrorOnNonPARRequest",
|
||||||
schema.OpenIDConnectClientConfiguration{EnforcePAR: true},
|
schema.OpenIDConnectClient{EnforcePAR: true},
|
||||||
true,
|
true,
|
||||||
&fosite.Request{Form: map[string][]string{oidc.FormParameterRequestURI: {"https://example.com"}}},
|
&fosite.Request{Form: map[string][]string{oidc.FormParameterRequestURI: {"https://example.com"}}},
|
||||||
"invalid_request",
|
"invalid_request",
|
||||||
"The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Pushed Authorization Requests are enforced for this client but no such request was sent. The request_uri parameter 'https://example.com' is malformed."},
|
"The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Pushed Authorization Requests are enforced for this client but no such request was sent. The request_uri parameter 'https://example.com' is malformed."},
|
||||||
{
|
{
|
||||||
"ShouldEnforcePARAndNotErrorOnPARRequest",
|
"ShouldEnforcePARAndNotErrorOnPARRequest",
|
||||||
schema.OpenIDConnectClientConfiguration{EnforcePAR: true},
|
schema.OpenIDConnectClient{EnforcePAR: true},
|
||||||
true,
|
true,
|
||||||
&fosite.Request{Form: map[string][]string{oidc.FormParameterRequestURI: {fmt.Sprintf("%sabc", oidc.RedirectURIPrefixPushedAuthorizationRequestURN)}}},
|
&fosite.Request{Form: map[string][]string{oidc.FormParameterRequestURI: {fmt.Sprintf("%sabc", oidc.RedirectURIPrefixPushedAuthorizationRequestURN)}}},
|
||||||
"",
|
"",
|
||||||
|
@ -511,7 +533,7 @@ func TestNewClientPAR(t *testing.T) {
|
||||||
func TestNewClientResponseModes(t *testing.T) {
|
func TestNewClientResponseModes(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
have schema.OpenIDConnectClientConfiguration
|
have schema.OpenIDConnectClient
|
||||||
expected []fosite.ResponseModeType
|
expected []fosite.ResponseModeType
|
||||||
r *fosite.AuthorizeRequest
|
r *fosite.AuthorizeRequest
|
||||||
err string
|
err string
|
||||||
|
@ -519,7 +541,7 @@ func TestNewClientResponseModes(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"ShouldEnforceResponseModePolicyAndAllowDefaultModeQuery",
|
"ShouldEnforceResponseModePolicyAndAllowDefaultModeQuery",
|
||||||
schema.OpenIDConnectClientConfiguration{ResponseModes: []string{oidc.ResponseModeQuery}},
|
schema.OpenIDConnectClient{ResponseModes: []string{oidc.ResponseModeQuery}},
|
||||||
[]fosite.ResponseModeType{fosite.ResponseModeQuery},
|
[]fosite.ResponseModeType{fosite.ResponseModeQuery},
|
||||||
&fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeDefault, Request: fosite.Request{Form: map[string][]string{oidc.FormParameterResponseMode: nil}}},
|
&fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeDefault, Request: fosite.Request{Form: map[string][]string{oidc.FormParameterResponseMode: nil}}},
|
||||||
"",
|
"",
|
||||||
|
@ -527,7 +549,7 @@ func TestNewClientResponseModes(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ShouldEnforceResponseModePolicyAndFailOnDefaultMode",
|
"ShouldEnforceResponseModePolicyAndFailOnDefaultMode",
|
||||||
schema.OpenIDConnectClientConfiguration{ResponseModes: []string{oidc.ResponseModeFormPost}},
|
schema.OpenIDConnectClient{ResponseModes: []string{oidc.ResponseModeFormPost}},
|
||||||
[]fosite.ResponseModeType{fosite.ResponseModeFormPost},
|
[]fosite.ResponseModeType{fosite.ResponseModeFormPost},
|
||||||
&fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeDefault, Request: fosite.Request{Form: map[string][]string{oidc.FormParameterResponseMode: nil}}},
|
&fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeDefault, Request: fosite.Request{Form: map[string][]string{oidc.FormParameterResponseMode: nil}}},
|
||||||
"unsupported_response_mode",
|
"unsupported_response_mode",
|
||||||
|
@ -535,7 +557,7 @@ func TestNewClientResponseModes(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ShouldNotEnforceConfiguredResponseMode",
|
"ShouldNotEnforceConfiguredResponseMode",
|
||||||
schema.OpenIDConnectClientConfiguration{ResponseModes: []string{oidc.ResponseModeFormPost}},
|
schema.OpenIDConnectClient{ResponseModes: []string{oidc.ResponseModeFormPost}},
|
||||||
[]fosite.ResponseModeType{fosite.ResponseModeFormPost},
|
[]fosite.ResponseModeType{fosite.ResponseModeFormPost},
|
||||||
&fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeQuery, Request: fosite.Request{Form: map[string][]string{oidc.FormParameterResponseMode: {oidc.ResponseModeQuery}}}},
|
&fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeQuery, Request: fosite.Request{Form: map[string][]string{oidc.FormParameterResponseMode: {oidc.ResponseModeQuery}}}},
|
||||||
"",
|
"",
|
||||||
|
@ -543,7 +565,7 @@ func TestNewClientResponseModes(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ShouldNotEnforceUnconfiguredResponseMode",
|
"ShouldNotEnforceUnconfiguredResponseMode",
|
||||||
schema.OpenIDConnectClientConfiguration{ResponseModes: []string{}},
|
schema.OpenIDConnectClient{ResponseModes: []string{}},
|
||||||
[]fosite.ResponseModeType{},
|
[]fosite.ResponseModeType{},
|
||||||
&fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeDefault, Request: fosite.Request{Form: map[string][]string{oidc.FormParameterResponseMode: {oidc.ResponseModeQuery}}}},
|
&fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeDefault, Request: fosite.Request{Form: map[string][]string{oidc.FormParameterResponseMode: {oidc.ResponseModeQuery}}}},
|
||||||
"",
|
"",
|
||||||
|
@ -588,7 +610,7 @@ func TestNewClient_JSONWebKeySetURI(t *testing.T) {
|
||||||
ok bool
|
ok bool
|
||||||
)
|
)
|
||||||
|
|
||||||
client = oidc.NewClient(schema.OpenIDConnectClientConfiguration{
|
client = oidc.NewClient(schema.OpenIDConnectClient{
|
||||||
TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretPost,
|
TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretPost,
|
||||||
PublicKeys: schema.OpenIDConnectClientPublicKeys{
|
PublicKeys: schema.OpenIDConnectClientPublicKeys{
|
||||||
URI: MustParseRequestURI("https://google.com"),
|
URI: MustParseRequestURI("https://google.com"),
|
||||||
|
@ -603,7 +625,7 @@ func TestNewClient_JSONWebKeySetURI(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(t, "https://google.com", clientf.GetJSONWebKeysURI())
|
assert.Equal(t, "https://google.com", clientf.GetJSONWebKeysURI())
|
||||||
|
|
||||||
client = oidc.NewClient(schema.OpenIDConnectClientConfiguration{
|
client = oidc.NewClient(schema.OpenIDConnectClient{
|
||||||
TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretPost,
|
TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretPost,
|
||||||
PublicKeys: schema.OpenIDConnectClientPublicKeys{
|
PublicKeys: schema.OpenIDConnectClientPublicKeys{
|
||||||
URI: nil,
|
URI: nil,
|
||||||
|
|
|
@ -23,7 +23,7 @@ import (
|
||||||
"github.com/authelia/authelia/v4/internal/utils"
|
"github.com/authelia/authelia/v4/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewConfig(config *schema.OpenIDConnectConfiguration, templates *templates.Provider) (c *Config) {
|
func NewConfig(config *schema.OpenIDConnect, templates *templates.Provider) (c *Config) {
|
||||||
c = &Config{
|
c = &Config{
|
||||||
GlobalSecret: []byte(utils.HashSHA256FromString(config.HMACSecret)),
|
GlobalSecret: []byte(utils.HashSHA256FromString(config.HMACSecret)),
|
||||||
SendDebugMessagesToClients: config.EnableClientDebugMessages,
|
SendDebugMessagesToClients: config.EnableClientDebugMessages,
|
||||||
|
|
|
@ -118,6 +118,14 @@ const (
|
||||||
SigningAlgHMACUsingSHA512 = "HS512"
|
SigningAlgHMACUsingSHA512 = "HS512"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// JWS Algorithm Prefixes.
|
||||||
|
const (
|
||||||
|
SigningAlgPrefixRSA = "RS"
|
||||||
|
SigningAlgPrefixHMAC = "HS"
|
||||||
|
SigningAlgPrefixRSAPSS = "PS"
|
||||||
|
SigningAlgPrefixECDSA = "ES"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
KeyUseSignature = "sig"
|
KeyUseSignature = "sig"
|
||||||
)
|
)
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewOpenIDConnectWellKnownConfiguration generates a new OpenIDConnectWellKnownConfiguration.
|
// NewOpenIDConnectWellKnownConfiguration generates a new OpenIDConnectWellKnownConfiguration.
|
||||||
func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration) (config OpenIDConnectWellKnownConfiguration) {
|
func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnect) (config OpenIDConnectWellKnownConfiguration) {
|
||||||
config = OpenIDConnectWellKnownConfiguration{
|
config = OpenIDConnectWellKnownConfiguration{
|
||||||
OAuth2WellKnownConfiguration: OAuth2WellKnownConfiguration{
|
OAuth2WellKnownConfiguration: OAuth2WellKnownConfiguration{
|
||||||
CommonDiscoveryOptions: CommonDiscoveryOptions{
|
CommonDiscoveryOptions: CommonDiscoveryOptions{
|
||||||
|
@ -73,6 +73,15 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration
|
||||||
SigningAlgHMACUsingSHA256,
|
SigningAlgHMACUsingSHA256,
|
||||||
SigningAlgHMACUsingSHA384,
|
SigningAlgHMACUsingSHA384,
|
||||||
SigningAlgHMACUsingSHA512,
|
SigningAlgHMACUsingSHA512,
|
||||||
|
SigningAlgRSAUsingSHA256,
|
||||||
|
SigningAlgRSAUsingSHA384,
|
||||||
|
SigningAlgRSAUsingSHA512,
|
||||||
|
SigningAlgECDSAUsingP256AndSHA256,
|
||||||
|
SigningAlgECDSAUsingP384AndSHA384,
|
||||||
|
SigningAlgECDSAUsingP521AndSHA512,
|
||||||
|
SigningAlgRSAPSSUsingSHA256,
|
||||||
|
SigningAlgRSAPSSUsingSHA384,
|
||||||
|
SigningAlgRSAPSSUsingSHA512,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
OAuth2DiscoveryOptions: OAuth2DiscoveryOptions{
|
OAuth2DiscoveryOptions: OAuth2DiscoveryOptions{
|
||||||
|
@ -90,6 +99,15 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration
|
||||||
SigningAlgHMACUsingSHA256,
|
SigningAlgHMACUsingSHA256,
|
||||||
SigningAlgHMACUsingSHA384,
|
SigningAlgHMACUsingSHA384,
|
||||||
SigningAlgHMACUsingSHA512,
|
SigningAlgHMACUsingSHA512,
|
||||||
|
SigningAlgRSAUsingSHA256,
|
||||||
|
SigningAlgRSAUsingSHA384,
|
||||||
|
SigningAlgRSAUsingSHA512,
|
||||||
|
SigningAlgECDSAUsingP256AndSHA256,
|
||||||
|
SigningAlgECDSAUsingP384AndSHA384,
|
||||||
|
SigningAlgECDSAUsingP521AndSHA512,
|
||||||
|
SigningAlgRSAPSSUsingSHA256,
|
||||||
|
SigningAlgRSAPSSUsingSHA384,
|
||||||
|
SigningAlgRSAPSSUsingSHA512,
|
||||||
},
|
},
|
||||||
IntrospectionEndpointAuthMethodsSupported: []string{
|
IntrospectionEndpointAuthMethodsSupported: []string{
|
||||||
ClientAuthMethodClientSecretBasic,
|
ClientAuthMethodClientSecretBasic,
|
||||||
|
@ -104,6 +122,7 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration
|
||||||
OpenIDConnectDiscoveryOptions: OpenIDConnectDiscoveryOptions{
|
OpenIDConnectDiscoveryOptions: OpenIDConnectDiscoveryOptions{
|
||||||
IDTokenSigningAlgValuesSupported: []string{
|
IDTokenSigningAlgValuesSupported: []string{
|
||||||
SigningAlgRSAUsingSHA256,
|
SigningAlgRSAUsingSHA256,
|
||||||
|
SigningAlgNone,
|
||||||
},
|
},
|
||||||
UserinfoSigningAlgValuesSupported: []string{
|
UserinfoSigningAlgValuesSupported: []string{
|
||||||
SigningAlgRSAUsingSHA256,
|
SigningAlgRSAUsingSHA256,
|
||||||
|
@ -111,11 +130,17 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration
|
||||||
},
|
},
|
||||||
RequestObjectSigningAlgValuesSupported: []string{
|
RequestObjectSigningAlgValuesSupported: []string{
|
||||||
SigningAlgRSAUsingSHA256,
|
SigningAlgRSAUsingSHA256,
|
||||||
|
SigningAlgRSAUsingSHA384,
|
||||||
|
SigningAlgRSAUsingSHA512,
|
||||||
|
SigningAlgECDSAUsingP256AndSHA256,
|
||||||
|
SigningAlgECDSAUsingP384AndSHA384,
|
||||||
|
SigningAlgECDSAUsingP521AndSHA512,
|
||||||
|
SigningAlgRSAPSSUsingSHA256,
|
||||||
|
SigningAlgRSAPSSUsingSHA384,
|
||||||
|
SigningAlgRSAPSSUsingSHA512,
|
||||||
SigningAlgNone,
|
SigningAlgNone,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
OpenIDConnectFrontChannelLogoutDiscoveryOptions: &OpenIDConnectFrontChannelLogoutDiscoveryOptions{},
|
|
||||||
OpenIDConnectBackChannelLogoutDiscoveryOptions: &OpenIDConnectBackChannelLogoutDiscoveryOptions{},
|
|
||||||
OpenIDConnectPromptCreateDiscoveryOptions: &OpenIDConnectPromptCreateDiscoveryOptions{
|
OpenIDConnectPromptCreateDiscoveryOptions: &OpenIDConnectPromptCreateDiscoveryOptions{
|
||||||
PromptValuesSupported: []string{
|
PromptValuesSupported: []string{
|
||||||
PromptNone,
|
PromptNone,
|
||||||
|
@ -134,25 +159,8 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, alg := range c.Discovery.RequestObjectSigningAlgs {
|
|
||||||
if !utils.IsStringInSlice(alg, config.RequestObjectSigningAlgValuesSupported) {
|
|
||||||
config.RequestObjectSigningAlgValuesSupported = append(config.RequestObjectSigningAlgValuesSupported, alg)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !utils.IsStringInSlice(alg, config.RevocationEndpointAuthSigningAlgValuesSupported) {
|
|
||||||
config.RevocationEndpointAuthSigningAlgValuesSupported = append(config.RevocationEndpointAuthSigningAlgValuesSupported, alg)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !utils.IsStringInSlice(alg, config.TokenEndpointAuthSigningAlgValuesSupported) {
|
|
||||||
config.TokenEndpointAuthSigningAlgValuesSupported = append(config.TokenEndpointAuthSigningAlgValuesSupported, alg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(SortedSigningAlgs(config.IDTokenSigningAlgValuesSupported))
|
sort.Sort(SortedSigningAlgs(config.IDTokenSigningAlgValuesSupported))
|
||||||
sort.Sort(SortedSigningAlgs(config.UserinfoSigningAlgValuesSupported))
|
sort.Sort(SortedSigningAlgs(config.UserinfoSigningAlgValuesSupported))
|
||||||
sort.Sort(SortedSigningAlgs(config.RequestObjectSigningAlgValuesSupported))
|
|
||||||
sort.Sort(SortedSigningAlgs(config.RevocationEndpointAuthSigningAlgValuesSupported))
|
|
||||||
sort.Sort(SortedSigningAlgs(config.TokenEndpointAuthSigningAlgValuesSupported))
|
|
||||||
|
|
||||||
if c.EnablePKCEPlainChallenge {
|
if c.EnablePKCEPlainChallenge {
|
||||||
config.CodeChallengeMethodsSupported = append(config.CodeChallengeMethodsSupported, PKCEChallengeMethodPlain)
|
config.CodeChallengeMethodsSupported = append(config.CodeChallengeMethodsSupported, PKCEChallengeMethodPlain)
|
||||||
|
|
|
@ -25,118 +25,51 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
||||||
expectedRequestObjectSigAlgsSupported, expectedRevocationSigAlgsSupported, expectedTokenAuthSigAlgsSupported []string
|
expectedRequestObjectSigAlgsSupported, expectedRevocationSigAlgsSupported, expectedTokenAuthSigAlgsSupported []string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublic",
|
desc: "ShouldHaveStandardCodeChallengeMethods",
|
||||||
pkcePlainChallenge: false,
|
pkcePlainChallenge: false,
|
||||||
clients: map[string]oidc.Client{"a": &oidc.BaseClient{}},
|
clients: map[string]oidc.Client{"a": &oidc.BaseClient{}},
|
||||||
expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256},
|
expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256},
|
||||||
expectSubjectTypesSupported: []string{oidc.SubjectTypePublic, oidc.SubjectTypePairwise},
|
expectSubjectTypesSupported: []string{oidc.SubjectTypePublic, oidc.SubjectTypePairwise},
|
||||||
expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256},
|
expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
|
||||||
expectedUserInfoSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
|
expectedUserInfoSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
|
||||||
expectedRequestObjectSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
|
expectedRequestObjectSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgRSAUsingSHA384, oidc.SigningAlgRSAUsingSHA512, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgECDSAUsingP384AndSHA384, oidc.SigningAlgECDSAUsingP521AndSHA512, oidc.SigningAlgRSAPSSUsingSHA256, oidc.SigningAlgRSAPSSUsingSHA384, oidc.SigningAlgRSAPSSUsingSHA512, oidc.SigningAlgNone},
|
||||||
expectedRevocationSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512},
|
expectedRevocationSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512, oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgRSAUsingSHA384, oidc.SigningAlgRSAUsingSHA512, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgECDSAUsingP384AndSHA384, oidc.SigningAlgECDSAUsingP521AndSHA512, oidc.SigningAlgRSAPSSUsingSHA256, oidc.SigningAlgRSAPSSUsingSHA384, oidc.SigningAlgRSAPSSUsingSHA512},
|
||||||
expectedTokenAuthSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512},
|
expectedTokenAuthSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512, oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgRSAUsingSHA384, oidc.SigningAlgRSAUsingSHA512, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgECDSAUsingP384AndSHA384, oidc.SigningAlgECDSAUsingP521AndSHA512, oidc.SigningAlgRSAPSSUsingSHA256, oidc.SigningAlgRSAPSSUsingSHA384, oidc.SigningAlgRSAPSSUsingSHA512},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "ShouldIncludeDiscoveryInfo",
|
desc: "ShouldHaveAllCodeChallengeMethods",
|
||||||
|
pkcePlainChallenge: true,
|
||||||
|
clients: map[string]oidc.Client{"a": &oidc.BaseClient{}},
|
||||||
|
expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256, oidc.PKCEChallengeMethodPlain},
|
||||||
|
expectSubjectTypesSupported: []string{oidc.SubjectTypePublic, oidc.SubjectTypePairwise},
|
||||||
|
expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
|
||||||
|
expectedUserInfoSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
|
||||||
|
expectedRequestObjectSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgRSAUsingSHA384, oidc.SigningAlgRSAUsingSHA512, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgECDSAUsingP384AndSHA384, oidc.SigningAlgECDSAUsingP521AndSHA512, oidc.SigningAlgRSAPSSUsingSHA256, oidc.SigningAlgRSAPSSUsingSHA384, oidc.SigningAlgRSAPSSUsingSHA512, oidc.SigningAlgNone},
|
||||||
|
expectedRevocationSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512, oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgRSAUsingSHA384, oidc.SigningAlgRSAUsingSHA512, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgECDSAUsingP384AndSHA384, oidc.SigningAlgECDSAUsingP521AndSHA512, oidc.SigningAlgRSAPSSUsingSHA256, oidc.SigningAlgRSAPSSUsingSHA384, oidc.SigningAlgRSAPSSUsingSHA512},
|
||||||
|
expectedTokenAuthSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512, oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgRSAUsingSHA384, oidc.SigningAlgRSAUsingSHA512, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgECDSAUsingP384AndSHA384, oidc.SigningAlgECDSAUsingP521AndSHA512, oidc.SigningAlgRSAPSSUsingSHA256, oidc.SigningAlgRSAPSSUsingSHA384, oidc.SigningAlgRSAPSSUsingSHA512},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ShouldIncludeDiscoveredResponseObjectSigningAlgs",
|
||||||
pkcePlainChallenge: false,
|
pkcePlainChallenge: false,
|
||||||
clients: map[string]oidc.Client{"a": &oidc.BaseClient{}},
|
clients: map[string]oidc.Client{"a": &oidc.BaseClient{}},
|
||||||
discovery: schema.OpenIDConnectDiscovery{
|
discovery: schema.OpenIDConnectDiscovery{
|
||||||
ResponseObjectSigningAlgs: []string{oidc.SigningAlgECDSAUsingP521AndSHA512},
|
ResponseObjectSigningAlgs: []string{oidc.SigningAlgECDSAUsingP521AndSHA512},
|
||||||
RequestObjectSigningAlgs: []string{oidc.SigningAlgECDSAUsingP256AndSHA256},
|
|
||||||
},
|
},
|
||||||
expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256},
|
expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256},
|
||||||
expectSubjectTypesSupported: []string{oidc.SubjectTypePublic, oidc.SubjectTypePairwise},
|
expectSubjectTypesSupported: []string{oidc.SubjectTypePublic, oidc.SubjectTypePairwise},
|
||||||
expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgECDSAUsingP521AndSHA512},
|
expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgECDSAUsingP521AndSHA512, oidc.SigningAlgNone},
|
||||||
expectedUserInfoSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgECDSAUsingP521AndSHA512, oidc.SigningAlgNone},
|
expectedUserInfoSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgECDSAUsingP521AndSHA512, oidc.SigningAlgNone},
|
||||||
expectedRequestObjectSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgNone},
|
expectedRequestObjectSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgRSAUsingSHA384, oidc.SigningAlgRSAUsingSHA512, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgECDSAUsingP384AndSHA384, oidc.SigningAlgECDSAUsingP521AndSHA512, oidc.SigningAlgRSAPSSUsingSHA256, oidc.SigningAlgRSAPSSUsingSHA384, oidc.SigningAlgRSAPSSUsingSHA512, oidc.SigningAlgNone},
|
||||||
expectedRevocationSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512, oidc.SigningAlgECDSAUsingP256AndSHA256},
|
expectedRevocationSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512, oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgRSAUsingSHA384, oidc.SigningAlgRSAUsingSHA512, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgECDSAUsingP384AndSHA384, oidc.SigningAlgECDSAUsingP521AndSHA512, oidc.SigningAlgRSAPSSUsingSHA256, oidc.SigningAlgRSAPSSUsingSHA384, oidc.SigningAlgRSAPSSUsingSHA512},
|
||||||
expectedTokenAuthSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512, oidc.SigningAlgECDSAUsingP256AndSHA256},
|
expectedTokenAuthSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512, oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgRSAUsingSHA384, oidc.SigningAlgRSAUsingSHA512, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgECDSAUsingP384AndSHA384, oidc.SigningAlgECDSAUsingP521AndSHA512, oidc.SigningAlgRSAPSSUsingSHA256, oidc.SigningAlgRSAPSSUsingSHA384, oidc.SigningAlgRSAPSSUsingSHA512},
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublic",
|
|
||||||
pkcePlainChallenge: true,
|
|
||||||
clients: map[string]oidc.Client{"a": &oidc.BaseClient{}},
|
|
||||||
expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256, oidc.PKCEChallengeMethodPlain},
|
|
||||||
expectSubjectTypesSupported: []string{oidc.SubjectTypePublic, oidc.SubjectTypePairwise},
|
|
||||||
expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256},
|
|
||||||
expectedUserInfoSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
|
|
||||||
expectedRequestObjectSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
|
|
||||||
expectedRevocationSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512},
|
|
||||||
expectedTokenAuthSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublicPairwise",
|
|
||||||
pkcePlainChallenge: false,
|
|
||||||
clients: map[string]oidc.Client{"a": &oidc.BaseClient{SectorIdentifier: "yes"}},
|
|
||||||
expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256},
|
|
||||||
expectSubjectTypesSupported: []string{oidc.SubjectTypePublic, oidc.SubjectTypePairwise},
|
|
||||||
expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256},
|
|
||||||
expectedUserInfoSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
|
|
||||||
expectedRequestObjectSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
|
|
||||||
expectedRevocationSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512},
|
|
||||||
expectedTokenAuthSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublicPairwise",
|
|
||||||
pkcePlainChallenge: true,
|
|
||||||
clients: map[string]oidc.Client{"a": &oidc.BaseClient{SectorIdentifier: "yes"}},
|
|
||||||
expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256, oidc.PKCEChallengeMethodPlain},
|
|
||||||
expectSubjectTypesSupported: []string{oidc.SubjectTypePublic, oidc.SubjectTypePairwise},
|
|
||||||
expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256},
|
|
||||||
expectedUserInfoSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
|
|
||||||
expectedRequestObjectSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
|
|
||||||
expectedRevocationSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512},
|
|
||||||
expectedTokenAuthSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "ShouldHaveTokenAuthMethodsNone",
|
|
||||||
pkcePlainChallenge: true,
|
|
||||||
clients: map[string]oidc.Client{"a": &oidc.BaseClient{SectorIdentifier: "yes"}},
|
|
||||||
expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256, oidc.PKCEChallengeMethodPlain},
|
|
||||||
expectSubjectTypesSupported: []string{oidc.SubjectTypePublic, oidc.SubjectTypePairwise},
|
|
||||||
expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256},
|
|
||||||
expectedUserInfoSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
|
|
||||||
expectedRequestObjectSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
|
|
||||||
expectedRevocationSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512},
|
|
||||||
expectedTokenAuthSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "ShouldHaveTokenAuthMethodsNone",
|
|
||||||
pkcePlainChallenge: true,
|
|
||||||
clients: map[string]oidc.Client{
|
|
||||||
"a": &oidc.BaseClient{SectorIdentifier: "yes"},
|
|
||||||
"b": &oidc.BaseClient{SectorIdentifier: "yes"},
|
|
||||||
},
|
|
||||||
expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256, oidc.PKCEChallengeMethodPlain},
|
|
||||||
expectSubjectTypesSupported: []string{oidc.SubjectTypePublic, oidc.SubjectTypePairwise},
|
|
||||||
expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256},
|
|
||||||
expectedUserInfoSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
|
|
||||||
expectedRequestObjectSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
|
|
||||||
expectedRevocationSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512},
|
|
||||||
expectedTokenAuthSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "ShouldHaveTokenAuthMethodsNone",
|
|
||||||
pkcePlainChallenge: true,
|
|
||||||
clients: map[string]oidc.Client{
|
|
||||||
"a": &oidc.BaseClient{SectorIdentifier: "yes"},
|
|
||||||
"b": &oidc.BaseClient{SectorIdentifier: "yes"},
|
|
||||||
},
|
|
||||||
expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256, oidc.PKCEChallengeMethodPlain},
|
|
||||||
expectSubjectTypesSupported: []string{oidc.SubjectTypePublic, oidc.SubjectTypePairwise},
|
|
||||||
expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256},
|
|
||||||
expectedUserInfoSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
|
|
||||||
expectedRequestObjectSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
|
|
||||||
expectedRevocationSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512},
|
|
||||||
expectedTokenAuthSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
c := schema.OpenIDConnectConfiguration{
|
c := schema.OpenIDConnect{
|
||||||
EnablePKCEPlainChallenge: tc.pkcePlainChallenge,
|
EnablePKCEPlainChallenge: tc.pkcePlainChallenge,
|
||||||
PAR: schema.OpenIDConnectPARConfiguration{
|
PAR: schema.OpenIDConnectPAR{
|
||||||
Enforce: tc.enforcePAR,
|
Enforce: tc.enforcePAR,
|
||||||
},
|
},
|
||||||
Discovery: tc.discovery,
|
Discovery: tc.discovery,
|
||||||
|
@ -169,12 +102,12 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewOpenIDConnectProviderDiscovery(t *testing.T) {
|
func TestNewOpenIDConnectProviderDiscovery(t *testing.T) {
|
||||||
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnect{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: keyRSA2048,
|
IssuerPrivateKey: keyRSA2048,
|
||||||
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
||||||
EnablePKCEPlainChallenge: true,
|
EnablePKCEPlainChallenge: true,
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClient{
|
||||||
{
|
{
|
||||||
ID: "a-client",
|
ID: "a-client",
|
||||||
Secret: tOpenIDConnectPlainTextClientSecret,
|
Secret: tOpenIDConnectPlainTextClientSecret,
|
||||||
|
@ -210,11 +143,11 @@ func TestNewOpenIDConnectProviderDiscovery(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
func TestNewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
||||||
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnect{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: keyRSA2048,
|
IssuerPrivateKey: keyRSA2048,
|
||||||
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClient{
|
||||||
{
|
{
|
||||||
ID: "a-client",
|
ID: "a-client",
|
||||||
Secret: tOpenIDConnectPlainTextClientSecret,
|
Secret: tOpenIDConnectPlainTextClientSecret,
|
||||||
|
@ -281,35 +214,13 @@ func TestNewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfiguration(t *test
|
||||||
assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, oidc.ClientAuthMethodPrivateKeyJWT)
|
assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, oidc.ClientAuthMethodPrivateKeyJWT)
|
||||||
assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, oidc.ClientAuthMethodNone)
|
assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, oidc.ClientAuthMethodNone)
|
||||||
|
|
||||||
assert.Len(t, disco.IntrospectionEndpointAuthMethodsSupported, 2)
|
assert.Equal(t, []string{oidc.ClientAuthMethodClientSecretBasic, oidc.ClientAuthMethodNone}, disco.IntrospectionEndpointAuthMethodsSupported)
|
||||||
assert.Contains(t, disco.IntrospectionEndpointAuthMethodsSupported, oidc.ClientAuthMethodClientSecretBasic)
|
assert.Equal(t, []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeImplicit, oidc.GrantTypeRefreshToken}, disco.GrantTypesSupported)
|
||||||
assert.Contains(t, disco.IntrospectionEndpointAuthMethodsSupported, oidc.ClientAuthMethodNone)
|
assert.Equal(t, []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512, oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgRSAUsingSHA384, oidc.SigningAlgRSAUsingSHA512, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgECDSAUsingP384AndSHA384, oidc.SigningAlgECDSAUsingP521AndSHA512, oidc.SigningAlgRSAPSSUsingSHA256, oidc.SigningAlgRSAPSSUsingSHA384, oidc.SigningAlgRSAPSSUsingSHA512}, disco.RevocationEndpointAuthSigningAlgValuesSupported)
|
||||||
|
assert.Equal(t, []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512, oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgRSAUsingSHA384, oidc.SigningAlgRSAUsingSHA512, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgECDSAUsingP384AndSHA384, oidc.SigningAlgECDSAUsingP521AndSHA512, oidc.SigningAlgRSAPSSUsingSHA256, oidc.SigningAlgRSAPSSUsingSHA384, oidc.SigningAlgRSAPSSUsingSHA512}, disco.TokenEndpointAuthSigningAlgValuesSupported)
|
||||||
assert.Len(t, disco.GrantTypesSupported, 3)
|
assert.Equal(t, []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone}, disco.IDTokenSigningAlgValuesSupported)
|
||||||
assert.Contains(t, disco.GrantTypesSupported, oidc.GrantTypeAuthorizationCode)
|
assert.Equal(t, []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone}, disco.UserinfoSigningAlgValuesSupported)
|
||||||
assert.Contains(t, disco.GrantTypesSupported, oidc.GrantTypeRefreshToken)
|
assert.Equal(t, []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgRSAUsingSHA384, oidc.SigningAlgRSAUsingSHA512, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgECDSAUsingP384AndSHA384, oidc.SigningAlgECDSAUsingP521AndSHA512, oidc.SigningAlgRSAPSSUsingSHA256, oidc.SigningAlgRSAPSSUsingSHA384, oidc.SigningAlgRSAPSSUsingSHA512, oidc.SigningAlgNone}, disco.RequestObjectSigningAlgValuesSupported)
|
||||||
assert.Contains(t, disco.GrantTypesSupported, oidc.GrantTypeImplicit)
|
|
||||||
|
|
||||||
assert.Len(t, disco.RevocationEndpointAuthSigningAlgValuesSupported, 3)
|
|
||||||
assert.Equal(t, disco.RevocationEndpointAuthSigningAlgValuesSupported[0], oidc.SigningAlgHMACUsingSHA256)
|
|
||||||
assert.Equal(t, disco.RevocationEndpointAuthSigningAlgValuesSupported[1], oidc.SigningAlgHMACUsingSHA384)
|
|
||||||
assert.Equal(t, disco.RevocationEndpointAuthSigningAlgValuesSupported[2], oidc.SigningAlgHMACUsingSHA512)
|
|
||||||
|
|
||||||
assert.Len(t, disco.TokenEndpointAuthSigningAlgValuesSupported, 3)
|
|
||||||
assert.Equal(t, disco.TokenEndpointAuthSigningAlgValuesSupported[0], oidc.SigningAlgHMACUsingSHA256)
|
|
||||||
assert.Equal(t, disco.TokenEndpointAuthSigningAlgValuesSupported[1], oidc.SigningAlgHMACUsingSHA384)
|
|
||||||
assert.Equal(t, disco.TokenEndpointAuthSigningAlgValuesSupported[2], oidc.SigningAlgHMACUsingSHA512)
|
|
||||||
|
|
||||||
assert.Len(t, disco.IDTokenSigningAlgValuesSupported, 1)
|
|
||||||
assert.Contains(t, disco.IDTokenSigningAlgValuesSupported, oidc.SigningAlgRSAUsingSHA256)
|
|
||||||
|
|
||||||
assert.Len(t, disco.UserinfoSigningAlgValuesSupported, 2)
|
|
||||||
assert.Equal(t, disco.UserinfoSigningAlgValuesSupported[0], oidc.SigningAlgRSAUsingSHA256)
|
|
||||||
assert.Equal(t, disco.UserinfoSigningAlgValuesSupported[1], oidc.SigningAlgNone)
|
|
||||||
|
|
||||||
require.Len(t, disco.RequestObjectSigningAlgValuesSupported, 2)
|
|
||||||
assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, disco.RequestObjectSigningAlgValuesSupported[0])
|
|
||||||
assert.Equal(t, oidc.SigningAlgNone, disco.RequestObjectSigningAlgValuesSupported[1])
|
|
||||||
|
|
||||||
assert.Len(t, disco.ClaimsSupported, 18)
|
assert.Len(t, disco.ClaimsSupported, 18)
|
||||||
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimAuthenticationMethodsReference)
|
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimAuthenticationMethodsReference)
|
||||||
|
@ -337,11 +248,11 @@ func TestNewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfiguration(t *test
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewOpenIDConnectProvider_GetOAuth2WellKnownConfiguration(t *testing.T) {
|
func TestNewOpenIDConnectProvider_GetOAuth2WellKnownConfiguration(t *testing.T) {
|
||||||
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnect{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: keyRSA2048,
|
IssuerPrivateKey: keyRSA2048,
|
||||||
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClient{
|
||||||
{
|
{
|
||||||
ID: "a-client",
|
ID: "a-client",
|
||||||
Secret: tOpenIDConnectPlainTextClientSecret,
|
Secret: tOpenIDConnectPlainTextClientSecret,
|
||||||
|
@ -427,12 +338,12 @@ func TestNewOpenIDConnectProvider_GetOAuth2WellKnownConfiguration(t *testing.T)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfigurationWithPlainPKCE(t *testing.T) {
|
func TestNewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfigurationWithPlainPKCE(t *testing.T) {
|
||||||
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnect{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: keyRSA2048,
|
IssuerPrivateKey: keyRSA2048,
|
||||||
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
||||||
EnablePKCEPlainChallenge: true,
|
EnablePKCEPlainChallenge: true,
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClient{
|
||||||
{
|
{
|
||||||
ID: "a-client",
|
ID: "a-client",
|
||||||
Secret: tOpenIDConnectPlainTextClientSecret,
|
Secret: tOpenIDConnectPlainTextClientSecret,
|
||||||
|
|
|
@ -13,17 +13,17 @@ import (
|
||||||
"github.com/golang-jwt/jwt/v4"
|
"github.com/golang-jwt/jwt/v4"
|
||||||
fjwt "github.com/ory/fosite/token/jwt"
|
fjwt "github.com/ory/fosite/token/jwt"
|
||||||
"github.com/ory/x/errorsx"
|
"github.com/ory/x/errorsx"
|
||||||
|
|
||||||
"gopkg.in/square/go-jose.v2"
|
"gopkg.in/square/go-jose.v2"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewKeyManager news up a KeyManager.
|
// NewKeyManager news up a KeyManager.
|
||||||
func NewKeyManager(config *schema.OpenIDConnectConfiguration) (manager *KeyManager) {
|
func NewKeyManager(config *schema.OpenIDConnect) (manager *KeyManager) {
|
||||||
manager = &KeyManager{
|
manager = &KeyManager{
|
||||||
kids: map[string]*JWK{},
|
alg2kid: config.Discovery.DefaultKeyIDs,
|
||||||
algs: map[string]*JWK{},
|
kids: map[string]*JWK{},
|
||||||
|
algs: map[string]*JWK{},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, sjwk := range config.IssuerPrivateKeys {
|
for _, sjwk := range config.IssuerPrivateKeys {
|
||||||
|
@ -31,10 +31,6 @@ func NewKeyManager(config *schema.OpenIDConnectConfiguration) (manager *KeyManag
|
||||||
|
|
||||||
manager.kids[sjwk.KeyID] = jwk
|
manager.kids[sjwk.KeyID] = jwk
|
||||||
manager.algs[jwk.alg.Alg()] = jwk
|
manager.algs[jwk.alg.Alg()] = jwk
|
||||||
|
|
||||||
if jwk.kid == config.Discovery.DefaultKeyID {
|
|
||||||
manager.kid = jwk.kid
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return manager
|
return manager
|
||||||
|
@ -42,14 +38,29 @@ func NewKeyManager(config *schema.OpenIDConnectConfiguration) (manager *KeyManag
|
||||||
|
|
||||||
// The KeyManager type handles JWKs and signing operations.
|
// The KeyManager type handles JWKs and signing operations.
|
||||||
type KeyManager struct {
|
type KeyManager struct {
|
||||||
kid string
|
alg2kid map[string]string
|
||||||
kids map[string]*JWK
|
kids map[string]*JWK
|
||||||
algs map[string]*JWK
|
algs map[string]*JWK
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetKeyID returns the default key id.
|
// GetDefaultKeyID returns the default key id.
|
||||||
func (m *KeyManager) GetKeyID(ctx context.Context) string {
|
func (m *KeyManager) GetDefaultKeyID(ctx context.Context) string {
|
||||||
return m.kid
|
return m.alg2kid[SigningAlgRSAUsingSHA256]
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKeyID returns the JWK Key ID given an kid/alg or the default if it doesn't exist.
|
||||||
|
func (m *KeyManager) GetKeyID(ctx context.Context, kid, alg string) string {
|
||||||
|
if kid != "" {
|
||||||
|
if jwk, ok := m.kids[kid]; ok {
|
||||||
|
return jwk.KeyID()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if jwk, ok := m.algs[alg]; ok {
|
||||||
|
return jwk.KeyID()
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.alg2kid[SigningAlgRSAUsingSHA256]
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetKeyIDFromAlgStrict returns the key id given an alg or an error if it doesn't exist.
|
// GetKeyIDFromAlgStrict returns the key id given an alg or an error if it doesn't exist.
|
||||||
|
@ -67,7 +78,20 @@ func (m *KeyManager) GetKeyIDFromAlg(ctx context.Context, alg string) string {
|
||||||
return jwks.kid
|
return jwks.kid
|
||||||
}
|
}
|
||||||
|
|
||||||
return m.kid
|
return m.alg2kid[SigningAlgRSAUsingSHA256]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the JWK given an kid/alg or nil if it doesn't exist.
|
||||||
|
func (m *KeyManager) Get(ctx context.Context, kid, alg string) *JWK {
|
||||||
|
if kid != "" {
|
||||||
|
return m.kids[kid]
|
||||||
|
}
|
||||||
|
|
||||||
|
if jwk, ok := m.algs[alg]; ok {
|
||||||
|
return jwk
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetByAlg returns the JWK given an alg or nil if it doesn't exist.
|
// GetByAlg returns the JWK given an alg or nil if it doesn't exist.
|
||||||
|
@ -82,7 +106,7 @@ func (m *KeyManager) GetByAlg(ctx context.Context, alg string) *JWK {
|
||||||
// GetByKID returns the JWK given an key id or nil if it doesn't exist. If given a blank string it returns the default.
|
// GetByKID returns the JWK given an key id or nil if it doesn't exist. If given a blank string it returns the default.
|
||||||
func (m *KeyManager) GetByKID(ctx context.Context, kid string) *JWK {
|
func (m *KeyManager) GetByKID(ctx context.Context, kid string) *JWK {
|
||||||
if kid == "" {
|
if kid == "" {
|
||||||
return m.kids[m.kid]
|
return m.kids[m.alg2kid[SigningAlgRSAUsingSHA256]]
|
||||||
}
|
}
|
||||||
|
|
||||||
if jwk, ok := m.kids[kid]; ok {
|
if jwk, ok := m.kids[kid]; ok {
|
||||||
|
|
|
@ -17,10 +17,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestKeyManager(t *testing.T) {
|
func TestKeyManager(t *testing.T) {
|
||||||
config := &schema.OpenIDConnectConfiguration{
|
config := &schema.OpenIDConnect{
|
||||||
Discovery: schema.OpenIDConnectDiscovery{
|
|
||||||
DefaultKeyID: "kid-RS256-sig",
|
|
||||||
},
|
|
||||||
IssuerPrivateKeys: []schema.JWK{
|
IssuerPrivateKeys: []schema.JWK{
|
||||||
{
|
{
|
||||||
Use: oidc.KeyUseSignature,
|
Use: oidc.KeyUseSignature,
|
||||||
|
@ -79,8 +76,16 @@ func TestKeyManager(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config.Discovery.DefaultKeyIDs = map[string]string{}
|
||||||
|
|
||||||
for i, key := range config.IssuerPrivateKeys {
|
for i, key := range config.IssuerPrivateKeys {
|
||||||
config.IssuerPrivateKeys[i].KeyID = fmt.Sprintf("kid-%s-%s", key.Algorithm, key.Use)
|
kid := fmt.Sprintf("kid-%s-%s", key.Algorithm, key.Use)
|
||||||
|
|
||||||
|
config.IssuerPrivateKeys[i].KeyID = kid
|
||||||
|
|
||||||
|
if _, ok := config.Discovery.DefaultKeyIDs[key.Algorithm]; !ok {
|
||||||
|
config.Discovery.DefaultKeyIDs[key.Algorithm] = kid
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
manager := oidc.NewKeyManager(config)
|
manager := oidc.NewKeyManager(config)
|
||||||
|
@ -89,7 +94,18 @@ func TestKeyManager(t *testing.T) {
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
assert.Equal(t, "kid-RS256-sig", manager.GetKeyID(ctx))
|
assert.Equal(t, "kid-RS256-sig", manager.GetDefaultKeyID(ctx))
|
||||||
|
|
||||||
|
require.NotNil(t, manager.Get(ctx, "kid-RS256-sig", oidc.SigningAlgRSAUsingSHA256))
|
||||||
|
assert.Equal(t, "kid-RS256-sig", manager.Get(ctx, "kid-RS256-sig", oidc.SigningAlgRSAUsingSHA256).KeyID())
|
||||||
|
assert.Equal(t, "kid-RS256-sig", manager.Get(ctx, "", oidc.SigningAlgRSAUsingSHA256).KeyID())
|
||||||
|
assert.Nil(t, manager.Get(ctx, "", "NOKEY"))
|
||||||
|
|
||||||
|
assert.Equal(t, "kid-RS256-sig", manager.GetKeyID(ctx, "", oidc.SigningAlgRSAUsingSHA256))
|
||||||
|
assert.Equal(t, "kid-RS256-sig", manager.GetKeyID(ctx, "kid-RS256-sig", oidc.SigningAlgRSAPSSUsingSHA256))
|
||||||
|
assert.Equal(t, "kid-RS256-sig", manager.GetKeyID(ctx, "", ""))
|
||||||
|
assert.Equal(t, "kid-PS256-sig", manager.GetKeyID(ctx, "kid-PS256-sig", oidc.SigningAlgRSAPSSUsingSHA256))
|
||||||
|
assert.Equal(t, "kid-PS256-sig", manager.GetKeyID(ctx, "", oidc.SigningAlgRSAPSSUsingSHA256))
|
||||||
|
|
||||||
var (
|
var (
|
||||||
jwk *oidc.JWK
|
jwk *oidc.JWK
|
||||||
|
@ -107,7 +123,7 @@ func TestKeyManager(t *testing.T) {
|
||||||
|
|
||||||
jwk = manager.GetByKID(ctx, "")
|
jwk = manager.GetByKID(ctx, "")
|
||||||
assert.NotNil(t, jwk)
|
assert.NotNil(t, jwk)
|
||||||
assert.Equal(t, config.Discovery.DefaultKeyID, jwk.KeyID())
|
assert.Equal(t, config.Discovery.DefaultKeyIDs[oidc.SigningAlgRSAUsingSHA256], jwk.KeyID())
|
||||||
|
|
||||||
jwk, err = manager.GetByHeader(ctx, &fjwt.Headers{Extra: map[string]any{oidc.JWTHeaderKeyIdentifier: "notalg"}})
|
jwk, err = manager.GetByHeader(ctx, &fjwt.Headers{Extra: map[string]any{oidc.JWTHeaderKeyIdentifier: "notalg"}})
|
||||||
assert.EqualError(t, err, "jwt header 'kid' with value 'notalg' does not match a managed jwk")
|
assert.EqualError(t, err, "jwt header 'kid' with value 'notalg' does not match a managed jwk")
|
||||||
|
@ -126,7 +142,7 @@ func TestKeyManager(t *testing.T) {
|
||||||
assert.Equal(t, "", kid)
|
assert.Equal(t, "", kid)
|
||||||
|
|
||||||
kid = manager.GetKeyIDFromAlg(ctx, "notalg")
|
kid = manager.GetKeyIDFromAlg(ctx, "notalg")
|
||||||
assert.Equal(t, config.Discovery.DefaultKeyID, kid)
|
assert.Equal(t, config.Discovery.DefaultKeyIDs[oidc.SigningAlgRSAUsingSHA256], kid)
|
||||||
|
|
||||||
set := manager.Set(ctx)
|
set := manager.Set(ctx)
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewOpenIDConnectProvider new-ups a OpenIDConnectProvider.
|
// NewOpenIDConnectProvider new-ups a OpenIDConnectProvider.
|
||||||
func NewOpenIDConnectProvider(config *schema.OpenIDConnectConfiguration, store storage.Provider, templates *templates.Provider) (provider *OpenIDConnectProvider) {
|
func NewOpenIDConnectProvider(config *schema.OpenIDConnect, store storage.Provider, templates *templates.Provider) (provider *OpenIDConnectProvider) {
|
||||||
if config == nil {
|
if config == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,12 +18,12 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_NotConfigured(t *testing
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing.T) {
|
func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing.T) {
|
||||||
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnect{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: keyRSA2048,
|
IssuerPrivateKey: keyRSA2048,
|
||||||
EnablePKCEPlainChallenge: true,
|
EnablePKCEPlainChallenge: true,
|
||||||
HMACSecret: badhmac,
|
HMACSecret: badhmac,
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClient{
|
||||||
{
|
{
|
||||||
ID: myclient,
|
ID: myclient,
|
||||||
Secret: tOpenIDConnectPlainTextClientSecret,
|
Secret: tOpenIDConnectPlainTextClientSecret,
|
||||||
|
@ -50,11 +50,11 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *testing.T) {
|
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *testing.T) {
|
||||||
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnect{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: keyRSA2048,
|
IssuerPrivateKey: keyRSA2048,
|
||||||
HMACSecret: badhmac,
|
HMACSecret: badhmac,
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClient{
|
||||||
{
|
{
|
||||||
ID: "a-client",
|
ID: "a-client",
|
||||||
Secret: tOpenIDConnectPlainTextClientSecret,
|
Secret: tOpenIDConnectPlainTextClientSecret,
|
||||||
|
|
|
@ -18,8 +18,8 @@ import (
|
||||||
"github.com/authelia/authelia/v4/internal/storage"
|
"github.com/authelia/authelia/v4/internal/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewStore returns a Store when provided with a schema.OpenIDConnectConfiguration and storage.Provider.
|
// NewStore returns a Store when provided with a schema.OpenIDConnect and storage.Provider.
|
||||||
func NewStore(config *schema.OpenIDConnectConfiguration, provider storage.Provider) (store *Store) {
|
func NewStore(config *schema.OpenIDConnect, provider storage.Provider) (store *Store) {
|
||||||
logger := logging.Logger()
|
logger := logging.Logger()
|
||||||
|
|
||||||
store = &Store{
|
store = &Store{
|
||||||
|
|
|
@ -24,10 +24,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) {
|
func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) {
|
||||||
s := oidc.NewStore(&schema.OpenIDConnectConfiguration{
|
s := oidc.NewStore(&schema.OpenIDConnect{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: keyRSA2048,
|
IssuerPrivateKey: keyRSA2048,
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClient{
|
||||||
{
|
{
|
||||||
ID: myclient,
|
ID: myclient,
|
||||||
Description: myclientdesc,
|
Description: myclientdesc,
|
||||||
|
@ -56,10 +56,10 @@ func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOpenIDConnectStore_GetInternalClient(t *testing.T) {
|
func TestOpenIDConnectStore_GetInternalClient(t *testing.T) {
|
||||||
s := oidc.NewStore(&schema.OpenIDConnectConfiguration{
|
s := oidc.NewStore(&schema.OpenIDConnect{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: keyRSA2048,
|
IssuerPrivateKey: keyRSA2048,
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClient{
|
||||||
{
|
{
|
||||||
ID: myclient,
|
ID: myclient,
|
||||||
Description: myclientdesc,
|
Description: myclientdesc,
|
||||||
|
@ -83,7 +83,7 @@ func TestOpenIDConnectStore_GetInternalClient(t *testing.T) {
|
||||||
func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) {
|
func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) {
|
||||||
id := myclient
|
id := myclient
|
||||||
|
|
||||||
c1 := schema.OpenIDConnectClientConfiguration{
|
c1 := schema.OpenIDConnectClient{
|
||||||
ID: id,
|
ID: id,
|
||||||
Description: myclientdesc,
|
Description: myclientdesc,
|
||||||
Policy: onefactor,
|
Policy: onefactor,
|
||||||
|
@ -91,10 +91,10 @@ func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) {
|
||||||
Secret: tOpenIDConnectPlainTextClientSecret,
|
Secret: tOpenIDConnectPlainTextClientSecret,
|
||||||
}
|
}
|
||||||
|
|
||||||
s := oidc.NewStore(&schema.OpenIDConnectConfiguration{
|
s := oidc.NewStore(&schema.OpenIDConnect{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: keyRSA2048,
|
IssuerPrivateKey: keyRSA2048,
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{c1},
|
Clients: []schema.OpenIDConnectClient{c1},
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
client, err := s.GetFullClient(id)
|
client, err := s.GetFullClient(id)
|
||||||
|
@ -111,7 +111,7 @@ func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
|
func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
|
||||||
c1 := schema.OpenIDConnectClientConfiguration{
|
c1 := schema.OpenIDConnectClient{
|
||||||
ID: myclient,
|
ID: myclient,
|
||||||
Description: myclientdesc,
|
Description: myclientdesc,
|
||||||
Policy: onefactor,
|
Policy: onefactor,
|
||||||
|
@ -119,10 +119,10 @@ func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
|
||||||
Secret: tOpenIDConnectPlainTextClientSecret,
|
Secret: tOpenIDConnectPlainTextClientSecret,
|
||||||
}
|
}
|
||||||
|
|
||||||
s := oidc.NewStore(&schema.OpenIDConnectConfiguration{
|
s := oidc.NewStore(&schema.OpenIDConnect{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: keyRSA2048,
|
IssuerPrivateKey: keyRSA2048,
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{c1},
|
Clients: []schema.OpenIDConnectClient{c1},
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
client, err := s.GetFullClient("another-client")
|
client, err := s.GetFullClient("another-client")
|
||||||
|
@ -131,10 +131,10 @@ func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOpenIDConnectStore_IsValidClientID(t *testing.T) {
|
func TestOpenIDConnectStore_IsValidClientID(t *testing.T) {
|
||||||
s := oidc.NewStore(&schema.OpenIDConnectConfiguration{
|
s := oidc.NewStore(&schema.OpenIDConnect{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: keyRSA2048,
|
IssuerPrivateKey: keyRSA2048,
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClient{
|
||||||
{
|
{
|
||||||
ID: myclient,
|
ID: myclient,
|
||||||
Description: myclientdesc,
|
Description: myclientdesc,
|
||||||
|
@ -169,8 +169,8 @@ func (s *StoreSuite) SetupTest() {
|
||||||
s.ctx = context.Background()
|
s.ctx = context.Background()
|
||||||
s.ctrl = gomock.NewController(s.T())
|
s.ctrl = gomock.NewController(s.T())
|
||||||
s.mock = mocks.NewMockStorage(s.ctrl)
|
s.mock = mocks.NewMockStorage(s.ctrl)
|
||||||
s.store = oidc.NewStore(&schema.OpenIDConnectConfiguration{
|
s.store = oidc.NewStore(&schema.OpenIDConnect{
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClient{
|
||||||
{
|
{
|
||||||
ID: "hs256",
|
ID: "hs256",
|
||||||
Secret: tOpenIDConnectPBKDF2ClientSecret,
|
Secret: tOpenIDConnectPBKDF2ClientSecret,
|
||||||
|
|
|
@ -122,8 +122,10 @@ type BaseClient struct {
|
||||||
ResponseTypes []string
|
ResponseTypes []string
|
||||||
ResponseModes []fosite.ResponseModeType
|
ResponseModes []fosite.ResponseModeType
|
||||||
|
|
||||||
IDTokenSigningAlg string
|
IDTokenSigningAlg string
|
||||||
UserinfoSigningAlg string
|
IDTokenSigningKeyID string
|
||||||
|
UserinfoSigningAlg string
|
||||||
|
UserinfoSigningKeyID string
|
||||||
|
|
||||||
Policy authorization.Level
|
Policy authorization.Level
|
||||||
|
|
||||||
|
@ -152,8 +154,11 @@ type Client interface {
|
||||||
GetSectorIdentifier() string
|
GetSectorIdentifier() string
|
||||||
GetConsentResponseBody(consent *model.OAuth2ConsentSession) ConsentGetResponseBody
|
GetConsentResponseBody(consent *model.OAuth2ConsentSession) ConsentGetResponseBody
|
||||||
|
|
||||||
GetUserinfoSigningAlg() string
|
|
||||||
GetIDTokenSigningAlg() string
|
GetIDTokenSigningAlg() string
|
||||||
|
GetIDTokenSigningKeyID() string
|
||||||
|
|
||||||
|
GetUserinfoSigningAlg() string
|
||||||
|
GetUserinfoSigningKeyID() string
|
||||||
|
|
||||||
GetPAREnforcement() bool
|
GetPAREnforcement() bool
|
||||||
GetPKCEEnforcement() bool
|
GetPKCEEnforcement() bool
|
||||||
|
|
|
@ -95,10 +95,3 @@ func isSigningAlgLess(i, j string) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
SigningAlgPrefixRSA = "RS"
|
|
||||||
SigningAlgPrefixHMAC = "HS"
|
|
||||||
SigningAlgPrefixRSAPSS = "PS"
|
|
||||||
SigningAlgPrefixECDSA = "ES"
|
|
||||||
)
|
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
jose "gopkg.in/square/go-jose.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsSigningAlgLess(t *testing.T) {
|
||||||
|
assert.False(t, isSigningAlgLess(SigningAlgRSAUsingSHA256, SigningAlgRSAUsingSHA256))
|
||||||
|
assert.False(t, isSigningAlgLess(SigningAlgRSAUsingSHA256, SigningAlgHMACUsingSHA256))
|
||||||
|
assert.True(t, isSigningAlgLess(SigningAlgHMACUsingSHA256, SigningAlgNone))
|
||||||
|
assert.True(t, isSigningAlgLess(SigningAlgHMACUsingSHA256, SigningAlgRSAUsingSHA512))
|
||||||
|
assert.True(t, isSigningAlgLess(SigningAlgHMACUsingSHA256, SigningAlgRSAPSSUsingSHA256))
|
||||||
|
assert.True(t, isSigningAlgLess(SigningAlgHMACUsingSHA256, SigningAlgECDSAUsingP521AndSHA512))
|
||||||
|
assert.True(t, isSigningAlgLess(SigningAlgRSAUsingSHA256, SigningAlgECDSAUsingP521AndSHA512))
|
||||||
|
assert.True(t, isSigningAlgLess(SigningAlgECDSAUsingP521AndSHA512, "JS121"))
|
||||||
|
assert.False(t, isSigningAlgLess("JS121", SigningAlgECDSAUsingP521AndSHA512))
|
||||||
|
assert.False(t, isSigningAlgLess("JS121", "TS512"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSortedJSONWebKey(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
have []jose.JSONWebKey
|
||||||
|
expected []jose.JSONWebKey
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ShouldOrderByKID",
|
||||||
|
[]jose.JSONWebKey{
|
||||||
|
{KeyID: "abc"},
|
||||||
|
{KeyID: "123"},
|
||||||
|
},
|
||||||
|
[]jose.JSONWebKey{
|
||||||
|
{KeyID: "123"},
|
||||||
|
{KeyID: "abc"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldOrderByAlg",
|
||||||
|
[]jose.JSONWebKey{
|
||||||
|
{Algorithm: "RS256"},
|
||||||
|
{Algorithm: "HS256"},
|
||||||
|
},
|
||||||
|
[]jose.JSONWebKey{
|
||||||
|
{Algorithm: "HS256"},
|
||||||
|
{Algorithm: "RS256"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
sort.Sort(SortedJSONWebKey(tc.have))
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expected, tc.have)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
package random
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCryptographical(t *testing.T) {
|
||||||
|
p := &Cryptographical{}
|
||||||
|
|
||||||
|
data := make([]byte, 10)
|
||||||
|
|
||||||
|
n, err := p.Read(data)
|
||||||
|
assert.Equal(t, 10, n)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
data2, err := p.BytesErr()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, data2, 72)
|
||||||
|
|
||||||
|
data2 = p.Bytes()
|
||||||
|
assert.Len(t, data2, 72)
|
||||||
|
|
||||||
|
data2 = p.BytesCustom(74, []byte(CharSetAlphabetic))
|
||||||
|
assert.Len(t, data2, 74)
|
||||||
|
|
||||||
|
data2, err = p.BytesCustomErr(76, []byte(CharSetAlphabetic))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, data2, 76)
|
||||||
|
|
||||||
|
data2, err = p.BytesCustomErr(-5, []byte(CharSetAlphabetic))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, data2, 72)
|
||||||
|
|
||||||
|
strdata := p.StringCustom(10, CharSetAlphabetic)
|
||||||
|
assert.Len(t, strdata, 10)
|
||||||
|
|
||||||
|
strdata, err = p.StringCustomErr(11, CharSetAlphabetic)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, strdata, 11)
|
||||||
|
|
||||||
|
i := p.Intn(999)
|
||||||
|
assert.Greater(t, i, 0)
|
||||||
|
assert.Less(t, i, 999)
|
||||||
|
|
||||||
|
i, err = p.IntnErr(999)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Greater(t, i, 0)
|
||||||
|
assert.Less(t, i, 999)
|
||||||
|
|
||||||
|
i, err = p.IntnErr(-4)
|
||||||
|
assert.EqualError(t, err, "n must be more than 0")
|
||||||
|
assert.Equal(t, 0, i)
|
||||||
|
|
||||||
|
bi := p.Int(big.NewInt(999))
|
||||||
|
assert.Greater(t, bi.Int64(), int64(0))
|
||||||
|
assert.Less(t, bi.Int64(), int64(999))
|
||||||
|
|
||||||
|
bi = p.Int(nil)
|
||||||
|
assert.Equal(t, int64(-1), bi.Int64())
|
||||||
|
|
||||||
|
bi, err = p.IntErr(nil)
|
||||||
|
assert.Nil(t, bi)
|
||||||
|
assert.EqualError(t, err, "max is required")
|
||||||
|
|
||||||
|
bi, err = p.IntErr(big.NewInt(-1))
|
||||||
|
assert.Nil(t, bi)
|
||||||
|
assert.EqualError(t, err, "max must be 1 or more")
|
||||||
|
|
||||||
|
prime, err := p.Prime(64)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, prime)
|
||||||
|
}
|
|
@ -112,6 +112,10 @@ func (r *Mathematical) Intn(n int) int {
|
||||||
|
|
||||||
// IntnErr returns a random int error combination with a maximum of n.
|
// IntnErr returns a random int error combination with a maximum of n.
|
||||||
func (r *Mathematical) IntnErr(n int) (output int, err error) {
|
func (r *Mathematical) IntnErr(n int) (output int, err error) {
|
||||||
|
if n <= 0 {
|
||||||
|
return 0, fmt.Errorf("n must be more than 0")
|
||||||
|
}
|
||||||
|
|
||||||
return r.Intn(n), nil
|
return r.Intn(n), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,15 +136,11 @@ func (r *Mathematical) IntErr(max *big.Int) (value *big.Int, err error) {
|
||||||
return nil, fmt.Errorf("max is required")
|
return nil, fmt.Errorf("max is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
if max.Sign() <= 0 {
|
if max.Int64() <= 0 {
|
||||||
return nil, fmt.Errorf("max must be 1 or more")
|
return nil, fmt.Errorf("max must be 1 or more")
|
||||||
}
|
}
|
||||||
|
|
||||||
r.lock.Lock()
|
return big.NewInt(int64(r.Intn(int(max.Int64())))), nil
|
||||||
|
|
||||||
defer r.lock.Unlock()
|
|
||||||
|
|
||||||
return big.NewInt(int64(r.Intn(max.Sign()))), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prime returns a number of the given bit length that is prime with high probability. Prime will return error for any
|
// Prime returns a number of the given bit length that is prime with high probability. Prime will return error for any
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
package random
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMathematical(t *testing.T) {
|
||||||
|
p := NewMathematical()
|
||||||
|
|
||||||
|
data := make([]byte, 10)
|
||||||
|
|
||||||
|
n, err := p.Read(data)
|
||||||
|
assert.Equal(t, 10, n)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
data2, err := p.BytesErr()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, data2, 72)
|
||||||
|
|
||||||
|
data2 = p.Bytes()
|
||||||
|
assert.Len(t, data2, 72)
|
||||||
|
|
||||||
|
data2 = p.BytesCustom(74, []byte(CharSetAlphabetic))
|
||||||
|
assert.Len(t, data2, 74)
|
||||||
|
|
||||||
|
data2, err = p.BytesCustomErr(76, []byte(CharSetAlphabetic))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, data2, 76)
|
||||||
|
|
||||||
|
data2, err = p.BytesCustomErr(-5, []byte(CharSetAlphabetic))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, data2, 72)
|
||||||
|
|
||||||
|
strdata := p.StringCustom(10, CharSetAlphabetic)
|
||||||
|
assert.Len(t, strdata, 10)
|
||||||
|
|
||||||
|
strdata, err = p.StringCustomErr(11, CharSetAlphabetic)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, strdata, 11)
|
||||||
|
|
||||||
|
i := p.Intn(999)
|
||||||
|
assert.Greater(t, i, 0)
|
||||||
|
assert.Less(t, i, 999)
|
||||||
|
|
||||||
|
i, err = p.IntnErr(999)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Greater(t, i, 0)
|
||||||
|
assert.Less(t, i, 999)
|
||||||
|
|
||||||
|
i, err = p.IntnErr(-4)
|
||||||
|
assert.EqualError(t, err, "n must be more than 0")
|
||||||
|
assert.Equal(t, 0, i)
|
||||||
|
|
||||||
|
bi := p.Int(big.NewInt(999))
|
||||||
|
assert.Greater(t, bi.Int64(), int64(0))
|
||||||
|
assert.Less(t, bi.Int64(), int64(999))
|
||||||
|
|
||||||
|
bi = p.Int(nil)
|
||||||
|
assert.Equal(t, int64(-1), bi.Int64())
|
||||||
|
|
||||||
|
bi, err = p.IntErr(nil)
|
||||||
|
assert.Nil(t, bi)
|
||||||
|
assert.EqualError(t, err, "max is required")
|
||||||
|
|
||||||
|
bi, err = p.IntErr(big.NewInt(-1))
|
||||||
|
assert.Nil(t, bi)
|
||||||
|
assert.EqualError(t, err, "max must be 1 or more")
|
||||||
|
|
||||||
|
prime, err := p.Prime(64)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, prime)
|
||||||
|
}
|
|
@ -12,12 +12,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewRegulator create a regulator instance.
|
// NewRegulator create a regulator instance.
|
||||||
func NewRegulator(config schema.RegulationConfiguration, provider storage.RegulatorProvider, clock utils.Clock) *Regulator {
|
func NewRegulator(config schema.RegulationConfiguration, store storage.RegulatorProvider, clock utils.Clock) *Regulator {
|
||||||
return &Regulator{
|
return &Regulator{
|
||||||
enabled: config.MaxRetries > 0,
|
enabled: config.MaxRetries > 0,
|
||||||
storageProvider: provider,
|
store: store,
|
||||||
clock: clock,
|
clock: clock,
|
||||||
config: config,
|
config: config,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ func NewRegulator(config schema.RegulationConfiguration, provider storage.Regula
|
||||||
func (r *Regulator) Mark(ctx Context, successful, banned bool, username, requestURI, requestMethod, authType string) error {
|
func (r *Regulator) Mark(ctx Context, successful, banned bool, username, requestURI, requestMethod, authType string) error {
|
||||||
ctx.RecordAuthn(successful, banned, strings.ToLower(authType))
|
ctx.RecordAuthn(successful, banned, strings.ToLower(authType))
|
||||||
|
|
||||||
return r.storageProvider.AppendAuthenticationLog(ctx, model.AuthenticationAttempt{
|
return r.store.AppendAuthenticationLog(ctx, model.AuthenticationAttempt{
|
||||||
Time: r.clock.Now(),
|
Time: r.clock.Now(),
|
||||||
Successful: successful,
|
Successful: successful,
|
||||||
Banned: banned,
|
Banned: banned,
|
||||||
|
@ -46,7 +46,7 @@ func (r *Regulator) Regulate(ctx context.Context, username string) (time.Time, e
|
||||||
return time.Time{}, nil
|
return time.Time{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
attempts, err := r.storageProvider.LoadAuthenticationLogs(ctx, username, r.clock.Now().Add(-r.config.BanTime), 10, 0)
|
attempts, err := r.store.LoadAuthenticationLogs(ctx, username, r.clock.Now().Add(-r.config.BanTime), 10, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return time.Time{}, nil
|
return time.Time{}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,46 +1,69 @@
|
||||||
package regulation_test
|
package regulation_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"fmt"
|
||||||
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
"github.com/valyala/fasthttp"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
"github.com/authelia/authelia/v4/internal/mocks"
|
"github.com/authelia/authelia/v4/internal/mocks"
|
||||||
"github.com/authelia/authelia/v4/internal/model"
|
"github.com/authelia/authelia/v4/internal/model"
|
||||||
"github.com/authelia/authelia/v4/internal/regulation"
|
"github.com/authelia/authelia/v4/internal/regulation"
|
||||||
"github.com/authelia/authelia/v4/internal/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type RegulatorSuite struct {
|
type RegulatorSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
|
|
||||||
ctx context.Context
|
mock *mocks.MockAutheliaCtx
|
||||||
ctrl *gomock.Controller
|
|
||||||
storageMock *mocks.MockStorage
|
|
||||||
config schema.RegulationConfiguration
|
|
||||||
clock utils.TestingClock
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *RegulatorSuite) SetupTest() {
|
func (s *RegulatorSuite) SetupTest() {
|
||||||
s.ctrl = gomock.NewController(s.T())
|
s.mock = mocks.NewMockAutheliaCtx(s.T())
|
||||||
s.storageMock = mocks.NewMockStorage(s.ctrl)
|
s.mock.Ctx.Configuration.Regulation = schema.RegulationConfiguration{
|
||||||
s.ctx = context.Background()
|
|
||||||
|
|
||||||
s.config = schema.RegulationConfiguration{
|
|
||||||
MaxRetries: 3,
|
MaxRetries: 3,
|
||||||
BanTime: time.Second * 180,
|
BanTime: time.Second * 180,
|
||||||
FindTime: time.Second * 30,
|
FindTime: time.Second * 30,
|
||||||
}
|
}
|
||||||
s.clock.Set(time.Now())
|
|
||||||
|
s.mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedFor, "127.0.0.1")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *RegulatorSuite) TearDownTest() {
|
func (s *RegulatorSuite) TearDownTest() {
|
||||||
s.ctrl.Finish()
|
s.mock.Ctrl.Finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RegulatorSuite) TestShouldMark() {
|
||||||
|
regulator := regulation.NewRegulator(s.mock.Ctx.Configuration.Regulation, s.mock.StorageMock, &s.mock.Clock)
|
||||||
|
|
||||||
|
s.mock.StorageMock.EXPECT().AppendAuthenticationLog(s.mock.Ctx, model.AuthenticationAttempt{
|
||||||
|
Time: s.mock.Clock.Now(),
|
||||||
|
Successful: true,
|
||||||
|
Banned: false,
|
||||||
|
Username: "john",
|
||||||
|
Type: "1fa",
|
||||||
|
RemoteIP: model.NewNullIP(net.ParseIP("127.0.0.1")),
|
||||||
|
RequestURI: "https://google.com",
|
||||||
|
RequestMethod: fasthttp.MethodGet,
|
||||||
|
})
|
||||||
|
|
||||||
|
s.NoError(regulator.Mark(s.mock.Ctx, true, false, "john", "https://google.com", fasthttp.MethodGet, "1fa"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RegulatorSuite) TestShouldHandleRegulateError() {
|
||||||
|
regulator := regulation.NewRegulator(s.mock.Ctx.Configuration.Regulation, s.mock.StorageMock, &s.mock.Clock)
|
||||||
|
|
||||||
|
s.mock.StorageMock.EXPECT().LoadAuthenticationLogs(s.mock.Ctx, "john", s.mock.Clock.Now().Add(-s.mock.Ctx.Configuration.Regulation.BanTime), 10, 0).Return(nil, fmt.Errorf("failed"))
|
||||||
|
|
||||||
|
until, err := regulator.Regulate(s.mock.Ctx, "john")
|
||||||
|
|
||||||
|
s.NoError(err)
|
||||||
|
s.Equal(time.Time{}, until)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *RegulatorSuite) TestShouldNotThrowWhenUserIsLegitimate() {
|
func (s *RegulatorSuite) TestShouldNotThrowWhenUserIsLegitimate() {
|
||||||
|
@ -48,17 +71,17 @@ func (s *RegulatorSuite) TestShouldNotThrowWhenUserIsLegitimate() {
|
||||||
{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: true,
|
Successful: true,
|
||||||
Time: s.clock.Now().Add(-4 * time.Minute),
|
Time: s.mock.Clock.Now().Add(-4 * time.Minute),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
s.storageMock.EXPECT().
|
s.mock.StorageMock.EXPECT().
|
||||||
LoadAuthenticationLogs(s.ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||||
Return(attemptsInDB, nil)
|
Return(attemptsInDB, nil)
|
||||||
|
|
||||||
regulator := regulation.NewRegulator(s.config, s.storageMock, &s.clock)
|
regulator := regulation.NewRegulator(s.mock.Ctx.Configuration.Regulation, s.mock.StorageMock, &s.mock.Clock)
|
||||||
|
|
||||||
_, err := regulator.Regulate(s.ctx, "john")
|
_, err := regulator.Regulate(s.mock.Ctx, "john")
|
||||||
assert.NoError(s.T(), err)
|
assert.NoError(s.T(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,27 +92,27 @@ func (s *RegulatorSuite) TestShouldNotThrowWhenFailedAuthenticationNotInFindTime
|
||||||
{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-1 * time.Second),
|
Time: s.mock.Clock.Now().Add(-1 * time.Second),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-90 * time.Second),
|
Time: s.mock.Clock.Now().Add(-90 * time.Second),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-180 * time.Second),
|
Time: s.mock.Clock.Now().Add(-180 * time.Second),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
s.storageMock.EXPECT().
|
s.mock.StorageMock.EXPECT().
|
||||||
LoadAuthenticationLogs(s.ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||||
Return(attemptsInDB, nil)
|
Return(attemptsInDB, nil)
|
||||||
|
|
||||||
regulator := regulation.NewRegulator(s.config, s.storageMock, &s.clock)
|
regulator := regulation.NewRegulator(s.mock.Ctx.Configuration.Regulation, s.mock.StorageMock, &s.mock.Clock)
|
||||||
|
|
||||||
_, err := regulator.Regulate(s.ctx, "john")
|
_, err := regulator.Regulate(s.mock.Ctx, "john")
|
||||||
assert.NoError(s.T(), err)
|
assert.NoError(s.T(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,32 +123,32 @@ func (s *RegulatorSuite) TestShouldBanUserIfLatestAttemptsAreWithinFinTime() {
|
||||||
{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-1 * time.Second),
|
Time: s.mock.Clock.Now().Add(-1 * time.Second),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-4 * time.Second),
|
Time: s.mock.Clock.Now().Add(-4 * time.Second),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-6 * time.Second),
|
Time: s.mock.Clock.Now().Add(-6 * time.Second),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-180 * time.Second),
|
Time: s.mock.Clock.Now().Add(-180 * time.Second),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
s.storageMock.EXPECT().
|
s.mock.StorageMock.EXPECT().
|
||||||
LoadAuthenticationLogs(s.ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||||
Return(attemptsInDB, nil)
|
Return(attemptsInDB, nil)
|
||||||
|
|
||||||
regulator := regulation.NewRegulator(s.config, s.storageMock, &s.clock)
|
regulator := regulation.NewRegulator(s.mock.Ctx.Configuration.Regulation, s.mock.StorageMock, &s.mock.Clock)
|
||||||
|
|
||||||
_, err := regulator.Regulate(s.ctx, "john")
|
_, err := regulator.Regulate(s.mock.Ctx, "john")
|
||||||
assert.Equal(s.T(), regulation.ErrUserIsBanned, err)
|
assert.Equal(s.T(), regulation.ErrUserIsBanned, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,27 +161,27 @@ func (s *RegulatorSuite) TestShouldCheckUserIsStillBanned() {
|
||||||
{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-31 * time.Second),
|
Time: s.mock.Clock.Now().Add(-31 * time.Second),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-34 * time.Second),
|
Time: s.mock.Clock.Now().Add(-34 * time.Second),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-36 * time.Second),
|
Time: s.mock.Clock.Now().Add(-36 * time.Second),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
s.storageMock.EXPECT().
|
s.mock.StorageMock.EXPECT().
|
||||||
LoadAuthenticationLogs(s.ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||||
Return(attemptsInDB, nil)
|
Return(attemptsInDB, nil)
|
||||||
|
|
||||||
regulator := regulation.NewRegulator(s.config, s.storageMock, &s.clock)
|
regulator := regulation.NewRegulator(s.mock.Ctx.Configuration.Regulation, s.mock.StorageMock, &s.mock.Clock)
|
||||||
|
|
||||||
_, err := regulator.Regulate(s.ctx, "john")
|
_, err := regulator.Regulate(s.mock.Ctx, "john")
|
||||||
assert.Equal(s.T(), regulation.ErrUserIsBanned, err)
|
assert.Equal(s.T(), regulation.ErrUserIsBanned, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,22 +190,22 @@ func (s *RegulatorSuite) TestShouldCheckUserIsNotYetBanned() {
|
||||||
{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-34 * time.Second),
|
Time: s.mock.Clock.Now().Add(-34 * time.Second),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-36 * time.Second),
|
Time: s.mock.Clock.Now().Add(-36 * time.Second),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
s.storageMock.EXPECT().
|
s.mock.StorageMock.EXPECT().
|
||||||
LoadAuthenticationLogs(s.ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||||
Return(attemptsInDB, nil)
|
Return(attemptsInDB, nil)
|
||||||
|
|
||||||
regulator := regulation.NewRegulator(s.config, s.storageMock, &s.clock)
|
regulator := regulation.NewRegulator(s.mock.Ctx.Configuration.Regulation, s.mock.StorageMock, &s.mock.Clock)
|
||||||
|
|
||||||
_, err := regulator.Regulate(s.ctx, "john")
|
_, err := regulator.Regulate(s.mock.Ctx, "john")
|
||||||
assert.NoError(s.T(), err)
|
assert.NoError(s.T(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,7 +214,7 @@ func (s *RegulatorSuite) TestShouldCheckUserWasAboutToBeBanned() {
|
||||||
{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-14 * time.Second),
|
Time: s.mock.Clock.Now().Add(-14 * time.Second),
|
||||||
},
|
},
|
||||||
// more than 30 seconds elapsed between this auth and the preceding one.
|
// more than 30 seconds elapsed between this auth and the preceding one.
|
||||||
// In that case we don't need to regulate the user even though the number
|
// In that case we don't need to regulate the user even though the number
|
||||||
|
@ -199,22 +222,22 @@ func (s *RegulatorSuite) TestShouldCheckUserWasAboutToBeBanned() {
|
||||||
{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-94 * time.Second),
|
Time: s.mock.Clock.Now().Add(-94 * time.Second),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-96 * time.Second),
|
Time: s.mock.Clock.Now().Add(-96 * time.Second),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
s.storageMock.EXPECT().
|
s.mock.StorageMock.EXPECT().
|
||||||
LoadAuthenticationLogs(s.ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||||
Return(attemptsInDB, nil)
|
Return(attemptsInDB, nil)
|
||||||
|
|
||||||
regulator := regulation.NewRegulator(s.config, s.storageMock, &s.clock)
|
regulator := regulation.NewRegulator(s.mock.Ctx.Configuration.Regulation, s.mock.StorageMock, &s.mock.Clock)
|
||||||
|
|
||||||
_, err := regulator.Regulate(s.ctx, "john")
|
_, err := regulator.Regulate(s.mock.Ctx, "john")
|
||||||
assert.NoError(s.T(), err)
|
assert.NoError(s.T(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,34 +246,34 @@ func (s *RegulatorSuite) TestShouldCheckRegulationHasBeenResetOnSuccessfulAttemp
|
||||||
{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-90 * time.Second),
|
Time: s.mock.Clock.Now().Add(-90 * time.Second),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: true,
|
Successful: true,
|
||||||
Time: s.clock.Now().Add(-93 * time.Second),
|
Time: s.mock.Clock.Now().Add(-93 * time.Second),
|
||||||
},
|
},
|
||||||
// The user was almost banned but he did a successful attempt. Therefore, even if the next
|
// The user was almost banned but he did a successful attempt. Therefore, even if the next
|
||||||
// failure happens within FindTime, he should not be banned.
|
// failure happens within FindTime, he should not be banned.
|
||||||
{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-94 * time.Second),
|
Time: s.mock.Clock.Now().Add(-94 * time.Second),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-96 * time.Second),
|
Time: s.mock.Clock.Now().Add(-96 * time.Second),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
s.storageMock.EXPECT().
|
s.mock.StorageMock.EXPECT().
|
||||||
LoadAuthenticationLogs(s.ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||||
Return(attemptsInDB, nil)
|
Return(attemptsInDB, nil)
|
||||||
|
|
||||||
regulator := regulation.NewRegulator(s.config, s.storageMock, &s.clock)
|
regulator := regulation.NewRegulator(s.mock.Ctx.Configuration.Regulation, s.mock.StorageMock, &s.mock.Clock)
|
||||||
|
|
||||||
_, err := regulator.Regulate(s.ctx, "john")
|
_, err := regulator.Regulate(s.mock.Ctx, "john")
|
||||||
assert.NoError(s.T(), err)
|
assert.NoError(s.T(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,22 +288,22 @@ func (s *RegulatorSuite) TestShouldHaveRegulatorDisabled() {
|
||||||
{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-31 * time.Second),
|
Time: s.mock.Clock.Now().Add(-31 * time.Second),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-34 * time.Second),
|
Time: s.mock.Clock.Now().Add(-34 * time.Second),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Successful: false,
|
Successful: false,
|
||||||
Time: s.clock.Now().Add(-36 * time.Second),
|
Time: s.mock.Clock.Now().Add(-36 * time.Second),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
s.storageMock.EXPECT().
|
s.mock.StorageMock.EXPECT().
|
||||||
LoadAuthenticationLogs(s.ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||||
Return(attemptsInDB, nil)
|
Return(attemptsInDB, nil)
|
||||||
|
|
||||||
// Check Disabled Functionality.
|
// Check Disabled Functionality.
|
||||||
|
@ -290,8 +313,8 @@ func (s *RegulatorSuite) TestShouldHaveRegulatorDisabled() {
|
||||||
BanTime: time.Second * 180,
|
BanTime: time.Second * 180,
|
||||||
}
|
}
|
||||||
|
|
||||||
regulator := regulation.NewRegulator(config, s.storageMock, &s.clock)
|
regulator := regulation.NewRegulator(config, s.mock.StorageMock, &s.mock.Clock)
|
||||||
_, err := regulator.Regulate(s.ctx, "john")
|
_, err := regulator.Regulate(s.mock.Ctx, "john")
|
||||||
assert.NoError(s.T(), err)
|
assert.NoError(s.T(), err)
|
||||||
|
|
||||||
// Check Enabled Functionality.
|
// Check Enabled Functionality.
|
||||||
|
@ -301,7 +324,7 @@ func (s *RegulatorSuite) TestShouldHaveRegulatorDisabled() {
|
||||||
BanTime: time.Second * 180,
|
BanTime: time.Second * 180,
|
||||||
}
|
}
|
||||||
|
|
||||||
regulator = regulation.NewRegulator(config, s.storageMock, &s.clock)
|
regulator = regulation.NewRegulator(config, s.mock.StorageMock, &s.mock.Clock)
|
||||||
_, err = regulator.Regulate(s.ctx, "john")
|
_, err = regulator.Regulate(s.mock.Ctx, "john")
|
||||||
assert.Equal(s.T(), regulation.ErrUserIsBanned, err)
|
assert.Equal(s.T(), regulation.ErrUserIsBanned, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ type Regulator struct {
|
||||||
|
|
||||||
config schema.RegulationConfiguration
|
config schema.RegulationConfiguration
|
||||||
|
|
||||||
storageProvider storage.RegulatorProvider
|
store storage.RegulatorProvider
|
||||||
|
|
||||||
clock utils.Clock
|
clock utils.Clock
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,59 +29,6 @@ const (
|
||||||
tableEncryption = "encryption"
|
tableEncryption = "encryption"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OAuth2SessionType represents the potential OAuth 2.0 session types.
|
|
||||||
type OAuth2SessionType int
|
|
||||||
|
|
||||||
// Representation of specific OAuth 2.0 session types.
|
|
||||||
const (
|
|
||||||
OAuth2SessionTypeAccessToken OAuth2SessionType = iota
|
|
||||||
OAuth2SessionTypeAuthorizeCode
|
|
||||||
OAuth2SessionTypeOpenIDConnect
|
|
||||||
OAuth2SessionTypePAR
|
|
||||||
OAuth2SessionTypePKCEChallenge
|
|
||||||
OAuth2SessionTypeRefreshToken
|
|
||||||
)
|
|
||||||
|
|
||||||
// String returns a string representation of this OAuth2SessionType.
|
|
||||||
func (s OAuth2SessionType) String() string {
|
|
||||||
switch s {
|
|
||||||
case OAuth2SessionTypeAccessToken:
|
|
||||||
return "access token"
|
|
||||||
case OAuth2SessionTypeAuthorizeCode:
|
|
||||||
return "authorization code"
|
|
||||||
case OAuth2SessionTypeOpenIDConnect:
|
|
||||||
return "openid connect"
|
|
||||||
case OAuth2SessionTypePAR:
|
|
||||||
return "pushed authorization request context"
|
|
||||||
case OAuth2SessionTypePKCEChallenge:
|
|
||||||
return "pkce challenge"
|
|
||||||
case OAuth2SessionTypeRefreshToken:
|
|
||||||
return "refresh token"
|
|
||||||
default:
|
|
||||||
return "invalid"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Table returns the table name for this session type.
|
|
||||||
func (s OAuth2SessionType) Table() string {
|
|
||||||
switch s {
|
|
||||||
case OAuth2SessionTypeAccessToken:
|
|
||||||
return tableOAuth2AccessTokenSession
|
|
||||||
case OAuth2SessionTypeAuthorizeCode:
|
|
||||||
return tableOAuth2AuthorizeCodeSession
|
|
||||||
case OAuth2SessionTypeOpenIDConnect:
|
|
||||||
return tableOAuth2OpenIDConnectSession
|
|
||||||
case OAuth2SessionTypePAR:
|
|
||||||
return tableOAuth2PARContext
|
|
||||||
case OAuth2SessionTypePKCEChallenge:
|
|
||||||
return tableOAuth2PKCERequestSession
|
|
||||||
case OAuth2SessionTypeRefreshToken:
|
|
||||||
return tableOAuth2RefreshTokenSession
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
encryptionNameCheck = "check"
|
encryptionNameCheck = "check"
|
||||||
)
|
)
|
||||||
|
|
|
@ -93,3 +93,56 @@ func (r EncryptionValidationTableResult) ResultDescriptor() string {
|
||||||
|
|
||||||
return "SUCCESS"
|
return "SUCCESS"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OAuth2SessionType represents the potential OAuth 2.0 session types.
|
||||||
|
type OAuth2SessionType int
|
||||||
|
|
||||||
|
// Representation of specific OAuth 2.0 session types.
|
||||||
|
const (
|
||||||
|
OAuth2SessionTypeAccessToken OAuth2SessionType = iota
|
||||||
|
OAuth2SessionTypeAuthorizeCode
|
||||||
|
OAuth2SessionTypeOpenIDConnect
|
||||||
|
OAuth2SessionTypePAR
|
||||||
|
OAuth2SessionTypePKCEChallenge
|
||||||
|
OAuth2SessionTypeRefreshToken
|
||||||
|
)
|
||||||
|
|
||||||
|
// String returns a string representation of this OAuth2SessionType.
|
||||||
|
func (s OAuth2SessionType) String() string {
|
||||||
|
switch s {
|
||||||
|
case OAuth2SessionTypeAccessToken:
|
||||||
|
return "access token"
|
||||||
|
case OAuth2SessionTypeAuthorizeCode:
|
||||||
|
return "authorization code"
|
||||||
|
case OAuth2SessionTypeOpenIDConnect:
|
||||||
|
return "openid connect"
|
||||||
|
case OAuth2SessionTypePAR:
|
||||||
|
return "pushed authorization request context"
|
||||||
|
case OAuth2SessionTypePKCEChallenge:
|
||||||
|
return "pkce challenge"
|
||||||
|
case OAuth2SessionTypeRefreshToken:
|
||||||
|
return "refresh token"
|
||||||
|
default:
|
||||||
|
return "invalid"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Table returns the table name for this session type.
|
||||||
|
func (s OAuth2SessionType) Table() string {
|
||||||
|
switch s {
|
||||||
|
case OAuth2SessionTypeAccessToken:
|
||||||
|
return tableOAuth2AccessTokenSession
|
||||||
|
case OAuth2SessionTypeAuthorizeCode:
|
||||||
|
return tableOAuth2AuthorizeCodeSession
|
||||||
|
case OAuth2SessionTypeOpenIDConnect:
|
||||||
|
return tableOAuth2OpenIDConnectSession
|
||||||
|
case OAuth2SessionTypePAR:
|
||||||
|
return tableOAuth2PARContext
|
||||||
|
case OAuth2SessionTypePKCEChallenge:
|
||||||
|
return tableOAuth2PKCERequestSession
|
||||||
|
case OAuth2SessionTypeRefreshToken:
|
||||||
|
return tableOAuth2RefreshTokenSession
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEncryptionValidationResult(t *testing.T) {
|
||||||
|
result := &EncryptionValidationResult{
|
||||||
|
InvalidCheckValue: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, result.Success())
|
||||||
|
assert.True(t, result.Checked())
|
||||||
|
|
||||||
|
result = &EncryptionValidationResult{
|
||||||
|
InvalidCheckValue: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.False(t, result.Success())
|
||||||
|
assert.True(t, result.Checked())
|
||||||
|
|
||||||
|
result = &EncryptionValidationResult{
|
||||||
|
InvalidCheckValue: false,
|
||||||
|
Tables: map[string]EncryptionValidationTableResult{
|
||||||
|
tableWebAuthnDevices: {
|
||||||
|
Invalid: 10,
|
||||||
|
Total: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, "FAILURE", result.Tables[tableWebAuthnDevices].ResultDescriptor())
|
||||||
|
|
||||||
|
assert.False(t, result.Success())
|
||||||
|
assert.True(t, result.Checked())
|
||||||
|
|
||||||
|
result = &EncryptionValidationResult{
|
||||||
|
InvalidCheckValue: false,
|
||||||
|
Tables: map[string]EncryptionValidationTableResult{
|
||||||
|
tableWebAuthnDevices: {
|
||||||
|
Error: fmt.Errorf("failed to check table"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.False(t, result.Success())
|
||||||
|
assert.False(t, result.Checked())
|
||||||
|
assert.Equal(t, "N/A", result.Tables[tableWebAuthnDevices].ResultDescriptor())
|
||||||
|
|
||||||
|
result = &EncryptionValidationResult{
|
||||||
|
InvalidCheckValue: false,
|
||||||
|
Tables: map[string]EncryptionValidationTableResult{
|
||||||
|
tableWebAuthnDevices: {
|
||||||
|
Total: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, result.Success())
|
||||||
|
assert.True(t, result.Checked())
|
||||||
|
assert.Equal(t, "SUCCESS", result.Tables[tableWebAuthnDevices].ResultDescriptor())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOAuth2SessionType(t *testing.T) {
|
||||||
|
assert.Equal(t, "access token", OAuth2SessionTypeAccessToken.String())
|
||||||
|
assert.Equal(t, tableOAuth2AccessTokenSession, OAuth2SessionTypeAccessToken.Table())
|
||||||
|
|
||||||
|
assert.Equal(t, "authorization code", OAuth2SessionTypeAuthorizeCode.String())
|
||||||
|
assert.Equal(t, tableOAuth2AuthorizeCodeSession, OAuth2SessionTypeAuthorizeCode.Table())
|
||||||
|
|
||||||
|
assert.Equal(t, "openid connect", OAuth2SessionTypeOpenIDConnect.String())
|
||||||
|
assert.Equal(t, tableOAuth2OpenIDConnectSession, OAuth2SessionTypeOpenIDConnect.Table())
|
||||||
|
|
||||||
|
assert.Equal(t, "pushed authorization request context", OAuth2SessionTypePAR.String())
|
||||||
|
assert.Equal(t, tableOAuth2PARContext, OAuth2SessionTypePAR.Table())
|
||||||
|
|
||||||
|
assert.Equal(t, "pkce challenge", OAuth2SessionTypePKCEChallenge.String())
|
||||||
|
assert.Equal(t, tableOAuth2PKCERequestSession, OAuth2SessionTypePKCEChallenge.Table())
|
||||||
|
|
||||||
|
assert.Equal(t, "refresh token", OAuth2SessionTypeRefreshToken.String())
|
||||||
|
assert.Equal(t, tableOAuth2RefreshTokenSession, OAuth2SessionTypeRefreshToken.Table())
|
||||||
|
|
||||||
|
assert.Equal(t, "invalid", OAuth2SessionType(-1).String())
|
||||||
|
assert.Equal(t, "", OAuth2SessionType(-1).Table())
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestShouldEncryptAndDecriptUsingAES(t *testing.T) {
|
func TestShouldEncryptAndDecriptUsingAES(t *testing.T) {
|
||||||
var key [32]byte = sha256.Sum256([]byte("the key"))
|
var key = sha256.Sum256([]byte("the key"))
|
||||||
|
|
||||||
var secret = "the secret"
|
var secret = "the secret"
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ func TestShouldEncryptAndDecriptUsingAES(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldFailDecryptOnInvalidKey(t *testing.T) {
|
func TestShouldFailDecryptOnInvalidKey(t *testing.T) {
|
||||||
var key [32]byte = sha256.Sum256([]byte("the key"))
|
var key = sha256.Sum256([]byte("the key"))
|
||||||
|
|
||||||
var secret = "the secret"
|
var secret = "the secret"
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ func TestShouldFailDecryptOnInvalidKey(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldFailDecryptOnInvalidCypherText(t *testing.T) {
|
func TestShouldFailDecryptOnInvalidCypherText(t *testing.T) {
|
||||||
var key [32]byte = sha256.Sum256([]byte("the key"))
|
var key = sha256.Sum256([]byte("the key"))
|
||||||
|
|
||||||
encryptedSecret := []byte("abc123")
|
encryptedSecret := []byte("abc123")
|
||||||
|
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewWriteCloser creates a new io.WriteCloser from an io.Writer.
|
|
||||||
func NewWriteCloser(wr io.Writer) io.WriteCloser {
|
|
||||||
return &WriteCloser{wr: wr}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteCloser is a io.Writer with an io.Closer.
|
|
||||||
type WriteCloser struct {
|
|
||||||
wr io.Writer
|
|
||||||
|
|
||||||
closed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write to the io.Writer.
|
|
||||||
func (w *WriteCloser) Write(p []byte) (n int, err error) {
|
|
||||||
if w.closed {
|
|
||||||
return -1, errors.New("already closed")
|
|
||||||
}
|
|
||||||
|
|
||||||
return w.wr.Write(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the io.Closer.
|
|
||||||
func (w *WriteCloser) Close() error {
|
|
||||||
w.closed = true
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -8,6 +8,97 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestIsStringAbsURL(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
have string
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ShouldBeAbs",
|
||||||
|
"https://google.com",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldNotBeAbs",
|
||||||
|
"google.com",
|
||||||
|
"could not parse 'google.com' as a URL",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
theError := IsStringAbsURL(tc.have)
|
||||||
|
|
||||||
|
if tc.err == "" {
|
||||||
|
assert.NoError(t, theError)
|
||||||
|
} else {
|
||||||
|
assert.EqualError(t, theError, tc.err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsStringInSliceF(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
needle string
|
||||||
|
haystack []string
|
||||||
|
isEqual func(needle, item string) bool
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ShouldBePresent",
|
||||||
|
"good",
|
||||||
|
[]string{"good"},
|
||||||
|
func(needle, item string) bool {
|
||||||
|
return needle == item
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldNotBePresent",
|
||||||
|
"bad",
|
||||||
|
[]string{"good"},
|
||||||
|
func(needle, item string) bool {
|
||||||
|
return needle == item
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tc.expected, IsStringInSliceF(tc.needle, tc.haystack, tc.isEqual))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringHTMLEscape(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
have string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ShouldNotAlterAlphaNum",
|
||||||
|
"abc123",
|
||||||
|
"abc123",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldEscapeSpecial",
|
||||||
|
"abc123><@#@",
|
||||||
|
"abc123><@#@",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tc.expected, StringHTMLEscape(tc.have))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestStringSplitDelimitedEscaped(t *testing.T) {
|
func TestStringSplitDelimitedEscaped(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc, have string
|
desc, have string
|
||||||
|
|
|
@ -209,11 +209,51 @@ func TestParseTimeString(t *testing.T) {
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
index, actual, err := matchParseTimeStringWithLayouts(tc.have, StandardTimeLayouts)
|
index, actualA, errA := matchParseTimeStringWithLayouts(tc.have, StandardTimeLayouts)
|
||||||
|
actualB, errB := ParseTimeStringWithLayouts(tc.have, StandardTimeLayouts)
|
||||||
|
actualC, errC := ParseTimeString(tc.have)
|
||||||
|
|
||||||
|
if tc.err == "" {
|
||||||
|
assert.NoError(t, errA)
|
||||||
|
assert.NoError(t, errB)
|
||||||
|
assert.NoError(t, errC)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.index, index)
|
||||||
|
assert.Equal(t, tc.expected.UnixNano(), actualA.UnixNano())
|
||||||
|
assert.Equal(t, tc.expected.UnixNano(), actualB.UnixNano())
|
||||||
|
assert.Equal(t, tc.expected.UnixNano(), actualC.UnixNano())
|
||||||
|
} else {
|
||||||
|
assert.EqualError(t, errA, tc.err)
|
||||||
|
assert.EqualError(t, errB, tc.err)
|
||||||
|
assert.EqualError(t, errC, tc.err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseTimeStringWithLayouts(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
have string
|
||||||
|
index int
|
||||||
|
expected time.Time
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
{"ShouldParseIntegerAsUnix", "1675899060", -1, time.Unix(1675899060, 0), ""},
|
||||||
|
{"ShouldParseIntegerAsUnixMilli", "1675899060000", -2, time.Unix(1675899060, 0), ""},
|
||||||
|
{"ShouldParseIntegerAsUnixMicro", "1675899060000000", -3, time.Unix(1675899060, 0), ""},
|
||||||
|
{"ShouldNotParseSuperLargeInteger", "9999999999999999999999999999999999999999", -999, time.Unix(0, 0), "time value was detected as an integer but the integer could not be parsed: strconv.ParseInt: parsing \"9999999999999999999999999999999999999999\": value out of range"},
|
||||||
|
{"ShouldParseSimpleTime", "Jan 2 15:04:05 2006", 0, time.Unix(1136214245, 0), ""},
|
||||||
|
{"ShouldNotParseInvalidTime", "abc", -998, time.Unix(0, 0), "failed to find a suitable time layout for time 'abc'"},
|
||||||
|
{"ShouldMatchDate", "2020-05-01", 6, time.Unix(1588291200, 0), ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
actual, err := ParseTimeStringWithLayouts(tc.have, StandardTimeLayouts)
|
||||||
|
|
||||||
if tc.err == "" {
|
if tc.err == "" {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, tc.index, index)
|
|
||||||
assert.Equal(t, tc.expected.UnixNano(), actual.UnixNano())
|
assert.Equal(t, tc.expected.UnixNano(), actual.UnixNano())
|
||||||
} else {
|
} else {
|
||||||
assert.EqualError(t, err, tc.err)
|
assert.EqualError(t, err, tc.err)
|
||||||
|
|
|
@ -59,3 +59,8 @@ func TestIsRedirectionSafe_ShouldReturnFalseOnBadDomain(t *testing.T) {
|
||||||
assert.False(t, isURLSafe("https://secure.example.comc", "example.com"))
|
assert.False(t, isURLSafe("https://secure.example.comc", "example.com"))
|
||||||
assert.False(t, isURLSafe("https://secure.example.co", "example.com"))
|
assert.False(t, isURLSafe("https://secure.example.co", "example.com"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHasDomainSuffix(t *testing.T) {
|
||||||
|
assert.False(t, HasDomainSuffix("abc", ""))
|
||||||
|
assert.False(t, HasDomainSuffix("", ""))
|
||||||
|
}
|
||||||
|
|
|
@ -23,8 +23,8 @@
|
||||||
"@fortawesome/free-solid-svg-icons": "6.4.0",
|
"@fortawesome/free-solid-svg-icons": "6.4.0",
|
||||||
"@fortawesome/react-fontawesome": "0.2.0",
|
"@fortawesome/react-fontawesome": "0.2.0",
|
||||||
"@mui/icons-material": "5.11.16",
|
"@mui/icons-material": "5.11.16",
|
||||||
"@mui/material": "5.13.1",
|
"@mui/material": "5.13.2",
|
||||||
"@mui/styles": "5.13.1",
|
"@mui/styles": "5.13.2",
|
||||||
"@simplewebauthn/browser": "7.2.0",
|
"@simplewebauthn/browser": "7.2.0",
|
||||||
"@simplewebauthn/typescript-types": "7.0.0",
|
"@simplewebauthn/typescript-types": "7.0.0",
|
||||||
"axios": "1.4.0",
|
"axios": "1.4.0",
|
||||||
|
@ -77,17 +77,17 @@
|
||||||
"@limegrass/eslint-plugin-import-alias": "1.0.6",
|
"@limegrass/eslint-plugin-import-alias": "1.0.6",
|
||||||
"@testing-library/jest-dom": "5.16.5",
|
"@testing-library/jest-dom": "5.16.5",
|
||||||
"@testing-library/react": "14.0.0",
|
"@testing-library/react": "14.0.0",
|
||||||
"@types/node": "20.2.1",
|
"@types/node": "20.2.5",
|
||||||
"@types/react": "18.2.6",
|
"@types/react": "18.2.7",
|
||||||
"@types/react-dom": "18.2.4",
|
"@types/react-dom": "18.2.4",
|
||||||
"@types/testing-library__jest-dom": "5.14.5",
|
"@types/testing-library__jest-dom": "5.14.6",
|
||||||
"@types/zxcvbn": "4.4.1",
|
"@types/zxcvbn": "4.4.1",
|
||||||
"@typescript-eslint/eslint-plugin": "5.59.6",
|
"@typescript-eslint/eslint-plugin": "5.59.7",
|
||||||
"@typescript-eslint/parser": "5.59.6",
|
"@typescript-eslint/parser": "5.59.7",
|
||||||
"@vitejs/plugin-react": "4.0.0",
|
"@vitejs/plugin-react": "4.0.0",
|
||||||
"@vitest/coverage-istanbul": "0.31.1",
|
"@vitest/coverage-istanbul": "0.31.1",
|
||||||
"esbuild": "0.17.19",
|
"esbuild": "0.17.19",
|
||||||
"eslint": "8.40.0",
|
"eslint": "8.41.0",
|
||||||
"eslint-config-prettier": "8.8.0",
|
"eslint-config-prettier": "8.8.0",
|
||||||
"eslint-config-react-app": "7.0.1",
|
"eslint-config-react-app": "7.0.1",
|
||||||
"eslint-formatter-rdjson": "1.0.5",
|
"eslint-formatter-rdjson": "1.0.5",
|
||||||
|
@ -97,14 +97,14 @@
|
||||||
"eslint-plugin-prettier": "4.2.1",
|
"eslint-plugin-prettier": "4.2.1",
|
||||||
"eslint-plugin-react": "7.32.2",
|
"eslint-plugin-react": "7.32.2",
|
||||||
"eslint-plugin-react-hooks": "4.6.0",
|
"eslint-plugin-react-hooks": "4.6.0",
|
||||||
"happy-dom": "9.19.2",
|
"happy-dom": "9.20.3",
|
||||||
"husky": "8.0.3",
|
"husky": "8.0.3",
|
||||||
"prettier": "2.8.8",
|
"prettier": "2.8.8",
|
||||||
"react-test-renderer": "18.2.0",
|
"react-test-renderer": "18.2.0",
|
||||||
"typescript": "5.0.4",
|
"typescript": "5.0.4",
|
||||||
"vite": "4.3.8",
|
"vite": "4.3.9",
|
||||||
"vite-plugin-eslint": "1.8.1",
|
"vite-plugin-eslint": "1.8.1",
|
||||||
"vite-plugin-istanbul": "4.0.1",
|
"vite-plugin-istanbul": "4.1.0",
|
||||||
"vite-plugin-svgr": "3.2.0",
|
"vite-plugin-svgr": "3.2.0",
|
||||||
"vite-tsconfig-paths": "4.2.0",
|
"vite-tsconfig-paths": "4.2.0",
|
||||||
"vitest": "0.31.1",
|
"vitest": "0.31.1",
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue