chore(deps): Update to openidconnect 0.4

Signed-off-by: MATILLAT Quentin <qmatillat@gmail.com>
This commit is contained in:
MATILLAT Quentin 2025-01-25 21:30:16 +01:00
parent f0d9126652
commit 2800b88b82
No known key found for this signature in database
GPG key ID: B9BAF56E288158D2
5 changed files with 68 additions and 118 deletions

View file

@ -19,8 +19,8 @@ tower-service = "0.3"
tower-layer = "0.3"
tower-sessions = { version = "0.14", default-features = false, features = [ "axum-core" ] }
http = "1.2"
openidconnect = "3.5"
openidconnect = "4.0"
serde = "1.0"
futures-util = "0.3"
reqwest = { version = "0.11", default-features = false }
reqwest = { version = "0.12", default-features = false }
urlencoding = "2.1"

View file

@ -17,7 +17,7 @@ dotenvy = "0.15"
[dev-dependencies]
testcontainers = "0.23"
tokio = { version = "1.43", features = ["rt-multi-thread"] }
reqwest = { version = "0.11", features = ["rustls-tls"], default-features = false }
reqwest = { version = "0.12", features = ["rustls-tls"], default-features = false }
env_logger = "0.11"
log = "0.4"
headless_chrome = "1.0"

View file

@ -16,11 +16,13 @@ pub enum ExtractorError {
#[error("could not build rp initiated logout uri")]
FailedToCreateRpInitiatedLogoutUri,
}
#[derive(Debug, Error)]
pub enum MiddlewareError {
#[error("configuration: {0:?}")]
Configuration(#[from] openidconnect::ConfigurationError),
#[error("access token hash invalid")]
AccessTokenHashInvalid,
@ -33,6 +35,9 @@ pub enum MiddlewareError {
#[error("signing: {0:?}")]
Signing(#[from] openidconnect::SigningError),
#[error("signature verification: {0:?}")]
Signature(#[from] openidconnect::SignatureVerificationError),
#[error("claims verification: {0:?}")]
ClaimsVerification(#[from] openidconnect::ClaimsVerificationError),
@ -49,7 +54,7 @@ pub enum MiddlewareError {
RequestToken(
#[from]
openidconnect::RequestTokenError<
openidconnect::reqwest::Error<reqwest::Error>,
openidconnect::HttpClientError<openidconnect::reqwest::Error>,
StandardErrorResponse<CoreErrorResponseType>,
>,
),
@ -76,7 +81,12 @@ pub enum Error {
InvalidEndSessionEndpoint(http::uri::InvalidUri),
#[error("discovery: {0:?}")]
Discovery(#[from] openidconnect::DiscoveryError<openidconnect::reqwest::Error<reqwest::Error>>),
Discovery(
#[from]
openidconnect::DiscoveryError<
openidconnect::HttpClientError<openidconnect::reqwest::Error>,
>,
),
#[error("extractor: {0:?}")]
Extractor(#[from] ExtractorError),

View file

@ -8,14 +8,13 @@ use http::Uri;
use openidconnect::{
core::{
CoreAuthDisplay, CoreAuthPrompt, CoreClaimName, CoreClaimType, CoreClientAuthMethod,
CoreErrorResponseType, CoreGenderClaim, CoreGrantType, CoreJsonWebKey, CoreJsonWebKeyType,
CoreJsonWebKeyUse, CoreJweContentEncryptionAlgorithm, CoreJweKeyManagementAlgorithm,
CoreJwsSigningAlgorithm, CoreResponseMode, CoreResponseType, CoreRevocableToken,
CoreRevocationErrorResponse, CoreSubjectIdentifierType, CoreTokenIntrospectionResponse,
CoreTokenType,
CoreErrorResponseType, CoreGenderClaim, CoreGrantType, CoreJsonWebKey,
CoreJweContentEncryptionAlgorithm, CoreJweKeyManagementAlgorithm, CoreJwsSigningAlgorithm,
CoreResponseMode, CoreResponseType, CoreRevocableToken, CoreRevocationErrorResponse,
CoreSubjectIdentifierType, CoreTokenIntrospectionResponse, CoreTokenType,
},
AccessToken, ClientId, ClientSecret, CsrfToken, EmptyExtraTokenFields, HttpRequest,
HttpResponse, IdTokenFields, IssuerUrl, Nonce, PkceCodeVerifier, RefreshToken,
AccessToken, ClientId, ClientSecret, CsrfToken, EmptyExtraTokenFields, EndpointMaybeSet,
EndpointNotSet, EndpointSet, IdTokenFields, IssuerUrl, Nonce, PkceCodeVerifier, RefreshToken,
StandardErrorResponse, StandardTokenResponse,
};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
@ -41,7 +40,6 @@ type OidcTokenResponse<AC> = StandardTokenResponse<
CoreGenderClaim,
CoreJweContentEncryptionAlgorithm,
CoreJwsSigningAlgorithm,
CoreJsonWebKeyType,
>,
CoreTokenType,
>;
@ -51,25 +49,34 @@ pub type IdToken<AZ> = openidconnect::IdToken<
CoreGenderClaim,
CoreJweContentEncryptionAlgorithm,
CoreJwsSigningAlgorithm,
CoreJsonWebKeyType,
>;
type Client<AC> = openidconnect::Client<
type Client<
AC,
HasAuthUrl = EndpointSet,
HasDeviceAuthUrl = EndpointNotSet,
HasIntrospectionUrl = EndpointNotSet,
HasRevocationUrl = EndpointNotSet,
HasTokenUrl = EndpointMaybeSet,
HasUserInfoUrl = EndpointMaybeSet,
> = openidconnect::Client<
AC,
CoreAuthDisplay,
CoreGenderClaim,
CoreJweContentEncryptionAlgorithm,
CoreJwsSigningAlgorithm,
CoreJsonWebKeyType,
CoreJsonWebKeyUse,
CoreJsonWebKey,
CoreAuthPrompt,
StandardErrorResponse<CoreErrorResponseType>,
OidcTokenResponse<AC>,
CoreTokenType,
CoreTokenIntrospectionResponse,
CoreRevocableToken,
CoreRevocationErrorResponse,
HasAuthUrl,
HasDeviceAuthUrl,
HasIntrospectionUrl,
HasRevocationUrl,
HasTokenUrl,
HasUserInfoUrl,
>;
pub type ProviderMetadata = openidconnect::ProviderMetadata<
@ -81,9 +88,6 @@ pub type ProviderMetadata = openidconnect::ProviderMetadata<
CoreGrantType,
CoreJweContentEncryptionAlgorithm,
CoreJweKeyManagementAlgorithm,
CoreJwsSigningAlgorithm,
CoreJsonWebKeyType,
CoreJsonWebKeyUse,
CoreJsonWebKey,
CoreResponseMode,
CoreResponseType,
@ -197,38 +201,8 @@ impl<AC: AdditionalClaims> OidcClient<AC> {
//TODO remove borrow with next breaking version
client: &reqwest::Client,
) -> Result<Self, Error> {
// modified version of `openidconnect::reqwest::async_client::async_http_client`.
let async_http_client = |request: HttpRequest| async move {
let mut request_builder = client
.request(request.method, request.url.as_str())
.body(request.body);
for (name, value) in &request.headers {
request_builder = request_builder.header(name.as_str(), value.as_bytes());
}
let request = request_builder
.build()
.map_err(openidconnect::reqwest::Error::Reqwest)?;
let response = client
.execute(request)
.await
.map_err(openidconnect::reqwest::Error::Reqwest)?;
let status_code = response.status();
let headers = response.headers().to_owned();
let chunks = response
.bytes()
.await
.map_err(openidconnect::reqwest::Error::Reqwest)?;
Ok(HttpResponse {
status_code,
headers,
body: chunks.to_vec(),
})
};
let provider_metadata =
ProviderMetadata::discover_async(IssuerUrl::new(issuer)?, async_http_client).await?;
ProviderMetadata::discover_async(IssuerUrl::new(issuer)?, client).await?;
Self::from_provider_metadata_and_client(
provider_metadata,
application_base_url,

View file

@ -1,6 +1,5 @@
use std::{
marker::PhantomData,
pin::Pin,
task::{Context, Poll},
};
@ -9,17 +8,16 @@ use axum::{
response::{IntoResponse, Redirect},
};
use axum_core::{extract::FromRequestParts, response::Response};
use futures_util::{future::BoxFuture, Future};
use futures_util::future::BoxFuture;
use http::{request::Parts, uri::PathAndQuery, Request, Uri};
use tower_layer::Layer;
use tower_service::Service;
use tower_sessions::Session;
use openidconnect::{
core::{CoreAuthenticationFlow, CoreErrorResponseType, CoreGenderClaim},
AccessToken, AccessTokenHash, AuthorizationCode, CsrfToken, HttpRequest, HttpResponse,
IdTokenClaims, Nonce, OAuth2TokenResponse, PkceCodeChallenge, PkceCodeVerifier, RedirectUrl,
RefreshToken,
core::{CoreAuthenticationFlow, CoreErrorResponseType, CoreGenderClaim, CoreJsonWebKey},
AccessToken, AccessTokenHash, AuthorizationCode, CsrfToken, IdTokenClaims, IdTokenVerifier,
Nonce, OAuth2TokenResponse, PkceCodeChallenge, PkceCodeVerifier, RedirectUrl, RefreshToken,
RequestTokenError::ServerResponse,
Scope, TokenResponse,
};
@ -145,22 +143,27 @@ where
let token_response = oidcclient
.client
.exchange_code(AuthorizationCode::new(query.code.to_string()))
.exchange_code(AuthorizationCode::new(query.code.to_string()))?
// Set the PKCE code verifier.
.set_pkce_verifier(PkceCodeVerifier::new(
login_session.pkce_verifier.secret().to_string(),
))
.request_async(async_http_client(&oidcclient.http_client))
.request_async(&oidcclient.http_client)
.await?;
// Extract the ID token claims after verifying its authenticity and nonce.
let id_token = token_response
.id_token()
.ok_or(MiddlewareError::IdTokenMissing)?;
let claims = id_token
.claims(&oidcclient.client.id_token_verifier(), &login_session.nonce)?;
let id_token_verifier = oidcclient.client.id_token_verifier();
let claims = id_token.claims(&id_token_verifier, &login_session.nonce)?;
validate_access_token_hash(id_token, token_response.access_token(), claims)?;
validate_access_token_hash(
id_token,
id_token_verifier,
token_response.access_token(),
claims,
)?;
login_session.authenticated = Some(AuthenticatedSession {
id_token: id_token.clone(),
@ -428,12 +431,16 @@ fn insert_extensions<AC: AdditionalClaims>(
/// Returns `Ok` when access token is valid
fn validate_access_token_hash<AC: AdditionalClaims>(
id_token: &IdToken<AC>,
id_token_verifier: IdTokenVerifier<CoreJsonWebKey>,
access_token: &AccessToken,
claims: &IdTokenClaims<AC, CoreGenderClaim>,
) -> Result<(), MiddlewareError> {
if let Some(expected_access_token_hash) = claims.access_token_hash() {
let actual_access_token_hash =
AccessTokenHash::from_token(access_token, &id_token.signing_alg()?)?;
let actual_access_token_hash = AccessTokenHash::from_token(
access_token,
id_token.signing_alg()?,
id_token.signing_key(&id_token_verifier)?,
)?;
if actual_access_token_hash == *expected_access_token_hash {
Ok(())
} else {
@ -456,24 +463,27 @@ async fn try_refresh_token<AC: AdditionalClaims>(
)>,
MiddlewareError,
> {
let mut refresh_request = client.client.exchange_refresh_token(refresh_token);
let mut refresh_request = client.client.exchange_refresh_token(refresh_token)?;
for scope in client.scopes.iter() {
refresh_request = refresh_request.add_scope(Scope::new(scope.to_string()));
}
match refresh_request
.request_async(async_http_client(&client.http_client))
.await
{
match refresh_request.request_async(&client.http_client).await {
Ok(token_response) => {
// Extract the ID token claims after verifying its authenticity and nonce.
let id_token = token_response
.id_token()
.ok_or(MiddlewareError::IdTokenMissing)?;
let claims = id_token.claims(&client.client.id_token_verifier(), nonce)?;
let id_token_verifier = client.client.id_token_verifier();
let claims = id_token.claims(&id_token_verifier, nonce)?;
validate_access_token_hash(id_token, token_response.access_token(), claims)?;
validate_access_token_hash(
id_token,
id_token_verifier,
token_response.access_token(),
claims,
)?;
let authenticated_session = AuthenticatedSession {
id_token: id_token.clone(),
@ -494,47 +504,3 @@ async fn try_refresh_token<AC: AdditionalClaims>(
Err(err) => Err(err.into()),
}
}
/// `openidconnect::reqwest::async_http_client` that uses a custom `reqwest::client`
fn async_http_client<'a>(
client: &'a reqwest::Client,
) -> impl FnOnce(
HttpRequest,
) -> Pin<
Box<
dyn Future<Output = Result<HttpResponse, openidconnect::reqwest::Error<reqwest::Error>>>
+ Send
+ 'a,
>,
> {
move |request: HttpRequest| {
Box::pin(async move {
let mut request_builder = client
.request(request.method, request.url.as_str())
.body(request.body);
for (name, value) in &request.headers {
request_builder = request_builder.header(name.as_str(), value.as_bytes());
}
let request = request_builder
.build()
.map_err(openidconnect::reqwest::Error::Reqwest)?;
let response = client
.execute(request)
.await
.map_err(openidconnect::reqwest::Error::Reqwest)?;
let status_code = response.status();
let headers = response.headers().to_owned();
let chunks = response
.bytes()
.await
.map_err(openidconnect::reqwest::Error::Reqwest)?;
Ok(HttpResponse {
status_code,
headers,
body: chunks.to_vec(),
})
})
}
}