Merge remote-tracking branch 'origin/master' into refactor-trust-provider

# Conflicts:
#	docs/data/configkeys.json
pull/4713/head
James Elliott 2023-03-10 16:26:11 +11:00
commit 1a52dde169
No known key found for this signature in database
GPG Key ID: 0F1C4A096E857E49
68 changed files with 2203 additions and 1211 deletions

View File

@ -15,7 +15,7 @@ RUN yarn global add pnpm && \
# =======================================
# ===== Build image for the backend =====
# =======================================
FROM golang:1.20.1-alpine AS builder-backend
FROM golang:1.20.2-alpine AS builder-backend
WORKDIR /go/src/app

View File

@ -13,7 +13,7 @@ RUN yarn install --frozen-lockfile && yarn build
# =======================================
# ===== Build image for the backend =====
# =======================================
FROM golang:1.20.1-alpine AS builder-backend
FROM golang:1.20.2-alpine AS builder-backend
WORKDIR /go/src/app

View File

@ -6,7 +6,7 @@ info:
Authelia is an open-source authentication and authorization server providing 2-factor authentication and single
sign-on (SSO) for your applications via a web portal.
contact:
name: Authelia Support
name: Support
url: https://www.authelia.com/contact/
email: team@authelia.com
license:
@ -2940,9 +2940,9 @@ components:
- "address"
- "phone"
openid.spec.IntrospectionRequest:
type: object
required:
- "token"
type: object
properties:
token:
description: >
@ -2952,8 +2952,8 @@ components:
this is the "refresh_token" value returned from the token endpoint
as defined in OAuth 2.0 [RFC6749], Section 5.1. Other token types
are outside the scope of this specification.
type: string
example: "authelia_at_cr4i4EtTn2F4k6mX4XzxbsBewkxCGn"
type: string
token_type_hint:
description: >
A hint about the type of the token submitted for
@ -2965,27 +2965,61 @@ components:
is able to detect the token type automatically. Values for this
field are defined in the "OAuth Token Type Hints" registry defined
in OAuth Token Revocation [RFC7009].
type: string
example: "access_token"
enum:
- "access_token"
- "refresh_token"
example: "access_token"
type: string
openid.spec.AccessRequest.ClientAuth:
oneOf:
- $ref: '#/components/schemas/openid.spec.AccessRequest.ClientAuth.Base'
- $ref: '#/components/schemas/openid.spec.AccessRequest.ClientAuth.Secret'
- $ref: '#/components/schemas/openid.spec.AccessRequest.ClientAuth.JWT'
openid.spec.AccessRequest.ClientAuth.Base:
required:
- "client_id"
type: object
properties:
client_id:
description: >
REQUIRED if the client is not authenticating with the
authorization server as described in Section 3.2.1. of [RFC6749].
The client identifier as described in Section 2.2 of [RFC6749].
REQUIRED if the client is not authenticating with the authorization server as described in
Section 3.2.1. of [RFC6749]. The client identifier as described in Section 2.2 of [RFC6749].
example: "my_client"
type: string
example: "authelia_dc_mn123kjn12kj3123njk"
openid.spec.AccessRequest.ClientAuth.Secret:
required:
- "client_secret"
type: object
properties:
client_secret:
description: >
REQUIRED. The client secret. The client MAY omit the
parameter if the client secret is an empty string.
type: string
format: password
type: string
openid.spec.AccessRequest.ClientAuth.JWT:
allOf:
- $ref: '#/components/schemas/openid.spec.AccessRequest.ClientAuth.Base'
- type: object
required:
- "client_assertion"
- "client_assertion_type"
properties:
client_assertion:
description: >
The value of the client_assertion_type parameter MUST be
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
enum:
- "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
example: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
type: string
client_assertion_type:
description: >
A JWT signed with HS256 using the client secret value or RS256 using a registered public key.
Theoretically a properly formed JWT signed using HS256 with the client secret as the HMAC key should
work but this has not been tested.
format: password
type: string
openid.spec.AccessRequest.AuthorizationCodeFlow:
allOf:
- $ref: '#/components/schemas/openid.spec.AccessRequest.ClientAuth'
@ -2995,22 +3029,22 @@ components:
- "grant_type"
properties:
grant_type:
description: Value MUST be set to "urn:ietf:params:oauth:grant-type:device_code".
type: string
description: Value MUST be set to "code".
enum:
- "authorization_code"
type: string
code:
description: The Authorization Code.
type: string
example: "authelia_ac_1j2kn3knj12n3kj12n"
type: string
code_verifier:
description: The Authorization Code Verifier (PKCE).
type: string
example: "88a25754f7c0b3b3b88cf6cd4e29e8356b160524fdc1cb329a94471825628fd3"
type: string
redirect_uri:
description: The original Redirect URI used in the Authorization Request.
type: string
example: "https://app.example.com/oidc/callback"
type: string
openid.spec.AccessRequest.DeviceCodeFlow:
allOf:
- $ref: '#/components/schemas/openid.spec.AccessRequest.ClientAuth'
@ -3021,13 +3055,13 @@ components:
properties:
grant_type:
description: Value MUST be set to "urn:ietf:params:oauth:grant-type:device_code".
type: string
enum:
- "urn:ietf:params:oauth:grant-type:device_code"
type: string
device_code:
description: The Device Authorization Code.
type: string
example: "authelia_dc_mn123kjn12kj3123njk"
type: string
openid.spec.AccessRequest.RefreshTokenFlow:
allOf:
- $ref: '#/components/schemas/openid.spec.AccessRequest.ClientAuth'
@ -3038,12 +3072,13 @@ components:
properties:
grant_type:
description: Value MUST be set to "refresh_token".
type: string
enum:
- "refresh_token"
type: string
refresh_token:
description: The Refresh Token.
example: "authelia_rt_1n2j3kihn12kj3n12k"
type: string
scope:
description: >
The scope of the access request as described by
@ -3051,20 +3086,30 @@ components:
not originally granted by the resource owner, and if omitted is
treated as equal to the scope originally granted by the
resource owner.
example: "openid profile groups"
type: string
openid.spec.AccessResponse:
type: object
required:
- "access_token"
- "token_type"
- "expires_in"
properties:
access_token:
description: The access token issued by the authorization server.
type: string
example: "authelia_at_cr4i4EtTn2F4k6mX4XzxbsBewkxCGn"
refresh_token:
type: string
id_token:
description: The id token issued by the authorization server.
example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
type: string
refresh_token:
description: >
The refresh token, which can be used to obtain new access tokens using the
same authorization grant as described in Section 6.
token_type:
example: "authelia_rt_kGBoSMbfVGP2RR6Kvujv3Xg7uXV2i"
type: string
token_type:
description: >
The access token type provides the client with the information
required to successfully utilize the access token to make a protected
@ -3073,21 +3118,26 @@ components:
type.
enum:
- "bearer"
example: "bearer"
type: string
expires_in:
type: integer
description: >
The lifetime in seconds of the access token. For
example, the value "3600" denotes that the access token will
expire in one hour from the time the response was generated.
If omitted, the authorization server SHOULD provide the
expiration time via other means or document the default value.
example: 3600
type: integer
state:
type: string
description: Exactly the state value passed in the authorization request if present.
scope:
example: "5dVZhNfri5XZS6wadskuzUk4MHYCvEcUgidjMeBjsktAhY7EKB"
type: string
scope:
description: >
The scope of the access token as described by Section 3.3 if it differs from the requested scope.
example: "openid profile groups"
type: string
openid.spec.AuthorizeRequest:
type: object
required:
@ -3098,14 +3148,14 @@ components:
properties:
scope:
description: The requested scope.
type: string
example: "openid profile groups"
type: string
response_type:
$ref: '#/components/schemas/openid.spec.ResponseType'
client_id:
description: The OAuth 2.0 client identifier.
type: string
example: "app"
type: string
redirect_uri:
description: >
Redirection URI to which the response will be sent. This URI MUST exactly match one of the
@ -3115,15 +3165,15 @@ components:
that the Client Type is confidential, as defined in Section 2.1 of OAuth 2.0, and provided the OP
allows the use of http Redirection URIs in this case. The Redirection URI MAY use an alternate
scheme, such as one that is intended to identify a callback into a native application.
type: string
example: "https://app.example.com"
type: string
state:
description: >
Opaque value used to maintain state between the request and the callback. Typically, Cross-Site
Request Forgery (CSRF, XSRF) mitigation is done by cryptographically binding the value of this
parameter with a browser cookie.
type: string
example: "oV84Vsy7wyCgRk2h4aZBmXZq4q3g2f"
type: string
response_mode:
$ref: '#/components/schemas/openid.spec.ResponseMode'
nonce:
@ -3132,14 +3182,23 @@ components:
The value is passed through unmodified from the Authentication Request to the ID Token. Sufficient
entropy MUST be present in the nonce values used to prevent attackers from guessing values. For
implementation notes, see Section 15.5.2.
type: string
example: "TRMLqchoKGQNcooXvBvUy9PtmLdJGf"
type: string
display:
$ref: '#/components/schemas/openid.spec.DisplayType'
prompt:
description: >
Not Supported: Space delimited, case sensitive list of ASCII string values that specifies whether
the Authorization Server prompts the End-User for reauthentication and consent.
enum:
- "none"
- "login"
- "consent"
- "select_account"
- "login consent"
- "login select_account"
- "consent select_account"
example: "consent"
type: string
max_age:
description: >
@ -3217,34 +3276,32 @@ components:
description: >
A Subject Identifier is a locally unique and never reassigned identifier within the Issuer for the
End-User, which is intended to be consumed by the Client.
type: string
enum:
- "public"
- "pairwise"
type: string
openid.spec.ClientAuthMethod:
description: The OAuth 2.0 / OpenID Connect 1.0 Client Authentication Method.
type: string
enum:
- "client_secret_basic"
- "client_secret_post"
- "client_secret_jwt"
- "private_key_jwt"
- "none"
type: string
openid.spec.DisplayType:
description: >
ASCII string value that specifies how the Authorization Server displays the authentication and consent user
interface pages to the End-User.
type: string
example: "page"
enum:
- "page"
- "popup"
- "touch"
- "wap"
example: "page"
type: string
openid.spec.ResponseType:
description: The OAuth 2.0 / OpenID Connect 1.0 Response Type.
type: string
example: "code"
enum:
- "code"
- "id_token"
@ -3254,21 +3311,21 @@ components:
- "token id_token"
- "code id_token token"
- "none"
example: "code"
type: string
openid.spec.ResponseMode:
description: >
Informs the Authorization Server of the mechanism to be used for returning parameters from the Authorization
Endpoint. This use of this parameter is NOT RECOMMENDED when the Response Mode that would be requested is
the default mode specified for the Response Type.
type: string
example: "query"
enum:
- "query"
- "fragment"
- "form_post"
example: "query"
type: string
openid.spec.GrantType:
description: The OAuth 2.0 / OpenID Connect 1.0 Grant Type.
type: string
example: "authorization_code"
enum:
- "authorization_code"
- "refresh_token"
@ -3276,21 +3333,23 @@ components:
- "password"
- "client_credentials"
- "urn:ietf:params:oauth:grant-type:device_code"
example: "authorization_code"
type: string
openid.spec.CodeChallengeMethod:
description: The RFC7636 Code Challenge Verifier Method.
type: string
example: "S256"
enum:
- "plain"
- "S256"
example: "S256"
type: string
openid.spec.ClaimType:
description: The representation of claims.
type: string
example: "normal"
enum:
- "normal"
- "aggregated"
- "distributed"
example: "normal"
type: string
jose.spec.None:
description: The JSON Web Signature Algorithm
type: string
@ -3298,13 +3357,12 @@ components:
- "none"
jose.spec.JWS.None:
description: The JSON Web Signature Algorithm
type: string
oneOf:
- $ref: '#/components/schemas/jose.spec.None'
- $ref: '#/components/schemas/jose.spec.jws'
type: string
jose.spec.jws:
description: The JSON Web Signature Algorithm
type: string
enum:
- "HS256"
- "HS384"
@ -3318,9 +3376,9 @@ components:
- "PS256"
- "PS384"
- "PS512"
type: string
jose.spec.JWE.alg:
description: The JSON Web Encryption Algorithm (CEK)
type: string
enum:
- "RSA1_5"
- "RSA-OAEP"
@ -3339,9 +3397,9 @@ components:
- "PBES2-HS256+A128KW"
- "PBES2-HS384+A192KW"
- "PBES2-HS512+A256KW"
type: string
jose.spec.JWE.enc:
description: The JSON Web Encryption Algorithm (Claims)
type: string
enum:
- "A128CBC-HS256"
- "A192CBC-HS384"
@ -3350,6 +3408,7 @@ components:
- "A256CBC"
- "A128GCM"
- "A256GCM"
type: string
jose.spec.JWK.base:
type: object
properties:
@ -3359,21 +3418,20 @@ components:
the public key. The "use" parameter is employed to indicate whether
a public key is used for encrypting data or verifying the signature
on data.
type: string
example: "sig"
enum:
- "sig"
- "enc"
example: "sig"
type: string
key_ops:
description: >
The "key_ops" (key operations) parameter identifies the operation(s)
for which the key is intended to be used. The "key_ops" parameter is
intended for use cases in which public, private, or symmetric keys
may be present.
type: array
example: ["sign"]
type: array
items:
type: string
enum:
- "sign"
- "verify"
@ -3383,6 +3441,7 @@ components:
- "unwrapKey"
- "deriveKey"
- "deriveBits"
type: string
kid:
description: >
The "kid" (key ID) parameter is used to match a specific key. This
@ -3427,8 +3486,8 @@ components:
OPTIONAL.
type: array
items:
type: string
format: byte
type: string
x5t:
description: >
The "x5t" (X.509 certificate SHA-1 thumbprint) parameter is a
@ -3437,8 +3496,8 @@ components:
thumbprints are also sometimes known as certificate fingerprints.
The key in the certificate MUST match the public key represented by
other members of the JWK. Use of this member is OPTIONAL.
type: string
format: byte
type: string
x5t#S256:
description: >
The "x5t#S256" (X.509 certificate SHA-256 thumbprint) parameter is a
@ -3447,17 +3506,17 @@ components:
thumbprints are also sometimes known as certificate fingerprints.
The key in the certificate MUST match the public key represented by
other members of the JWK. Use of this member is OPTIONAL.
type: string
format: byte
type: string
jose.spec.JWK.RSA:
description: RSA Public Key in JSON Web Key format as defined by RFC7517 and RFC7518.
allOf:
- $ref: '#/components/schemas/jose.spec.JWK.base'
- type: object
required:
- required:
- "kty"
- "n"
- "e"
type: object
properties:
kty:
description: >

View File

@ -272,6 +272,23 @@ Allows [PKCE] `plain` challenges when set to `true`.
*__Security Notice:__* Changing this value is generally discouraged. Applications should use the `S256` [PKCE] challenge
method instead.
### pushed_authorizations
Controls the behaviour of [Pushed Authorization Requests].
#### enforce
{{< confkey type="boolean" default="false" required="no" >}}
When enabled all authorization requests must use the [Pushed Authorization Requests] flow.
#### context_lifespan
{{< confkey type="duration" default="5m" required="no" >}}
The maximum amount of time between the [Pushed Authorization Requests] flow being initiated and the generated
`request_uri` being utilized by a client.
### cors
Some [OpenID Connect 1.0] Endpoints need to allow cross-origin resource sharing, however some are optional. This section allows
@ -285,6 +302,7 @@ A list of endpoints to configure with cross-origin resource sharing headers. It
option is at least in this list. The potential endpoints which this can be enabled on are as follows:
* authorization
* pushed-authorization-request
* token
* revocation
* introspection
@ -472,6 +490,12 @@ See the [Response Modes](../../integration/openid-connect/introduction.md#respon
The authorization policy for this client: either `one_factor` or `two_factor`.
#### enforce_par
{{< confkey type="boolean" default="false" required="no" >}}
Enforces the use of a [Pushed Authorization Requests] flow for this client.
#### enforce_pkce
{{< confkey type="bool" default="false" required="no" >}}
@ -550,3 +574,4 @@ To integrate Authelia's [OpenID Connect 1.0] implementation with a relying party
[Authorization Code Flow]: https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth
[Subject Identifier Type]: https://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes
[Pairwise Identifier Algorithm]: https://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg
[Pushed Authorization Requests]: https://datatracker.ietf.org/doc/html/rfc9126

View File

@ -36,3 +36,4 @@ this instance if you wanted to downgrade to pre1 you would need to use an Authel
| 5 | 4.35.1 | Fixed the oauth2_consent_session table to accept NULL subjects for users who are not yet signed in |
| 6 | 4.37.0 | Adjusted the OpenID Connect tables to allow pre-configured consent improvements |
| 7 | 4.37.3 | Fixed some schema inconsistencies most notably the MySQL/MariaDB Engine and Collation |
| 8 | 4.38.0 | OpenID Connect 1.0 Pushed Authorization Requests |

View File

@ -0,0 +1,53 @@
---
title: "Accessibility"
description: "Authelia Development Accessibility Guidelines"
lead: "This section covers the accessibility guidelines we aim to respect during development."
date: 2023-03-06T11:42:13+11:00
draft: false
images: []
menu:
contributing:
parent: "guidelines"
weight: 350
toc: true
---
## Backend
There are no specific guidelines for backend accessibility other than ensuring there are reasonable logging and this is
extremely subjective.
## Frontend
### Translations
We aim to ensure as much of the web frontend information displayed to users is translated by default. This allows for
both automatic and manual translations by the community to be contributed to the code base. In addition it allows for
admins to locally override these values.
### Responsive Design
We aim to make the web frontend responsive to multiple screen resolutions. There are a few guidelines which we aim to
abide by:
- The available space is utilized efficiently in order to avoid scrolling where possible.
- The user only has to scroll in one direction to view available information. This direction should always be
vertically.
Recommendations on resolutions which are common:
- Desktop/Laptop:
1. 1920x1080
2. 1366x768
3. 2560x1440
4. 1280x720
- Tablet Devices (With Touch and Landscape):
1. 768x1024
2. 810x1080
3. 800x1280
- Mobile Devices (With Touch and Landscape):
1. 360x800
2. 390x844
3. 414x896
4. 412x915

View File

@ -8,7 +8,7 @@ images: []
menu:
contributing:
parent: "guidelines"
weight: 320
weight: 350
toc: true
aliases:
- /docs/contributing/style-guide.html

View File

@ -210,14 +210,71 @@ These endpoints can be utilized to discover other endpoints and metadata about t
These endpoints implement OpenID Connect elements.
| Endpoint | Path | Discovery Attribute |
|:-------------------:|:-----------------------------------------------:|:----------------------:|
| [JSON Web Key Sets] | https://auth.example.com/jwks.json | jwks_uri |
|:-------------------------------:|:--------------------------------------------------------------:|:-------------------------------------:|
| [JSON Web Key Set] | https://auth.example.com/jwks.json | jwks_uri |
| [Authorization] | https://auth.example.com/api/oidc/authorization | authorization_endpoint |
| [Pushed Authorization Requests] | https://auth.example.com/api/oidc/pushed-authorization-request | pushed_authorization_request_endpoint |
| [Token] | https://auth.example.com/api/oidc/token | token_endpoint |
| [UserInfo] | https://auth.example.com/api/oidc/userinfo | userinfo_endpoint |
| [Introspection] | https://auth.example.com/api/oidc/introspection | introspection_endpoint |
| [Revocation] | https://auth.example.com/api/oidc/revocation | revocation_endpoint |
## Security
The following information covers some security topics some users may wish to be familiar with.
#### Pushed Authorization Requests Endpoint
The [Pushed Authorization Requests] endpoint is discussed in depth in [RFC9126] as well as in the
[OAuth 2.0 Pushed Authorization Requests](https://oauth.net/2/pushed-authorization-requests/) documentation.
Essentially it's a special endpoint that takes the same parameters as the [Authorization] endpoint (including
[Proof Key Code Exchange](#proof-key-code-exchange)) with a few caveats:
1. The same [Client Authentication] mechanism required by the [Token] endpoint **MUST** be used.
2. The request **MUST** use the [HTTP POST method].
3. The request **MUST** use the `application/x-www-form-urlencoded` content type (i.e. the parameters **MUST** be in the
body, not the URI).
4. The request **MUST** occur over the back-channel.
The response of this endpoint is a JSON Object with two key-value pairs:
- `request_uri`
- `expires_in`
The `expires_in` indicates how long the `request_uri` is valid for. The `request_uri` is used as a parameter to the
[Authorization] endpoint instead of the standard parameters (as the `request_uri` parameter).
The advantages of this approach are as follows:
1. [Pushed Authorization Requests] cannot be created or influenced by any party other than the Relying Party (client).
2. Since you can force all [Authorization] requests to be initiated via [Pushed Authorization Requests] you drastically
improve the authorization flows resistance to phishing attacks (this can be done globally or on a per-client basis).
3. Since the [Pushed Authorization Requests] endpoint requires all of the same [Client Authentication] mechanisms as the
[Token] endpoint:
1. Clients using the confidential [Client Type] can't have [Pushed Authorization Requests] generated by parties who do not
have the credentials.
2. Clients using the public [Client Type] and utilizing [Proof Key Code Exchange](#proof-key-code-exchange) never
transmit the verifier over any front-channel making even the `plain` challenge method relatively secure.
#### Proof Key Code Exchange
The [Proof Key Code Exchange] mechanism is discussed in depth in [RFC7636] as well as in the
[OAuth 2.0 Proof Key Code Exchange](https://oauth.net/2/pkce/) documentation.
Essentially a random opaque value is generated by the Relying Party and optionally (but recommended) passed through a
SHA256 hash. The original value is saved by the Relying Party, and the hashed value is sent in the [Authorization]
request in the `code_verifier` parameter with the `code_challenge_method` set to `S256` (or `plain` using a bad practice
of not hashing the opaque value).
When the Relying Party requests the token from the [Token] endpoint, they must include the `code_verifier` parameter
again (in the body), but this time they send the value without it being hashed.
The advantages of this approach are as follows:
1. Provided the value was hashed it's certain that the Relying Party which generated the authorization request is the
same party as the one requesting the token or is permitted by the Relying Party to make this request.
2. Even when using the public [Client Type] there is a form of authentication on the [Token] endpoint.
[ID Token]: https://openid.net/specs/openid-connect-core-1_0.html#IDToken
[Access Token]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.4
[Refresh Token]: https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens
@ -230,14 +287,23 @@ These endpoints implement OpenID Connect elements.
[OpenID Connect Discovery]: https://openid.net/specs/openid-connect-discovery-1_0.html
[OAuth 2.0 Authorization Server Metadata]: https://datatracker.ietf.org/doc/html/rfc8414
[JSON Web Key Sets]: https://datatracker.ietf.org/doc/html/rfc7517#section-5
[JSON Web Key Set]: https://datatracker.ietf.org/doc/html/rfc7517#section-5
[Authorization]: https://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint
[Pushed Authorization Requests]: https://datatracker.ietf.org/doc/html/rfc9126
[Token]: https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
[UserInfo]: https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
[Introspection]: https://datatracker.ietf.org/doc/html/rfc7662
[Revocation]: https://datatracker.ietf.org/doc/html/rfc7009
[Proof Key Code Exchange]: https://www.rfc-editor.org/rfc/rfc7636.html
[RFC8176]: https://datatracker.ietf.org/doc/html/rfc8176
[RFC4122]: https://datatracker.ietf.org/doc/html/rfc4122
[Subject Identifier Types]: https://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes
[Client Authentication]: https://datatracker.ietf.org/doc/html/rfc6749#section-2.3
[Client Type]: https://oauth.net/2/client-types/
[HTTP POST method]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST
[Proof Key Code Exchange]: #proof-key-code-exchange
[RFC4122]: https://datatracker.ietf.org/doc/html/rfc4122
[RFC7636]: https://datatracker.ietf.org/doc/html/rfc7636
[RFC8176]: https://datatracker.ietf.org/doc/html/rfc8176
[RFC9126]: https://datatracker.ietf.org/doc/html/rfc9126

View File

@ -53,3 +53,20 @@ deprecated__* and we instead recommended that users remove this from their confi
Plaintext is either denoted by the `$plaintext$` prefix where everything after the prefix is the secret. In addition if
the secret does not start with the `$` character it's considered as a plaintext secret for the time being but is
deprecated as is the `$plaintext$` prefix.
## Frequently Asked Questions
### Why isn't my application able to retrieve the token even though I've consented?
The most common cause for this issue is when the affected application can not make requests to the Token [Endpoint].
This becomes obvious when the log level is set to `debug` or `trace` and a presence of requests to the Authorization
[Endpoint] without errors but an absence of requests made to the Token [Endpoint].
These requests can be identified by looking at the `path` field in the logs, or by messages prefixed with
`Authorization Request` indicating a request to the Authorization [Endpoint] and `Access Request` indicating a request
to the Token [Endpoint].
All causes should be clearly logged by the client application, and all errors that do not match this scenario are
clearly logged by Authelia. It's not possible for us to log requests that never occur however.
[Endpoint]: ./introduction.md#discoverable-endpoints

View File

@ -383,7 +383,7 @@ proxy_set_header X-Forwarded-For $remote_addr;
set $upstream_authelia http://authelia:9091/api/authz/auth-request;
## Virtual endpoint created by nginx to forward auth requests.
location /authelia {
location /internal/authelia/authz {
## Essential Proxy Configuration
internal;
proxy_pass $upstream_authelia;
@ -423,7 +423,7 @@ and is paired with [authelia-location.conf](#authelia-locationconf).*
{{< details "/config/nginx/snippets/authelia-authrequest.conf" >}}
```nginx
## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource.
auth_request /authelia;
auth_request /internal/authelia/authz;
## Set the $target_url variable based on the original request.
@ -478,7 +478,7 @@ implementation `AuthRequest` which contains the `HeaderAuthorization` and `Heade
set $upstream_authelia http://authelia:9091/api/authz/auth-request/basic;
# Virtual endpoint created by nginx to forward auth requests.
location /authelia-basic {
location /internal/authelia/authz/basic {
## Essential Proxy Configuration
internal;
proxy_pass $upstream_authelia;
@ -526,7 +526,7 @@ endpoint. It's recommended to use [authelia-authrequest.conf](#authelia-authrequ
{{< details "/config/nginx/snippets/authelia-authrequest-basic.conf" >}}
```nginx
## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource.
auth_request /authelia-basic;
auth_request /internal/authelia/authz/basic;
## Comment this line if you're using nginx without the http_set_misc module.
set_escape_uri $target_url $scheme://$http_host$request_uri;
@ -570,7 +570,7 @@ if ($request_uri = "/force-basic") {
}
## A new virtual endpoint to used if the auth_request failed
location /authelia-detect {
location /internal/authelia/authz/detect {
internal;
if ($is_basic_auth) {
@ -598,7 +598,7 @@ endpoint. It's recommended to use [authelia-authrequest.conf](#authelia-authrequ
{{< details "/config/nginx/snippets/authelia-authrequest-detect.conf" >}}
```nginx
## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource.
auth_request /authelia;
auth_request /internal/authelia/authz;
## Comment this line if you're using nginx without the http_set_misc module.
set_escape_uri $target_url $scheme://$http_host$request_uri;
@ -619,7 +619,7 @@ proxy_set_header Remote-Name $name;
proxy_set_header Remote-Email $email;
## If the subreqest returns 200 pass to the backend, if the subrequest returns 401 redirect to the portal.
error_page 401 =302 /authelia-detect?rd=$target_url;
error_page 401 =302 /internal/authelia/authz/detect?rd=$target_url;
```
{{< /details >}}

View File

@ -16,16 +16,16 @@ aliases:
---
| Proxy | [Implementation] | [Standard](#standard) | [Kubernetes](#kubernetes) | [XHR Redirect](#xhr-redirect) | [Request Method](#request-method) |
|:---------------------:|:----------------:|:------------------------------------------------------------------:|:-------------------------------------------------------------------------------------:|:---------------------------------:|:---------------------------------:|
| [Traefik] | [ForwardAuth] | {{% support support="full" link="traefik.md" %}} | {{% support support="full" link="../../integration/kubernetes/traefik-ingress.md" %}} | {{% support support="full" %}} | {{% support support="full" %}} |
| [Caddy] | [ForwardAuth] | {{% support support="full" link="caddy.md" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} | {{% support support="full" %}} |
| [Envoy] | [ExtAuthz] | {{% support support="full" link="envoy.md" %}} | {{% support support="full" link="../../integration/kubernetes/istio.md" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} |
| [NGINX] | [AuthRequest] | {{% support support="full" link="nginx.md" %}} | {{% support support="full" link="../../integration/kubernetes/nginx-ingress.md" %}} | {{% support %}} | {{% support support="full" %}} |
| [NGINX Proxy Manager] | [AuthRequest] | {{% support support="full" link="nginx-proxy-manager/index.md" %}} | {{% support support="unknown" %}} | {{% support %}} | {{% support support="full" %}} |
| [SWAG] | [AuthRequest] | {{% support support="full" link="swag.md" %}} | {{% support support="unknown" %}} | {{% support %}} | {{% support support="full" %}} |
| [HAProxy] | [AuthRequest] | {{% support support="full" link="haproxy.md" %}} | {{% support support="unknown" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} |
| [Skipper] | [ForwardAuth] | {{% support support="full" link="skipper.md" %}} | {{% support support="unknown" %}} | {{% support support="unknown" %}} | {{% support support="unknown" %}} |
| [Traefik] 1.x | [ForwardAuth] | {{% support support="full" link="traefikv1.md" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} | {{% support support="full" %}} |
|:---------------------------------------:|:----------------:|:---------------------------------------------------:|:-------------------------------------------------------------------------------------:|:---------------------------------:|:---------------------------------:|
| [Traefik] ([guide](/i/traefik)) | [ForwardAuth] | {{% support support="full" link="/i/traefik" %}} | {{% support support="full" link="../../integration/kubernetes/traefik-ingress.md" %}} | {{% support support="full" %}} | {{% support support="full" %}} |
| [Caddy] ([guide](/i/caddy)) | [ForwardAuth] | {{% support support="full" link="/i/caddy" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} | {{% support support="full" %}} |
| [Envoy] ([guide](/i/envoy)) | [ExtAuthz] | {{% support support="full" link="/i/envoy" %}} | {{% support support="full" link="../../integration/kubernetes/istio.md" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} |
| [NGINX] ([guide](/i/nginx)) | [AuthRequest] | {{% support support="full" link="/i/nginx" %}} | {{% support support="full" link="../../integration/kubernetes/nginx-ingress.md" %}} | {{% support %}} | {{% support support="full" %}} |
| [NGINX Proxy Manager] ([guide](/i/npm)) | [AuthRequest] | {{% support support="full" link="/i/npm" %}} | {{% support support="unknown" %}} | {{% support %}} | {{% support support="full" %}} |
| [SWAG] ([guide](/i/swag)) | [AuthRequest] | {{% support support="full" link="/i/swag" %}} | {{% support support="unknown" %}} | {{% support %}} | {{% support support="full" %}} |
| [HAProxy] ([guide](/i/haproxy)) | [AuthRequest] | {{% support support="full" link="/i/haproxy" %}} | {{% support support="unknown" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} |
| [Skipper] ([guide](/i/skipper)) | [ForwardAuth] | {{% support support="full" link="/i/skipper" %}} | {{% support support="unknown" %}} | {{% support support="unknown" %}} | {{% support support="unknown" %}} |
| [Traefik] 1.x ([guide](/i/traefik/v1)) | [ForwardAuth] | {{% support support="full" link="/i/traefik/v1" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} | {{% support support="full" %}} |
| [Apache] | N/A | {{% support link="#apache" %}} | {{% support %}} | {{% support %}} | {{% support %}} |
| [IIS] | N/A | {{% support link="#iis" %}} | {{% support %}} | {{% support %}} | {{% support %}} |

View File

@ -57,7 +57,7 @@ In addition this represents a bad user experience in some instances such as:
- Users sometimes visit the `https://app.example.com/authelia` URL which doesn't automatically redirect the user to
`https://app.example.com` (if they visit `https://app.example.com` then they'll be redirected to authenticate then
redirected back to their original URL).
redirected back to their original URL)
- Administrators may wish to setup [OpenID Connect 1.0](../../configuration/identity-providers/open-id-connect.md) in
which case it also doesn't represent a good user experience as the `issuer` will be
`https://app.example.com/authelia` for example
@ -147,8 +147,8 @@ services:
- '443:443'
volumes:
- ${PWD}/data/swag:/config
## Uncomment the line below if you want to use the Authelia configuration snippets.
#- ${PWD}/data/nginx/snippets:/snippets:ro
## Uncomment the above line if you want to use the Authelia configuration snippets.
environment:
PUID: '1000'
PGID: '1000'

View File

@ -11,6 +11,7 @@ menu:
weight: 371
toc: true
aliases:
- /i/traefik/v1
- /docs/deployment/supported-proxies/traefik1.x.html
---

View File

@ -15,16 +15,16 @@ toc: false
The following table is a support matrix for Authelia features and specific reverse proxies.
| Proxy | Standard | Kubernetes | XHR Redirect | Request Method |
|:---------------------:|:--------------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------:|:---------------------------------:|:---------------------------------:|
| [Traefik] | {{% support support="full" link="../../integration/proxies/traefik.md" %}} | {{% support support="full" link="../../integration/kubernetes/traefik-ingress.md" %}} | {{% support support="full" %}} | {{% support support="full" %}} |
| [Caddy] | {{% support support="full" link="../../integration/proxies/caddy.md" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} | {{% support support="full" %}} |
| [Envoy] | {{% support support="full" link="../../integration/proxies/envoy.md" %}} | {{% support support="full" link="../../integration/kubernetes/istio.md" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} |
| [NGINX] | {{% support support="full" link="../../integration/proxies/nginx.md" %}} | {{% support support="full" link="../../integration/kubernetes/nginx-ingress.md" %}} | {{% support %}} | {{% support support="full" %}} |
| [NGINX Proxy Manager] | {{% support support="full" link="../../integration/proxies/nginx-proxy-manager/index.md" %}} | {{% support %}} | {{% support %}} | {{% support support="full" %}} |
| [SWAG] | {{% support support="full" link="../../integration/proxies/swag.md" %}} | {{% support %}} | {{% support %}} | {{% support support="full" %}} |
| [HAProxy] | {{% support support="full" link="../../integration/proxies/haproxy.md" %}} | {{% support support="unknown" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} |
| [Traefik] 1.x | {{% support support="full" link="../../integration/proxies/traefikv1.md" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} | {{% support support="full" %}} |
| [Skipper] | {{% support support="full" link="../../integration/proxies/skipper.md" %}} | {{% support %}} | {{% support support="unknown" %}} | {{% support support="unknown" %}} |
|:---------------------------------------:|:-------------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------:|:---------------------------------:|:---------------------------------:|
| [Traefik] ([guide](/i/traefik)) | {{% support support="full" link="/i/traefik" %}} | {{% support support="full" link="../../integration/kubernetes/traefik-ingress.md" %}} | {{% support support="full" %}} | {{% support support="full" %}} |
| [Caddy] ([guide](/i/caddy)) | {{% support support="full" link="/i/caddy" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} | {{% support support="full" %}} |
| [Envoy] ([guide](/i/envoy)) | {{% support support="full" link="/i/envoy" %}} | {{% support support="full" link="../../integration/kubernetes/istio.md" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} |
| [NGINX] ([guide](/i/nginx)) | {{% support support="full" link="/i/nginx" %}} | {{% support support="full" link="../../integration/kubernetes/nginx-ingress.md" %}} | {{% support %}} | {{% support support="full" %}} |
| [NGINX Proxy Manager] ([guide](/i/npm)) | {{% support support="full" link="/i/npm" %}} | {{% support support="unknown" %}} | {{% support %}} | {{% support support="full" %}} |
| [SWAG] ([guide](/i/swag)) | {{% support support="full" link="/i/swag" %}} | {{% support support="unknown" %}} | {{% support %}} | {{% support support="full" %}} |
| [HAProxy] ([guide](/i/haproxy)) | {{% support support="full" link="/i/haproxy" %}} | {{% support support="unknown" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} |
| [Skipper] ([guide](/i/skipper)) | {{% support support="full" link="/i/skipper" %}} | {{% support support="unknown" %}} | {{% support support="unknown" %}} | {{% support support="unknown" %}} |
| [Traefik] 1.x ([guide](/i/traefik/v1)) | {{% support support="full" link="/i/traefik/v1" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} | {{% support support="full" %}} |
| [Apache] | {{% support %}} | {{% support %}} | {{% support %}} | {{% support %}} |
| [IIS] | {{% support %}} | {{% support %}} | {{% support %}} | {{% support %}} |

View File

@ -0,0 +1,17 @@
---
title: "Frequently Asked Questions"
description: "This guide shows a list of other frequently asked question documents as well as some general ones"
lead: "This guide shows a list of other frequently asked question documents as well as some general ones."
date: 2023-03-06T11:04:10+11:00
draft: false
images: []
menu:
reference:
parent: "guides"
weight: 220
toc: true
---
## Identity Providers
- [OpenID Connect 1.0 Integration](../../integration/openid-connect/specific-information.md#frequently-asked-questions)

File diff suppressed because one or more lines are too long

View File

@ -38,8 +38,8 @@
"version": "auto-changelog -p && git add CHANGELOG.md"
},
"devDependencies": {
"@babel/cli": "7.20.7",
"@babel/core": "7.20.12",
"@babel/cli": "7.21.0",
"@babel/core": "7.21.0",
"@babel/preset-env": "7.20.2",
"@fullhuman/postcss-purgecss": "5.0.0",
"@hyas/images": "0.3.2",
@ -49,7 +49,7 @@
"bootstrap": "5.2.3",
"bootstrap-icons": "1.10.3",
"clipboard": "2.0.11",
"eslint": "8.32.0",
"eslint": "8.35.0",
"exec-bin": "1.0.0",
"flexsearch": "0.7.31",
"highlight.js": "11.7.0",
@ -68,6 +68,6 @@
"stylelint-config-standard-scss": "6.1.0"
},
"otherDependencies": {
"hugo": "0.110.0"
"hugo": "0.111.2"
}
}

File diff suppressed because it is too large Load Diff

16
go.mod
View File

@ -5,7 +5,7 @@ go 1.20
require (
github.com/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
github.com/deckarep/golang-set/v2 v2.1.0
github.com/deckarep/golang-set/v2 v2.2.0
github.com/duosecurity/duo_api_golang v0.0.0-20230203160531-b221c950c2b0
github.com/fasthttp/router v1.4.17
github.com/fasthttp/session/v2 v2.4.16
@ -33,7 +33,7 @@ require (
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
github.com/ory/fosite v0.44.0
github.com/ory/herodot v0.9.13
github.com/ory/x v0.0.542
github.com/ory/x v0.0.543
github.com/otiai10/copy v1.9.0
github.com/pkg/errors v0.9.1
github.com/pquerna/otp v1.4.0
@ -45,10 +45,10 @@ require (
github.com/trustelem/zxcvbn v1.0.1
github.com/valyala/fasthttp v1.44.0
github.com/wneessen/go-mail v0.3.8
golang.org/x/net v0.7.0
golang.org/x/net v0.8.0
golang.org/x/sync v0.1.0
golang.org/x/term v0.5.0
golang.org/x/text v0.7.0
golang.org/x/term v0.6.0
golang.org/x/text v0.8.0
gopkg.in/square/go-jose.v2 v2.6.0
gopkg.in/yaml.v3 v3.0.1
)
@ -115,10 +115,10 @@ require (
github.com/ysmood/gson v0.7.3 // indirect
github.com/ysmood/leakless v0.8.0 // indirect
golang.org/x/crypto v0.6.0 // indirect
golang.org/x/mod v0.7.0 // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/tools v0.4.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/tools v0.6.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71 // indirect
google.golang.org/grpc v1.50.1 // indirect

30
go.sum
View File

@ -96,8 +96,8 @@ github.com/dave/jennifer v1.6.0/go.mod h1:AxTG893FiZKqxy3FP1kL80VMshSMuz2G+Egvsz
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI=
github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
github.com/deckarep/golang-set/v2 v2.2.0 h1:2pMQd3Soi6qfw7E5MMKaEh5W5ES18bW3AbFFnGl6LgQ=
github.com/deckarep/golang-set/v2 v2.2.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
github.com/dgraph-io/ristretto v0.0.1/go.mod h1:T40EBc7CJke8TkpiYfGGKAeFjSaxuFXhuXRyumBd6RE=
github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
@ -387,8 +387,8 @@ github.com/ory/go-convenience v0.1.0/go.mod h1:uEY/a60PL5c12nYz4V5cHY03IBmwIAEm8
github.com/ory/herodot v0.9.13 h1:cN/Z4eOkErl/9W7hDIDLb79IO/bfsH+8yscBjRpB4IU=
github.com/ory/herodot v0.9.13/go.mod h1:IWDs9kSvFQqw/cQ8zi5ksyYvITiUU4dI7glUrhZcJYo=
github.com/ory/viper v1.7.5/go.mod h1:ypOuyJmEUb3oENywQZRgeAMwqgOyDqwboO1tj3DjTaM=
github.com/ory/x v0.0.542 h1:3moNM1xRT3GYUSoNet4ZF8bFWE5jY/G526mR9lQVmA0=
github.com/ory/x v0.0.542/go.mod h1:ktXUvx51Ok1gMGr3ysvktanqr+eiB4FXglt4nF4w2Uo=
github.com/ory/x v0.0.543 h1:I6bl6IV2Ok07io6M2dnaRaJHP5oRU096T9FYoe8m48U=
github.com/ory/x v0.0.543/go.mod h1:ktXUvx51Ok1gMGr3ysvktanqr+eiB4FXglt4nF4w2Uo=
github.com/otiai10/copy v1.9.0 h1:7KFNiCgZ91Ru4qW4CWPf/7jqtxLagGRmIxWldPP9VY4=
github.com/otiai10/copy v1.9.0/go.mod h1:hsfX19wcn0UWIHUQ3/4fHuehhk2UyArQ9dVFAn3FczI=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
@ -608,8 +608,9 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -657,8 +658,8 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -751,13 +752,13 @@ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -768,8 +769,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -827,8 +828,9 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4=
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -29,10 +29,17 @@ type OpenIDConnectConfiguration struct {
EnablePKCEPlainChallenge bool `koanf:"enable_pkce_plain_challenge"`
CORS OpenIDConnectCORSConfiguration `koanf:"cors"`
PAR OpenIDConnectPARConfiguration `koanf:"pushed_authorizations"`
Clients []OpenIDConnectClientConfiguration `koanf:"clients"`
}
// OpenIDConnectPARConfiguration represents an OpenID Connect PAR config.
type OpenIDConnectPARConfiguration struct {
Enforce bool `koanf:"enforce"`
ContextLifespan time.Duration `koanf:"context_lifespan"`
}
// OpenIDConnectCORSConfiguration represents an OpenID Connect CORS config.
type OpenIDConnectCORSConfiguration struct {
Endpoints []string `koanf:"endpoints"`
@ -59,6 +66,7 @@ type OpenIDConnectClientConfiguration struct {
Policy string `koanf:"authorization_policy"`
EnforcePAR bool `koanf:"enforce_par"`
EnforcePKCE bool `koanf:"enforce_pkce"`
PKCEChallengeMethod string `koanf:"pkce_challenge_method"`

View File

@ -30,6 +30,8 @@ var Keys = []string{
"identity_providers.oidc.cors.endpoints",
"identity_providers.oidc.cors.allowed_origins",
"identity_providers.oidc.cors.allowed_origins_from_client_redirect_uris",
"identity_providers.oidc.pushed_authorizations.enforce",
"identity_providers.oidc.pushed_authorizations.context_lifespan",
"identity_providers.oidc.clients",
"identity_providers.oidc.clients[].id",
"identity_providers.oidc.clients[].description",
@ -43,6 +45,7 @@ var Keys = []string{
"identity_providers.oidc.clients[].response_types",
"identity_providers.oidc.clients[].response_modes",
"identity_providers.oidc.clients[].authorization_policy",
"identity_providers.oidc.clients[].enforce_par",
"identity_providers.oidc.clients[].enforce_pkce",
"identity_providers.oidc.clients[].pkce_challenge_method",
"identity_providers.oidc.clients[].userinfo_signing_algorithm",

View File

@ -140,6 +140,20 @@ type PasswordDigest struct {
algorithm.Digest
}
// IsPlainText returns true if the underlying algorithm.Digest is a *plaintext.Digest.
func (d *PasswordDigest) IsPlainText() bool {
if d == nil || d.Digest == nil {
return false
}
switch d.Digest.(type) {
case *plaintext.Digest:
return true
default:
return false
}
}
// NewX509CertificateChain creates a new *X509CertificateChain from a given string, parsing each PEM block one by one.
func NewX509CertificateChain(in string) (chain *X509CertificateChain, err error) {
if in == "" {

View File

@ -168,7 +168,7 @@ func TestShouldNotRaiseErrorOnValidCertificatesDirectory(t *testing.T) {
require.Len(t, validator.Warnings(), 1)
assert.EqualError(t, validator.Warnings()[0], "access control: no rules have been specified so the 'default_policy' of 'two_factor' is going to be applied to all requests")
}
}.
*/
func TestValidateDefault2FAMethod(t *testing.T) {

View File

@ -160,6 +160,7 @@ const (
"an empty id"
errFmtOIDCClientInvalidSecret = "identity_providers: oidc: client '%s': option 'secret' is required"
errFmtOIDCClientInvalidSecretPlainText = "identity_providers: oidc: client '%s': option 'secret' is plaintext but it should be a hashed value as plaintext values are deprecated and will be removed when oidc becomes stable"
errFmtOIDCClientPublicInvalidSecret = "identity_providers: oidc: client '%s': option 'secret' is " +
"required to be empty when option 'public' is true"
errFmtOIDCClientRedirectURICantBeParsed = "identity_providers: oidc: client '%s': option 'redirect_uris' has an " +
@ -392,7 +393,7 @@ var (
validOIDCGrantTypes = []string{oidc.GrantTypeImplicit, oidc.GrantTypeRefreshToken, oidc.GrantTypeAuthorizationCode, oidc.GrantTypePassword, oidc.GrantTypeClientCredentials}
validOIDCResponseModes = []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment}
validOIDCUserinfoAlgorithms = []string{oidc.SigningAlgorithmNone, oidc.SigningAlgorithmRSAWithSHA256}
validOIDCCORSEndpoints = []string{oidc.EndpointAuthorization, oidc.EndpointToken, oidc.EndpointIntrospection, oidc.EndpointRevocation, oidc.EndpointUserinfo}
validOIDCCORSEndpoints = []string{oidc.EndpointAuthorization, oidc.EndpointPushedAuthorizationRequest, oidc.EndpointToken, oidc.EndpointIntrospection, oidc.EndpointRevocation, oidc.EndpointUserinfo}
validOIDCClientConsentModes = []string{"auto", oidc.ClientConsentModeImplicit.String(), oidc.ClientConsentModeExplicit.String(), oidc.ClientConsentModePreConfigured.String()}
)

View File

@ -166,6 +166,8 @@ func validateOIDCClients(config *schema.OpenIDConnectConfiguration, val *schema.
} else {
if client.Secret == nil {
val.Push(fmt.Errorf(errFmtOIDCClientInvalidSecret, client.ID))
} else if client.Secret.IsPlainText() {
val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidSecretPlainText, client.ID))
}
}

View File

@ -80,7 +80,7 @@ func TestShouldRaiseErrorWhenCORSEndpointsNotValid(t *testing.T) {
require.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: cors: option 'endpoints' contains an invalid value 'invalid_endpoint': must be one of 'authorization', 'token', 'introspection', 'revocation', 'userinfo'")
assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: cors: option 'endpoints' contains an invalid value 'invalid_endpoint': must be one of 'authorization', 'pushed-authorization-request', 'token', 'introspection', 'revocation', 'userinfo'")
}
func TestShouldRaiseErrorWhenOIDCPKCEEnforceValueInvalid(t *testing.T) {
@ -179,8 +179,8 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
},
},
Errors: []string{
fmt.Sprintf(errFmtOIDCClientInvalidSecret, ""),
errFmtOIDCClientsWithEmptyID,
"identity_providers: oidc: client '': option 'secret' is required",
"identity_providers: oidc: one or more clients have been configured with an empty id",
},
},
{
@ -195,7 +195,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
},
},
},
Errors: []string{fmt.Sprintf(errFmtOIDCClientInvalidPolicy, "client-1", "a-policy")},
Errors: []string{"identity_providers: oidc: client 'client-1': option 'policy' must be 'one_factor' or 'two_factor' but it is configured as 'a-policy'"},
},
{
Name: "ClientIDDuplicated",
@ -427,7 +427,7 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadGrantTypes(t *testing.T)
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "good_id",
Secret: MustDecodeSecret("$plaintext$good_secret"),
Secret: MustDecodeSecret(goodOpenIDConnectClientSecret),
Policy: "two_factor",
GrantTypes: []string{"bad_grant_type"},
RedirectURIs: []string{
@ -454,7 +454,7 @@ func TestShouldNotErrorOnCertificateValid(t *testing.T) {
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "good_id",
Secret: MustDecodeSecret("$plaintext$good_secret"),
Secret: MustDecodeSecret(goodOpenIDConnectClientSecret),
Policy: "two_factor",
RedirectURIs: []string{
"https://google.com/callback",
@ -480,7 +480,7 @@ func TestShouldRaiseErrorOnCertificateNotValid(t *testing.T) {
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "good_id",
Secret: MustDecodeSecret("$plaintext$good_secret"),
Secret: MustDecodeSecret(goodOpenIDConnectClientSecret),
Policy: "two_factor",
RedirectURIs: []string{
"https://google.com/callback",
@ -507,7 +507,7 @@ func TestShouldRaiseErrorOnKeySizeTooSmall(t *testing.T) {
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "good_id",
Secret: MustDecodeSecret("$plaintext$good_secret"),
Secret: MustDecodeSecret(goodOpenIDConnectClientSecret),
Policy: "two_factor",
RedirectURIs: []string{
"https://google.com/callback",
@ -587,7 +587,7 @@ func TestValidateIdentityProvidersShouldRaiseWarningOnSecurityIssue(t *testing.T
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "good_id",
Secret: MustDecodeSecret("$plaintext$good_secret"),
Secret: MustDecodeSecret(goodOpenIDConnectClientSecret),
Policy: "two_factor",
RedirectURIs: []string{
"https://google.com/callback",
@ -623,7 +623,7 @@ func TestValidateIdentityProvidersShouldRaiseErrorsOnInvalidClientTypes(t *testi
},
{
ID: "client-with-bad-redirect-uri",
Secret: MustDecodeSecret("$plaintext$a-secret"),
Secret: MustDecodeSecret(goodOpenIDConnectClientSecret),
Public: false,
Policy: "two_factor",
RedirectURIs: []string{
@ -702,6 +702,33 @@ func TestValidateIdentityProvidersShouldNotRaiseErrorsOnValidClientOptions(t *te
assert.Len(t, validator.Warnings(), 0)
}
func TestValidateIdentityProvidersShouldRaiseWarningOnPlainTextClients(t *testing.T) {
validator := schema.NewStructValidator()
config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "hmac1",
IssuerPrivateKey: MustParseRSAPrivateKey(testKey1),
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "client-with-invalid-secret_standard",
Secret: MustDecodeSecret("$plaintext$a-secret"),
Policy: "two_factor",
RedirectURIs: []string{
"https://localhost",
},
},
},
},
}
ValidateIdentityProviders(config, validator)
assert.Len(t, validator.Errors(), 0)
require.Len(t, validator.Warnings(), 1)
assert.EqualError(t, validator.Warnings()[0], "identity_providers: oidc: client 'client-with-invalid-secret_standard': option 'secret' is plaintext but it should be a hashed value as plaintext values are deprecated and will be removed when oidc becomes stable")
}
func TestValidateIdentityProvidersShouldSetDefaultValues(t *testing.T) {
timeDay := time.Hour * 24
@ -713,7 +740,7 @@ func TestValidateIdentityProvidersShouldSetDefaultValues(t *testing.T) {
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "a-client",
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
Secret: MustDecodeSecret(goodOpenIDConnectClientSecret),
RedirectURIs: []string{
"https://google.com",
},
@ -722,7 +749,7 @@ func TestValidateIdentityProvidersShouldSetDefaultValues(t *testing.T) {
{
ID: "b-client",
Description: "Normal Description",
Secret: MustDecodeSecret("$plaintext$b-client-secret"),
Secret: MustDecodeSecret(goodOpenIDConnectClientSecret),
Policy: policyOneFactor,
UserinfoSigningAlgorithm: "RS256",
RedirectURIs: []string{
@ -745,7 +772,7 @@ func TestValidateIdentityProvidersShouldSetDefaultValues(t *testing.T) {
},
{
ID: "c-client",
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
Secret: MustDecodeSecret(goodOpenIDConnectClientSecret),
RedirectURIs: []string{
"https://google.com",
},
@ -753,7 +780,7 @@ func TestValidateIdentityProvidersShouldSetDefaultValues(t *testing.T) {
},
{
ID: "d-client",
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
Secret: MustDecodeSecret(goodOpenIDConnectClientSecret),
RedirectURIs: []string{
"https://google.com",
},
@ -761,7 +788,7 @@ func TestValidateIdentityProvidersShouldSetDefaultValues(t *testing.T) {
},
{
ID: "e-client",
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
Secret: MustDecodeSecret(goodOpenIDConnectClientSecret),
RedirectURIs: []string{
"https://google.com",
},
@ -1019,4 +1046,6 @@ AQmB98tdGLggbyXiODV2h+Rd37aFGb0QHzerIIsVNtMwlPCcp733D4kWJqTUYWZ+
KBL3XEahgs6Os5EYZ4aBAkEAjKE+2/nBYUdHVusjMXeNsE5rqwJND5zvYzmToG7+
xhv4RUAe4dHL4IDQoQRjhr3Nw+JYvtzBx0Iq/178xMnGKg==
-----END RSA PRIVATE KEY-----`
goodOpenIDConnectClientSecret = "$pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng" //nolint:gosec
)

View File

@ -53,10 +53,20 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr
return
}
if err = client.ValidateAuthorizationPolicy(requester); err != nil {
if err = client.ValidatePARPolicy(requester, ctx.Providers.OpenIDConnect.GetPushedAuthorizeRequestURIPrefix(ctx)); err != nil {
rfc := fosite.ErrorToRFC6749Error(err)
ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' failed to validate the authorization policy: %s", requester.GetID(), clientID, rfc.WithExposeDebug(true).GetDescription())
ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' failed to validate the PAR policy: %s", requester.GetID(), clientID, rfc.WithExposeDebug(true).GetDescription())
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, err)
return
}
if err = client.ValidatePKCEPolicy(requester); err != nil {
rfc := fosite.ErrorToRFC6749Error(err)
ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' failed to validate the PKCE policy: %s", requester.GetID(), clientID, rfc.WithExposeDebug(true).GetDescription())
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, err)
@ -95,13 +105,13 @@ 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)
oidcSession := oidc.NewSessionWithAuthorizeRequest(issuer, ctx.Providers.OpenIDConnect.KeyManager.GetActiveKeyID(),
session := oidc.NewSessionWithAuthorizeRequest(issuer, ctx.Providers.OpenIDConnect.KeyManager.GetActiveKeyID(),
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(), oidcSession.ClientID, oidcSession.Subject, oidcSession.Username, oidcSession.Claims)
requester.GetID(), session.ClientID, session.Subject, session.Username, session.Claims)
if responder, err = ctx.Providers.OpenIDConnect.NewAuthorizeResponse(ctx, requester, oidcSession); err != nil {
if responder, err = ctx.Providers.OpenIDConnect.NewAuthorizeResponse(ctx, requester, session); err != nil {
rfc := fosite.ErrorToRFC6749Error(err)
ctx.Logger.Errorf("Authorization Response for Request with id '%s' on client with id '%s' could not be created: %s", requester.GetID(), clientID, rfc.WithExposeDebug(true).GetDescription())
@ -125,3 +135,62 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr
ctx.Providers.OpenIDConnect.WriteAuthorizeResponse(ctx, rw, requester, responder)
}
// OpenIDConnectPushedAuthorizationRequest handles POST requests to the OAuth 2.0 Pushed Authorization Requests endpoint.
//
// RFC9126 https://www.rfc-editor.org/rfc/rfc9126.html
func OpenIDConnectPushedAuthorizationRequest(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, r *http.Request) {
var (
requester fosite.AuthorizeRequester
responder fosite.PushedAuthorizeResponder
err error
)
if requester, err = ctx.Providers.OpenIDConnect.NewPushedAuthorizeRequest(ctx, r); err != nil {
rfc := fosite.ErrorToRFC6749Error(err)
ctx.Logger.Errorf("Pushed Authorization Request failed with error: %s", rfc.WithExposeDebug(true).GetDescription())
ctx.Providers.OpenIDConnect.WritePushedAuthorizeError(ctx, rw, requester, err)
return
}
var client *oidc.Client
clientID := requester.GetClient().GetID()
if client, err = ctx.Providers.OpenIDConnect.GetFullClient(clientID); err != nil {
if errors.Is(err, fosite.ErrNotFound) {
ctx.Logger.Errorf("Pushed Authorization Request with id '%s' on client with id '%s' could not be processed: client was not found", requester.GetID(), clientID)
} else {
ctx.Logger.Errorf("Pushed Authorization Request with id '%s' on client with id '%s' could not be processed: failed to find client: %+v", requester.GetID(), clientID, err)
}
ctx.Providers.OpenIDConnect.WritePushedAuthorizeError(ctx, rw, requester, err)
return
}
if err = client.ValidatePKCEPolicy(requester); err != nil {
rfc := fosite.ErrorToRFC6749Error(err)
ctx.Logger.Errorf("Pushed Authorization Request with id '%s' on client with id '%s' failed to validate the PKCE policy: %s", requester.GetID(), clientID, rfc.WithExposeDebug(true).GetDescription())
ctx.Providers.OpenIDConnect.WritePushedAuthorizeError(ctx, rw, requester, err)
return
}
if responder, err = ctx.Providers.OpenIDConnect.NewPushedAuthorizeResponse(ctx, requester, oidc.NewSession()); err != nil {
rfc := fosite.ErrorToRFC6749Error(err)
ctx.Logger.Errorf("Pushed Authorization Request failed with error: %s", rfc.WithExposeDebug(true).GetDescription())
ctx.Providers.OpenIDConnect.WritePushedAuthorizeError(ctx, rw, requester, err)
return
}
ctx.Providers.OpenIDConnect.WritePushedAuthorizeResponse(ctx, rw, requester, responder)
}

View File

@ -9,9 +9,8 @@ import (
mail "net/mail"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
templates "github.com/authelia/authelia/v4/internal/templates"
gomock "github.com/golang/mock/gomock"
)
// MockNotifier is a mock of Notifier interface.

View File

@ -10,11 +10,10 @@ import (
reflect "reflect"
time "time"
gomock "github.com/golang/mock/gomock"
uuid "github.com/google/uuid"
model "github.com/authelia/authelia/v4/internal/model"
storage "github.com/authelia/authelia/v4/internal/storage"
gomock "github.com/golang/mock/gomock"
uuid "github.com/google/uuid"
)
// MockStorage is a mock of Provider interface.
@ -40,6 +39,7 @@ func (m *MockStorage) EXPECT() *MockStorageMockRecorder {
return m.recorder
}
// AppendAuthenticationLog mocks base method.
func (m *MockStorage) AppendAuthenticationLog(arg0 context.Context, arg1 model.AuthenticationAttempt) error {
m.ctrl.T.Helper()
@ -270,6 +270,21 @@ func (mr *MockStorageMockRecorder) LoadOAuth2ConsentSessionByChallengeID(arg0, a
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadOAuth2ConsentSessionByChallengeID", reflect.TypeOf((*MockStorage)(nil).LoadOAuth2ConsentSessionByChallengeID), arg0, arg1)
}
// LoadOAuth2PARContext mocks base method.
func (m *MockStorage) LoadOAuth2PARContext(arg0 context.Context, arg1 string) (*model.OAuth2PARContext, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "LoadOAuth2PARContext", arg0, arg1)
ret0, _ := ret[0].(*model.OAuth2PARContext)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// LoadOAuth2PARContext indicates an expected call of LoadOAuth2PARContext.
func (mr *MockStorageMockRecorder) LoadOAuth2PARContext(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadOAuth2PARContext", reflect.TypeOf((*MockStorage)(nil).LoadOAuth2PARContext), arg0, arg1)
}
// LoadOAuth2Session mocks base method.
func (m *MockStorage) LoadOAuth2Session(arg0 context.Context, arg1 storage.OAuth2SessionType, arg2 string) (*model.OAuth2Session, error) {
m.ctrl.T.Helper()
@ -435,6 +450,20 @@ func (mr *MockStorageMockRecorder) LoadWebauthnDevicesByUsername(arg0, arg1 inte
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadWebauthnDevicesByUsername", reflect.TypeOf((*MockStorage)(nil).LoadWebauthnDevicesByUsername), arg0, arg1)
}
// RevokeOAuth2PARContext mocks base method.
func (m *MockStorage) RevokeOAuth2PARContext(arg0 context.Context, arg1 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RevokeOAuth2PARContext", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// RevokeOAuth2PARContext indicates an expected call of RevokeOAuth2PARContext.
func (mr *MockStorageMockRecorder) RevokeOAuth2PARContext(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RevokeOAuth2PARContext", reflect.TypeOf((*MockStorage)(nil).RevokeOAuth2PARContext), arg0, arg1)
}
// RevokeOAuth2Session mocks base method.
func (m *MockStorage) RevokeOAuth2Session(arg0 context.Context, arg1 storage.OAuth2SessionType, arg2 string) error {
m.ctrl.T.Helper()
@ -576,6 +605,20 @@ func (mr *MockStorageMockRecorder) SaveOAuth2ConsentSessionSubject(arg0, arg1 in
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveOAuth2ConsentSessionSubject", reflect.TypeOf((*MockStorage)(nil).SaveOAuth2ConsentSessionSubject), arg0, arg1)
}
// SaveOAuth2PARContext mocks base method.
func (m *MockStorage) SaveOAuth2PARContext(arg0 context.Context, arg1 model.OAuth2PARContext) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SaveOAuth2PARContext", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// SaveOAuth2PARContext indicates an expected call of SaveOAuth2PARContext.
func (mr *MockStorageMockRecorder) SaveOAuth2PARContext(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveOAuth2PARContext", reflect.TypeOf((*MockStorage)(nil).SaveOAuth2PARContext), arg0, arg1)
}
// SaveOAuth2Session mocks base method.
func (m *MockStorage) SaveOAuth2Session(arg0 context.Context, arg1 storage.OAuth2SessionType, arg2 model.OAuth2Session) error {
m.ctrl.T.Helper()

View File

@ -7,9 +7,8 @@ package mocks
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
model "github.com/authelia/authelia/v4/internal/model"
gomock "github.com/golang/mock/gomock"
)
// MockTOTP is a mock of Provider interface.

View File

@ -7,9 +7,8 @@ package mocks
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
authentication "github.com/authelia/authelia/v4/internal/authentication"
gomock "github.com/golang/mock/gomock"
)
// MockUserProvider is a mock of UserProvider interface.

View File

@ -39,6 +39,14 @@ func NewOAuth2ConsentSession(subject uuid.UUID, r fosite.Requester) (consent *OA
return consent, nil
}
// NewOAuth2BlacklistedJTI creates a new OAuth2BlacklistedJTI.
func NewOAuth2BlacklistedJTI(jti string, exp time.Time) (jtiBlacklist OAuth2BlacklistedJTI) {
return OAuth2BlacklistedJTI{
Signature: fmt.Sprintf("%x", sha256.Sum256([]byte(jti))),
ExpiresAt: exp,
}
}
// NewOAuth2SessionFromRequest creates a new OAuth2Session from a signature and fosite.Requester.
func NewOAuth2SessionFromRequest(signature string, r fosite.Requester) (session *OAuth2Session, err error) {
var (
@ -77,12 +85,43 @@ func NewOAuth2SessionFromRequest(signature string, r fosite.Requester) (session
}, nil
}
// NewOAuth2BlacklistedJTI creates a new OAuth2BlacklistedJTI.
func NewOAuth2BlacklistedJTI(jti string, exp time.Time) (jtiBlacklist OAuth2BlacklistedJTI) {
return OAuth2BlacklistedJTI{
Signature: fmt.Sprintf("%x", sha256.Sum256([]byte(jti))),
ExpiresAt: exp,
// NewOAuth2PARContext creates a new Pushed Authorization Request Context as a OAuth2PARContext.
func NewOAuth2PARContext(contextID string, r fosite.AuthorizeRequester) (context *OAuth2PARContext, err error) {
var (
s *OpenIDSession
ok bool
req *fosite.AuthorizeRequest
session []byte
)
if s, ok = r.GetSession().(*OpenIDSession); !ok {
return nil, fmt.Errorf("can't convert type '%T' to an *OAuth2Session", r.GetSession())
}
if session, err = json.Marshal(s); err != nil {
return nil, err
}
var handled StringSlicePipeDelimited
if req, ok = r.(*fosite.AuthorizeRequest); ok {
handled = StringSlicePipeDelimited(req.HandledResponseTypes)
}
return &OAuth2PARContext{
Signature: contextID,
RequestID: r.GetID(),
ClientID: r.GetClient().GetID(),
RequestedAt: r.GetRequestedAt(),
Scopes: StringSlicePipeDelimited(r.GetRequestedScopes()),
Audience: StringSlicePipeDelimited(r.GetRequestedAudience()),
HandledResponseTypes: handled,
ResponseMode: string(r.GetResponseMode()),
DefaultResponseMode: string(r.GetDefaultResponseMode()),
Revoked: false,
Form: r.GetRequestForm().Encode(),
Session: session,
}, nil
}
// OAuth2ConsentPreConfig stores information about an OAuth2.0 Pre-Configured Consent.
@ -264,6 +303,70 @@ func (s *OAuth2Session) ToRequest(ctx context.Context, session fosite.Session, s
}, nil
}
// OAuth2PARContext holds relevant information about a Pushed Authorization Request in order to process the authorization.
type OAuth2PARContext struct {
ID int `db:"id"`
Signature string `db:"signature"`
RequestID string `db:"request_id"`
ClientID string `db:"client_id"`
RequestedAt time.Time `db:"requested_at"`
Scopes StringSlicePipeDelimited `db:"scopes"`
Audience StringSlicePipeDelimited `db:"audience"`
HandledResponseTypes StringSlicePipeDelimited `db:"handled_response_types"`
ResponseMode string `db:"response_mode"`
DefaultResponseMode string `db:"response_mode_default"`
Revoked bool `db:"revoked"`
Form string `db:"form_data"`
Session []byte `db:"session_data"`
}
func (par *OAuth2PARContext) ToAuthorizeRequest(ctx context.Context, session fosite.Session, store fosite.Storage) (request *fosite.AuthorizeRequest, err error) {
if session != nil {
if err = json.Unmarshal(par.Session, session); err != nil {
return nil, err
}
}
var (
client fosite.Client
form url.Values
)
if client, err = store.GetClient(ctx, par.ClientID); err != nil {
return nil, err
}
if form, err = url.ParseQuery(par.Form); err != nil {
return nil, err
}
request = fosite.NewAuthorizeRequest()
request.Request = fosite.Request{
ID: par.RequestID,
RequestedAt: par.RequestedAt,
Client: client,
RequestedScope: fosite.Arguments(par.Scopes),
RequestedAudience: fosite.Arguments(par.Audience),
Form: form,
Session: session,
}
if par.ResponseMode != "" {
request.ResponseMode = fosite.ResponseModeType(par.ResponseMode)
}
if par.DefaultResponseMode != "" {
request.DefaultResponseMode = fosite.ResponseModeType(par.DefaultResponseMode)
}
if len(par.HandledResponseTypes) != 0 {
request.HandledResponseTypes = fosite.Arguments(par.HandledResponseTypes)
}
return request, nil
}
// OpenIDSession holds OIDC Session information.
type OpenIDSession struct {
*openid.DefaultSession `json:"id_token"`

View File

@ -1,7 +1,7 @@
package oidc
import (
"fmt"
"strings"
"github.com/ory/fosite"
"github.com/ory/x/errorsx"
@ -32,6 +32,8 @@ func NewClient(config schema.OpenIDConnectClientConfiguration) (client *Client)
ResponseTypes: config.ResponseTypes,
ResponseModes: []fosite.ResponseModeType{fosite.ResponseModeDefault},
EnforcePAR: config.EnforcePAR,
UserinfoSigningAlgorithm: config.UserinfoSigningAlgorithm,
Policy: authorization.NewLevel(config.Policy),
@ -46,22 +48,22 @@ func NewClient(config schema.OpenIDConnectClientConfiguration) (client *Client)
return client
}
// ValidateAuthorizationPolicy is a helper function to validate additional policy constraints on a per-client basis.
func (c *Client) ValidateAuthorizationPolicy(r fosite.Requester) (err error) {
// ValidatePKCEPolicy is a helper function to validate PKCE policy constraints on a per-client basis.
func (c *Client) ValidatePKCEPolicy(r fosite.Requester) (err error) {
form := r.GetRequestForm()
if c.EnforcePKCE {
if form.Get("code_challenge") == "" {
if form.Get(FormParameterCodeChallenge) == "" {
return errorsx.WithStack(fosite.ErrInvalidRequest.
WithHint("Clients must include a code_challenge when performing the authorize code flow, but it is missing.").
WithDebug("The server is configured in a way that enforces PKCE for this client."))
}
if c.EnforcePKCEChallengeMethod {
if method := form.Get("code_challenge_method"); method != c.PKCEChallengeMethod {
if method := form.Get(FormParameterCodeChallengeMethod); method != c.PKCEChallengeMethod {
return errorsx.WithStack(fosite.ErrInvalidRequest.
WithHint(fmt.Sprintf("Client must use code_challenge_method=%s, %s is not allowed.", c.PKCEChallengeMethod, method)).
WithDebug(fmt.Sprintf("The server is configured in a way that enforces PKCE %s as challenge method for this client.", c.PKCEChallengeMethod)))
WithHintf("Client must use code_challenge_method=%s, %s is not allowed.", c.PKCEChallengeMethod, method).
WithDebugf("The server is configured in a way that enforces PKCE %s as challenge method for this client.", c.PKCEChallengeMethod))
}
}
}
@ -69,6 +71,23 @@ func (c *Client) ValidateAuthorizationPolicy(r fosite.Requester) (err error) {
return nil
}
// ValidatePARPolicy is a helper function to validate additional policy constraints on a per-client basis.
func (c *Client) ValidatePARPolicy(r fosite.Requester, prefix string) (err error) {
form := r.GetRequestForm()
if c.EnforcePAR {
if requestURI := form.Get(FormParameterRequestURI); !strings.HasPrefix(requestURI, prefix) {
if requestURI == "" {
return errorsx.WithStack(ErrPAREnforcedClientMissingPAR.WithDebug("The request_uri parameter was empty."))
}
return errorsx.WithStack(ErrPAREnforcedClientMissingPAR.WithDebugf("The request_uri parameter '%s' is malformed.", requestURI))
}
}
return nil
}
// IsAuthenticationLevelSufficient returns if the provided authentication.Level is sufficient for the client of the AutheliaClient.
func (c *Client) IsAuthenticationLevelSufficient(level authentication.Level) bool {
if level == authentication.NotAuthenticated {
@ -105,7 +124,7 @@ func (c *Client) GetID() string {
}
// GetHashedSecret returns the Secret.
func (c *Client) GetHashedSecret() []byte {
func (c *Client) GetHashedSecret() (secret []byte) {
if c.Secret == nil {
return []byte(nil)
}
@ -114,7 +133,7 @@ func (c *Client) GetHashedSecret() []byte {
}
// GetRedirectURIs returns the RedirectURIs.
func (c *Client) GetRedirectURIs() []string {
func (c *Client) GetRedirectURIs() (redirectURIs []string) {
return c.RedirectURIs
}

View File

@ -224,7 +224,7 @@ func TestNewClientPKCE(t *testing.T) {
expectedEnforcePKCE bool
expectedEnforcePKCEChallengeMethod bool
expected string
req *fosite.Request
r *fosite.Request
err string
}{
{
@ -288,8 +288,8 @@ func TestNewClientPKCE(t *testing.T) {
assert.Equal(t, tc.expectedEnforcePKCEChallengeMethod, client.EnforcePKCEChallengeMethod)
assert.Equal(t, tc.expected, client.PKCEChallengeMethod)
if tc.req != nil {
err := client.ValidateAuthorizationPolicy(tc.req)
if tc.r != nil {
err := client.ValidatePKCEPolicy(tc.r)
if tc.err != "" {
assert.EqualError(t, err, tc.err)

View File

@ -6,6 +6,7 @@ import (
"hash"
"html/template"
"net/url"
"path"
"time"
"github.com/hashicorp/go-retryablehttp"
@ -23,8 +24,8 @@ import (
"github.com/authelia/authelia/v4/internal/utils"
)
func NewConfig(config *schema.OpenIDConnectConfiguration, templates *templates.Provider) *Config {
c := &Config{
func NewConfig(config *schema.OpenIDConnectConfiguration, templates *templates.Provider) (c *Config) {
c = &Config{
GlobalSecret: []byte(utils.HashSHA256FromString(config.HMACSecret)),
SendDebugMessagesToClients: config.EnableClientDebugMessages,
MinParameterEntropy: config.MinimumParameterEntropy,
@ -39,18 +40,23 @@ func NewConfig(config *schema.OpenIDConnectConfiguration, templates *templates.P
EnforcePublicClients: config.EnforcePKCE != "never",
AllowPlainChallengeMethod: config.EnablePKCEPlainChallenge,
},
PAR: PARConfig{
Enforced: config.PAR.Enforce,
ContextLifespan: config.PAR.ContextLifespan,
URIPrefix: urnPARPrefix,
},
Templates: templates,
}
c.Strategy.Core = &HMACCoreStrategy{
Enigma: &hmac.HMACStrategy{Config: c},
Config: c,
prefix: tokenPrefixFmt,
}
return c
}
// Config is an implementation of the fosite.Configurator.
type Config struct {
// GlobalSecret is the global secret used to sign and verify signatures.
GlobalSecret []byte
@ -67,7 +73,7 @@ type Config struct {
JWTScopeField jwt.JWTScopeFieldEnum
JWTMaxDuration time.Duration
Hasher *AdaptiveHasher
Hasher *Hasher
Hash HashConfig
Strategy StrategyConfig
PAR PARConfig
@ -91,11 +97,13 @@ type Config struct {
Templates *templates.Provider
}
// HashConfig holds specific fosite.Configurator information for hashing.
type HashConfig struct {
ClientSecrets fosite.Hasher
HMAC func() (h hash.Hash)
}
// StrategyConfig holds specific fosite.Configurator information for various strategies.
type StrategyConfig struct {
Core oauth2.CoreStrategy
OpenID openid.OpenIDConnectTokenStrategy
@ -105,17 +113,20 @@ type StrategyConfig struct {
ClientAuthentication fosite.ClientAuthenticationStrategy
}
// PARConfig holds specific fosite.Configurator information for Pushed Authorization Requests.
type PARConfig struct {
Enforced bool
URIPrefix string
ContextLifespan time.Duration
}
// IssuersConfig holds specific fosite.Configurator information for the issuer.
type IssuersConfig struct {
IDToken string
AccessToken string
}
// HandlersConfig holds specific fosite.Configurator handlers configuration information.
type HandlersConfig struct {
// ResponseMode provides an extension handler for custom response modes.
ResponseMode fosite.ResponseModeHandler
@ -136,18 +147,21 @@ type HandlersConfig struct {
PushedAuthorizeEndpoint fosite.PushedAuthorizeEndpointHandlers
}
// GrantTypeJWTBearerConfig holds specific fosite.Configurator information for the JWT Bearer Grant Type.
type GrantTypeJWTBearerConfig struct {
OptionalClientAuth bool
OptionalJTIClaim bool
OptionalIssuedDate bool
}
// ProofKeyCodeExchangeConfig holds specific fosite.Configurator information for PKCE.
type ProofKeyCodeExchangeConfig struct {
Enforce bool
EnforcePublicClients bool
AllowPlainChallengeMethod bool
}
// LifespanConfig holds specific fosite.Configurator information for various lifespans.
type LifespanConfig struct {
AccessToken time.Duration
AuthorizeCode time.Duration
@ -161,6 +175,7 @@ const (
PromptConsent = "consent"
)
// LoadHandlers reloads the handlers based on the current configuration.
func (c *Config) LoadHandlers(store *Store, strategy jwt.Signer) {
validator := openid.NewOpenIDConnectRequestValidator(strategy, c)
@ -277,6 +292,10 @@ func (c *Config) LoadHandlers(store *Store, strategy jwt.Signer) {
if h, ok := handler.(fosite.RevocationHandler); ok {
x.Revocation.Append(h)
}
if h, ok := handler.(fosite.PushedAuthorizeEndpointHandler); ok {
x.PushedAuthorizeEndpoint.Append(h)
}
}
c.Handlers = x
@ -515,13 +534,24 @@ func (c *Config) GetFormPostHTMLTemplate(ctx context.Context) (tmpl *template.Te
// GetTokenURL returns the token URL.
func (c *Config) GetTokenURL(ctx context.Context) (tokenURL string) {
if ctx, ok := ctx.(OpenIDConnectContext); ok {
tokenURI, err := ctx.IssuerURL()
if err != nil {
return c.TokenURL
}
tokenURI.Path = path.Join(tokenURI.Path, EndpointPathToken)
return tokenURI.String()
}
return c.TokenURL
}
// GetSecretsHasher returns the client secrets hashing function.
func (c *Config) GetSecretsHasher(ctx context.Context) (hasher fosite.Hasher) {
if c.Hash.ClientSecrets == nil {
c.Hash.ClientSecrets, _ = NewAdaptiveHasher()
c.Hash.ClientSecrets, _ = NewHasher()
}
return c.Hash.ClientSecrets
@ -583,7 +613,7 @@ func (c *Config) EnforcePushedAuthorize(ctx context.Context) bool {
// GetPushedAuthorizeContextLifespan is the lifespan of the short-lived PAR context.
func (c *Config) GetPushedAuthorizeContextLifespan(ctx context.Context) (lifespan time.Duration) {
if c.PAR.ContextLifespan == 0 {
if c.PAR.ContextLifespan.Seconds() == 0 {
c.PAR.ContextLifespan = lifespanPARContextDefault
}

View File

@ -73,6 +73,25 @@ const (
GrantTypeClientCredentials = "client_credentials"
)
// Client Auth Method strings.
const (
ClientAuthMethodClientSecretBasic = "client_secret_basic"
ClientAuthMethodClientSecretPost = "client_secret_post"
ClientAuthMethodClientSecretJWT = "client_secret_jwt"
ClientAuthMethodNone = "none"
)
// Response Type strings.
const (
ResponseTypeAuthorizationCodeFlow = "code"
ResponseTypeImplicitFlowIDToken = "id_token"
ResponseTypeImplicitFlowToken = "token"
ResponseTypeImplicitFlowBoth = "id_token token"
ResponseTypeHybridFlowIDToken = "code id_token"
ResponseTypeHybridFlowToken = "code token"
ResponseTypeHybridFlowBoth = "code id_token token"
)
// Signing Algorithm strings.
const (
SigningAlgorithmNone = none
@ -91,6 +110,12 @@ const (
PKCEChallengeMethodSHA256 = "S256"
)
const (
FormParameterRequestURI = "request_uri"
FormParameterCodeChallenge = "code_challenge"
FormParameterCodeChallengeMethod = "code_challenge_method"
)
// Endpoints.
const (
EndpointAuthorization = "authorization"
@ -98,6 +123,7 @@ const (
EndpointUserinfo = "userinfo"
EndpointIntrospection = "introspection"
EndpointRevocation = "revocation"
EndpointPushedAuthorizationRequest = "pushed-authorization-request"
)
// JWT Headers.
@ -107,7 +133,9 @@ const (
)
const (
tokenPrefixFmt = "authelia_%s_" //nolint:gosec
tokenPrefixOrgAutheliaFmt = "authelia_%s_" //nolint:gosec
tokenPrefixOrgOryFmt = "ory_%s_" //nolint:gosec
tokenPrefixPartAccessToken = "at"
tokenPrefixPartRefreshToken = "rt"
tokenPrefixPartAuthorizeCode = "ac"
@ -127,6 +155,8 @@ const (
EndpointPathUserinfo = EndpointPathRoot + "/" + EndpointUserinfo
EndpointPathIntrospection = EndpointPathRoot + "/" + EndpointIntrospection
EndpointPathRevocation = EndpointPathRoot + "/" + EndpointRevocation
EndpointPathPushedAuthorizationRequest = EndpointPathRoot + "/" + EndpointPushedAuthorizationRequest
)
// Authentication Method Reference Values https://datatracker.ietf.org/doc/html/rfc8176

View File

@ -19,7 +19,6 @@ type HMACCoreStrategy struct {
fosite.RefreshTokenLifespanProvider
fosite.AuthorizeCodeLifespanProvider
}
prefix string
}
// AccessTokenSignature implements oauth2.AccessTokenStrategy.
@ -112,11 +111,11 @@ func (h *HMACCoreStrategy) ValidateAuthorizeCode(ctx context.Context, r fosite.R
}
func (h *HMACCoreStrategy) getPrefix(part string) string {
if len(h.prefix) == 0 {
return ""
}
return h.getCustomPrefix(tokenPrefixOrgAutheliaFmt, part)
}
return fmt.Sprintf(h.prefix, part)
func (h *HMACCoreStrategy) getCustomPrefix(tokenPrefixFmt, part string) string {
return fmt.Sprintf(tokenPrefixFmt, part)
}
func (h *HMACCoreStrategy) setPrefix(token, part string) string {
@ -124,5 +123,9 @@ func (h *HMACCoreStrategy) setPrefix(token, part string) string {
}
func (h *HMACCoreStrategy) trimPrefix(token, part string) string {
if strings.HasPrefix(token, h.getCustomPrefix(tokenPrefixOrgOryFmt, part)) {
return strings.TrimPrefix(token, h.getCustomPrefix(tokenPrefixOrgOryFmt, part))
}
return strings.TrimPrefix(token, h.getPrefix(part))
}

View File

@ -0,0 +1,56 @@
package oidc
import (
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestHMACCoreStrategy_TrimPrefix(t *testing.T) {
testCases := []struct {
name string
have string
part string
expected string
}{
{"ShouldTrimAutheliaPrefix", "authelia_at_example", tokenPrefixPartAccessToken, "example"},
{"ShouldTrimOryPrefix", "ory_at_example", tokenPrefixPartAccessToken, "example"},
{"ShouldTrimOnlyAutheliaPrefix", "authelia_at_ory_at_example", tokenPrefixPartAccessToken, "ory_at_example"},
{"ShouldTrimOnlyOryPrefix", "ory_at_authelia_at_example", tokenPrefixPartAccessToken, "authelia_at_example"},
{"ShouldNotTrimGitHubPrefix", "gh_at_example", tokenPrefixPartAccessToken, "gh_at_example"},
}
strategy := &HMACCoreStrategy{}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, strategy.trimPrefix(tc.have, tc.part))
})
}
}
func TestHMACCoreStrategy_GetSetPrefix(t *testing.T) {
testCases := []struct {
name string
have string
expectedSet string
expectedGet string
}{
{"ShouldAddPrefix", "example", "authelia_%s_example", "authelia_%s_"},
}
strategy := &HMACCoreStrategy{}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
for _, part := range []string{tokenPrefixPartAccessToken, tokenPrefixPartAuthorizeCode, tokenPrefixPartRefreshToken} {
t.Run(strings.ToUpper(part), func(t *testing.T) {
assert.Equal(t, fmt.Sprintf(tc.expectedSet, part), strategy.setPrefix(tc.have, part))
assert.Equal(t, fmt.Sprintf(tc.expectedGet, part), strategy.getPrefix(part))
})
}
})
}
}

View File

@ -1,21 +1,29 @@
package oidc
import (
"github.com/authelia/authelia/v4/internal/configuration/schema"
)
// NewOpenIDConnectWellKnownConfiguration generates a new OpenIDConnectWellKnownConfiguration.
func NewOpenIDConnectWellKnownConfiguration(enablePKCEPlainChallenge bool, clients map[string]*Client) (config OpenIDConnectWellKnownConfiguration) {
func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration, clients map[string]*Client) (config OpenIDConnectWellKnownConfiguration) {
config = OpenIDConnectWellKnownConfiguration{
CommonDiscoveryOptions: CommonDiscoveryOptions{
SubjectTypesSupported: []string{
SubjectTypePublic,
},
ResponseTypesSupported: []string{
"code",
"token",
"id_token",
"code token",
"code id_token",
"token id_token",
"code token id_token",
"none",
ResponseTypeAuthorizationCodeFlow,
ResponseTypeImplicitFlowIDToken,
ResponseTypeImplicitFlowToken,
ResponseTypeImplicitFlowBoth,
ResponseTypeHybridFlowIDToken,
ResponseTypeHybridFlowToken,
ResponseTypeHybridFlowBoth,
},
GrantTypesSupported: []string{
GrantTypeAuthorizationCode,
GrantTypeImplicit,
GrantTypeRefreshToken,
},
ResponseModesSupported: []string{
ResponseModeFormPost,
@ -49,6 +57,12 @@ func NewOpenIDConnectWellKnownConfiguration(enablePKCEPlainChallenge bool, clien
ClaimPreferredUsername,
ClaimFullName,
},
TokenEndpointAuthMethodsSupported: []string{
ClientAuthMethodClientSecretBasic,
ClientAuthMethodClientSecretPost,
ClientAuthMethodClientSecretJWT,
ClientAuthMethodNone,
},
},
OAuth2DiscoveryOptions: OAuth2DiscoveryOptions{
CodeChallengeMethodsSupported: []string{
@ -68,6 +82,9 @@ func NewOpenIDConnectWellKnownConfiguration(enablePKCEPlainChallenge bool, clien
SigningAlgorithmRSAWithSHA256,
},
},
PushedAuthorizationDiscoveryOptions: PushedAuthorizationDiscoveryOptions{
RequirePushedAuthorizationRequests: c.PAR.Enforce,
},
}
var pairwise, public bool
@ -86,7 +103,7 @@ func NewOpenIDConnectWellKnownConfiguration(enablePKCEPlainChallenge bool, clien
config.SubjectTypesSupported = append(config.SubjectTypesSupported, SubjectTypePairwise)
}
if enablePKCEPlainChallenge {
if c.EnablePKCEPlainChallenge {
config.CodeChallengeMethodsSupported = append(config.CodeChallengeMethodsSupported, PKCEChallengeMethodPlain)
}

View File

@ -4,12 +4,15 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/authelia/authelia/v4/internal/configuration/schema"
)
func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
testCases := []struct {
desc string
pkcePlainChallenge bool
enforcePAR bool
clients map[string]*Client
expectCodeChallengeMethodsSupported, expectSubjectTypesSupported []string
@ -63,7 +66,14 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
actual := NewOpenIDConnectWellKnownConfiguration(tc.pkcePlainChallenge, tc.clients)
c := schema.OpenIDConnectConfiguration{
EnablePKCEPlainChallenge: tc.pkcePlainChallenge,
PAR: schema.OpenIDConnectPARConfiguration{
Enforce: tc.enforcePAR,
},
}
actual := NewOpenIDConnectWellKnownConfiguration(&c, tc.clients)
for _, codeChallengeMethod := range tc.expectCodeChallengeMethodsSupported {
assert.Contains(t, actual.CodeChallengeMethodsSupported, codeChallengeMethod)
}

View File

@ -9,11 +9,27 @@ import (
var errPasswordsDoNotMatch = errors.New("the passwords don't match")
var (
// ErrIssuerCouldNotDerive is sent when the issuer couldn't be determined from the headers.
ErrIssuerCouldNotDerive = fosite.ErrServerError.WithHint("Could not safely derive the issuer.")
// ErrSubjectCouldNotLookup is sent when the Subject Identifier for a user couldn't be generated or obtained from the database.
ErrSubjectCouldNotLookup = fosite.ErrServerError.WithHint("Could not lookup user subject.")
// ErrConsentCouldNotPerform is sent when the Consent Session couldn't be performed for varying reasons.
ErrConsentCouldNotPerform = fosite.ErrServerError.WithHint("Could not perform consent.")
// ErrConsentCouldNotGenerate is sent when the Consent Session failed to be generated for some reason, usually a failed UUIDv4 generation.
ErrConsentCouldNotGenerate = fosite.ErrServerError.WithHint("Could not generate the consent session.")
// ErrConsentCouldNotSave is sent when the Consent Session couldn't be saved to the database.
ErrConsentCouldNotSave = fosite.ErrServerError.WithHint("Could not save the consent session.")
// ErrConsentCouldNotLookup is sent when the Consent ID is not a known UUID.
ErrConsentCouldNotLookup = fosite.ErrServerError.WithHint("Failed to lookup the consent session.")
// ErrConsentMalformedChallengeID is sent when the Consent ID is not a UUID.
ErrConsentMalformedChallengeID = fosite.ErrServerError.WithHint("Malformed consent session challenge ID.")
// ErrPAREnforcedClientMissingPAR is sent when a client has EnforcePAR configured but the Authorization Request was not Pushed.
ErrPAREnforcedClientMissingPAR = fosite.ErrInvalidRequest.WithHint("Pushed Authorization Requests are enforced for this client but no such request was sent.")
)

View File

@ -8,8 +8,9 @@ import (
"github.com/go-crypt/crypt/algorithm/plaintext"
)
func NewAdaptiveHasher() (hasher *AdaptiveHasher, err error) {
hasher = &AdaptiveHasher{}
// NewHasher returns a new Hasher.
func NewHasher() (hasher *Hasher, err error) {
hasher = &Hasher{}
if hasher.decoder, err = crypt.NewDefaultDecoder(); err != nil {
return nil, err
@ -22,13 +23,13 @@ func NewAdaptiveHasher() (hasher *AdaptiveHasher, err error) {
return hasher, nil
}
// AdaptiveHasher implements the fosite.Hasher interface without an actual hashing algo.
type AdaptiveHasher struct {
// Hasher implements the fosite.Hasher interface and adaptively compares hashes.
type Hasher struct {
decoder algorithm.DecoderRegister
}
// Compare compares the hash with the data and returns an error if they don't match.
func (h *AdaptiveHasher) Compare(_ context.Context, hash, data []byte) (err error) {
func (h Hasher) Compare(_ context.Context, hash, data []byte) (err error) {
var digest algorithm.Digest
if digest, err = h.decoder.Decode(string(hash)); err != nil {
@ -43,6 +44,6 @@ func (h *AdaptiveHasher) Compare(_ context.Context, hash, data []byte) (err erro
}
// Hash creates a new hash from data.
func (h *AdaptiveHasher) Hash(_ context.Context, data []byte) (hash []byte, err error) {
func (h Hasher) Hash(_ context.Context, data []byte) (hash []byte, err error) {
return data, nil
}

View File

@ -9,7 +9,7 @@ import (
)
func TestShouldNotRaiseErrorOnEqualPasswordsPlainText(t *testing.T) {
hasher, err := NewAdaptiveHasher()
hasher, err := NewHasher()
require.NoError(t, err)
@ -22,7 +22,7 @@ func TestShouldNotRaiseErrorOnEqualPasswordsPlainText(t *testing.T) {
}
func TestShouldNotRaiseErrorOnEqualPasswordsPlainTextWithSeparator(t *testing.T) {
hasher, err := NewAdaptiveHasher()
hasher, err := NewHasher()
require.NoError(t, err)
@ -35,7 +35,7 @@ func TestShouldNotRaiseErrorOnEqualPasswordsPlainTextWithSeparator(t *testing.T)
}
func TestShouldRaiseErrorOnNonEqualPasswordsPlainText(t *testing.T) {
hasher, err := NewAdaptiveHasher()
hasher, err := NewHasher()
require.NoError(t, err)
@ -48,7 +48,7 @@ func TestShouldRaiseErrorOnNonEqualPasswordsPlainText(t *testing.T) {
}
func TestShouldHashPassword(t *testing.T) {
hasher := AdaptiveHasher{}
hasher := Hasher{}
data := []byte("abc")

View File

@ -37,7 +37,7 @@ func NewOpenIDConnectProvider(config *schema.OpenIDConnectConfiguration, store s
provider.Config.LoadHandlers(provider.Store, provider.KeyManager.Strategy())
provider.discovery = NewOpenIDConnectWellKnownConfiguration(config.EnablePKCEPlainChallenge, provider.Store.clients)
provider.discovery = NewOpenIDConnectWellKnownConfiguration(config, provider.Store.clients)
return provider, nil
}
@ -50,12 +50,12 @@ func (p *OpenIDConnectProvider) GetOAuth2WellKnownConfiguration(issuer string) O
}
options.Issuer = issuer
options.JWKSURI = fmt.Sprintf("%s%s", issuer, EndpointPathJWKs)
options.IntrospectionEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathIntrospection)
options.TokenEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathToken)
options.AuthorizationEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathAuthorization)
options.PushedAuthorizationRequestEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathPushedAuthorizationRequest)
options.TokenEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathToken)
options.IntrospectionEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathIntrospection)
options.RevocationEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathRevocation)
return options
@ -72,14 +72,14 @@ func (p *OpenIDConnectProvider) GetOpenIDConnectWellKnownConfiguration(issuer st
}
options.Issuer = issuer
options.JWKSURI = fmt.Sprintf("%s%s", issuer, EndpointPathJWKs)
options.IntrospectionEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathIntrospection)
options.TokenEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathToken)
options.AuthorizationEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathAuthorization)
options.RevocationEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathRevocation)
options.PushedAuthorizationRequestEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathPushedAuthorizationRequest)
options.TokenEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathToken)
options.UserinfoEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathUserinfo)
options.IntrospectionEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathIntrospection)
options.RevocationEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathRevocation)
return options
}

View File

@ -142,15 +142,25 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
assert.Len(t, disco.SubjectTypesSupported, 1)
assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic)
assert.Len(t, disco.ResponseTypesSupported, 8)
assert.Contains(t, disco.ResponseTypesSupported, "code")
assert.Contains(t, disco.ResponseTypesSupported, "token")
assert.Contains(t, disco.ResponseTypesSupported, "id_token")
assert.Contains(t, disco.ResponseTypesSupported, "code token")
assert.Contains(t, disco.ResponseTypesSupported, "code id_token")
assert.Contains(t, disco.ResponseTypesSupported, "token id_token")
assert.Contains(t, disco.ResponseTypesSupported, "code token id_token")
assert.Contains(t, disco.ResponseTypesSupported, "none")
assert.Len(t, disco.ResponseTypesSupported, 7)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeAuthorizationCodeFlow)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowIDToken)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowToken)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowBoth)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowIDToken)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowToken)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowBoth)
assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 4)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone)
assert.Len(t, disco.GrantTypesSupported, 3)
assert.Contains(t, disco.GrantTypesSupported, GrantTypeAuthorizationCode)
assert.Contains(t, disco.GrantTypesSupported, GrantTypeRefreshToken)
assert.Contains(t, disco.GrantTypesSupported, GrantTypeImplicit)
assert.Len(t, disco.IDTokenSigningAlgValuesSupported, 1)
assert.Contains(t, disco.IDTokenSigningAlgValuesSupported, SigningAlgorithmRSAWithSHA256)
@ -231,15 +241,25 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
assert.Len(t, disco.SubjectTypesSupported, 1)
assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic)
assert.Len(t, disco.ResponseTypesSupported, 8)
assert.Contains(t, disco.ResponseTypesSupported, "code")
assert.Contains(t, disco.ResponseTypesSupported, "token")
assert.Contains(t, disco.ResponseTypesSupported, "id_token")
assert.Contains(t, disco.ResponseTypesSupported, "code token")
assert.Contains(t, disco.ResponseTypesSupported, "code id_token")
assert.Contains(t, disco.ResponseTypesSupported, "token id_token")
assert.Contains(t, disco.ResponseTypesSupported, "code token id_token")
assert.Contains(t, disco.ResponseTypesSupported, "none")
assert.Len(t, disco.ResponseTypesSupported, 7)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeAuthorizationCodeFlow)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowIDToken)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowToken)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowBoth)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowIDToken)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowToken)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowBoth)
assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 4)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone)
assert.Len(t, disco.GrantTypesSupported, 3)
assert.Contains(t, disco.GrantTypesSupported, GrantTypeAuthorizationCode)
assert.Contains(t, disco.GrantTypesSupported, GrantTypeRefreshToken)
assert.Contains(t, disco.GrantTypesSupported, GrantTypeImplicit)
assert.Len(t, disco.ClaimsSupported, 18)
assert.Contains(t, disco.ClaimsSupported, ClaimAuthenticationMethodsReference)

View File

@ -165,7 +165,7 @@ func (s *Store) InvalidateAuthorizeCodeSession(ctx context.Context, code string)
// This implements a portion of oauth2.AuthorizeCodeStorage.
func (s *Store) GetAuthorizeCodeSession(ctx context.Context, code string, session fosite.Session) (request fosite.Requester, err error) {
// TODO: Implement the fosite.ErrInvalidatedAuthorizeCode error above. This requires splitting the invalidated sessions and deleted sessions.
return s.loadSessionBySignature(ctx, storage.OAuth2SessionTypeAuthorizeCode, code, session)
return s.loadRequesterBySignature(ctx, storage.OAuth2SessionTypeAuthorizeCode, code, session)
}
// CreateAccessTokenSession stores the authorization request for a given access token.
@ -190,7 +190,7 @@ func (s *Store) RevokeAccessToken(ctx context.Context, requestID string) (err er
// GetAccessTokenSession gets the authorization request for a given access token.
// This implements a portion of oauth2.AccessTokenStorage.
func (s *Store) GetAccessTokenSession(ctx context.Context, signature string, session fosite.Session) (request fosite.Requester, err error) {
return s.loadSessionBySignature(ctx, storage.OAuth2SessionTypeAccessToken, signature, session)
return s.loadRequesterBySignature(ctx, storage.OAuth2SessionTypeAccessToken, signature, session)
}
// CreateRefreshTokenSession stores the authorization request for a given refresh token.
@ -223,7 +223,7 @@ func (s *Store) RevokeRefreshTokenMaybeGracePeriod(ctx context.Context, requestI
// GetRefreshTokenSession gets the authorization request for a given refresh token.
// This implements a portion of oauth2.RefreshTokenStorage.
func (s *Store) GetRefreshTokenSession(ctx context.Context, signature string, session fosite.Session) (request fosite.Requester, err error) {
return s.loadSessionBySignature(ctx, storage.OAuth2SessionTypeRefreshToken, signature, session)
return s.loadRequesterBySignature(ctx, storage.OAuth2SessionTypeRefreshToken, signature, session)
}
// CreatePKCERequestSession stores the authorization request for a given PKCE request.
@ -241,7 +241,7 @@ func (s *Store) DeletePKCERequestSession(ctx context.Context, signature string)
// GetPKCERequestSession gets the authorization request for a given PKCE request.
// This implements a portion of pkce.PKCERequestStorage.
func (s *Store) GetPKCERequestSession(ctx context.Context, signature string, session fosite.Session) (requester fosite.Requester, err error) {
return s.loadSessionBySignature(ctx, storage.OAuth2SessionTypePKCEChallenge, signature, session)
return s.loadRequesterBySignature(ctx, storage.OAuth2SessionTypePKCEChallenge, signature, session)
}
// CreateOpenIDConnectSession creates an open id connect session for a given authorize code.
@ -263,7 +263,37 @@ func (s *Store) DeleteOpenIDConnectSession(ctx context.Context, authorizeCode st
// - or an arbitrary error if an error occurred.
// This implements a portion of openid.OpenIDConnectRequestStorage.
func (s *Store) GetOpenIDConnectSession(ctx context.Context, authorizeCode string, request fosite.Requester) (r fosite.Requester, err error) {
return s.loadSessionBySignature(ctx, storage.OAuth2SessionTypeOpenIDConnect, authorizeCode, request.GetSession())
return s.loadRequesterBySignature(ctx, storage.OAuth2SessionTypeOpenIDConnect, authorizeCode, request.GetSession())
}
// CreatePARSession stores the pushed authorization request context. The requestURI is used to derive the key.
// This implements a portion of fosite.PARStorage.
func (s *Store) CreatePARSession(ctx context.Context, requestURI string, request fosite.AuthorizeRequester) (err error) {
var par *model.OAuth2PARContext
if par, err = model.NewOAuth2PARContext(requestURI, request); err != nil {
return err
}
return s.provider.SaveOAuth2PARContext(ctx, *par)
}
// GetPARSession gets the push authorization request context. The caller is expected to merge the AuthorizeRequest.
// This implements a portion of fosite.PARStorage.
func (s *Store) GetPARSession(ctx context.Context, requestURI string) (request fosite.AuthorizeRequester, err error) {
var par *model.OAuth2PARContext
if par, err = s.provider.LoadOAuth2PARContext(ctx, requestURI); err != nil {
return nil, err
}
return par.ToAuthorizeRequest(ctx, NewSession(), s)
}
// DeletePARSession deletes the context.
// This implements a portion of fosite.PARStorage.
func (s *Store) DeletePARSession(ctx context.Context, requestURI string) (err error) {
return s.provider.RevokeOAuth2PARContext(ctx, requestURI)
}
// IsJWTUsed implements an interface required for RFC7523.
@ -280,7 +310,7 @@ func (s *Store) MarkJWTUsedForTime(ctx context.Context, jti string, exp time.Tim
return s.SetClientAssertionJWT(ctx, jti, exp)
}
func (s *Store) loadSessionBySignature(ctx context.Context, sessionType storage.OAuth2SessionType, signature string, session fosite.Session) (r fosite.Requester, err error) {
func (s *Store) loadRequesterBySignature(ctx context.Context, sessionType storage.OAuth2SessionType, signature string, session fosite.Session) (r fosite.Requester, err error) {
var (
sessionModel *model.OAuth2Session
)

View File

@ -1,6 +1,7 @@
package oidc
import (
"context"
"net/url"
"time"
@ -118,6 +119,8 @@ type Client struct {
ResponseTypes []string
ResponseModes []fosite.ResponseModeType
EnforcePAR bool
UserinfoSigningAlgorithm string
Policy authorization.Level
@ -643,3 +646,10 @@ type OpenIDConnectWellKnownConfiguration struct {
OpenIDConnectFrontChannelLogoutDiscoveryOptions
OpenIDConnectBackChannelLogoutDiscoveryOptions
}
// OpenIDConnectContext represents the context implementation that is used by some OpenID Connect 1.0 implementations.
type OpenIDConnectContext interface {
context.Context
IssuerURL() (issuerURL *url.URL, err error)
}

View File

@ -331,6 +331,15 @@ func handleRouter(config *schema.Configuration, providers middlewares.Providers)
r.GET("/api/oidc/authorize", policyCORSAuthorization.Middleware(bridgeOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectAuthorization))))
r.POST("/api/oidc/authorize", policyCORSAuthorization.Middleware(bridgeOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectAuthorization))))
policyCORSPAR := middlewares.NewCORSPolicyBuilder().
WithAllowedMethods(fasthttp.MethodOptions, fasthttp.MethodPost).
WithAllowedOrigins(allowedOrigins...).
WithEnabled(utils.IsStringInSliceFold(oidc.EndpointPushedAuthorizationRequest, config.IdentityProviders.OIDC.CORS.Endpoints)).
Build()
r.OPTIONS(oidc.EndpointPathPushedAuthorizationRequest, policyCORSPAR.HandleOnlyOPTIONS)
r.POST(oidc.EndpointPathPushedAuthorizationRequest, policyCORSPAR.Middleware(bridgeOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectPushedAuthorizationRequest))))
policyCORSToken := middlewares.NewCORSPolicyBuilder().
WithAllowCredentials(true).
WithAllowedMethods(fasthttp.MethodOptions, fasthttp.MethodPost).

View File

@ -13,15 +13,16 @@ const (
tableUserPreferences = "user_preferences"
tableWebauthnDevices = "webauthn_devices"
tableOAuth2BlacklistedJTI = "oauth2_blacklisted_jti"
tableOAuth2ConsentSession = "oauth2_consent_session"
tableOAuth2ConsentPreConfiguration = "oauth2_consent_preconfiguration"
tableOAuth2AuthorizeCodeSession = "oauth2_authorization_code_session"
tableOAuth2AccessTokenSession = "oauth2_access_token_session" //nolint:gosec // This is not a hardcoded credential.
tableOAuth2RefreshTokenSession = "oauth2_refresh_token_session" //nolint:gosec // This is not a hardcoded credential.
tableOAuth2PKCERequestSession = "oauth2_pkce_request_session"
tableOAuth2AuthorizeCodeSession = "oauth2_authorization_code_session"
tableOAuth2OpenIDConnectSession = "oauth2_openid_connect_session"
tableOAuth2BlacklistedJTI = "oauth2_blacklisted_jti"
tableOAuth2PARContext = "oauth2_par_context"
tableOAuth2PKCERequestSession = "oauth2_pkce_request_session"
tableOAuth2RefreshTokenSession = "oauth2_refresh_token_session" //nolint:gosec // This is not a hardcoded credential.
tableMigrations = "migrations"
tableEncryption = "encryption"
@ -32,26 +33,29 @@ type OAuth2SessionType int
// Representation of specific OAuth 2.0 session types.
const (
OAuth2SessionTypeAuthorizeCode OAuth2SessionType = iota
OAuth2SessionTypeAccessToken
OAuth2SessionTypeRefreshToken
OAuth2SessionTypePKCEChallenge
OAuth2SessionTypeAccessToken OAuth2SessionType = iota
OAuth2SessionTypeAuthorizeCode
OAuth2SessionTypeOpenIDConnect
OAuth2SessionTypePAR
OAuth2SessionTypePKCEChallenge
OAuth2SessionTypeRefreshToken
)
// String returns a string representation of this OAuth2SessionType.
func (s OAuth2SessionType) String() string {
switch s {
case OAuth2SessionTypeAuthorizeCode:
return "authorization code"
case OAuth2SessionTypeAccessToken:
return "access token"
case OAuth2SessionTypeRefreshToken:
return "refresh token"
case OAuth2SessionTypePKCEChallenge:
return "pkce challenge"
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"
}
@ -60,16 +64,18 @@ func (s OAuth2SessionType) String() string {
// Table returns the table name for this session type.
func (s OAuth2SessionType) Table() string {
switch s {
case OAuth2SessionTypeAuthorizeCode:
return tableOAuth2AuthorizeCodeSession
case OAuth2SessionTypeAccessToken:
return tableOAuth2AccessTokenSession
case OAuth2SessionTypeRefreshToken:
return tableOAuth2RefreshTokenSession
case OAuth2SessionTypePKCEChallenge:
return tableOAuth2PKCERequestSession
case OAuth2SessionTypeAuthorizeCode:
return tableOAuth2AuthorizeCodeSession
case OAuth2SessionTypeOpenIDConnect:
return tableOAuth2OpenIDConnectSession
case OAuth2SessionTypePAR:
return tableOAuth2PARContext
case OAuth2SessionTypePKCEChallenge:
return tableOAuth2PKCERequestSession
case OAuth2SessionTypeRefreshToken:
return tableOAuth2RefreshTokenSession
default:
return ""
}
@ -119,7 +125,7 @@ const (
)
var (
reMigration = regexp.MustCompile(`^V(\d{4})\.([^.]+)\.(all|sqlite|postgres|mysql)\.(up|down)\.sql$`)
reMigration = regexp.MustCompile(`^V(?P<Version>\d{4})\.(?P<Name>[^.]+)\.(?P<Provider>(all|sqlite|postgres|mysql))\.(?P<Direction>(up|down))\.sql$`)
)
const (

View File

@ -130,15 +130,15 @@ func skipMigration(providerName string, up bool, target, prior int, migration *m
}
func scanMigration(m string) (migration model.SchemaMigration, err error) {
result := reMigration.FindStringSubmatch(m)
if result == nil || len(result) != 5 {
if !reMigration.MatchString(m) {
return model.SchemaMigration{}, errors.New("invalid migration: could not parse the format")
}
result := reMigration.FindStringSubmatch(m)
migration = model.SchemaMigration{
Name: strings.ReplaceAll(result[2], "_", " "),
Provider: result[3],
Name: strings.ReplaceAll(result[reMigration.SubexpIndex("Name")], "_", " "),
Provider: result[reMigration.SubexpIndex("Provider")],
}
data, err := migrationsFS.ReadFile(fmt.Sprintf("migrations/%s", m))
@ -148,22 +148,22 @@ func scanMigration(m string) (migration model.SchemaMigration, err error) {
migration.Query = string(data)
switch result[4] {
switch direction := result[reMigration.SubexpIndex("Direction")]; direction {
case "up":
migration.Up = true
case "down":
migration.Up = false
default:
return model.SchemaMigration{}, fmt.Errorf("invalid migration: value in position 4 '%s' must be up or down", result[4])
return model.SchemaMigration{}, fmt.Errorf("invalid migration: value in Direction group '%s' must be up or down", direction)
}
migration.Version, _ = strconv.Atoi(result[1])
migration.Version, _ = strconv.Atoi(result[reMigration.SubexpIndex("Version")])
switch migration.Provider {
case providerAll, providerSQLite, providerMySQL, providerPostgres:
break
default:
return model.SchemaMigration{}, fmt.Errorf("invalid migration: value in position 3 '%s' must be all, sqlite, postgres, or mysql", result[3])
return model.SchemaMigration{}, fmt.Errorf("invalid migration: value in Provider group '%s' must be all, sqlite, postgres, or mysql", migration.Provider)
}
return migration, nil

View File

@ -0,0 +1 @@
DROP TABLE IF EXISTS oauth2_par_context;

View File

@ -0,0 +1,17 @@
CREATE TABLE IF NOT EXISTS oauth2_par_context (
id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
request_id VARCHAR(40) NOT NULL,
client_id VARCHAR(255) NOT NULL,
signature VARCHAR(255) NOT NULL,
requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
scopes TEXT NOT NULL,
audience TEXT NOT NULL,
handled_response_types TEXT NOT NULL,
response_mode TEXT NOT NULL,
response_mode_default TEXT NOT NULL,
revoked BOOLEAN NOT NULL DEFAULT FALSE,
form_data TEXT NOT NULL,
session_data BLOB NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;
CREATE UNIQUE INDEX oauth2_par_context_signature_key ON oauth2_par_context (signature);

View File

@ -0,0 +1,17 @@
CREATE TABLE IF NOT EXISTS oauth2_par_context (
id SERIAL CONSTRAINT oauth2_par_context_pkey PRIMARY KEY,
request_id VARCHAR(40) NOT NULL,
client_id VARCHAR(255) NOT NULL,
signature VARCHAR(255) NOT NULL,
requested_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
scopes TEXT NOT NULL,
audience TEXT NULL DEFAULT '',
handled_response_types TEXT NOT NULL DEFAULT '',
response_mode TEXT NOT NULL DEFAULT '',
response_mode_default TEXT NOT NULL DEFAULT '',
revoked BOOLEAN NOT NULL DEFAULT FALSE,
form_data TEXT NOT NULL,
session_data BYTEA NOT NULL
);
CREATE UNIQUE INDEX oauth2_par_context_signature_key ON oauth2_par_context (signature);

View File

@ -0,0 +1,17 @@
CREATE TABLE IF NOT EXISTS oauth2_par_context (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
signature VARCHAR(255) NOT NULL,
request_id VARCHAR(40) NOT NULL,
client_id VARCHAR(255) NOT NULL,
requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
scopes TEXT NOT NULL,
audience TEXT NOT NULL,
handled_response_types TEXT NOT NULL,
response_mode TEXT NOT NULL,
response_mode_default TEXT NOT NULL,
revoked BOOLEAN NOT NULL DEFAULT FALSE,
form_data TEXT NOT NULL,
session_data BLOB NOT NULL
);
CREATE UNIQUE INDEX oauth2_par_context_signature_key ON oauth2_par_context (signature);

View File

@ -9,7 +9,7 @@ import (
const (
// This is the latest schema version for the purpose of tests.
LatestVersion = 7
LatestVersion = 8
)
func TestShouldObtainCorrectUpMigrations(t *testing.T) {

View File

@ -24,8 +24,8 @@ type Provider interface {
LoadUserInfo(ctx context.Context, username string) (info model.UserInfo, err error)
SaveUserOpaqueIdentifier(ctx context.Context, subject model.UserOpaqueIdentifier) (err error)
LoadUserOpaqueIdentifier(ctx context.Context, opaqueUUID uuid.UUID) (subject *model.UserOpaqueIdentifier, err error)
LoadUserOpaqueIdentifiers(ctx context.Context) (opaqueIDs []model.UserOpaqueIdentifier, err error)
LoadUserOpaqueIdentifier(ctx context.Context, identifier uuid.UUID) (subject *model.UserOpaqueIdentifier, err error)
LoadUserOpaqueIdentifiers(ctx context.Context) (identifiers []model.UserOpaqueIdentifier, err error)
LoadUserOpaqueIdentifierBySignature(ctx context.Context, service, sectorID, username string) (subject *model.UserOpaqueIdentifier, err error)
SaveIdentityVerification(ctx context.Context, verification model.IdentityVerification) (err error)
@ -65,6 +65,10 @@ type Provider interface {
DeactivateOAuth2SessionByRequestID(ctx context.Context, sessionType OAuth2SessionType, requestID string) (err error)
LoadOAuth2Session(ctx context.Context, sessionType OAuth2SessionType, signature string) (session *model.OAuth2Session, err error)
SaveOAuth2PARContext(ctx context.Context, par model.OAuth2PARContext) (err error)
LoadOAuth2PARContext(ctx context.Context, signature string) (par *model.OAuth2PARContext, err error)
RevokeOAuth2PARContext(ctx context.Context, signature string) (err error)
SaveOAuth2BlacklistedJTI(ctx context.Context, blacklistedJTI model.OAuth2BlacklistedJTI) (err error)
LoadOAuth2BlacklistedJTI(ctx context.Context, signature string) (blacklistedJTI *model.OAuth2BlacklistedJTI, err error)

View File

@ -70,6 +70,13 @@ func NewSQLProvider(config *schema.Configuration, name, driverName, dataSourceNa
sqlSelectUserOpaqueIdentifiers: fmt.Sprintf(queryFmtSelectUserOpaqueIdentifiers, tableUserOpaqueIdentifier),
sqlSelectUserOpaqueIdentifierBySignature: fmt.Sprintf(queryFmtSelectUserOpaqueIdentifierBySignature, tableUserOpaqueIdentifier),
sqlUpsertOAuth2BlacklistedJTI: fmt.Sprintf(queryFmtUpsertOAuth2BlacklistedJTI, tableOAuth2BlacklistedJTI),
sqlSelectOAuth2BlacklistedJTI: fmt.Sprintf(queryFmtSelectOAuth2BlacklistedJTI, tableOAuth2BlacklistedJTI),
sqlInsertOAuth2PARContext: fmt.Sprintf(queryFmtInsertOAuth2PARContext, tableOAuth2PARContext),
sqlSelectOAuth2PARContext: fmt.Sprintf(queryFmtSelectOAuth2PARContext, tableOAuth2PARContext),
sqlRevokeOAuth2PARContext: fmt.Sprintf(queryFmtRevokeOAuth2Session, tableOAuth2PARContext),
sqlInsertOAuth2ConsentPreConfiguration: fmt.Sprintf(queryFmtInsertOAuth2ConsentPreConfiguration, tableOAuth2ConsentPreConfiguration),
sqlSelectOAuth2ConsentPreConfigurations: fmt.Sprintf(queryFmtSelectOAuth2ConsentPreConfigurations, tableOAuth2ConsentPreConfiguration),
@ -79,13 +86,6 @@ func NewSQLProvider(config *schema.Configuration, name, driverName, dataSourceNa
sqlUpdateOAuth2ConsentSessionGranted: fmt.Sprintf(queryFmtUpdateOAuth2ConsentSessionGranted, tableOAuth2ConsentSession),
sqlSelectOAuth2ConsentSessionByChallengeID: fmt.Sprintf(queryFmtSelectOAuth2ConsentSessionByChallengeID, tableOAuth2ConsentSession),
sqlInsertOAuth2AuthorizeCodeSession: fmt.Sprintf(queryFmtInsertOAuth2Session, tableOAuth2AuthorizeCodeSession),
sqlSelectOAuth2AuthorizeCodeSession: fmt.Sprintf(queryFmtSelectOAuth2Session, tableOAuth2AuthorizeCodeSession),
sqlRevokeOAuth2AuthorizeCodeSession: fmt.Sprintf(queryFmtRevokeOAuth2Session, tableOAuth2AuthorizeCodeSession),
sqlRevokeOAuth2AuthorizeCodeSessionByRequestID: fmt.Sprintf(queryFmtRevokeOAuth2SessionByRequestID, tableOAuth2AuthorizeCodeSession),
sqlDeactivateOAuth2AuthorizeCodeSession: fmt.Sprintf(queryFmtDeactivateOAuth2Session, tableOAuth2AuthorizeCodeSession),
sqlDeactivateOAuth2AuthorizeCodeSessionByRequestID: fmt.Sprintf(queryFmtDeactivateOAuth2SessionByRequestID, tableOAuth2AuthorizeCodeSession),
sqlInsertOAuth2AccessTokenSession: fmt.Sprintf(queryFmtInsertOAuth2Session, tableOAuth2AccessTokenSession),
sqlSelectOAuth2AccessTokenSession: fmt.Sprintf(queryFmtSelectOAuth2Session, tableOAuth2AccessTokenSession),
sqlRevokeOAuth2AccessTokenSession: fmt.Sprintf(queryFmtRevokeOAuth2Session, tableOAuth2AccessTokenSession),
@ -93,19 +93,12 @@ func NewSQLProvider(config *schema.Configuration, name, driverName, dataSourceNa
sqlDeactivateOAuth2AccessTokenSession: fmt.Sprintf(queryFmtDeactivateOAuth2Session, tableOAuth2AccessTokenSession),
sqlDeactivateOAuth2AccessTokenSessionByRequestID: fmt.Sprintf(queryFmtDeactivateOAuth2SessionByRequestID, tableOAuth2AccessTokenSession),
sqlInsertOAuth2RefreshTokenSession: fmt.Sprintf(queryFmtInsertOAuth2Session, tableOAuth2RefreshTokenSession),
sqlSelectOAuth2RefreshTokenSession: fmt.Sprintf(queryFmtSelectOAuth2Session, tableOAuth2RefreshTokenSession),
sqlRevokeOAuth2RefreshTokenSession: fmt.Sprintf(queryFmtRevokeOAuth2Session, tableOAuth2RefreshTokenSession),
sqlRevokeOAuth2RefreshTokenSessionByRequestID: fmt.Sprintf(queryFmtRevokeOAuth2SessionByRequestID, tableOAuth2RefreshTokenSession),
sqlDeactivateOAuth2RefreshTokenSession: fmt.Sprintf(queryFmtDeactivateOAuth2Session, tableOAuth2RefreshTokenSession),
sqlDeactivateOAuth2RefreshTokenSessionByRequestID: fmt.Sprintf(queryFmtDeactivateOAuth2SessionByRequestID, tableOAuth2RefreshTokenSession),
sqlInsertOAuth2PKCERequestSession: fmt.Sprintf(queryFmtInsertOAuth2Session, tableOAuth2PKCERequestSession),
sqlSelectOAuth2PKCERequestSession: fmt.Sprintf(queryFmtSelectOAuth2Session, tableOAuth2PKCERequestSession),
sqlRevokeOAuth2PKCERequestSession: fmt.Sprintf(queryFmtRevokeOAuth2Session, tableOAuth2PKCERequestSession),
sqlRevokeOAuth2PKCERequestSessionByRequestID: fmt.Sprintf(queryFmtRevokeOAuth2SessionByRequestID, tableOAuth2PKCERequestSession),
sqlDeactivateOAuth2PKCERequestSession: fmt.Sprintf(queryFmtDeactivateOAuth2Session, tableOAuth2PKCERequestSession),
sqlDeactivateOAuth2PKCERequestSessionByRequestID: fmt.Sprintf(queryFmtDeactivateOAuth2SessionByRequestID, tableOAuth2PKCERequestSession),
sqlInsertOAuth2AuthorizeCodeSession: fmt.Sprintf(queryFmtInsertOAuth2Session, tableOAuth2AuthorizeCodeSession),
sqlSelectOAuth2AuthorizeCodeSession: fmt.Sprintf(queryFmtSelectOAuth2Session, tableOAuth2AuthorizeCodeSession),
sqlRevokeOAuth2AuthorizeCodeSession: fmt.Sprintf(queryFmtRevokeOAuth2Session, tableOAuth2AuthorizeCodeSession),
sqlRevokeOAuth2AuthorizeCodeSessionByRequestID: fmt.Sprintf(queryFmtRevokeOAuth2SessionByRequestID, tableOAuth2AuthorizeCodeSession),
sqlDeactivateOAuth2AuthorizeCodeSession: fmt.Sprintf(queryFmtDeactivateOAuth2Session, tableOAuth2AuthorizeCodeSession),
sqlDeactivateOAuth2AuthorizeCodeSessionByRequestID: fmt.Sprintf(queryFmtDeactivateOAuth2SessionByRequestID, tableOAuth2AuthorizeCodeSession),
sqlInsertOAuth2OpenIDConnectSession: fmt.Sprintf(queryFmtInsertOAuth2Session, tableOAuth2OpenIDConnectSession),
sqlSelectOAuth2OpenIDConnectSession: fmt.Sprintf(queryFmtSelectOAuth2Session, tableOAuth2OpenIDConnectSession),
@ -114,8 +107,19 @@ func NewSQLProvider(config *schema.Configuration, name, driverName, dataSourceNa
sqlDeactivateOAuth2OpenIDConnectSession: fmt.Sprintf(queryFmtDeactivateOAuth2Session, tableOAuth2OpenIDConnectSession),
sqlDeactivateOAuth2OpenIDConnectSessionByRequestID: fmt.Sprintf(queryFmtDeactivateOAuth2SessionByRequestID, tableOAuth2OpenIDConnectSession),
sqlUpsertOAuth2BlacklistedJTI: fmt.Sprintf(queryFmtUpsertOAuth2BlacklistedJTI, tableOAuth2BlacklistedJTI),
sqlSelectOAuth2BlacklistedJTI: fmt.Sprintf(queryFmtSelectOAuth2BlacklistedJTI, tableOAuth2BlacklistedJTI),
sqlInsertOAuth2PKCERequestSession: fmt.Sprintf(queryFmtInsertOAuth2Session, tableOAuth2PKCERequestSession),
sqlSelectOAuth2PKCERequestSession: fmt.Sprintf(queryFmtSelectOAuth2Session, tableOAuth2PKCERequestSession),
sqlRevokeOAuth2PKCERequestSession: fmt.Sprintf(queryFmtRevokeOAuth2Session, tableOAuth2PKCERequestSession),
sqlRevokeOAuth2PKCERequestSessionByRequestID: fmt.Sprintf(queryFmtRevokeOAuth2SessionByRequestID, tableOAuth2PKCERequestSession),
sqlDeactivateOAuth2PKCERequestSession: fmt.Sprintf(queryFmtDeactivateOAuth2Session, tableOAuth2PKCERequestSession),
sqlDeactivateOAuth2PKCERequestSessionByRequestID: fmt.Sprintf(queryFmtDeactivateOAuth2SessionByRequestID, tableOAuth2PKCERequestSession),
sqlInsertOAuth2RefreshTokenSession: fmt.Sprintf(queryFmtInsertOAuth2Session, tableOAuth2RefreshTokenSession),
sqlSelectOAuth2RefreshTokenSession: fmt.Sprintf(queryFmtSelectOAuth2Session, tableOAuth2RefreshTokenSession),
sqlRevokeOAuth2RefreshTokenSession: fmt.Sprintf(queryFmtRevokeOAuth2Session, tableOAuth2RefreshTokenSession),
sqlRevokeOAuth2RefreshTokenSessionByRequestID: fmt.Sprintf(queryFmtRevokeOAuth2SessionByRequestID, tableOAuth2RefreshTokenSession),
sqlDeactivateOAuth2RefreshTokenSession: fmt.Sprintf(queryFmtDeactivateOAuth2Session, tableOAuth2RefreshTokenSession),
sqlDeactivateOAuth2RefreshTokenSessionByRequestID: fmt.Sprintf(queryFmtDeactivateOAuth2SessionByRequestID, tableOAuth2RefreshTokenSession),
sqlInsertMigration: fmt.Sprintf(queryFmtInsertMigration, tableMigrations),
sqlSelectMigrations: fmt.Sprintf(queryFmtSelectMigrations, tableMigrations),
@ -224,13 +228,18 @@ type SQLProvider struct {
sqlDeactivateOAuth2AccessTokenSession string
sqlDeactivateOAuth2AccessTokenSessionByRequestID string
// Table: oauth2_refresh_token_session.
sqlInsertOAuth2RefreshTokenSession string
sqlSelectOAuth2RefreshTokenSession string
sqlRevokeOAuth2RefreshTokenSession string
sqlRevokeOAuth2RefreshTokenSessionByRequestID string
sqlDeactivateOAuth2RefreshTokenSession string
sqlDeactivateOAuth2RefreshTokenSessionByRequestID string
// Table: oauth2_openid_connect_session.
sqlInsertOAuth2OpenIDConnectSession string
sqlSelectOAuth2OpenIDConnectSession string
sqlRevokeOAuth2OpenIDConnectSession string
sqlRevokeOAuth2OpenIDConnectSessionByRequestID string
sqlDeactivateOAuth2OpenIDConnectSession string
sqlDeactivateOAuth2OpenIDConnectSessionByRequestID string
// Table: oauth2_par_context.
sqlInsertOAuth2PARContext string
sqlSelectOAuth2PARContext string
sqlRevokeOAuth2PARContext string
// Table: oauth2_pkce_request_session.
sqlInsertOAuth2PKCERequestSession string
@ -240,13 +249,13 @@ type SQLProvider struct {
sqlDeactivateOAuth2PKCERequestSession string
sqlDeactivateOAuth2PKCERequestSessionByRequestID string
// Table: oauth2_openid_connect_session.
sqlInsertOAuth2OpenIDConnectSession string
sqlSelectOAuth2OpenIDConnectSession string
sqlRevokeOAuth2OpenIDConnectSession string
sqlRevokeOAuth2OpenIDConnectSessionByRequestID string
sqlDeactivateOAuth2OpenIDConnectSession string
sqlDeactivateOAuth2OpenIDConnectSessionByRequestID string
// Table: oauth2_refresh_token_session.
sqlInsertOAuth2RefreshTokenSession string
sqlSelectOAuth2RefreshTokenSession string
sqlRevokeOAuth2RefreshTokenSession string
sqlRevokeOAuth2RefreshTokenSessionByRequestID string
sqlDeactivateOAuth2RefreshTokenSession string
sqlDeactivateOAuth2RefreshTokenSessionByRequestID string
sqlUpsertOAuth2BlacklistedJTI string
sqlSelectOAuth2BlacklistedJTI string
@ -339,19 +348,19 @@ func (p *SQLProvider) Rollback(ctx context.Context) (err error) {
}
// SaveUserOpaqueIdentifier saves a new opaque user identifier to the database.
func (p *SQLProvider) SaveUserOpaqueIdentifier(ctx context.Context, opaqueID model.UserOpaqueIdentifier) (err error) {
if _, err = p.db.ExecContext(ctx, p.sqlInsertUserOpaqueIdentifier, opaqueID.Service, opaqueID.SectorID, opaqueID.Username, opaqueID.Identifier); err != nil {
return fmt.Errorf("error inserting user opaque id for user '%s' with opaque id '%s': %w", opaqueID.Username, opaqueID.Identifier.String(), err)
func (p *SQLProvider) SaveUserOpaqueIdentifier(ctx context.Context, subject model.UserOpaqueIdentifier) (err error) {
if _, err = p.db.ExecContext(ctx, p.sqlInsertUserOpaqueIdentifier, subject.Service, subject.SectorID, subject.Username, subject.Identifier); err != nil {
return fmt.Errorf("error inserting user opaque id for user '%s' with opaque id '%s': %w", subject.Username, subject.Identifier.String(), err)
}
return nil
}
// LoadUserOpaqueIdentifier selects an opaque user identifier from the database.
func (p *SQLProvider) LoadUserOpaqueIdentifier(ctx context.Context, opaqueUUID uuid.UUID) (opaqueID *model.UserOpaqueIdentifier, err error) {
opaqueID = &model.UserOpaqueIdentifier{}
func (p *SQLProvider) LoadUserOpaqueIdentifier(ctx context.Context, identifier uuid.UUID) (subject *model.UserOpaqueIdentifier, err error) {
subject = &model.UserOpaqueIdentifier{}
if err = p.db.GetContext(ctx, opaqueID, p.sqlSelectUserOpaqueIdentifier, opaqueUUID); err != nil {
if err = p.db.GetContext(ctx, subject, p.sqlSelectUserOpaqueIdentifier, identifier); err != nil {
switch {
case errors.Is(err, sql.ErrNoRows):
return nil, nil
@ -360,11 +369,11 @@ func (p *SQLProvider) LoadUserOpaqueIdentifier(ctx context.Context, opaqueUUID u
}
}
return opaqueID, nil
return subject, nil
}
// LoadUserOpaqueIdentifiers selects an opaque user identifiers from the database.
func (p *SQLProvider) LoadUserOpaqueIdentifiers(ctx context.Context) (opaqueIDs []model.UserOpaqueIdentifier, err error) {
func (p *SQLProvider) LoadUserOpaqueIdentifiers(ctx context.Context) (identifiers []model.UserOpaqueIdentifier, err error) {
var rows *sqlx.Rows
if rows, err = p.db.QueryxContext(ctx, p.sqlSelectUserOpaqueIdentifiers); err != nil {
@ -380,17 +389,17 @@ func (p *SQLProvider) LoadUserOpaqueIdentifiers(ctx context.Context) (opaqueIDs
return nil, fmt.Errorf("error selecting user opaque identifiers: error scanning row: %w", err)
}
opaqueIDs = append(opaqueIDs, *opaqueID)
identifiers = append(identifiers, *opaqueID)
}
return opaqueIDs, nil
return identifiers, nil
}
// LoadUserOpaqueIdentifierBySignature selects an opaque user identifier from the database given a service name, sector id, and username.
func (p *SQLProvider) LoadUserOpaqueIdentifierBySignature(ctx context.Context, service, sectorID, username string) (opaqueID *model.UserOpaqueIdentifier, err error) {
opaqueID = &model.UserOpaqueIdentifier{}
func (p *SQLProvider) LoadUserOpaqueIdentifierBySignature(ctx context.Context, service, sectorID, username string) (subject *model.UserOpaqueIdentifier, err error) {
subject = &model.UserOpaqueIdentifier{}
if err = p.db.GetContext(ctx, opaqueID, p.sqlSelectUserOpaqueIdentifierBySignature, service, sectorID, username); err != nil {
if err = p.db.GetContext(ctx, subject, p.sqlSelectUserOpaqueIdentifierBySignature, service, sectorID, username); err != nil {
switch {
case errors.Is(err, sql.ErrNoRows):
return nil, nil
@ -399,7 +408,7 @@ func (p *SQLProvider) LoadUserOpaqueIdentifierBySignature(ctx context.Context, s
}
}
return opaqueID, nil
return subject, nil
}
// SaveOAuth2ConsentSession inserts an OAuth2.0 consent session.
@ -496,22 +505,22 @@ func (p *SQLProvider) SaveOAuth2Session(ctx context.Context, sessionType OAuth2S
var query string
switch sessionType {
case OAuth2SessionTypeAuthorizeCode:
query = p.sqlInsertOAuth2AuthorizeCodeSession
case OAuth2SessionTypeAccessToken:
query = p.sqlInsertOAuth2AccessTokenSession
case OAuth2SessionTypeRefreshToken:
query = p.sqlInsertOAuth2RefreshTokenSession
case OAuth2SessionTypePKCEChallenge:
query = p.sqlInsertOAuth2PKCERequestSession
case OAuth2SessionTypeAuthorizeCode:
query = p.sqlInsertOAuth2AuthorizeCodeSession
case OAuth2SessionTypeOpenIDConnect:
query = p.sqlInsertOAuth2OpenIDConnectSession
case OAuth2SessionTypePKCEChallenge:
query = p.sqlInsertOAuth2PKCERequestSession
case OAuth2SessionTypeRefreshToken:
query = p.sqlInsertOAuth2RefreshTokenSession
default:
return fmt.Errorf("error inserting oauth2 session for subject '%s' and request id '%s': unknown oauth2 session type '%s'", session.Subject, session.RequestID, sessionType)
}
if session.Session, err = p.encrypt(session.Session); err != nil {
return fmt.Errorf("error encrypting the oauth2 %s session data for subject '%s' and request id '%s' and challenge id '%s': %w", sessionType, session.Subject, session.RequestID, session.ChallengeID.String(), err)
return fmt.Errorf("error encrypting oauth2 %s session data for subject '%s' and request id '%s' and challenge id '%s': %w", sessionType, session.Subject, session.RequestID, session.ChallengeID.String(), err)
}
_, err = p.db.ExecContext(ctx, query,
@ -532,16 +541,16 @@ func (p *SQLProvider) RevokeOAuth2Session(ctx context.Context, sessionType OAuth
var query string
switch sessionType {
case OAuth2SessionTypeAuthorizeCode:
query = p.sqlRevokeOAuth2AuthorizeCodeSession
case OAuth2SessionTypeAccessToken:
query = p.sqlRevokeOAuth2AccessTokenSession
case OAuth2SessionTypeRefreshToken:
query = p.sqlRevokeOAuth2RefreshTokenSession
case OAuth2SessionTypePKCEChallenge:
query = p.sqlRevokeOAuth2PKCERequestSession
case OAuth2SessionTypeAuthorizeCode:
query = p.sqlRevokeOAuth2AuthorizeCodeSession
case OAuth2SessionTypeOpenIDConnect:
query = p.sqlRevokeOAuth2OpenIDConnectSession
case OAuth2SessionTypePKCEChallenge:
query = p.sqlRevokeOAuth2PKCERequestSession
case OAuth2SessionTypeRefreshToken:
query = p.sqlRevokeOAuth2RefreshTokenSession
default:
return fmt.Errorf("error revoking oauth2 session with signature '%s': unknown oauth2 session type '%s'", signature, sessionType.String())
}
@ -558,16 +567,16 @@ func (p *SQLProvider) RevokeOAuth2SessionByRequestID(ctx context.Context, sessio
var query string
switch sessionType {
case OAuth2SessionTypeAuthorizeCode:
query = p.sqlRevokeOAuth2AuthorizeCodeSessionByRequestID
case OAuth2SessionTypeAccessToken:
query = p.sqlRevokeOAuth2AccessTokenSessionByRequestID
case OAuth2SessionTypeRefreshToken:
query = p.sqlRevokeOAuth2RefreshTokenSessionByRequestID
case OAuth2SessionTypePKCEChallenge:
query = p.sqlRevokeOAuth2PKCERequestSessionByRequestID
case OAuth2SessionTypeAuthorizeCode:
query = p.sqlRevokeOAuth2AuthorizeCodeSessionByRequestID
case OAuth2SessionTypeOpenIDConnect:
query = p.sqlRevokeOAuth2OpenIDConnectSessionByRequestID
case OAuth2SessionTypePKCEChallenge:
query = p.sqlRevokeOAuth2PKCERequestSessionByRequestID
case OAuth2SessionTypeRefreshToken:
query = p.sqlRevokeOAuth2RefreshTokenSessionByRequestID
default:
return fmt.Errorf("error revoking oauth2 session with request id '%s': unknown oauth2 session type '%s'", requestID, sessionType.String())
}
@ -584,16 +593,16 @@ func (p *SQLProvider) DeactivateOAuth2Session(ctx context.Context, sessionType O
var query string
switch sessionType {
case OAuth2SessionTypeAuthorizeCode:
query = p.sqlDeactivateOAuth2AuthorizeCodeSession
case OAuth2SessionTypeAccessToken:
query = p.sqlDeactivateOAuth2AccessTokenSession
case OAuth2SessionTypeRefreshToken:
query = p.sqlDeactivateOAuth2RefreshTokenSession
case OAuth2SessionTypePKCEChallenge:
query = p.sqlDeactivateOAuth2PKCERequestSession
case OAuth2SessionTypeAuthorizeCode:
query = p.sqlDeactivateOAuth2AuthorizeCodeSession
case OAuth2SessionTypeOpenIDConnect:
query = p.sqlDeactivateOAuth2OpenIDConnectSession
case OAuth2SessionTypePKCEChallenge:
query = p.sqlDeactivateOAuth2PKCERequestSession
case OAuth2SessionTypeRefreshToken:
query = p.sqlDeactivateOAuth2RefreshTokenSession
default:
return fmt.Errorf("error deactivating oauth2 session with signature '%s': unknown oauth2 session type '%s'", signature, sessionType.String())
}
@ -610,16 +619,16 @@ func (p *SQLProvider) DeactivateOAuth2SessionByRequestID(ctx context.Context, se
var query string
switch sessionType {
case OAuth2SessionTypeAuthorizeCode:
query = p.sqlDeactivateOAuth2AuthorizeCodeSession
case OAuth2SessionTypeAccessToken:
query = p.sqlDeactivateOAuth2AccessTokenSessionByRequestID
case OAuth2SessionTypeRefreshToken:
query = p.sqlDeactivateOAuth2RefreshTokenSessionByRequestID
case OAuth2SessionTypePKCEChallenge:
query = p.sqlDeactivateOAuth2PKCERequestSessionByRequestID
case OAuth2SessionTypeAuthorizeCode:
query = p.sqlDeactivateOAuth2AuthorizeCodeSession
case OAuth2SessionTypeOpenIDConnect:
query = p.sqlDeactivateOAuth2OpenIDConnectSessionByRequestID
case OAuth2SessionTypePKCEChallenge:
query = p.sqlDeactivateOAuth2PKCERequestSessionByRequestID
case OAuth2SessionTypeRefreshToken:
query = p.sqlDeactivateOAuth2RefreshTokenSessionByRequestID
default:
return fmt.Errorf("error deactivating oauth2 session with request id '%s': unknown oauth2 session type '%s'", requestID, sessionType.String())
}
@ -636,16 +645,16 @@ func (p *SQLProvider) LoadOAuth2Session(ctx context.Context, sessionType OAuth2S
var query string
switch sessionType {
case OAuth2SessionTypeAuthorizeCode:
query = p.sqlSelectOAuth2AuthorizeCodeSession
case OAuth2SessionTypeAccessToken:
query = p.sqlSelectOAuth2AccessTokenSession
case OAuth2SessionTypeRefreshToken:
query = p.sqlSelectOAuth2RefreshTokenSession
case OAuth2SessionTypePKCEChallenge:
query = p.sqlSelectOAuth2PKCERequestSession
case OAuth2SessionTypeAuthorizeCode:
query = p.sqlSelectOAuth2AuthorizeCodeSession
case OAuth2SessionTypeOpenIDConnect:
query = p.sqlSelectOAuth2OpenIDConnectSession
case OAuth2SessionTypePKCEChallenge:
query = p.sqlSelectOAuth2PKCERequestSession
case OAuth2SessionTypeRefreshToken:
query = p.sqlSelectOAuth2RefreshTokenSession
default:
return nil, fmt.Errorf("error selecting oauth2 session: unknown oauth2 session type '%s'", sessionType.String())
}
@ -663,6 +672,45 @@ func (p *SQLProvider) LoadOAuth2Session(ctx context.Context, sessionType OAuth2S
return session, nil
}
// SaveOAuth2PARContext save a OAuth2PARContext to the database.
func (p *SQLProvider) SaveOAuth2PARContext(ctx context.Context, par model.OAuth2PARContext) (err error) {
if par.Session, err = p.encrypt(par.Session); err != nil {
return fmt.Errorf("error encrypting oauth2 pushed authorization request context data for with signature '%s' and request id '%s': %w", par.Signature, par.RequestID, err)
}
if _, err = p.db.ExecContext(ctx, p.sqlInsertOAuth2PARContext,
par.Signature, par.RequestID, par.ClientID, par.RequestedAt, par.Scopes, par.Audience, par.HandledResponseTypes,
par.ResponseMode, par.DefaultResponseMode, par.Revoked, par.Form, par.Session); err != nil {
return fmt.Errorf("error inserting oauth2 pushed authorization request context data for with signature '%s' and request id '%s': %w", par.Signature, par.RequestID, err)
}
return nil
}
// LoadOAuth2PARContext loads a OAuth2PARContext from the database.
func (p *SQLProvider) LoadOAuth2PARContext(ctx context.Context, signature string) (par *model.OAuth2PARContext, err error) {
par = &model.OAuth2PARContext{}
if err = p.db.GetContext(ctx, par, p.sqlSelectOAuth2PARContext, signature); err != nil {
return nil, fmt.Errorf("error selecting oauth2 pushed authorization request context with signature '%s': %w", signature, err)
}
if par.Session, err = p.decrypt(par.Session); err != nil {
return nil, fmt.Errorf("error decrypting oauth2 oauth2 pushed authorization request context data with signature '%s' and request id '%s': %w", signature, par.RequestID, err)
}
return par, nil
}
// RevokeOAuth2PARContext marks a OAuth2PARContext as revoked in the database.
func (p *SQLProvider) RevokeOAuth2PARContext(ctx context.Context, signature string) (err error) {
if _, err = p.db.ExecContext(ctx, p.sqlRevokeOAuth2PARContext, signature); err != nil {
return fmt.Errorf("error revoking oauth2 pushed authorization request context with signature '%s': %w", signature, err)
}
return nil
}
// SaveOAuth2BlacklistedJTI saves a OAuth2BlacklistedJTI to the database.
func (p *SQLProvider) SaveOAuth2BlacklistedJTI(ctx context.Context, blacklistedJTI model.OAuth2BlacklistedJTI) (err error) {
if _, err = p.db.ExecContext(ctx, p.sqlUpsertOAuth2BlacklistedJTI, blacklistedJTI.Signature, blacklistedJTI.ExpiresAt); err != nil {
@ -762,7 +810,7 @@ func (p *SQLProvider) FindIdentityVerification(ctx context.Context, jti string)
// SaveTOTPConfiguration save a TOTP configuration of a given user in the database.
func (p *SQLProvider) SaveTOTPConfiguration(ctx context.Context, config model.TOTPConfiguration) (err error) {
if config.Secret, err = p.encrypt(config.Secret); err != nil {
return fmt.Errorf("error encrypting the TOTP configuration secret for user '%s': %w", config.Username, err)
return fmt.Errorf("error encrypting TOTP configuration secret for user '%s': %w", config.Username, err)
}
if _, err = p.db.ExecContext(ctx, p.sqlUpsertTOTPConfig,
@ -806,7 +854,7 @@ func (p *SQLProvider) LoadTOTPConfiguration(ctx context.Context, username string
}
if config.Secret, err = p.decrypt(config.Secret); err != nil {
return nil, fmt.Errorf("error decrypting the TOTP secret for user '%s': %w", username, err)
return nil, fmt.Errorf("error decrypting TOTP secret for user '%s': %w", username, err)
}
return config, nil
@ -836,7 +884,7 @@ func (p *SQLProvider) LoadTOTPConfigurations(ctx context.Context, limit, page in
// SaveWebauthnDevice saves a registered Webauthn device.
func (p *SQLProvider) SaveWebauthnDevice(ctx context.Context, device model.WebauthnDevice) (err error) {
if device.PublicKey, err = p.encrypt(device.PublicKey); err != nil {
return fmt.Errorf("error encrypting the Webauthn device public key for user '%s' kid '%x': %w", device.Username, device.KID, err)
return fmt.Errorf("error encrypting Webauthn device public key for user '%s' kid '%x': %w", device.Username, device.KID, err)
}
if _, err = p.db.ExecContext(ctx, p.sqlUpsertWebauthnDevice,

View File

@ -86,13 +86,6 @@ func NewPostgreSQLProvider(config *schema.Configuration, tconfig *tls.Config, gl
provider.sqlUpdateOAuth2ConsentSessionGranted = provider.db.Rebind(provider.sqlUpdateOAuth2ConsentSessionGranted)
provider.sqlSelectOAuth2ConsentSessionByChallengeID = provider.db.Rebind(provider.sqlSelectOAuth2ConsentSessionByChallengeID)
provider.sqlInsertOAuth2AuthorizeCodeSession = provider.db.Rebind(provider.sqlInsertOAuth2AuthorizeCodeSession)
provider.sqlRevokeOAuth2AuthorizeCodeSession = provider.db.Rebind(provider.sqlRevokeOAuth2AuthorizeCodeSession)
provider.sqlRevokeOAuth2AuthorizeCodeSessionByRequestID = provider.db.Rebind(provider.sqlRevokeOAuth2AuthorizeCodeSessionByRequestID)
provider.sqlDeactivateOAuth2AuthorizeCodeSession = provider.db.Rebind(provider.sqlDeactivateOAuth2AuthorizeCodeSession)
provider.sqlDeactivateOAuth2AuthorizeCodeSessionByRequestID = provider.db.Rebind(provider.sqlDeactivateOAuth2AuthorizeCodeSessionByRequestID)
provider.sqlSelectOAuth2AuthorizeCodeSession = provider.db.Rebind(provider.sqlSelectOAuth2AuthorizeCodeSession)
provider.sqlInsertOAuth2AccessTokenSession = provider.db.Rebind(provider.sqlInsertOAuth2AccessTokenSession)
provider.sqlRevokeOAuth2AccessTokenSession = provider.db.Rebind(provider.sqlRevokeOAuth2AccessTokenSession)
provider.sqlRevokeOAuth2AccessTokenSessionByRequestID = provider.db.Rebind(provider.sqlRevokeOAuth2AccessTokenSessionByRequestID)
@ -100,12 +93,23 @@ func NewPostgreSQLProvider(config *schema.Configuration, tconfig *tls.Config, gl
provider.sqlDeactivateOAuth2AccessTokenSessionByRequestID = provider.db.Rebind(provider.sqlDeactivateOAuth2AccessTokenSessionByRequestID)
provider.sqlSelectOAuth2AccessTokenSession = provider.db.Rebind(provider.sqlSelectOAuth2AccessTokenSession)
provider.sqlInsertOAuth2RefreshTokenSession = provider.db.Rebind(provider.sqlInsertOAuth2RefreshTokenSession)
provider.sqlRevokeOAuth2RefreshTokenSession = provider.db.Rebind(provider.sqlRevokeOAuth2RefreshTokenSession)
provider.sqlRevokeOAuth2RefreshTokenSessionByRequestID = provider.db.Rebind(provider.sqlRevokeOAuth2RefreshTokenSessionByRequestID)
provider.sqlDeactivateOAuth2RefreshTokenSession = provider.db.Rebind(provider.sqlDeactivateOAuth2RefreshTokenSession)
provider.sqlDeactivateOAuth2RefreshTokenSessionByRequestID = provider.db.Rebind(provider.sqlDeactivateOAuth2RefreshTokenSessionByRequestID)
provider.sqlSelectOAuth2RefreshTokenSession = provider.db.Rebind(provider.sqlSelectOAuth2RefreshTokenSession)
provider.sqlInsertOAuth2AuthorizeCodeSession = provider.db.Rebind(provider.sqlInsertOAuth2AuthorizeCodeSession)
provider.sqlRevokeOAuth2AuthorizeCodeSession = provider.db.Rebind(provider.sqlRevokeOAuth2AuthorizeCodeSession)
provider.sqlRevokeOAuth2AuthorizeCodeSessionByRequestID = provider.db.Rebind(provider.sqlRevokeOAuth2AuthorizeCodeSessionByRequestID)
provider.sqlDeactivateOAuth2AuthorizeCodeSession = provider.db.Rebind(provider.sqlDeactivateOAuth2AuthorizeCodeSession)
provider.sqlDeactivateOAuth2AuthorizeCodeSessionByRequestID = provider.db.Rebind(provider.sqlDeactivateOAuth2AuthorizeCodeSessionByRequestID)
provider.sqlSelectOAuth2AuthorizeCodeSession = provider.db.Rebind(provider.sqlSelectOAuth2AuthorizeCodeSession)
provider.sqlInsertOAuth2OpenIDConnectSession = provider.db.Rebind(provider.sqlInsertOAuth2OpenIDConnectSession)
provider.sqlRevokeOAuth2OpenIDConnectSession = provider.db.Rebind(provider.sqlRevokeOAuth2OpenIDConnectSession)
provider.sqlRevokeOAuth2OpenIDConnectSessionByRequestID = provider.db.Rebind(provider.sqlRevokeOAuth2OpenIDConnectSessionByRequestID)
provider.sqlDeactivateOAuth2OpenIDConnectSession = provider.db.Rebind(provider.sqlDeactivateOAuth2OpenIDConnectSession)
provider.sqlDeactivateOAuth2OpenIDConnectSessionByRequestID = provider.db.Rebind(provider.sqlDeactivateOAuth2OpenIDConnectSessionByRequestID)
provider.sqlSelectOAuth2OpenIDConnectSession = provider.db.Rebind(provider.sqlSelectOAuth2OpenIDConnectSession)
provider.sqlInsertOAuth2PARContext = provider.db.Rebind(provider.sqlInsertOAuth2PARContext)
provider.sqlRevokeOAuth2PARContext = provider.db.Rebind(provider.sqlRevokeOAuth2PARContext)
provider.sqlSelectOAuth2PARContext = provider.db.Rebind(provider.sqlSelectOAuth2PARContext)
provider.sqlInsertOAuth2PKCERequestSession = provider.db.Rebind(provider.sqlInsertOAuth2PKCERequestSession)
provider.sqlRevokeOAuth2PKCERequestSession = provider.db.Rebind(provider.sqlRevokeOAuth2PKCERequestSession)
@ -114,12 +118,12 @@ func NewPostgreSQLProvider(config *schema.Configuration, tconfig *tls.Config, gl
provider.sqlDeactivateOAuth2PKCERequestSessionByRequestID = provider.db.Rebind(provider.sqlDeactivateOAuth2PKCERequestSessionByRequestID)
provider.sqlSelectOAuth2PKCERequestSession = provider.db.Rebind(provider.sqlSelectOAuth2PKCERequestSession)
provider.sqlInsertOAuth2OpenIDConnectSession = provider.db.Rebind(provider.sqlInsertOAuth2OpenIDConnectSession)
provider.sqlRevokeOAuth2OpenIDConnectSession = provider.db.Rebind(provider.sqlRevokeOAuth2OpenIDConnectSession)
provider.sqlRevokeOAuth2OpenIDConnectSessionByRequestID = provider.db.Rebind(provider.sqlRevokeOAuth2OpenIDConnectSessionByRequestID)
provider.sqlDeactivateOAuth2OpenIDConnectSession = provider.db.Rebind(provider.sqlDeactivateOAuth2OpenIDConnectSession)
provider.sqlDeactivateOAuth2OpenIDConnectSessionByRequestID = provider.db.Rebind(provider.sqlDeactivateOAuth2OpenIDConnectSessionByRequestID)
provider.sqlSelectOAuth2OpenIDConnectSession = provider.db.Rebind(provider.sqlSelectOAuth2OpenIDConnectSession)
provider.sqlInsertOAuth2RefreshTokenSession = provider.db.Rebind(provider.sqlInsertOAuth2RefreshTokenSession)
provider.sqlRevokeOAuth2RefreshTokenSession = provider.db.Rebind(provider.sqlRevokeOAuth2RefreshTokenSession)
provider.sqlRevokeOAuth2RefreshTokenSessionByRequestID = provider.db.Rebind(provider.sqlRevokeOAuth2RefreshTokenSessionByRequestID)
provider.sqlDeactivateOAuth2RefreshTokenSession = provider.db.Rebind(provider.sqlDeactivateOAuth2RefreshTokenSession)
provider.sqlDeactivateOAuth2RefreshTokenSessionByRequestID = provider.db.Rebind(provider.sqlDeactivateOAuth2RefreshTokenSessionByRequestID)
provider.sqlSelectOAuth2RefreshTokenSession = provider.db.Rebind(provider.sqlSelectOAuth2RefreshTokenSession)
provider.sqlSelectOAuth2BlacklistedJTI = provider.db.Rebind(provider.sqlSelectOAuth2BlacklistedJTI)

View File

@ -314,6 +314,19 @@ const (
SET active = FALSE
WHERE request_id = ?;`
queryFmtSelectOAuth2PARContext = `
SELECT id, signature, request_id, client_id, requested_at, scopes, audience,
handled_response_types, response_mode, response_mode_default, revoked,
form_data, session_data
FROM %s
WHERE signature = ? AND revoked = FALSE;`
queryFmtInsertOAuth2PARContext = `
INSERT INTO %s (signature, request_id, client_id, requested_at, scopes, audience,
handled_response_types, response_mode, response_mode_default, revoked,
form_data, session_data)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`
queryFmtSelectOAuth2BlacklistedJTI = `
SELECT id, signature, expires_at
FROM %s

View File

@ -1,4 +1,4 @@
FROM golang:1.20.1-alpine
FROM golang:1.20.2-alpine
ARG USER_ID
ARG GROUP_ID

View File

@ -2,7 +2,7 @@
version: '3'
services:
k3d:
image: ghcr.io/k3d-io/k3d:5.4.7-dind
image: ghcr.io/k3d-io/k3d:5.4.8-dind
volumes:
- './example/kube:/authelia'
- './example/kube/authelia/configs/configuration.yml:/configmaps/authelia/configuration.yml'

View File

@ -164,7 +164,7 @@ http {
# to the virtual endpoint introduced by nginx and declared in the next block.
location / {
## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource.
auth_request /authelia;
auth_request /internal/authelia/authz;
## Set the $target_url variable based on the original request.
set $target_url $scheme://$http_host$request_uri;
@ -209,7 +209,7 @@ http {
}
# Virtual endpoint forwarding requests to Authelia server.
location /authelia {
location /internal/authelia/authz {
## Essential Proxy Configuration
internal;
proxy_pass $upstream_authelia;
@ -250,7 +250,7 @@ http {
# Used by suites to test the forwarded users and groups headers produced by Authelia.
location /headers {
## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource.
auth_request /authelia;
auth_request /internal/authelia/authz;
## Set the $target_url variable based on the original request.
set $target_url $scheme://$http_host$request_uri;
@ -307,7 +307,7 @@ http {
# to the virtual endpoint introduced by nginx and declared in the next block.
location / {
## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource.
auth_request /authelia;
auth_request /internal/authelia/authz;
## Set the $target_url variable based on the original request.
set $target_url $scheme://$http_host$request_uri;
@ -346,7 +346,7 @@ http {
}
# Virtual endpoint forwarding requests to Authelia server.
location /authelia {
location /internal/authelia/authz {
## Essential Proxy Configuration
internal;
proxy_pass $upstream_authelia;
@ -356,7 +356,6 @@ http {
# Those headers will be used by Authelia to deduce the target url of the user.
#
# X-Forwarded-Proto is mandatory since Authelia uses the "trust proxy" option.
# See https://expressjs.com/en/guide/behind-proxies.html
proxy_set_header X-Original-Method $request_method;
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

View File

@ -1132,6 +1132,7 @@ func (s *CLISuite) TestStorage05ShouldChangeEncryptionKey() {
s.Assert().Contains(output, "\n\n\tTable (oauth2_openid_connect_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n")
s.Assert().Contains(output, "\n\n\tTable (oauth2_pkce_request_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n")
s.Assert().Contains(output, "\n\n\tTable (oauth2_refresh_token_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n")
s.Assert().Contains(output, "\n\n\tTable (oauth2_par_context): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n")
s.Assert().Contains(output, "\n\n\tTable (totp_configurations): FAILURE\n\t\tInvalid Rows: 4\n\t\tTotal Rows: 4\n")
s.Assert().Contains(output, "\n\n\tTable (webauthn_devices): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n")
@ -1149,6 +1150,7 @@ func (s *CLISuite) TestStorage05ShouldChangeEncryptionKey() {
s.Assert().Contains(output, "\n\n\tTable (oauth2_openid_connect_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n")
s.Assert().Contains(output, "\n\n\tTable (oauth2_pkce_request_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n")
s.Assert().Contains(output, "\n\n\tTable (oauth2_refresh_token_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n")
s.Assert().Contains(output, "\n\n\tTable (oauth2_par_context): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n")
s.Assert().Contains(output, "\n\n\tTable (totp_configurations): SUCCESS\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 4\n")
s.Assert().Contains(output, "\n\n\tTable (webauthn_devices): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n")

View File

@ -1,8 +1,8 @@
This email has been sent to you in order to validate your identity.
This email has been sent to you in order to validate your identity. Purpose: {{ .Title }}.
If you did not initiate the process your credentials might have been compromised and you should reset your password and contact an administrator.
To setup your 2FA please visit the following URL: {{ .LinkURL }}
To confirm your identity please visit the following URL: {{ .LinkURL }}
This email was generated by a user with the IP {{ .RemoteIP }}.

View File

@ -104,8 +104,13 @@ func IsStringSliceContainsAll(needles []string, haystack []string) (inSlice bool
// IsStringSliceContainsAny checks if the haystack contains any of the strings in the needles.
func IsStringSliceContainsAny(needles []string, haystack []string) (inSlice bool) {
return IsStringSliceContainsAnyF(needles, haystack, IsStringInSlice)
}
// IsStringSliceContainsAnyF checks if the haystack contains any of the strings in the needles using the isInSlice func.
func IsStringSliceContainsAnyF(needles []string, haystack []string, isInSlice func(needle string, haystack []string) bool) (inSlice bool) {
for _, n := range needles {
if IsStringInSlice(n, haystack) {
if isInSlice(n, haystack) {
return true
}
}

View File

@ -26,12 +26,12 @@
"@fortawesome/free-solid-svg-icons": "6.3.0",
"@fortawesome/react-fontawesome": "0.2.0",
"@mui/icons-material": "5.11.11",
"@mui/material": "5.11.11",
"@mui/styles": "5.11.11",
"@mui/material": "5.11.12",
"@mui/styles": "5.11.12",
"axios": "1.3.4",
"broadcast-channel": "4.20.2",
"classnames": "2.3.2",
"i18next": "22.4.10",
"i18next": "22.4.11",
"i18next-browser-languagedetector": "7.0.1",
"i18next-http-backend": "2.1.1",
"qrcode.react": "3.1.0",
@ -148,18 +148,18 @@
"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "14.0.0",
"@types/jest": "29.4.0",
"@types/node": "18.14.4",
"@types/node": "18.15.0",
"@types/qrcode.react": "1.0.2",
"@types/react": "18.0.28",
"@types/react-dom": "18.0.11",
"@types/zxcvbn": "4.4.1",
"@typescript-eslint/eslint-plugin": "5.54.0",
"@typescript-eslint/parser": "5.54.0",
"@typescript-eslint/eslint-plugin": "5.54.1",
"@typescript-eslint/parser": "5.54.1",
"@vitejs/plugin-react": "3.1.0",
"esbuild": "0.17.10",
"esbuild": "0.17.11",
"esbuild-jest": "0.5.0",
"eslint": "8.35.0",
"eslint-config-prettier": "8.6.0",
"eslint-config-prettier": "8.7.0",
"eslint-config-react-app": "7.0.1",
"eslint-formatter-rdjson": "1.0.5",
"eslint-import-resolver-typescript": "3.5.3",
@ -169,8 +169,8 @@
"eslint-plugin-react": "7.32.2",
"eslint-plugin-react-hooks": "4.6.0",
"husky": "8.0.3",
"jest": "29.4.3",
"jest-environment-jsdom": "29.4.3",
"jest": "29.5.0",
"jest-environment-jsdom": "29.5.0",
"jest-transform-stub": "2.0.0",
"jest-watch-typeahead": "2.2.2",
"prettier": "2.8.4",

File diff suppressed because it is too large Load Diff