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"
|
||||
description: ""
|
||||
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
|
||||
draft: false
|
||||
images: []
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
title: "OpenID Connect 1.0 Clients"
|
||||
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."
|
||||
date: 2023-05-08T13:38:08+10:00
|
||||
date: 2023-05-15T10:32:10+10:00
|
||||
draft: false
|
||||
images: []
|
||||
menu:
|
||||
|
@ -28,39 +28,41 @@ intended for production use it's used to provide context and an indentation exam
|
|||
identity_providers:
|
||||
oidc:
|
||||
clients:
|
||||
- id: myapp
|
||||
description: My Application
|
||||
- id: 'myapp'
|
||||
description: 'My Application'
|
||||
secret: '$pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng' # The digest of 'insecure_secret'.
|
||||
sector_identifier: ''
|
||||
public: false
|
||||
redirect_uris:
|
||||
- https://oidc.example.com:8080/oauth2/callback
|
||||
- 'https://oidc.example.com:8080/oauth2/callback'
|
||||
audience: []
|
||||
scopes:
|
||||
- openid
|
||||
- groups
|
||||
- email
|
||||
- profile
|
||||
- 'openid'
|
||||
- 'groups'
|
||||
- 'email'
|
||||
- 'profile'
|
||||
grant_types:
|
||||
- refresh_token
|
||||
- authorization_code
|
||||
- 'refresh_token'
|
||||
- 'authorization_code'
|
||||
response_types:
|
||||
- code
|
||||
- 'code'
|
||||
response_modes:
|
||||
- form_post
|
||||
- query
|
||||
- fragment
|
||||
authorization_policy: two_factor
|
||||
consent_mode: explicit
|
||||
pre_configured_consent_duration: 1w
|
||||
- 'form_post'
|
||||
- 'query'
|
||||
- 'fragment'
|
||||
authorization_policy: 'two_factor'
|
||||
consent_mode: 'explicit'
|
||||
pre_configured_consent_duration: '1 week'
|
||||
enforce_par: 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_signing_alg: RS256
|
||||
id_token_signing_alg: RS256
|
||||
request_object_signing_alg: RS256
|
||||
userinfo_signing_alg: none
|
||||
```
|
||||
|
||||
## 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
|
||||
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
|
||||
|
||||
{{< 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) | `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
|
||||
|
||||
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.
|
||||
|
||||
##### 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
|
||||
|
||||
{{< 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
|
||||
client application or the administrator of the client application.
|
||||
|
@ -388,9 +438,15 @@ The key *__MUST__*:
|
|||
* A P-384 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.
|
||||
|
||||
##### 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
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
title: "OpenID Connect 1.0 Provider"
|
||||
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."
|
||||
date: 2023-05-08T13:38:08+10:00
|
||||
date: 2023-05-15T10:32:10+10:00
|
||||
draft: false
|
||||
images: []
|
||||
menu:
|
||||
|
@ -135,41 +135,31 @@ with 64 or more characters.
|
|||
|
||||
### 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" >}}
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
{{< 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.
|
||||
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
|
||||
|
||||
{{< 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.
|
||||
The default if this value is omitted is the first 7 characters of the public key SHA256 thumbprint encoded into
|
||||
hexadecimal.
|
||||
|
||||
#### 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.
|
||||
|
||||
#### 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
|
||||
|
||||
{{< confkey type="string" required="yes" >}}
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
The private key *__MUST__*:
|
||||
|
@ -217,15 +221,13 @@ it if present.
|
|||
|
||||
{{< confkey type="string" required="yes" >}}
|
||||
|
||||
*__Important Note:__ This can also be defined using a [secret](../../methods/secrets.md) which is __strongly recommended__
|
||||
especially for containerized deployments.*
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
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__*:
|
||||
|
||||
|
@ -240,7 +242,7 @@ key data for the first certificate in the chain.
|
|||
|
||||
{{< 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]
|
||||
JSON key's in the JWKs [Discoverable Endpoint](../../../integration/openid-connect/introduction.md#discoverable-endpoints)
|
||||
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
|
||||
changing this value.
|
||||
|
||||
This restriction can also be disabled entirely when set to `-1`.
|
||||
|
||||
### enforce_pkce
|
||||
|
||||
{{< 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
|
||||
|
||||
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
|
||||
[OpenID Connect 1.0]: https://openid.net/connect/
|
||||
|
|
|
@ -15,6 +15,11 @@ aliases:
|
|||
- /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
|
||||
|
||||
{{< config-alert-example >}}
|
||||
|
|
|
@ -32,6 +32,7 @@ storage:
|
|||
schema: 'public'
|
||||
username: 'authelia'
|
||||
password: 'mypassword'
|
||||
timeout: '5s'
|
||||
tls:
|
||||
server_name: 'postgres.example.com'
|
||||
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
|
||||
[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
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
[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
|
||||
|
||||
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/fsnotify/fsnotify v1.6.0
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.4
|
||||
github.com/go-crypt/crypt v0.2.7
|
||||
github.com/go-ldap/ldap/v3 v3.4.5-0.20230506142018-039466e6b835
|
||||
github.com/go-rod/rod v0.113.0
|
||||
github.com/go-crypt/crypt v0.2.9
|
||||
github.com/go-ldap/ldap/v3 v3.4.5-0.20230521105649-cdb0754f6668
|
||||
github.com/go-rod/rod v0.113.1
|
||||
github.com/go-sql-driver/mysql v1.7.1
|
||||
github.com/go-webauthn/webauthn v0.8.2
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
|
@ -70,7 +70,7 @@ require (
|
|||
github.com/ecordell/optgen v0.0.6 // indirect
|
||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // 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-webauthn/revoke v0.1.9 // 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/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-crypt/crypt v0.2.7 h1:Ir6E59c1wrskJhpJXMqaynHA2xAxpGN7nQXlLkbpzR0=
|
||||
github.com/go-crypt/crypt v0.2.7/go.mod h1:ulieouNs4qwFCq4wF61oyTQYXAXSoOv995EU4hcHwMU=
|
||||
github.com/go-crypt/x v0.2.0 h1:rHMiKRAu6kFc+xAnQywDb3iHGpvrFbIGXnP3IfCZ+2U=
|
||||
github.com/go-crypt/x v0.2.0/go.mod h1:uLo5o+Cc8nvahDASQpntR1g3ZMUoq2LM/859PkhykC4=
|
||||
github.com/go-crypt/crypt v0.2.9 h1:5gWWTId2Qyqs9ROIsegt5pnqo9wUSRLbhpkR6JSftjg=
|
||||
github.com/go-crypt/crypt v0.2.9/go.mod h1:JjzdTYE2mArb6nBoIvvpF7o46/rK/1pfmlArCRMTFUk=
|
||||
github.com/go-crypt/x v0.2.1 h1:OGw78Bswme9lffCOX6tyuC280ouU5391glsvThMtM5U=
|
||||
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/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-ldap/ldap/v3 v3.4.5-0.20230506142018-039466e6b835 h1:XgBmN9yZXIh9vJGzs2qYPb5ee8/VnOWLHHYcKXGXKME=
|
||||
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 h1:qbWHOCDBT8m2I1tDGP7S58dgi/xaDDCKuR5dbarLGOU=
|
||||
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.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/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||
github.com/go-rod/rod v0.113.0 h1:E7+GLjYVZnScewIB2u8+66joQLaDGbOLzSOT4orNHms=
|
||||
github.com/go-rod/rod v0.113.0/go.mod h1:aiedSEFg5DwG/fnNbUOTPMTTWX3MRj6vIs/a684Mthw=
|
||||
github.com/go-rod/rod v0.113.1 h1:+Qb4K/vkR7BOhW6FhfhtLzUD3l11+0XlF4do+27sOQk=
|
||||
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.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
|
|
|
@ -2,20 +2,8 @@ package authentication
|
|||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// 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
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -109,3 +97,7 @@ const fileAuthenticationMode = 0600
|
|||
// OWASP recommends to escape some special characters.
|
||||
// https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/LDAP_Injection_Prevention_Cheat_Sheet.md
|
||||
const specialLDAPRunes = ",#+<>;\"="
|
||||
|
||||
var (
|
||||
encodingUTF16LittleEndian = unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)
|
||||
)
|
||||
|
|
|
@ -23,7 +23,7 @@ import (
|
|||
type FileUserProvider struct {
|
||||
config *schema.FileAuthenticationBackend
|
||||
hash algorithm.Hash
|
||||
database *FileUserDatabase
|
||||
database FileUserDatabase
|
||||
mutex *sync.Mutex
|
||||
timeoutReload time.Time
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ func NewFileUserProvider(config *schema.FileAuthenticationBackend) (provider *Fi
|
|||
config: config,
|
||||
mutex: &sync.Mutex{},
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
if err = hash.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("failed to validate hash settings: %w", err)
|
||||
}
|
||||
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -12,9 +12,16 @@ import (
|
|||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// NewFileUserDatabase creates a new FileUserDatabase.
|
||||
func NewFileUserDatabase(filePath string, searchEmail, searchCI bool) (database *FileUserDatabase) {
|
||||
return &FileUserDatabase{
|
||||
type FileUserDatabase interface {
|
||||
Save() (err error)
|
||||
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{},
|
||||
Path: filePath,
|
||||
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.
|
||||
type FileUserDatabase struct {
|
||||
// YAMLUserDatabase is a user details database that is concurrency safe database and can be reloaded.
|
||||
type YAMLUserDatabase struct {
|
||||
*sync.RWMutex
|
||||
|
||||
Path string
|
||||
|
@ -39,7 +46,7 @@ type FileUserDatabase struct {
|
|||
}
|
||||
|
||||
// Save the database to disk.
|
||||
func (m *FileUserDatabase) Save() (err error) {
|
||||
func (m *YAMLUserDatabase) Save() (err error) {
|
||||
m.RLock()
|
||||
|
||||
defer m.RUnlock()
|
||||
|
@ -52,7 +59,7 @@ func (m *FileUserDatabase) Save() (err error) {
|
|||
}
|
||||
|
||||
// Load the database from disk.
|
||||
func (m *FileUserDatabase) Load() (err error) {
|
||||
func (m *YAMLUserDatabase) Load() (err error) {
|
||||
yml := &DatabaseModel{Users: map[string]UserDetailsModel{}}
|
||||
|
||||
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.
|
||||
func (m *FileUserDatabase) LoadAliases() (err error) {
|
||||
func (m *YAMLUserDatabase) LoadAliases() (err error) {
|
||||
if m.SearchEmail || m.SearchCI {
|
||||
for k, user := range m.Users {
|
||||
if m.SearchEmail && user.Email != "" {
|
||||
|
@ -91,7 +98,7 @@ func (m *FileUserDatabase) LoadAliases() (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *FileUserDatabase) loadAlias(k string) (err error) {
|
||||
func (m *YAMLUserDatabase) loadAlias(k string) (err error) {
|
||||
u := strings.ToLower(k)
|
||||
|
||||
if u != k {
|
||||
|
@ -113,7 +120,7 @@ func (m *FileUserDatabase) loadAlias(k string) (err error) {
|
|||
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)
|
||||
|
||||
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
|
||||
// username.
|
||||
func (m *FileUserDatabase) GetUserDetails(username string) (user DatabaseUserDetails, err error) {
|
||||
func (m *YAMLUserDatabase) GetUserDetails(username string) (user DatabaseUserDetails, err error) {
|
||||
m.RLock()
|
||||
|
||||
defer m.RUnlock()
|
||||
|
@ -172,7 +179,7 @@ func (m *FileUserDatabase) GetUserDetails(username string) (user DatabaseUserDet
|
|||
}
|
||||
|
||||
// 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 {
|
||||
return
|
||||
}
|
||||
|
@ -184,8 +191,8 @@ func (m *FileUserDatabase) SetUserDetails(username string, details *DatabaseUser
|
|||
m.Unlock()
|
||||
}
|
||||
|
||||
// ToDatabaseModel converts the FileUserDatabase into the DatabaseModel for saving.
|
||||
func (m *FileUserDatabase) ToDatabaseModel() (model *DatabaseModel) {
|
||||
// ToDatabaseModel converts the YAMLUserDatabase into the DatabaseModel for saving.
|
||||
func (m *YAMLUserDatabase) ToDatabaseModel() (model *DatabaseModel) {
|
||||
model = &DatabaseModel{
|
||||
Users: map[string]UserDetailsModel{},
|
||||
}
|
||||
|
@ -236,8 +243,8 @@ type DatabaseModel struct {
|
|||
Users map[string]UserDetailsModel `yaml:"users" valid:"required"`
|
||||
}
|
||||
|
||||
// ReadToFileUserDatabase reads the DatabaseModel into a FileUserDatabase.
|
||||
func (m *DatabaseModel) ReadToFileUserDatabase(db *FileUserDatabase) (err error) {
|
||||
// ReadToFileUserDatabase reads the DatabaseModel into a YAMLUserDatabase.
|
||||
func (m *DatabaseModel) ReadToFileUserDatabase(db *YAMLUserDatabase) (err error) {
|
||||
users := map[string]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
|
||||
|
||||
import (
|
||||
"log"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"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/require"
|
||||
|
||||
"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) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("skipping test due to being on windows")
|
||||
}
|
||||
|
||||
_ = os.Mkdir("/tmp/noperms/", 0000)
|
||||
err := checkDatabase("/tmp/noperms/users_database.yml")
|
||||
dir := t.TempDir()
|
||||
|
||||
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) {
|
||||
err := checkDatabase("./nonexistent.yml")
|
||||
_ = os.Remove("./nonexistent.yml")
|
||||
dir := t.TempDir()
|
||||
|
||||
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) {
|
||||
WithDatabase(UserDatabaseContent, func(path string) {
|
||||
WithDatabase(t, UserDatabaseContent, func(path string) {
|
||||
config := DefaultFileAuthenticationBackendConfiguration
|
||||
config.Path = path
|
||||
provider := NewFileUserProvider(&config)
|
||||
|
@ -68,7 +176,7 @@ func TestShouldCheckUserArgon2idPasswordIsCorrect(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestShouldCheckUserSHA512PasswordIsCorrect(t *testing.T) {
|
||||
WithDatabase(UserDatabaseContent, func(path string) {
|
||||
WithDatabase(t, UserDatabaseContent, func(path string) {
|
||||
config := DefaultFileAuthenticationBackendConfiguration
|
||||
config.Path = path
|
||||
|
||||
|
@ -84,7 +192,7 @@ func TestShouldCheckUserSHA512PasswordIsCorrect(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestShouldCheckUserPasswordIsWrong(t *testing.T) {
|
||||
WithDatabase(UserDatabaseContent, func(path string) {
|
||||
WithDatabase(t, UserDatabaseContent, func(path string) {
|
||||
config := DefaultFileAuthenticationBackendConfiguration
|
||||
config.Path = path
|
||||
|
||||
|
@ -100,7 +208,7 @@ func TestShouldCheckUserPasswordIsWrong(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestShouldCheckUserPasswordIsWrongForEnumerationCompare(t *testing.T) {
|
||||
WithDatabase(UserDatabaseContent, func(path string) {
|
||||
WithDatabase(t, UserDatabaseContent, func(path string) {
|
||||
config := DefaultFileAuthenticationBackendConfiguration
|
||||
config.Path = path
|
||||
|
||||
|
@ -115,7 +223,7 @@ func TestShouldCheckUserPasswordIsWrongForEnumerationCompare(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestShouldCheckUserPasswordOfUserThatDoesNotExist(t *testing.T) {
|
||||
WithDatabase(UserDatabaseContent, func(path string) {
|
||||
WithDatabase(t, UserDatabaseContent, func(path string) {
|
||||
config := DefaultFileAuthenticationBackendConfiguration
|
||||
config.Path = path
|
||||
|
||||
|
@ -131,7 +239,7 @@ func TestShouldCheckUserPasswordOfUserThatDoesNotExist(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestShouldRetrieveUserDetails(t *testing.T) {
|
||||
WithDatabase(UserDatabaseContent, func(path string) {
|
||||
WithDatabase(t, UserDatabaseContent, func(path string) {
|
||||
config := DefaultFileAuthenticationBackendConfiguration
|
||||
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) {
|
||||
WithDatabase(UserDatabaseContent, func(path string) {
|
||||
WithDatabase(t, UserDatabaseContent, func(path string) {
|
||||
config := DefaultFileAuthenticationBackendConfiguration
|
||||
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.
|
||||
func TestShouldUpdatePasswordHashingAlgorithmToArgon2id(t *testing.T) {
|
||||
WithDatabase(UserDatabaseContent, func(path string) {
|
||||
WithDatabase(t, UserDatabaseContent, func(path string) {
|
||||
config := DefaultFileAuthenticationBackendConfiguration
|
||||
config.Path = path
|
||||
|
||||
|
@ -180,7 +307,10 @@ func TestShouldUpdatePasswordHashingAlgorithmToArgon2id(t *testing.T) {
|
|||
|
||||
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")
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
@ -189,15 +319,15 @@ func TestShouldUpdatePasswordHashingAlgorithmToArgon2id(t *testing.T) {
|
|||
|
||||
assert.NoError(t, provider.StartupCheck())
|
||||
|
||||
ok, err := provider.CheckUserPassword("harry", "newpassword")
|
||||
ok, err = provider.CheckUserPassword("harry", "newpassword")
|
||||
assert.NoError(t, err)
|
||||
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) {
|
||||
WithDatabase(UserDatabaseContent, func(path string) {
|
||||
WithDatabase(t, UserDatabaseContent, func(path string) {
|
||||
config := DefaultFileAuthenticationBackendConfiguration
|
||||
config.Path = path
|
||||
config.Password.Algorithm = "sha2crypt"
|
||||
|
@ -207,7 +337,10 @@ func TestShouldUpdatePasswordHashingAlgorithmToSHA512(t *testing.T) {
|
|||
|
||||
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")
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
@ -216,15 +349,29 @@ func TestShouldUpdatePasswordHashingAlgorithmToSHA512(t *testing.T) {
|
|||
|
||||
assert.NoError(t, provider.StartupCheck())
|
||||
|
||||
ok, err := provider.CheckUserPassword("john", "newpassword")
|
||||
ok, err = provider.CheckUserPassword("john", "newpassword")
|
||||
assert.NoError(t, err)
|
||||
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) {
|
||||
WithDatabase(MalformedUserDatabaseContent, func(path string) {
|
||||
WithDatabase(t, MalformedUserDatabaseContent, func(path string) {
|
||||
config := DefaultFileAuthenticationBackendConfiguration
|
||||
config.Path = path
|
||||
|
||||
|
@ -235,7 +382,7 @@ func TestShouldRaiseWhenLoadingMalformedDatabaseForFirstTime(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestShouldRaiseWhenLoadingDatabaseWithBadSchemaForFirstTime(t *testing.T) {
|
||||
WithDatabase(BadSchemaUserDatabaseContent, func(path string) {
|
||||
WithDatabase(t, BadSchemaUserDatabaseContent, func(path string) {
|
||||
config := DefaultFileAuthenticationBackendConfiguration
|
||||
config.Path = path
|
||||
|
||||
|
@ -246,7 +393,7 @@ func TestShouldRaiseWhenLoadingDatabaseWithBadSchemaForFirstTime(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestShouldRaiseWhenLoadingDatabaseWithBadSHA512HashesForTheFirstTime(t *testing.T) {
|
||||
WithDatabase(BadSHA512HashContent, func(path string) {
|
||||
WithDatabase(t, BadSHA512HashContent, func(path string) {
|
||||
config := DefaultFileAuthenticationBackendConfiguration
|
||||
config.Path = path
|
||||
|
||||
|
@ -257,7 +404,7 @@ func TestShouldRaiseWhenLoadingDatabaseWithBadSHA512HashesForTheFirstTime(t *tes
|
|||
}
|
||||
|
||||
func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashSettingsForTheFirstTime(t *testing.T) {
|
||||
WithDatabase(BadArgon2idHashSettingsContent, func(path string) {
|
||||
WithDatabase(t, BadArgon2idHashSettingsContent, func(path string) {
|
||||
config := DefaultFileAuthenticationBackendConfiguration
|
||||
config.Path = path
|
||||
|
||||
|
@ -268,7 +415,7 @@ func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashSettingsForTheFirstTim
|
|||
}
|
||||
|
||||
func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashKeyForTheFirstTime(t *testing.T) {
|
||||
WithDatabase(BadArgon2idHashKeyContent, func(path string) {
|
||||
WithDatabase(t, BadArgon2idHashKeyContent, func(path string) {
|
||||
config := DefaultFileAuthenticationBackendConfiguration
|
||||
config.Path = path
|
||||
|
||||
|
@ -279,7 +426,7 @@ func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashKeyForTheFirstTime(t *
|
|||
}
|
||||
|
||||
func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashSaltForTheFirstTime(t *testing.T) {
|
||||
WithDatabase(BadArgon2idHashSaltContent, func(path string) {
|
||||
WithDatabase(t, BadArgon2idHashSaltContent, func(path string) {
|
||||
config := DefaultFileAuthenticationBackendConfiguration
|
||||
config.Path = path
|
||||
|
||||
|
@ -290,7 +437,7 @@ func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashSaltForTheFirstTime(t
|
|||
}
|
||||
|
||||
func TestShouldSupportHashPasswordWithoutCRYPT(t *testing.T) {
|
||||
WithDatabase(UserDatabaseWithoutCryptContent, func(path string) {
|
||||
WithDatabase(t, UserDatabaseWithoutCryptContent, func(path string) {
|
||||
config := DefaultFileAuthenticationBackendConfiguration
|
||||
config.Path = path
|
||||
|
||||
|
@ -306,7 +453,7 @@ func TestShouldSupportHashPasswordWithoutCRYPT(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestShouldNotAllowLoginOfDisabledUsers(t *testing.T) {
|
||||
WithDatabase(UserDatabaseContent, func(path string) {
|
||||
WithDatabase(t, UserDatabaseContent, func(path string) {
|
||||
config := DefaultFileAuthenticationBackendConfiguration
|
||||
config.Path = path
|
||||
|
||||
|
@ -322,7 +469,7 @@ func TestShouldNotAllowLoginOfDisabledUsers(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestShouldErrorOnInvalidCaseSensitiveFile(t *testing.T) {
|
||||
WithDatabase(UserDatabaseContentInvalidSearchCaseInsenstive, func(path string) {
|
||||
WithDatabase(t, UserDatabaseContentInvalidSearchCaseInsenstive, func(path string) {
|
||||
config := DefaultFileAuthenticationBackendConfiguration
|
||||
config.Path = path
|
||||
config.Search.Email = false
|
||||
|
@ -335,7 +482,7 @@ func TestShouldErrorOnInvalidCaseSensitiveFile(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestShouldErrorOnDuplicateEmail(t *testing.T) {
|
||||
WithDatabase(UserDatabaseContentInvalidSearchEmail, func(path string) {
|
||||
WithDatabase(t, UserDatabaseContentInvalidSearchEmail, func(path string) {
|
||||
config := DefaultFileAuthenticationBackendConfiguration
|
||||
config.Path = path
|
||||
config.Search.Email = true
|
||||
|
@ -349,7 +496,7 @@ func TestShouldErrorOnDuplicateEmail(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestShouldNotErrorOnEmailAsUsername(t *testing.T) {
|
||||
WithDatabase(UserDatabaseContentSearchEmailAsUsername, func(path string) {
|
||||
WithDatabase(t, UserDatabaseContentSearchEmailAsUsername, func(path string) {
|
||||
config := DefaultFileAuthenticationBackendConfiguration
|
||||
config.Path = path
|
||||
config.Search.Email = true
|
||||
|
@ -362,7 +509,7 @@ func TestShouldNotErrorOnEmailAsUsername(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestShouldErrorOnEmailAsUsernameWithDuplicateEmail(t *testing.T) {
|
||||
WithDatabase(UserDatabaseContentInvalidSearchEmailAsUsername, func(path string) {
|
||||
WithDatabase(t, UserDatabaseContentInvalidSearchEmailAsUsername, func(path string) {
|
||||
config := DefaultFileAuthenticationBackendConfiguration
|
||||
config.Path = path
|
||||
config.Search.Email = true
|
||||
|
@ -375,7 +522,7 @@ func TestShouldErrorOnEmailAsUsernameWithDuplicateEmail(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestShouldErrorOnEmailAsUsernameWithDuplicateEmailCase(t *testing.T) {
|
||||
WithDatabase(UserDatabaseContentInvalidSearchEmailAsUsernameCase, func(path string) {
|
||||
WithDatabase(t, UserDatabaseContentInvalidSearchEmailAsUsernameCase, func(path string) {
|
||||
config := DefaultFileAuthenticationBackendConfiguration
|
||||
config.Path = path
|
||||
config.Search.Email = false
|
||||
|
@ -388,7 +535,7 @@ func TestShouldErrorOnEmailAsUsernameWithDuplicateEmailCase(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestShouldAllowLookupByEmail(t *testing.T) {
|
||||
WithDatabase(UserDatabaseContent, func(path string) {
|
||||
WithDatabase(t, UserDatabaseContent, func(path string) {
|
||||
config := DefaultFileAuthenticationBackendConfiguration
|
||||
config.Path = path
|
||||
config.Search.Email = true
|
||||
|
@ -415,7 +562,7 @@ func TestShouldAllowLookupByEmail(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestShouldAllowLookupCI(t *testing.T) {
|
||||
WithDatabase(UserDatabaseContent, func(path string) {
|
||||
WithDatabase(t, UserDatabaseContent, func(path string) {
|
||||
config := DefaultFileAuthenticationBackendConfiguration
|
||||
config.Path = path
|
||||
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 (
|
||||
DefaultFileAuthenticationBackendConfiguration = schema.FileAuthenticationBackend{
|
||||
Path: "",
|
||||
|
@ -657,3 +937,17 @@ users:
|
|||
- admins
|
||||
- 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_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)
|
||||
// The password needs to be enclosed in quotes
|
||||
// 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})
|
||||
|
||||
err = p.modify(client, modifyRequest)
|
||||
|
|
|
@ -1604,7 +1604,7 @@ func TestShouldUpdateUserPasswordMSAD(t *testing.T) {
|
|||
[]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})
|
||||
|
||||
dialURLOIDs := mockFactory.EXPECT().
|
||||
|
@ -1715,7 +1715,7 @@ func TestShouldUpdateUserPasswordMSADWithReferrals(t *testing.T) {
|
|||
[]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})
|
||||
|
||||
dialURLOIDs := mockFactory.EXPECT().
|
||||
|
@ -1843,7 +1843,7 @@ func TestShouldUpdateUserPasswordMSADWithReferralsWithReferralConnectErr(t *test
|
|||
[]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})
|
||||
|
||||
dialURLOIDs := mockFactory.EXPECT().
|
||||
|
@ -1962,7 +1962,7 @@ func TestShouldUpdateUserPasswordMSADWithReferralsWithReferralModifyErr(t *testi
|
|||
[]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})
|
||||
|
||||
dialURLOIDs := mockFactory.EXPECT().
|
||||
|
@ -2094,7 +2094,7 @@ func TestShouldUpdateUserPasswordMSADWithoutReferrals(t *testing.T) {
|
|||
[]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})
|
||||
|
||||
dialURLOIDs := mockFactory.EXPECT().
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
)
|
||||
|
||||
// LDAPClientFactory an interface of factory of LDAP clients.
|
||||
|
@ -103,4 +102,30 @@ type LDAPSupportedControlTypes struct {
|
|||
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)
|
||||
case m.UserWildcard:
|
||||
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)
|
||||
case m.GroupWildcard:
|
||||
if subject.IsAnonymous() && strings.HasSuffix(domain, m.Name) {
|
||||
return true
|
||||
return len(domain) > len(m.Name)
|
||||
}
|
||||
|
||||
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{
|
||||
OIDC: &schema.OpenIDConnectConfiguration{
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||
IdentityProviders: schema.IdentityProviders{
|
||||
OIDC: &schema.OpenIDConnect{
|
||||
Clients: []schema.OpenIDConnectClient{
|
||||
{
|
||||
Policy: oneFactor,
|
||||
},
|
||||
|
|
|
@ -16,7 +16,8 @@ type RegexpGroupStringSubjectMatcher struct {
|
|||
|
||||
// IsMatch returns true if the underlying pattern matches the input given the subject.
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -24,16 +25,11 @@ func (r RegexpGroupStringSubjectMatcher) IsMatch(input string, subject Subject)
|
|||
return true
|
||||
}
|
||||
|
||||
matches := r.Pattern.FindAllStringSubmatch(input, -1)
|
||||
if matches == nil {
|
||||
if r.SubexpNameUser != -1 && !strings.EqualFold(subject.Username, matches[r.SubexpNameUser]) {
|
||||
return false
|
||||
}
|
||||
|
||||
if r.SubexpNameUser != -1 && !strings.EqualFold(subject.Username, matches[0][r.SubexpNameUser]) {
|
||||
return false
|
||||
}
|
||||
|
||||
if r.SubexpNameGroup != -1 && !utils.IsStringInSliceFold(matches[0][r.SubexpNameGroup], subject.Groups) {
|
||||
if r.SubexpNameGroup != -1 && !utils.IsStringInSliceFold(matches[r.SubexpNameGroup], subject.Groups) {
|
||||
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 (
|
||||
"net"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -217,3 +218,76 @@ func TestIsAuthLevelSufficient(t *testing.T) {
|
|||
assert.False(t, IsAuthLevelSufficient(authentication.OneFactor, 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
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ type Configuration struct {
|
|||
Default2FAMethod string `koanf:"default_2fa_method"`
|
||||
|
||||
Log LogConfiguration `koanf:"log"`
|
||||
IdentityProviders IdentityProvidersConfiguration `koanf:"identity_providers"`
|
||||
IdentityProviders IdentityProviders `koanf:"identity_providers"`
|
||||
AuthenticationBackend AuthenticationBackend `koanf:"authentication_backend"`
|
||||
Session SessionConfiguration `koanf:"session"`
|
||||
TOTP TOTPConfiguration `koanf:"totp"`
|
||||
|
|
|
@ -6,13 +6,13 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
// IdentityProvidersConfiguration represents the IdentityProviders 2.0 configuration for Authelia.
|
||||
type IdentityProvidersConfiguration struct {
|
||||
OIDC *OpenIDConnectConfiguration `koanf:"oidc"`
|
||||
// IdentityProviders represents the Identity Providers configuration for Authelia.
|
||||
type IdentityProviders struct {
|
||||
OIDC *OpenIDConnect `koanf:"oidc"`
|
||||
}
|
||||
|
||||
// OpenIDConnectConfiguration configuration for OpenID Connect.
|
||||
type OpenIDConnectConfiguration struct {
|
||||
// OpenIDConnect configuration for OpenID Connect 1.0.
|
||||
type OpenIDConnect struct {
|
||||
HMACSecret string `koanf:"hmac_secret"`
|
||||
IssuerPrivateKeys []JWK `koanf:"issuer_private_keys"`
|
||||
|
||||
|
@ -30,36 +30,39 @@ type OpenIDConnectConfiguration struct {
|
|||
EnforcePKCE string `koanf:"enforce_pkce"`
|
||||
EnablePKCEPlainChallenge bool `koanf:"enable_pkce_plain_challenge"`
|
||||
|
||||
PAR OpenIDConnectPARConfiguration `koanf:"pushed_authorizations"`
|
||||
CORS OpenIDConnectCORSConfiguration `koanf:"cors"`
|
||||
PAR OpenIDConnectPAR `koanf:"pushed_authorizations"`
|
||||
CORS OpenIDConnectCORS `koanf:"cors"`
|
||||
|
||||
Clients []OpenIDConnectClientConfiguration `koanf:"clients"`
|
||||
Clients []OpenIDConnectClient `koanf:"clients"`
|
||||
|
||||
Discovery OpenIDConnectDiscovery // MetaData value. Not configurable by users.
|
||||
}
|
||||
|
||||
// OpenIDConnectDiscovery is information discovered during validation reused for the discovery handlers.
|
||||
type OpenIDConnectDiscovery struct {
|
||||
DefaultKeyIDs map[string]string
|
||||
DefaultKeyID string
|
||||
ResponseObjectSigningKeyIDs []string
|
||||
ResponseObjectSigningAlgs []string
|
||||
RequestObjectSigningAlgs []string
|
||||
}
|
||||
|
||||
// OpenIDConnectPARConfiguration represents an OpenID Connect PAR config.
|
||||
type OpenIDConnectPARConfiguration struct {
|
||||
// OpenIDConnectPAR represents an OpenID Connect 1.0 PAR config.
|
||||
type OpenIDConnectPAR struct {
|
||||
Enforce bool `koanf:"enforce"`
|
||||
ContextLifespan time.Duration `koanf:"context_lifespan"`
|
||||
}
|
||||
|
||||
// OpenIDConnectCORSConfiguration represents an OpenID Connect CORS config.
|
||||
type OpenIDConnectCORSConfiguration struct {
|
||||
// OpenIDConnectCORS represents an OpenID Connect 1.0 CORS config.
|
||||
type OpenIDConnectCORS struct {
|
||||
Endpoints []string `koanf:"endpoints"`
|
||||
AllowedOrigins []url.URL `koanf:"allowed_origins"`
|
||||
|
||||
AllowedOriginsFromClientRedirectURIs bool `koanf:"allowed_origins_from_client_redirect_uris"`
|
||||
}
|
||||
|
||||
// OpenIDConnectClientConfiguration configuration for an OpenID Connect client.
|
||||
type OpenIDConnectClientConfiguration struct {
|
||||
// OpenIDConnectClient represents a configuration for an OpenID Connect 1.0 client.
|
||||
type OpenIDConnectClient struct {
|
||||
ID string `koanf:"id"`
|
||||
Description string `koanf:"description"`
|
||||
Secret *PasswordDigest `koanf:"secret"`
|
||||
|
@ -84,25 +87,28 @@ type OpenIDConnectClientConfiguration struct {
|
|||
|
||||
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"`
|
||||
IDTokenSigningKeyID string `koanf:"id_token_signing_key_id"`
|
||||
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"`
|
||||
|
||||
Discovery OpenIDConnectDiscovery
|
||||
}
|
||||
|
||||
// OpenIDConnectClientPublicKeys represents the Client Public Keys configuration for an OpenID Connect 1.0 client.
|
||||
type OpenIDConnectClientPublicKeys struct {
|
||||
URI *url.URL `koanf:"uri"`
|
||||
Values []JWK `koanf:"values"`
|
||||
}
|
||||
|
||||
// DefaultOpenIDConnectConfiguration contains defaults for OIDC.
|
||||
var DefaultOpenIDConnectConfiguration = OpenIDConnectConfiguration{
|
||||
var DefaultOpenIDConnectConfiguration = OpenIDConnect{
|
||||
AccessTokenLifespan: time.Hour,
|
||||
AuthorizeCodeLifespan: time.Minute,
|
||||
IDTokenLifespan: time.Hour,
|
||||
|
@ -113,12 +119,11 @@ var DefaultOpenIDConnectConfiguration = OpenIDConnectConfiguration{
|
|||
var defaultOIDCClientConsentPreConfiguredDuration = time.Hour * 24 * 7
|
||||
|
||||
// DefaultOpenIDConnectClientConfiguration contains defaults for OIDC Clients.
|
||||
var DefaultOpenIDConnectClientConfiguration = OpenIDConnectClientConfiguration{
|
||||
var DefaultOpenIDConnectClientConfiguration = OpenIDConnectClient{
|
||||
Policy: "two_factor",
|
||||
Scopes: []string{"openid", "groups", "profile", "email"},
|
||||
ResponseTypes: []string{"code"},
|
||||
ResponseModes: []string{"form_post"},
|
||||
|
||||
IDTokenSigningAlg: "RS256",
|
||||
UserinfoSigningAlg: "none",
|
||||
ConsentMode: "auto",
|
||||
|
|
|
@ -20,7 +20,7 @@ var Keys = []string{
|
|||
"identity_providers.oidc.hmac_secret",
|
||||
"identity_providers.oidc.issuer_private_keys",
|
||||
"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[].key",
|
||||
"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_pkce",
|
||||
"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_key_id",
|
||||
"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.values",
|
||||
"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[].key",
|
||||
"identity_providers.oidc.clients[].public_keys.values[].certificate_chain",
|
||||
|
|
|
@ -38,7 +38,7 @@ type ServerBuffers struct {
|
|||
// JWK represents a JWK.
|
||||
type JWK struct {
|
||||
KeyID string `koanf:"key_id"`
|
||||
Use string
|
||||
Use string `koanf:"use"`
|
||||
Algorithm string `koanf:"algorithm"`
|
||||
Key CryptographicKey `koanf:"key"`
|
||||
CertificateChain X509CertificateChain `koanf:"certificate_chain"`
|
||||
|
|
|
@ -147,13 +147,15 @@ const (
|
|||
errFmtOIDCProviderNoPrivateKey = "identity_providers: oidc: option `issuer_private_keys` or 'issuer_private_key' is required"
|
||||
errFmtOIDCProviderEnforcePKCEInvalidValue = "identity_providers: oidc: option 'enforce_pkce' must be 'never', " +
|
||||
"'public_clients_only' or 'always', but it's configured as '%s'"
|
||||
errFmtOIDCProviderInsecureParameterEntropy = "openid connect provider: SECURITY ISSUE - minimum parameter entropy is " +
|
||||
"configured to an unsafe value, it should be above 8 but it's configured to %d"
|
||||
errFmtOIDCProviderInsecureParameterEntropy = "identity_providers: oidc: option 'minimum_parameter_entropy' is " +
|
||||
"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"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
|
@ -436,7 +438,9 @@ const (
|
|||
attrOIDCRedirectURIs = "redirect_uris"
|
||||
attrOIDCTokenAuthMethod = "token_endpoint_auth_method"
|
||||
attrOIDCUsrSigAlg = "userinfo_signing_alg"
|
||||
attrOIDCUsrSigKID = "userinfo_signing_key_id"
|
||||
attrOIDCIDTokenSigAlg = "id_token_signing_alg"
|
||||
attrOIDCIDTokenSigKID = "id_token_signing_key_id"
|
||||
attrOIDCPKCEChallengeMethod = "pkce_challenge_method"
|
||||
)
|
||||
|
||||
|
@ -462,6 +466,7 @@ var (
|
|||
reKeyReplacer = regexp.MustCompile(`\[\d+]`)
|
||||
reDomainCharacters = regexp.MustCompile(`^[a-z0-9-]+(\.[a-z0-9-]+)+[a-z0-9]$`)
|
||||
reAuthzEndpointName = regexp.MustCompile(`^[a-zA-Z](([a-zA-Z0-9/._-]*)([a-zA-Z]))?$`)
|
||||
reOpenIDConnectKID = regexp.MustCompile(`^([a-zA-Z0-9](([a-zA-Z0-9._~-]*)([a-zA-Z0-9]))?)?$`)
|
||||
)
|
||||
|
||||
var replacedKeys = map[string]string{
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
package validator
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
@ -12,7 +10,7 @@ import (
|
|||
"strings"
|
||||
"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/oidc"
|
||||
|
@ -20,11 +18,11 @@ import (
|
|||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
func validateOIDC(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
||||
func validateOIDC(config *schema.OpenIDConnect, val *schema.StructValidator) {
|
||||
if config == nil {
|
||||
return
|
||||
}
|
||||
|
@ -35,8 +33,13 @@ func validateOIDC(config *schema.OpenIDConnectConfiguration, val *schema.StructV
|
|||
|
||||
sort.Sort(oidc.SortedSigningAlgs(config.Discovery.ResponseObjectSigningAlgs))
|
||||
|
||||
if config.MinimumParameterEntropy != 0 && config.MinimumParameterEntropy < 8 {
|
||||
val.PushWarning(fmt.Errorf(errFmtOIDCProviderInsecureParameterEntropy, config.MinimumParameterEntropy))
|
||||
switch {
|
||||
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 {
|
||||
|
@ -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 {
|
||||
case config.IssuerPrivateKey != nil:
|
||||
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{{
|
||||
Algorithm: oidc.SigningAlgRSAUsingSHA256,
|
||||
Use: oidc.KeyUseSignature,
|
||||
|
@ -77,34 +80,14 @@ func validateOIDCIssuerPrivateKey(config *schema.OpenIDConnectConfiguration) {
|
|||
}}, config.IssuerPrivateKeys...)
|
||||
}
|
||||
|
||||
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 validateOIDCIssuerPrivateKeys(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
||||
func validateOIDCIssuerPrivateKeys(config *schema.OpenIDConnect, val *schema.StructValidator) {
|
||||
var (
|
||||
props *JWKProperties
|
||||
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++ {
|
||||
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
|
||||
}
|
||||
case n > 7:
|
||||
case n > 100:
|
||||
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))
|
||||
}
|
||||
|
||||
kids[i] = config.IssuerPrivateKeys[i].KeyID
|
||||
config.Discovery.ResponseObjectSigningKeyIDs[i] = config.IssuerPrivateKeys[i].KeyID
|
||||
|
||||
if !utils.IsStringAlphaNumeric(config.IssuerPrivateKeys[i].KeyID) {
|
||||
val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysKeyIDNotAlphaNumeric, i+1, config.IssuerPrivateKeys[i].KeyID))
|
||||
if !reOpenIDConnectKID.MatchString(config.IssuerPrivateKeys[i].KeyID) {
|
||||
val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysKeyIDNotValid, i+1, config.IssuerPrivateKeys[i].KeyID))
|
||||
}
|
||||
|
||||
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 {
|
||||
case "":
|
||||
config.IssuerPrivateKeys[i].Use = props.Use
|
||||
|
@ -162,26 +145,26 @@ func validateOIDCIssuerPrivateKeysUseAlg(i int, props *JWKProperties, config *sc
|
|||
switch {
|
||||
case config.IssuerPrivateKeys[i].Algorithm == "":
|
||||
config.IssuerPrivateKeys[i].Algorithm = props.Algorithm
|
||||
|
||||
fallthrough
|
||||
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:
|
||||
val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysInvalidOptionOneOf, i+1, config.IssuerPrivateKeys[i].KeyID, attrOIDCAlgorithm, strJoinOr(validOIDCIssuerJWKSigningAlgs), config.IssuerPrivateKeys[i].Algorithm))
|
||||
}
|
||||
|
||||
if config.IssuerPrivateKeys[i].Algorithm != "" {
|
||||
if utils.IsStringInSlice(config.IssuerPrivateKeys[i].Algorithm, config.Discovery.ResponseObjectSigningAlgs) {
|
||||
val.Push(fmt.Errorf(errFmtOIDCProviderPrivateKeysAttributeNotUnique, i+1, config.IssuerPrivateKeys[i].KeyID, attrOIDCAlgorithm))
|
||||
} else {
|
||||
if !utils.IsStringInSlice(config.IssuerPrivateKeys[i].Algorithm, config.Discovery.ResponseObjectSigningAlgs) {
|
||||
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 (
|
||||
checkEqualKey bool
|
||||
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) {
|
||||
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)
|
||||
|
||||
if config.CORS.AllowedOriginsFromClientRedirectURIs {
|
||||
|
@ -245,7 +228,7 @@ func validateOIDCOptionsCORS(config *schema.OpenIDConnectConfiguration, validato
|
|||
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 {
|
||||
if origin.String() == "*" {
|
||||
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 _, redirectURI := range client.RedirectURIs {
|
||||
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 {
|
||||
if !utils.IsStringInSlice(endpoint, 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 (
|
||||
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].Secret != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
func validateOIDCClientPublicKeys(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
||||
func validateOIDCClientPublicKeys(c int, config *schema.OpenIDConnect, val *schema.StructValidator) {
|
||||
switch {
|
||||
case config.Clients[c].PublicKeys.URI != nil && len(config.Clients[c].PublicKeys.Values) != 0:
|
||||
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 (
|
||||
props *JWKProperties
|
||||
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 {
|
||||
case "":
|
||||
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 utils.IsURLHostComponent(config.Clients[c].SectorIdentifier) || utils.IsURLHostComponentWithPort(config.Clients[c].SectorIdentifier) {
|
||||
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 {
|
||||
case utils.IsStringInSlice(config.Clients[c].ConsentMode, []string{"", auto}):
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
switch responseType {
|
||||
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 {
|
||||
switch grantType {
|
||||
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 (
|
||||
parsedRedirectURI *url.URL
|
||||
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)
|
||||
|
||||
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 {
|
||||
case config.Clients[c].TokenEndpointAuthSigningAlg == "":
|
||||
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 {
|
||||
case config.TokenEndpointAuthSigningAlg == "":
|
||||
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) {
|
||||
switch config.Clients[c].UserinfoSigningKeyID {
|
||||
case "":
|
||||
if config.Clients[c].UserinfoSigningAlg == "" {
|
||||
config.Clients[c].UserinfoSigningAlg = schema.DefaultOpenIDConnectClientConfiguration.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)
|
||||
}
|
||||
}
|
||||
|
||||
switch config.Clients[c].IDTokenSigningKeyID {
|
||||
case "":
|
||||
if config.Clients[c].IDTokenSigningAlg == "" {
|
||||
config.Clients[c].IDTokenSigningAlg = schema.DefaultOpenIDConnectClientConfiguration.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
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
|
@ -9,6 +10,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"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/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)
|
||||
}
|
||||
}
|
||||
|
||||
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, 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)
|
||||
|
||||
session := oidc.NewSessionWithAuthorizeRequest(issuer, ctx.Providers.OpenIDConnect.KeyManager.GetKeyIDFromAlg(ctx, client.GetIDTokenSigningAlg()),
|
||||
userSession.Username, userSession.AuthenticationMethodRefs.MarshalRFC8176(), extraClaims, authTime, consent, requester)
|
||||
session := oidc.NewSessionWithAuthorizeRequest(issuer, ctx.Providers.OpenIDConnect.KeyManager.GetKeyID(ctx, client.GetIDTokenSigningKeyID(), client.GetIDTokenSigningAlg()), 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",
|
||||
requester.GetID(), session.ClientID, session.Subject, session.Username, session.Claims)
|
||||
|
|
|
@ -105,7 +105,7 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
|
|||
default:
|
||||
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)))
|
||||
|
||||
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) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
configuration := schema.Configuration{}
|
||||
userProvider := mocks.NewMockUserProvider(ctrl)
|
||||
|
|
|
@ -199,12 +199,12 @@ func (s *ClientAuthenticationStrategySuite) SetupTest() {
|
|||
|
||||
secret := tOpenIDConnectPlainTextClientSecret
|
||||
|
||||
s.provider = oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||
s.provider = oidc.NewOpenIDConnectProvider(&schema.OpenIDConnect{
|
||||
IssuerPrivateKeys: []schema.JWK{
|
||||
{Key: keyRSA2048, CertificateChain: certRSA2048, Use: oidc.KeyUseSignature, Algorithm: oidc.SigningAlgRSAUsingSHA256},
|
||||
},
|
||||
HMACSecret: "abc123",
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||
Clients: []schema.OpenIDConnectClient{
|
||||
{
|
||||
ID: "hs256",
|
||||
Secret: secret,
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
)
|
||||
|
||||
// NewClient creates a new Client.
|
||||
func NewClient(config schema.OpenIDConnectClientConfiguration) (client Client) {
|
||||
func NewClient(config schema.OpenIDConnectClient) (client Client) {
|
||||
base := &BaseClient{
|
||||
ID: config.ID,
|
||||
Description: config.Description,
|
||||
|
@ -35,7 +35,9 @@ func NewClient(config schema.OpenIDConnectClientConfiguration) (client Client) {
|
|||
EnforcePAR: config.EnforcePAR,
|
||||
|
||||
IDTokenSigningAlg: config.IDTokenSigningAlg,
|
||||
IDTokenSigningKeyID: config.IDTokenSigningKeyID,
|
||||
UserinfoSigningAlg: config.UserinfoSigningAlg,
|
||||
UserinfoSigningKeyID: config.UserinfoSigningKeyID,
|
||||
|
||||
Policy: authorization.NewLevel(config.Policy),
|
||||
|
||||
|
@ -151,6 +153,11 @@ func (c *BaseClient) GetIDTokenSigningAlg() (alg string) {
|
|||
return c.IDTokenSigningAlg
|
||||
}
|
||||
|
||||
// GetIDTokenSigningKeyID returns the IDTokenSigningKeyID.
|
||||
func (c *BaseClient) GetIDTokenSigningKeyID() (alg string) {
|
||||
return c.IDTokenSigningKeyID
|
||||
}
|
||||
|
||||
// GetUserinfoSigningAlg returns the UserinfoSigningAlg.
|
||||
func (c *BaseClient) GetUserinfoSigningAlg() string {
|
||||
if c.UserinfoSigningAlg == "" {
|
||||
|
@ -160,6 +167,11 @@ func (c *BaseClient) GetUserinfoSigningAlg() string {
|
|||
return c.UserinfoSigningAlg
|
||||
}
|
||||
|
||||
// GetUserinfoSigningKeyID returns the UserinfoSigningKeyID.
|
||||
func (c *BaseClient) GetUserinfoSigningKeyID() (kid string) {
|
||||
return c.UserinfoSigningKeyID
|
||||
}
|
||||
|
||||
// GetPAREnforcement returns EnforcePAR.
|
||||
func (c *BaseClient) GetPAREnforcement() bool {
|
||||
return c.EnforcePAR
|
||||
|
|
|
@ -18,7 +18,7 @@ import (
|
|||
)
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
config := schema.OpenIDConnectClientConfiguration{}
|
||||
config := schema.OpenIDConnectClient{}
|
||||
client := oidc.NewClient(config)
|
||||
assert.Equal(t, "", client.GetID())
|
||||
assert.Equal(t, "", client.GetDescription())
|
||||
|
@ -30,11 +30,12 @@ func TestNewClient(t *testing.T) {
|
|||
require.True(t, ok)
|
||||
assert.Equal(t, "", bclient.UserinfoSigningAlg)
|
||||
assert.Equal(t, oidc.SigningAlgNone, client.GetUserinfoSigningAlg())
|
||||
assert.Equal(t, "", client.GetUserinfoSigningKeyID())
|
||||
|
||||
_, ok = client.(*oidc.FullClient)
|
||||
assert.False(t, ok)
|
||||
|
||||
config = schema.OpenIDConnectClientConfiguration{
|
||||
config = schema.OpenIDConnectClient{
|
||||
ID: myclient,
|
||||
Description: myclientdesc,
|
||||
Policy: twofactor,
|
||||
|
@ -52,7 +53,7 @@ func TestNewClient(t *testing.T) {
|
|||
assert.Equal(t, fosite.ResponseModeFormPost, client.GetResponseModes()[0])
|
||||
assert.Equal(t, authorization.TwoFactor, client.GetAuthorizationPolicy())
|
||||
|
||||
config = schema.OpenIDConnectClientConfiguration{
|
||||
config = schema.OpenIDConnectClient{
|
||||
TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretPost,
|
||||
}
|
||||
|
||||
|
@ -64,12 +65,32 @@ func TestNewClient(t *testing.T) {
|
|||
|
||||
assert.Equal(t, "", fclient.UserinfoSigningAlg)
|
||||
assert.Equal(t, oidc.SigningAlgNone, client.GetUserinfoSigningAlg())
|
||||
assert.Equal(t, oidc.SigningAlgNone, fclient.GetUserinfoSigningAlg())
|
||||
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, oidc.SigningAlgRSAUsingSHA256, client.GetIDTokenSigningAlg())
|
||||
assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, fclient.GetIDTokenSigningAlg())
|
||||
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.GetTokenEndpointAuthMethod())
|
||||
|
||||
|
@ -81,6 +102,7 @@ func TestNewClient(t *testing.T) {
|
|||
assert.Equal(t, "", fclient.GetRequestObjectSigningAlgorithm())
|
||||
|
||||
fclient.RequestObjectSigningAlgorithm = oidc.SigningAlgRSAUsingSHA256
|
||||
|
||||
assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, fclient.GetRequestObjectSigningAlgorithm())
|
||||
|
||||
assert.Equal(t, "", fclient.JSONWebKeysURI)
|
||||
|
@ -355,7 +377,7 @@ func TestClient_GetResponseTypes(t *testing.T) {
|
|||
func TestNewClientPKCE(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
have schema.OpenIDConnectClientConfiguration
|
||||
have schema.OpenIDConnectClient
|
||||
expectedEnforcePKCE bool
|
||||
expectedEnforcePKCEChallengeMethod bool
|
||||
expected string
|
||||
|
@ -365,7 +387,7 @@ func TestNewClientPKCE(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
"ShouldNotEnforcePKCEAndNotErrorOnNonPKCERequest",
|
||||
schema.OpenIDConnectClientConfiguration{},
|
||||
schema.OpenIDConnectClient{},
|
||||
false,
|
||||
false,
|
||||
"",
|
||||
|
@ -375,7 +397,7 @@ func TestNewClientPKCE(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"ShouldEnforcePKCEAndErrorOnNonPKCERequest",
|
||||
schema.OpenIDConnectClientConfiguration{EnforcePKCE: true},
|
||||
schema.OpenIDConnectClient{EnforcePKCE: true},
|
||||
true,
|
||||
false,
|
||||
"",
|
||||
|
@ -385,7 +407,7 @@ func TestNewClientPKCE(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"ShouldEnforcePKCEAndNotErrorOnPKCERequest",
|
||||
schema.OpenIDConnectClientConfiguration{EnforcePKCE: true},
|
||||
schema.OpenIDConnectClient{EnforcePKCE: true},
|
||||
true,
|
||||
false,
|
||||
"",
|
||||
|
@ -394,7 +416,7 @@ func TestNewClientPKCE(t *testing.T) {
|
|||
"",
|
||||
},
|
||||
{"ShouldEnforcePKCEFromChallengeMethodAndErrorOnNonPKCERequest",
|
||||
schema.OpenIDConnectClientConfiguration{PKCEChallengeMethod: "S256"},
|
||||
schema.OpenIDConnectClient{PKCEChallengeMethod: "S256"},
|
||||
true,
|
||||
true,
|
||||
"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.",
|
||||
},
|
||||
{"ShouldEnforcePKCEFromChallengeMethodAndErrorOnInvalidChallengeMethod",
|
||||
schema.OpenIDConnectClientConfiguration{PKCEChallengeMethod: "S256"},
|
||||
schema.OpenIDConnectClient{PKCEChallengeMethod: "S256"},
|
||||
true,
|
||||
true,
|
||||
"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.",
|
||||
},
|
||||
{"ShouldEnforcePKCEFromChallengeMethodAndNotErrorOnValidRequest",
|
||||
schema.OpenIDConnectClientConfiguration{PKCEChallengeMethod: "S256"},
|
||||
schema.OpenIDConnectClient{PKCEChallengeMethod: "S256"},
|
||||
true,
|
||||
true,
|
||||
"S256",
|
||||
|
@ -448,7 +470,7 @@ func TestNewClientPKCE(t *testing.T) {
|
|||
func TestNewClientPAR(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
have schema.OpenIDConnectClientConfiguration
|
||||
have schema.OpenIDConnectClient
|
||||
expected bool
|
||||
r *fosite.Request
|
||||
err string
|
||||
|
@ -456,7 +478,7 @@ func TestNewClientPAR(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
"ShouldNotEnforcEPARAndNotErrorOnNonPARRequest",
|
||||
schema.OpenIDConnectClientConfiguration{},
|
||||
schema.OpenIDConnectClient{},
|
||||
false,
|
||||
&fosite.Request{},
|
||||
"",
|
||||
|
@ -464,7 +486,7 @@ func TestNewClientPAR(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"ShouldEnforcePARAndErrorOnNonPARRequest",
|
||||
schema.OpenIDConnectClientConfiguration{EnforcePAR: true},
|
||||
schema.OpenIDConnectClient{EnforcePAR: true},
|
||||
true,
|
||||
&fosite.Request{},
|
||||
"invalid_request",
|
||||
|
@ -472,14 +494,14 @@ func TestNewClientPAR(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"ShouldEnforcePARAndErrorOnNonPARRequest",
|
||||
schema.OpenIDConnectClientConfiguration{EnforcePAR: true},
|
||||
schema.OpenIDConnectClient{EnforcePAR: true},
|
||||
true,
|
||||
&fosite.Request{Form: map[string][]string{oidc.FormParameterRequestURI: {"https://example.com"}}},
|
||||
"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."},
|
||||
{
|
||||
"ShouldEnforcePARAndNotErrorOnPARRequest",
|
||||
schema.OpenIDConnectClientConfiguration{EnforcePAR: true},
|
||||
schema.OpenIDConnectClient{EnforcePAR: true},
|
||||
true,
|
||||
&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) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
have schema.OpenIDConnectClientConfiguration
|
||||
have schema.OpenIDConnectClient
|
||||
expected []fosite.ResponseModeType
|
||||
r *fosite.AuthorizeRequest
|
||||
err string
|
||||
|
@ -519,7 +541,7 @@ func TestNewClientResponseModes(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
"ShouldEnforceResponseModePolicyAndAllowDefaultModeQuery",
|
||||
schema.OpenIDConnectClientConfiguration{ResponseModes: []string{oidc.ResponseModeQuery}},
|
||||
schema.OpenIDConnectClient{ResponseModes: []string{oidc.ResponseModeQuery}},
|
||||
[]fosite.ResponseModeType{fosite.ResponseModeQuery},
|
||||
&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",
|
||||
schema.OpenIDConnectClientConfiguration{ResponseModes: []string{oidc.ResponseModeFormPost}},
|
||||
schema.OpenIDConnectClient{ResponseModes: []string{oidc.ResponseModeFormPost}},
|
||||
[]fosite.ResponseModeType{fosite.ResponseModeFormPost},
|
||||
&fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeDefault, Request: fosite.Request{Form: map[string][]string{oidc.FormParameterResponseMode: nil}}},
|
||||
"unsupported_response_mode",
|
||||
|
@ -535,7 +557,7 @@ func TestNewClientResponseModes(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"ShouldNotEnforceConfiguredResponseMode",
|
||||
schema.OpenIDConnectClientConfiguration{ResponseModes: []string{oidc.ResponseModeFormPost}},
|
||||
schema.OpenIDConnectClient{ResponseModes: []string{oidc.ResponseModeFormPost}},
|
||||
[]fosite.ResponseModeType{fosite.ResponseModeFormPost},
|
||||
&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",
|
||||
schema.OpenIDConnectClientConfiguration{ResponseModes: []string{}},
|
||||
schema.OpenIDConnectClient{ResponseModes: []string{}},
|
||||
[]fosite.ResponseModeType{},
|
||||
&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
|
||||
)
|
||||
|
||||
client = oidc.NewClient(schema.OpenIDConnectClientConfiguration{
|
||||
client = oidc.NewClient(schema.OpenIDConnectClient{
|
||||
TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretPost,
|
||||
PublicKeys: schema.OpenIDConnectClientPublicKeys{
|
||||
URI: MustParseRequestURI("https://google.com"),
|
||||
|
@ -603,7 +625,7 @@ func TestNewClient_JSONWebKeySetURI(t *testing.T) {
|
|||
|
||||
assert.Equal(t, "https://google.com", clientf.GetJSONWebKeysURI())
|
||||
|
||||
client = oidc.NewClient(schema.OpenIDConnectClientConfiguration{
|
||||
client = oidc.NewClient(schema.OpenIDConnectClient{
|
||||
TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretPost,
|
||||
PublicKeys: schema.OpenIDConnectClientPublicKeys{
|
||||
URI: nil,
|
||||
|
|
|
@ -23,7 +23,7 @@ import (
|
|||
"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{
|
||||
GlobalSecret: []byte(utils.HashSHA256FromString(config.HMACSecret)),
|
||||
SendDebugMessagesToClients: config.EnableClientDebugMessages,
|
||||
|
|
|
@ -118,6 +118,14 @@ const (
|
|||
SigningAlgHMACUsingSHA512 = "HS512"
|
||||
)
|
||||
|
||||
// JWS Algorithm Prefixes.
|
||||
const (
|
||||
SigningAlgPrefixRSA = "RS"
|
||||
SigningAlgPrefixHMAC = "HS"
|
||||
SigningAlgPrefixRSAPSS = "PS"
|
||||
SigningAlgPrefixECDSA = "ES"
|
||||
)
|
||||
|
||||
const (
|
||||
KeyUseSignature = "sig"
|
||||
)
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
)
|
||||
|
||||
// NewOpenIDConnectWellKnownConfiguration generates a new OpenIDConnectWellKnownConfiguration.
|
||||
func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration) (config OpenIDConnectWellKnownConfiguration) {
|
||||
func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnect) (config OpenIDConnectWellKnownConfiguration) {
|
||||
config = OpenIDConnectWellKnownConfiguration{
|
||||
OAuth2WellKnownConfiguration: OAuth2WellKnownConfiguration{
|
||||
CommonDiscoveryOptions: CommonDiscoveryOptions{
|
||||
|
@ -73,6 +73,15 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration
|
|||
SigningAlgHMACUsingSHA256,
|
||||
SigningAlgHMACUsingSHA384,
|
||||
SigningAlgHMACUsingSHA512,
|
||||
SigningAlgRSAUsingSHA256,
|
||||
SigningAlgRSAUsingSHA384,
|
||||
SigningAlgRSAUsingSHA512,
|
||||
SigningAlgECDSAUsingP256AndSHA256,
|
||||
SigningAlgECDSAUsingP384AndSHA384,
|
||||
SigningAlgECDSAUsingP521AndSHA512,
|
||||
SigningAlgRSAPSSUsingSHA256,
|
||||
SigningAlgRSAPSSUsingSHA384,
|
||||
SigningAlgRSAPSSUsingSHA512,
|
||||
},
|
||||
},
|
||||
OAuth2DiscoveryOptions: OAuth2DiscoveryOptions{
|
||||
|
@ -90,6 +99,15 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration
|
|||
SigningAlgHMACUsingSHA256,
|
||||
SigningAlgHMACUsingSHA384,
|
||||
SigningAlgHMACUsingSHA512,
|
||||
SigningAlgRSAUsingSHA256,
|
||||
SigningAlgRSAUsingSHA384,
|
||||
SigningAlgRSAUsingSHA512,
|
||||
SigningAlgECDSAUsingP256AndSHA256,
|
||||
SigningAlgECDSAUsingP384AndSHA384,
|
||||
SigningAlgECDSAUsingP521AndSHA512,
|
||||
SigningAlgRSAPSSUsingSHA256,
|
||||
SigningAlgRSAPSSUsingSHA384,
|
||||
SigningAlgRSAPSSUsingSHA512,
|
||||
},
|
||||
IntrospectionEndpointAuthMethodsSupported: []string{
|
||||
ClientAuthMethodClientSecretBasic,
|
||||
|
@ -104,6 +122,7 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration
|
|||
OpenIDConnectDiscoveryOptions: OpenIDConnectDiscoveryOptions{
|
||||
IDTokenSigningAlgValuesSupported: []string{
|
||||
SigningAlgRSAUsingSHA256,
|
||||
SigningAlgNone,
|
||||
},
|
||||
UserinfoSigningAlgValuesSupported: []string{
|
||||
SigningAlgRSAUsingSHA256,
|
||||
|
@ -111,11 +130,17 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration
|
|||
},
|
||||
RequestObjectSigningAlgValuesSupported: []string{
|
||||
SigningAlgRSAUsingSHA256,
|
||||
SigningAlgRSAUsingSHA384,
|
||||
SigningAlgRSAUsingSHA512,
|
||||
SigningAlgECDSAUsingP256AndSHA256,
|
||||
SigningAlgECDSAUsingP384AndSHA384,
|
||||
SigningAlgECDSAUsingP521AndSHA512,
|
||||
SigningAlgRSAPSSUsingSHA256,
|
||||
SigningAlgRSAPSSUsingSHA384,
|
||||
SigningAlgRSAPSSUsingSHA512,
|
||||
SigningAlgNone,
|
||||
},
|
||||
},
|
||||
OpenIDConnectFrontChannelLogoutDiscoveryOptions: &OpenIDConnectFrontChannelLogoutDiscoveryOptions{},
|
||||
OpenIDConnectBackChannelLogoutDiscoveryOptions: &OpenIDConnectBackChannelLogoutDiscoveryOptions{},
|
||||
OpenIDConnectPromptCreateDiscoveryOptions: &OpenIDConnectPromptCreateDiscoveryOptions{
|
||||
PromptValuesSupported: []string{
|
||||
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.UserinfoSigningAlgValuesSupported))
|
||||
sort.Sort(SortedSigningAlgs(config.RequestObjectSigningAlgValuesSupported))
|
||||
sort.Sort(SortedSigningAlgs(config.RevocationEndpointAuthSigningAlgValuesSupported))
|
||||
sort.Sort(SortedSigningAlgs(config.TokenEndpointAuthSigningAlgValuesSupported))
|
||||
|
||||
if c.EnablePKCEPlainChallenge {
|
||||
config.CodeChallengeMethodsSupported = append(config.CodeChallengeMethodsSupported, PKCEChallengeMethodPlain)
|
||||
|
|
|
@ -25,118 +25,51 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
|||
expectedRequestObjectSigAlgsSupported, expectedRevocationSigAlgsSupported, expectedTokenAuthSigAlgsSupported []string
|
||||
}{
|
||||
{
|
||||
desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublic",
|
||||
desc: "ShouldHaveStandardCodeChallengeMethods",
|
||||
pkcePlainChallenge: false,
|
||||
clients: map[string]oidc.Client{"a": &oidc.BaseClient{}},
|
||||
expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256},
|
||||
expectSubjectTypesSupported: []string{oidc.SubjectTypePublic, oidc.SubjectTypePairwise},
|
||||
expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256},
|
||||
expectedIDTokenSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone},
|
||||
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},
|
||||
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: "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,
|
||||
clients: map[string]oidc.Client{"a": &oidc.BaseClient{}},
|
||||
discovery: schema.OpenIDConnectDiscovery{
|
||||
ResponseObjectSigningAlgs: []string{oidc.SigningAlgECDSAUsingP521AndSHA512},
|
||||
RequestObjectSigningAlgs: []string{oidc.SigningAlgECDSAUsingP256AndSHA256},
|
||||
},
|
||||
expectCodeChallengeMethodsSupported: []string{oidc.PKCEChallengeMethodSHA256},
|
||||
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},
|
||||
expectedRequestObjectSigAlgsSupported: []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgNone},
|
||||
expectedRevocationSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512, oidc.SigningAlgECDSAUsingP256AndSHA256},
|
||||
expectedTokenAuthSigAlgsSupported: []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512, oidc.SigningAlgECDSAUsingP256AndSHA256},
|
||||
},
|
||||
{
|
||||
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},
|
||||
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},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
c := schema.OpenIDConnectConfiguration{
|
||||
c := schema.OpenIDConnect{
|
||||
EnablePKCEPlainChallenge: tc.pkcePlainChallenge,
|
||||
PAR: schema.OpenIDConnectPARConfiguration{
|
||||
PAR: schema.OpenIDConnectPAR{
|
||||
Enforce: tc.enforcePAR,
|
||||
},
|
||||
Discovery: tc.discovery,
|
||||
|
@ -169,12 +102,12 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNewOpenIDConnectProviderDiscovery(t *testing.T) {
|
||||
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnect{
|
||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||
IssuerPrivateKey: keyRSA2048,
|
||||
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
||||
EnablePKCEPlainChallenge: true,
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||
Clients: []schema.OpenIDConnectClient{
|
||||
{
|
||||
ID: "a-client",
|
||||
Secret: tOpenIDConnectPlainTextClientSecret,
|
||||
|
@ -210,11 +143,11 @@ func TestNewOpenIDConnectProviderDiscovery(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
||||
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnect{
|
||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||
IssuerPrivateKey: keyRSA2048,
|
||||
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||
Clients: []schema.OpenIDConnectClient{
|
||||
{
|
||||
ID: "a-client",
|
||||
Secret: tOpenIDConnectPlainTextClientSecret,
|
||||
|
@ -281,35 +214,13 @@ func TestNewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfiguration(t *test
|
|||
assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, oidc.ClientAuthMethodPrivateKeyJWT)
|
||||
assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, oidc.ClientAuthMethodNone)
|
||||
|
||||
assert.Len(t, disco.IntrospectionEndpointAuthMethodsSupported, 2)
|
||||
assert.Contains(t, disco.IntrospectionEndpointAuthMethodsSupported, oidc.ClientAuthMethodClientSecretBasic)
|
||||
assert.Contains(t, disco.IntrospectionEndpointAuthMethodsSupported, oidc.ClientAuthMethodNone)
|
||||
|
||||
assert.Len(t, disco.GrantTypesSupported, 3)
|
||||
assert.Contains(t, disco.GrantTypesSupported, oidc.GrantTypeAuthorizationCode)
|
||||
assert.Contains(t, disco.GrantTypesSupported, oidc.GrantTypeRefreshToken)
|
||||
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.Equal(t, []string{oidc.ClientAuthMethodClientSecretBasic, oidc.ClientAuthMethodNone}, disco.IntrospectionEndpointAuthMethodsSupported)
|
||||
assert.Equal(t, []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeImplicit, oidc.GrantTypeRefreshToken}, disco.GrantTypesSupported)
|
||||
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.Equal(t, []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone}, disco.IDTokenSigningAlgValuesSupported)
|
||||
assert.Equal(t, []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgNone}, disco.UserinfoSigningAlgValuesSupported)
|
||||
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.Len(t, disco.ClaimsSupported, 18)
|
||||
assert.Contains(t, disco.ClaimsSupported, oidc.ClaimAuthenticationMethodsReference)
|
||||
|
@ -337,11 +248,11 @@ func TestNewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfiguration(t *test
|
|||
}
|
||||
|
||||
func TestNewOpenIDConnectProvider_GetOAuth2WellKnownConfiguration(t *testing.T) {
|
||||
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnect{
|
||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||
IssuerPrivateKey: keyRSA2048,
|
||||
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||
Clients: []schema.OpenIDConnectClient{
|
||||
{
|
||||
ID: "a-client",
|
||||
Secret: tOpenIDConnectPlainTextClientSecret,
|
||||
|
@ -427,12 +338,12 @@ func TestNewOpenIDConnectProvider_GetOAuth2WellKnownConfiguration(t *testing.T)
|
|||
}
|
||||
|
||||
func TestNewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfigurationWithPlainPKCE(t *testing.T) {
|
||||
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnect{
|
||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||
IssuerPrivateKey: keyRSA2048,
|
||||
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
||||
EnablePKCEPlainChallenge: true,
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||
Clients: []schema.OpenIDConnectClient{
|
||||
{
|
||||
ID: "a-client",
|
||||
Secret: tOpenIDConnectPlainTextClientSecret,
|
||||
|
|
|
@ -13,15 +13,15 @@ import (
|
|||
"github.com/golang-jwt/jwt/v4"
|
||||
fjwt "github.com/ory/fosite/token/jwt"
|
||||
"github.com/ory/x/errorsx"
|
||||
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||
)
|
||||
|
||||
// NewKeyManager news up a KeyManager.
|
||||
func NewKeyManager(config *schema.OpenIDConnectConfiguration) (manager *KeyManager) {
|
||||
func NewKeyManager(config *schema.OpenIDConnect) (manager *KeyManager) {
|
||||
manager = &KeyManager{
|
||||
alg2kid: config.Discovery.DefaultKeyIDs,
|
||||
kids: map[string]*JWK{},
|
||||
algs: map[string]*JWK{},
|
||||
}
|
||||
|
@ -31,10 +31,6 @@ func NewKeyManager(config *schema.OpenIDConnectConfiguration) (manager *KeyManag
|
|||
|
||||
manager.kids[sjwk.KeyID] = jwk
|
||||
manager.algs[jwk.alg.Alg()] = jwk
|
||||
|
||||
if jwk.kid == config.Discovery.DefaultKeyID {
|
||||
manager.kid = jwk.kid
|
||||
}
|
||||
}
|
||||
|
||||
return manager
|
||||
|
@ -42,14 +38,29 @@ func NewKeyManager(config *schema.OpenIDConnectConfiguration) (manager *KeyManag
|
|||
|
||||
// The KeyManager type handles JWKs and signing operations.
|
||||
type KeyManager struct {
|
||||
kid string
|
||||
alg2kid map[string]string
|
||||
kids map[string]*JWK
|
||||
algs map[string]*JWK
|
||||
}
|
||||
|
||||
// GetKeyID returns the default key id.
|
||||
func (m *KeyManager) GetKeyID(ctx context.Context) string {
|
||||
return m.kid
|
||||
// GetDefaultKeyID returns the default key id.
|
||||
func (m *KeyManager) GetDefaultKeyID(ctx context.Context) string {
|
||||
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.
|
||||
|
@ -67,7 +78,20 @@ func (m *KeyManager) GetKeyIDFromAlg(ctx context.Context, alg string) string {
|
|||
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.
|
||||
|
@ -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.
|
||||
func (m *KeyManager) GetByKID(ctx context.Context, kid string) *JWK {
|
||||
if kid == "" {
|
||||
return m.kids[m.kid]
|
||||
return m.kids[m.alg2kid[SigningAlgRSAUsingSHA256]]
|
||||
}
|
||||
|
||||
if jwk, ok := m.kids[kid]; ok {
|
||||
|
|
|
@ -17,10 +17,7 @@ import (
|
|||
)
|
||||
|
||||
func TestKeyManager(t *testing.T) {
|
||||
config := &schema.OpenIDConnectConfiguration{
|
||||
Discovery: schema.OpenIDConnectDiscovery{
|
||||
DefaultKeyID: "kid-RS256-sig",
|
||||
},
|
||||
config := &schema.OpenIDConnect{
|
||||
IssuerPrivateKeys: []schema.JWK{
|
||||
{
|
||||
Use: oidc.KeyUseSignature,
|
||||
|
@ -79,8 +76,16 @@ func TestKeyManager(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
config.Discovery.DefaultKeyIDs = map[string]string{}
|
||||
|
||||
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)
|
||||
|
@ -89,7 +94,18 @@ func TestKeyManager(t *testing.T) {
|
|||
|
||||
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 (
|
||||
jwk *oidc.JWK
|
||||
|
@ -107,7 +123,7 @@ func TestKeyManager(t *testing.T) {
|
|||
|
||||
jwk = manager.GetByKID(ctx, "")
|
||||
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"}})
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
)
|
||||
|
||||
// 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 {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -18,12 +18,12 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_NotConfigured(t *testing
|
|||
}
|
||||
|
||||
func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing.T) {
|
||||
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnect{
|
||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||
IssuerPrivateKey: keyRSA2048,
|
||||
EnablePKCEPlainChallenge: true,
|
||||
HMACSecret: badhmac,
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||
Clients: []schema.OpenIDConnectClient{
|
||||
{
|
||||
ID: myclient,
|
||||
Secret: tOpenIDConnectPlainTextClientSecret,
|
||||
|
@ -50,11 +50,11 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing
|
|||
}
|
||||
|
||||
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *testing.T) {
|
||||
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||
provider := oidc.NewOpenIDConnectProvider(&schema.OpenIDConnect{
|
||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||
IssuerPrivateKey: keyRSA2048,
|
||||
HMACSecret: badhmac,
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||
Clients: []schema.OpenIDConnectClient{
|
||||
{
|
||||
ID: "a-client",
|
||||
Secret: tOpenIDConnectPlainTextClientSecret,
|
||||
|
|
|
@ -18,8 +18,8 @@ import (
|
|||
"github.com/authelia/authelia/v4/internal/storage"
|
||||
)
|
||||
|
||||
// NewStore returns a Store when provided with a schema.OpenIDConnectConfiguration and storage.Provider.
|
||||
func NewStore(config *schema.OpenIDConnectConfiguration, provider storage.Provider) (store *Store) {
|
||||
// NewStore returns a Store when provided with a schema.OpenIDConnect and storage.Provider.
|
||||
func NewStore(config *schema.OpenIDConnect, provider storage.Provider) (store *Store) {
|
||||
logger := logging.Logger()
|
||||
|
||||
store = &Store{
|
||||
|
|
|
@ -24,10 +24,10 @@ import (
|
|||
)
|
||||
|
||||
func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) {
|
||||
s := oidc.NewStore(&schema.OpenIDConnectConfiguration{
|
||||
s := oidc.NewStore(&schema.OpenIDConnect{
|
||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||
IssuerPrivateKey: keyRSA2048,
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||
Clients: []schema.OpenIDConnectClient{
|
||||
{
|
||||
ID: myclient,
|
||||
Description: myclientdesc,
|
||||
|
@ -56,10 +56,10 @@ func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestOpenIDConnectStore_GetInternalClient(t *testing.T) {
|
||||
s := oidc.NewStore(&schema.OpenIDConnectConfiguration{
|
||||
s := oidc.NewStore(&schema.OpenIDConnect{
|
||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||
IssuerPrivateKey: keyRSA2048,
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||
Clients: []schema.OpenIDConnectClient{
|
||||
{
|
||||
ID: myclient,
|
||||
Description: myclientdesc,
|
||||
|
@ -83,7 +83,7 @@ func TestOpenIDConnectStore_GetInternalClient(t *testing.T) {
|
|||
func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) {
|
||||
id := myclient
|
||||
|
||||
c1 := schema.OpenIDConnectClientConfiguration{
|
||||
c1 := schema.OpenIDConnectClient{
|
||||
ID: id,
|
||||
Description: myclientdesc,
|
||||
Policy: onefactor,
|
||||
|
@ -91,10 +91,10 @@ func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) {
|
|||
Secret: tOpenIDConnectPlainTextClientSecret,
|
||||
}
|
||||
|
||||
s := oidc.NewStore(&schema.OpenIDConnectConfiguration{
|
||||
s := oidc.NewStore(&schema.OpenIDConnect{
|
||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||
IssuerPrivateKey: keyRSA2048,
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{c1},
|
||||
Clients: []schema.OpenIDConnectClient{c1},
|
||||
}, nil)
|
||||
|
||||
client, err := s.GetFullClient(id)
|
||||
|
@ -111,7 +111,7 @@ func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
|
||||
c1 := schema.OpenIDConnectClientConfiguration{
|
||||
c1 := schema.OpenIDConnectClient{
|
||||
ID: myclient,
|
||||
Description: myclientdesc,
|
||||
Policy: onefactor,
|
||||
|
@ -119,10 +119,10 @@ func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
|
|||
Secret: tOpenIDConnectPlainTextClientSecret,
|
||||
}
|
||||
|
||||
s := oidc.NewStore(&schema.OpenIDConnectConfiguration{
|
||||
s := oidc.NewStore(&schema.OpenIDConnect{
|
||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||
IssuerPrivateKey: keyRSA2048,
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{c1},
|
||||
Clients: []schema.OpenIDConnectClient{c1},
|
||||
}, nil)
|
||||
|
||||
client, err := s.GetFullClient("another-client")
|
||||
|
@ -131,10 +131,10 @@ func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestOpenIDConnectStore_IsValidClientID(t *testing.T) {
|
||||
s := oidc.NewStore(&schema.OpenIDConnectConfiguration{
|
||||
s := oidc.NewStore(&schema.OpenIDConnect{
|
||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||
IssuerPrivateKey: keyRSA2048,
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||
Clients: []schema.OpenIDConnectClient{
|
||||
{
|
||||
ID: myclient,
|
||||
Description: myclientdesc,
|
||||
|
@ -169,8 +169,8 @@ func (s *StoreSuite) SetupTest() {
|
|||
s.ctx = context.Background()
|
||||
s.ctrl = gomock.NewController(s.T())
|
||||
s.mock = mocks.NewMockStorage(s.ctrl)
|
||||
s.store = oidc.NewStore(&schema.OpenIDConnectConfiguration{
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||
s.store = oidc.NewStore(&schema.OpenIDConnect{
|
||||
Clients: []schema.OpenIDConnectClient{
|
||||
{
|
||||
ID: "hs256",
|
||||
Secret: tOpenIDConnectPBKDF2ClientSecret,
|
||||
|
|
|
@ -123,7 +123,9 @@ type BaseClient struct {
|
|||
ResponseModes []fosite.ResponseModeType
|
||||
|
||||
IDTokenSigningAlg string
|
||||
IDTokenSigningKeyID string
|
||||
UserinfoSigningAlg string
|
||||
UserinfoSigningKeyID string
|
||||
|
||||
Policy authorization.Level
|
||||
|
||||
|
@ -152,8 +154,11 @@ type Client interface {
|
|||
GetSectorIdentifier() string
|
||||
GetConsentResponseBody(consent *model.OAuth2ConsentSession) ConsentGetResponseBody
|
||||
|
||||
GetUserinfoSigningAlg() string
|
||||
GetIDTokenSigningAlg() string
|
||||
GetIDTokenSigningKeyID() string
|
||||
|
||||
GetUserinfoSigningAlg() string
|
||||
GetUserinfoSigningKeyID() string
|
||||
|
||||
GetPAREnforcement() 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.
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -132,15 +136,11 @@ func (r *Mathematical) IntErr(max *big.Int) (value *big.Int, err error) {
|
|||
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")
|
||||
}
|
||||
|
||||
r.lock.Lock()
|
||||
|
||||
defer r.lock.Unlock()
|
||||
|
||||
return big.NewInt(int64(r.Intn(max.Sign()))), nil
|
||||
return big.NewInt(int64(r.Intn(int(max.Int64())))), nil
|
||||
}
|
||||
|
||||
// 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,10 +12,10 @@ import (
|
|||
)
|
||||
|
||||
// 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{
|
||||
enabled: config.MaxRetries > 0,
|
||||
storageProvider: provider,
|
||||
store: store,
|
||||
clock: clock,
|
||||
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 {
|
||||
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(),
|
||||
Successful: successful,
|
||||
Banned: banned,
|
||||
|
@ -46,7 +46,7 @@ func (r *Regulator) Regulate(ctx context.Context, username string) (time.Time, e
|
|||
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 {
|
||||
return time.Time{}, nil
|
||||
}
|
||||
|
|
|
@ -1,46 +1,69 @@
|
|||
package regulation_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/valyala/fasthttp"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||
"github.com/authelia/authelia/v4/internal/mocks"
|
||||
"github.com/authelia/authelia/v4/internal/model"
|
||||
"github.com/authelia/authelia/v4/internal/regulation"
|
||||
"github.com/authelia/authelia/v4/internal/utils"
|
||||
)
|
||||
|
||||
type RegulatorSuite struct {
|
||||
suite.Suite
|
||||
|
||||
ctx context.Context
|
||||
ctrl *gomock.Controller
|
||||
storageMock *mocks.MockStorage
|
||||
config schema.RegulationConfiguration
|
||||
clock utils.TestingClock
|
||||
mock *mocks.MockAutheliaCtx
|
||||
}
|
||||
|
||||
func (s *RegulatorSuite) SetupTest() {
|
||||
s.ctrl = gomock.NewController(s.T())
|
||||
s.storageMock = mocks.NewMockStorage(s.ctrl)
|
||||
s.ctx = context.Background()
|
||||
|
||||
s.config = schema.RegulationConfiguration{
|
||||
s.mock = mocks.NewMockAutheliaCtx(s.T())
|
||||
s.mock.Ctx.Configuration.Regulation = schema.RegulationConfiguration{
|
||||
MaxRetries: 3,
|
||||
BanTime: time.Second * 180,
|
||||
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() {
|
||||
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() {
|
||||
|
@ -48,17 +71,17 @@ func (s *RegulatorSuite) TestShouldNotThrowWhenUserIsLegitimate() {
|
|||
{
|
||||
Username: "john",
|
||||
Successful: true,
|
||||
Time: s.clock.Now().Add(-4 * time.Minute),
|
||||
Time: s.mock.Clock.Now().Add(-4 * time.Minute),
|
||||
},
|
||||
}
|
||||
|
||||
s.storageMock.EXPECT().
|
||||
LoadAuthenticationLogs(s.ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
s.mock.StorageMock.EXPECT().
|
||||
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -69,27 +92,27 @@ func (s *RegulatorSuite) TestShouldNotThrowWhenFailedAuthenticationNotInFindTime
|
|||
{
|
||||
Username: "john",
|
||||
Successful: false,
|
||||
Time: s.clock.Now().Add(-1 * time.Second),
|
||||
Time: s.mock.Clock.Now().Add(-1 * time.Second),
|
||||
},
|
||||
{
|
||||
Username: "john",
|
||||
Successful: false,
|
||||
Time: s.clock.Now().Add(-90 * time.Second),
|
||||
Time: s.mock.Clock.Now().Add(-90 * time.Second),
|
||||
},
|
||||
{
|
||||
Username: "john",
|
||||
Successful: false,
|
||||
Time: s.clock.Now().Add(-180 * time.Second),
|
||||
Time: s.mock.Clock.Now().Add(-180 * time.Second),
|
||||
},
|
||||
}
|
||||
|
||||
s.storageMock.EXPECT().
|
||||
LoadAuthenticationLogs(s.ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
s.mock.StorageMock.EXPECT().
|
||||
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -100,32 +123,32 @@ func (s *RegulatorSuite) TestShouldBanUserIfLatestAttemptsAreWithinFinTime() {
|
|||
{
|
||||
Username: "john",
|
||||
Successful: false,
|
||||
Time: s.clock.Now().Add(-1 * time.Second),
|
||||
Time: s.mock.Clock.Now().Add(-1 * time.Second),
|
||||
},
|
||||
{
|
||||
Username: "john",
|
||||
Successful: false,
|
||||
Time: s.clock.Now().Add(-4 * time.Second),
|
||||
Time: s.mock.Clock.Now().Add(-4 * time.Second),
|
||||
},
|
||||
{
|
||||
Username: "john",
|
||||
Successful: false,
|
||||
Time: s.clock.Now().Add(-6 * time.Second),
|
||||
Time: s.mock.Clock.Now().Add(-6 * time.Second),
|
||||
},
|
||||
{
|
||||
Username: "john",
|
||||
Successful: false,
|
||||
Time: s.clock.Now().Add(-180 * time.Second),
|
||||
Time: s.mock.Clock.Now().Add(-180 * time.Second),
|
||||
},
|
||||
}
|
||||
|
||||
s.storageMock.EXPECT().
|
||||
LoadAuthenticationLogs(s.ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
s.mock.StorageMock.EXPECT().
|
||||
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -138,27 +161,27 @@ func (s *RegulatorSuite) TestShouldCheckUserIsStillBanned() {
|
|||
{
|
||||
Username: "john",
|
||||
Successful: false,
|
||||
Time: s.clock.Now().Add(-31 * time.Second),
|
||||
Time: s.mock.Clock.Now().Add(-31 * time.Second),
|
||||
},
|
||||
{
|
||||
Username: "john",
|
||||
Successful: false,
|
||||
Time: s.clock.Now().Add(-34 * time.Second),
|
||||
Time: s.mock.Clock.Now().Add(-34 * time.Second),
|
||||
},
|
||||
{
|
||||
Username: "john",
|
||||
Successful: false,
|
||||
Time: s.clock.Now().Add(-36 * time.Second),
|
||||
Time: s.mock.Clock.Now().Add(-36 * time.Second),
|
||||
},
|
||||
}
|
||||
|
||||
s.storageMock.EXPECT().
|
||||
LoadAuthenticationLogs(s.ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
s.mock.StorageMock.EXPECT().
|
||||
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -167,22 +190,22 @@ func (s *RegulatorSuite) TestShouldCheckUserIsNotYetBanned() {
|
|||
{
|
||||
Username: "john",
|
||||
Successful: false,
|
||||
Time: s.clock.Now().Add(-34 * time.Second),
|
||||
Time: s.mock.Clock.Now().Add(-34 * time.Second),
|
||||
},
|
||||
{
|
||||
Username: "john",
|
||||
Successful: false,
|
||||
Time: s.clock.Now().Add(-36 * time.Second),
|
||||
Time: s.mock.Clock.Now().Add(-36 * time.Second),
|
||||
},
|
||||
}
|
||||
|
||||
s.storageMock.EXPECT().
|
||||
LoadAuthenticationLogs(s.ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
s.mock.StorageMock.EXPECT().
|
||||
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -191,7 +214,7 @@ func (s *RegulatorSuite) TestShouldCheckUserWasAboutToBeBanned() {
|
|||
{
|
||||
Username: "john",
|
||||
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.
|
||||
// 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",
|
||||
Successful: false,
|
||||
Time: s.clock.Now().Add(-94 * time.Second),
|
||||
Time: s.mock.Clock.Now().Add(-94 * time.Second),
|
||||
},
|
||||
{
|
||||
Username: "john",
|
||||
Successful: false,
|
||||
Time: s.clock.Now().Add(-96 * time.Second),
|
||||
Time: s.mock.Clock.Now().Add(-96 * time.Second),
|
||||
},
|
||||
}
|
||||
|
||||
s.storageMock.EXPECT().
|
||||
LoadAuthenticationLogs(s.ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
s.mock.StorageMock.EXPECT().
|
||||
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -223,34 +246,34 @@ func (s *RegulatorSuite) TestShouldCheckRegulationHasBeenResetOnSuccessfulAttemp
|
|||
{
|
||||
Username: "john",
|
||||
Successful: false,
|
||||
Time: s.clock.Now().Add(-90 * time.Second),
|
||||
Time: s.mock.Clock.Now().Add(-90 * time.Second),
|
||||
},
|
||||
{
|
||||
Username: "john",
|
||||
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
|
||||
// failure happens within FindTime, he should not be banned.
|
||||
{
|
||||
Username: "john",
|
||||
Successful: false,
|
||||
Time: s.clock.Now().Add(-94 * time.Second),
|
||||
Time: s.mock.Clock.Now().Add(-94 * time.Second),
|
||||
},
|
||||
{
|
||||
Username: "john",
|
||||
Successful: false,
|
||||
Time: s.clock.Now().Add(-96 * time.Second),
|
||||
Time: s.mock.Clock.Now().Add(-96 * time.Second),
|
||||
},
|
||||
}
|
||||
|
||||
s.storageMock.EXPECT().
|
||||
LoadAuthenticationLogs(s.ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
s.mock.StorageMock.EXPECT().
|
||||
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -265,22 +288,22 @@ func (s *RegulatorSuite) TestShouldHaveRegulatorDisabled() {
|
|||
{
|
||||
Username: "john",
|
||||
Successful: false,
|
||||
Time: s.clock.Now().Add(-31 * time.Second),
|
||||
Time: s.mock.Clock.Now().Add(-31 * time.Second),
|
||||
},
|
||||
{
|
||||
Username: "john",
|
||||
Successful: false,
|
||||
Time: s.clock.Now().Add(-34 * time.Second),
|
||||
Time: s.mock.Clock.Now().Add(-34 * time.Second),
|
||||
},
|
||||
{
|
||||
Username: "john",
|
||||
Successful: false,
|
||||
Time: s.clock.Now().Add(-36 * time.Second),
|
||||
Time: s.mock.Clock.Now().Add(-36 * time.Second),
|
||||
},
|
||||
}
|
||||
|
||||
s.storageMock.EXPECT().
|
||||
LoadAuthenticationLogs(s.ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
s.mock.StorageMock.EXPECT().
|
||||
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
|
||||
Return(attemptsInDB, nil)
|
||||
|
||||
// Check Disabled Functionality.
|
||||
|
@ -290,8 +313,8 @@ func (s *RegulatorSuite) TestShouldHaveRegulatorDisabled() {
|
|||
BanTime: time.Second * 180,
|
||||
}
|
||||
|
||||
regulator := regulation.NewRegulator(config, s.storageMock, &s.clock)
|
||||
_, err := regulator.Regulate(s.ctx, "john")
|
||||
regulator := regulation.NewRegulator(config, s.mock.StorageMock, &s.mock.Clock)
|
||||
_, err := regulator.Regulate(s.mock.Ctx, "john")
|
||||
assert.NoError(s.T(), err)
|
||||
|
||||
// Check Enabled Functionality.
|
||||
|
@ -301,7 +324,7 @@ func (s *RegulatorSuite) TestShouldHaveRegulatorDisabled() {
|
|||
BanTime: time.Second * 180,
|
||||
}
|
||||
|
||||
regulator = regulation.NewRegulator(config, s.storageMock, &s.clock)
|
||||
_, err = regulator.Regulate(s.ctx, "john")
|
||||
regulator = regulation.NewRegulator(config, s.mock.StorageMock, &s.mock.Clock)
|
||||
_, err = regulator.Regulate(s.mock.Ctx, "john")
|
||||
assert.Equal(s.T(), regulation.ErrUserIsBanned, err)
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ type Regulator struct {
|
|||
|
||||
config schema.RegulationConfiguration
|
||||
|
||||
storageProvider storage.RegulatorProvider
|
||||
store storage.RegulatorProvider
|
||||
|
||||
clock utils.Clock
|
||||
}
|
||||
|
|
|
@ -29,59 +29,6 @@ const (
|
|||
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 (
|
||||
encryptionNameCheck = "check"
|
||||
)
|
||||
|
|
|
@ -93,3 +93,56 @@ func (r EncryptionValidationTableResult) ResultDescriptor() string {
|
|||
|
||||
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) {
|
||||
var key [32]byte = sha256.Sum256([]byte("the key"))
|
||||
var key = sha256.Sum256([]byte("the key"))
|
||||
|
||||
var secret = "the secret"
|
||||
|
||||
|
@ -22,7 +22,7 @@ func TestShouldEncryptAndDecriptUsingAES(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"
|
||||
|
||||
|
@ -37,7 +37,7 @@ func TestShouldFailDecryptOnInvalidKey(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")
|
||||
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
||||
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) {
|
||||
testCases := []struct {
|
||||
desc, have string
|
||||
|
|
|
@ -209,11 +209,51 @@ func TestParseTimeString(t *testing.T) {
|
|||
|
||||
for _, tc := range testCases {
|
||||
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 == "" {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.index, index)
|
||||
assert.Equal(t, tc.expected.UnixNano(), actual.UnixNano())
|
||||
} else {
|
||||
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.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/react-fontawesome": "0.2.0",
|
||||
"@mui/icons-material": "5.11.16",
|
||||
"@mui/material": "5.13.1",
|
||||
"@mui/styles": "5.13.1",
|
||||
"@mui/material": "5.13.2",
|
||||
"@mui/styles": "5.13.2",
|
||||
"@simplewebauthn/browser": "7.2.0",
|
||||
"@simplewebauthn/typescript-types": "7.0.0",
|
||||
"axios": "1.4.0",
|
||||
|
@ -77,17 +77,17 @@
|
|||
"@limegrass/eslint-plugin-import-alias": "1.0.6",
|
||||
"@testing-library/jest-dom": "5.16.5",
|
||||
"@testing-library/react": "14.0.0",
|
||||
"@types/node": "20.2.1",
|
||||
"@types/react": "18.2.6",
|
||||
"@types/node": "20.2.5",
|
||||
"@types/react": "18.2.7",
|
||||
"@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",
|
||||
"@typescript-eslint/eslint-plugin": "5.59.6",
|
||||
"@typescript-eslint/parser": "5.59.6",
|
||||
"@typescript-eslint/eslint-plugin": "5.59.7",
|
||||
"@typescript-eslint/parser": "5.59.7",
|
||||
"@vitejs/plugin-react": "4.0.0",
|
||||
"@vitest/coverage-istanbul": "0.31.1",
|
||||
"esbuild": "0.17.19",
|
||||
"eslint": "8.40.0",
|
||||
"eslint": "8.41.0",
|
||||
"eslint-config-prettier": "8.8.0",
|
||||
"eslint-config-react-app": "7.0.1",
|
||||
"eslint-formatter-rdjson": "1.0.5",
|
||||
|
@ -97,14 +97,14 @@
|
|||
"eslint-plugin-prettier": "4.2.1",
|
||||
"eslint-plugin-react": "7.32.2",
|
||||
"eslint-plugin-react-hooks": "4.6.0",
|
||||
"happy-dom": "9.19.2",
|
||||
"happy-dom": "9.20.3",
|
||||
"husky": "8.0.3",
|
||||
"prettier": "2.8.8",
|
||||
"react-test-renderer": "18.2.0",
|
||||
"typescript": "5.0.4",
|
||||
"vite": "4.3.8",
|
||||
"vite": "4.3.9",
|
||||
"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-tsconfig-paths": "4.2.0",
|
||||
"vitest": "0.31.1",
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue