From 5b95ab71465acca095fb4002389cc7f79cef987a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Feb 2025 14:55:04 -0800 Subject: [PATCH] Bump oauth2 from 4.4.2 to 5.0.0 (#1532) * Bump oauth2 from 4.4.2 to 5.0.0 Bumps [oauth2](https://github.com/ramosbugs/oauth2-rs) from 4.4.2 to 5.0.0. - [Release notes](https://github.com/ramosbugs/oauth2-rs/releases) - [Upgrade guide](https://github.com/ramosbugs/oauth2-rs/blob/main/UPGRADE.md) - [Commits](https://github.com/ramosbugs/oauth2-rs/compare/4.4.2...5.0.0) --- updated-dependencies: - dependency-name: oauth2 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Fix `oauth 0.5` breakage Updates `divviup-api` to adapt to the breaking changes in `oauth 0.5`. Much of this follows from advice in the [upgrade guide][1]. - move entire crate to `http 1.1.0` - use `http-compat-1` feature on `trillium-http` - adopt builder pattern for constructing `oauth2::BasicClient` - add type alias `ConfiguredOauthClient` as a shorthand for `oauth2::BasicClient<...>` - add wrapper around `trillium_client::Client` so we can implement `oauth2::AsyncHttpClient` on it - translate `oauth2::HttpRequest/Response` to/from Trillium equivalents [1]: https://github.com/ramosbugs/oauth2-rs/blob/main/UPGRADE.md --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Tim Geoghegan --- Cargo.lock | 51 ++++++++++--------------- Cargo.toml | 4 +- src/handler/oauth2.rs | 89 +++++++++++++++++++++++++++++-------------- 3 files changed, 83 insertions(+), 61 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 083946de..22c9f331 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -511,7 +511,7 @@ dependencies = [ "axum-core", "bytes", "futures-util", - "http 1.1.0", + "http", "http-body", "http-body-util", "itoa", @@ -537,7 +537,7 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.1.0", + "http", "http-body", "http-body-util", "mime", @@ -2041,7 +2041,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.1.0", + "http", "indexmap 2.2.1", "slab", "tokio", @@ -2241,17 +2241,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - [[package]] name = "http" version = "1.1.0" @@ -2269,7 +2258,7 @@ version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bfa6ba9e2d8cf5299faf4721520ff7997989431d2c8fdf702eb8b1a4313b625" dependencies = [ - "http 1.1.0", + "http", "serde", "serde_json", ] @@ -2281,7 +2270,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" dependencies = [ "bytes", - "http 1.1.0", + "http", ] [[package]] @@ -2292,7 +2281,7 @@ checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" dependencies = [ "bytes", "futures-core", - "http 1.1.0", + "http", "http-body", "pin-project-lite", ] @@ -2325,7 +2314,7 @@ dependencies = [ "futures-channel", "futures-util", "h2", - "http 1.1.0", + "http", "http-body", "httparse", "httpdate", @@ -2343,7 +2332,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" dependencies = [ "futures-util", - "http 1.1.0", + "http", "hyper", "hyper-util", "rustls 0.23.5", @@ -2376,7 +2365,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.1.0", + "http", "http-body", "hyper", "pin-project-lite", @@ -2633,7 +2622,7 @@ checksum = "5a1c086d7079a5c47b40ce553dfb23c4a1d3f73b53592ee6504bdf93bfd07cb4" dependencies = [ "backoff", "derivative", - "http 1.1.0", + "http", "itertools", "janus_core", "janus_messages", @@ -2686,7 +2675,7 @@ dependencies = [ "futures", "hex", "hpke-dispatch 0.6.0", - "http 1.1.0", + "http", "http-api-problem", "janus_messages", "prio", @@ -3091,14 +3080,14 @@ dependencies = [ [[package]] name = "oauth2" -version = "4.4.2" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f" +checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" dependencies = [ - "base64 0.13.1", + "base64 0.22.1", "chrono", "getrandom", - "http 0.2.12", + "http", "rand", "serde", "serde_json", @@ -3157,7 +3146,7 @@ checksum = "91cf61a1868dacc576bf2b2a1c3e9ab150af7272909e80085c3173384fe11f76" dependencies = [ "async-trait", "futures-core", - "http 1.1.0", + "http", "opentelemetry", "opentelemetry-proto", "opentelemetry_sdk", @@ -3866,7 +3855,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http 1.1.0", + "http", "http-body", "http-body-util", "hyper", @@ -3905,7 +3894,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0a2a3fe45c9b89f416bc92174669d954594e152d158df845e6df173a9b7aed4" dependencies = [ "chrono", - "http 1.1.0", + "http", "thiserror 1.0.69", ] @@ -5281,7 +5270,7 @@ dependencies = [ "base64 0.22.1", "bytes", "h2", - "http 1.1.0", + "http", "http-body", "http-body-util", "hyper", @@ -5545,7 +5534,7 @@ dependencies = [ "encoding_rs", "futures-lite", "hashbrown 0.14.3", - "http 0.2.12", + "http", "httparse", "httpdate", "log", diff --git a/Cargo.toml b/Cargo.toml index bc96b9d4..149446f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,7 +74,7 @@ trillium-compression = "0.1.3" trillium-conn-id = "0.2.3" trillium-cookies = "0.4.2" trillium-forwarding = "0.2.4" -trillium-http = { version = "0.3.14", features = ["http-compat", "serde"] } +trillium-http = { version = "0.3.14", features = ["http-compat-1", "serde"] } trillium-logger = "0.4.5" trillium-macros = "0.0.6" trillium-prometheus = "0.2.0" @@ -94,7 +94,7 @@ opentelemetry_sdk = { version = "0.27.1", features = ["rt-tokio", "logs", "metri opentelemetry-otlp = { version = "0.27.0", optional = true } [dependencies.oauth2] -version = "4.4.2" +version = "5.0.0" default-features = false features = ["pkce-plain"] diff --git a/src/handler/oauth2.rs b/src/handler/oauth2.rs index 27c13fa3..b270959a 100644 --- a/src/handler/oauth2.rs +++ b/src/handler/oauth2.rs @@ -1,12 +1,12 @@ use crate::{User, USER_SESSION_KEY}; use oauth2::{ basic::{BasicClient, BasicErrorResponseType}, - AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken, HttpResponse, PkceCodeChallenge, - PkceCodeVerifier, RedirectUrl, RequestTokenError, Scope, StandardErrorResponse, TokenResponse, - TokenUrl, + AsyncHttpClient, AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken, EndpointNotSet, + EndpointSet, HttpRequest, HttpResponse, PkceCodeChallenge, PkceCodeVerifier, RedirectUrl, + RequestTokenError, Scope, StandardErrorResponse, TokenResponse, TokenUrl, }; use querystrong::QueryStrong; -use std::sync::Arc; +use std::{future::Future, pin::Pin, sync::Arc}; use trillium::{conn_try, conn_unwrap, Conn, KnownHeaderName::Authorization, Status}; use trillium_client::{Client, ClientSerdeError}; use trillium_http::Headers; @@ -14,6 +14,17 @@ use trillium_redirect::RedirectConnExt; use trillium_sessions::SessionConnExt; use url::Url; +/// Type alias for an oauth2::Client once we've finished configuring it in `OauthClient::new`. +/// Crate oauth's guide to upgrading to 0.5 recommends defining this kind of alias: +/// https://github.com/ramosbugs/oauth2-rs/blob/main/UPGRADE.md#add-typestate-generic-types-to-client +pub type ConfiguredOauthClient = BasicClient< + EndpointSet, // HasAuthURL + EndpointNotSet, // HasDeviceAuthURL + EndpointNotSet, // HasIntrospectionURL + EndpointNotSet, // HasRevocationURL + EndpointSet, // HasTokenURL +>; + #[derive(Clone, Debug)] pub struct Oauth2Config { pub authorize_url: Url, @@ -93,7 +104,7 @@ enum OauthError { #[error(transparent)] InvalidStatusCode(#[from] oauth2::http::status::InvalidStatusCode), #[error(transparent)] - HeaderConversionError(#[from] trillium_http::http_compat::HeaderConversionError), + HeaderConversionError(#[from] trillium_http::http_compat1::HeaderConversionError), #[error(transparent)] UrlError(#[from] url::ParseError), #[error("error response: {0}")] @@ -104,6 +115,8 @@ enum OauthError { Other(String), #[error("expected a successful status, but found {0:?}")] UnexpectedStatus(Option), + #[error(transparent)] + HttpCrateError(#[from] oauth2::http::Error), } impl From>> @@ -138,7 +151,7 @@ pub struct OauthClient(Arc); #[derive(Debug)] struct OauthClientInner { oauth_config: Oauth2Config, - oauth2_client: BasicClient, + oauth2_client: ConfiguredOauthClient, } impl OauthClient { @@ -153,20 +166,7 @@ impl OauthClient { .exchange_code(auth_code) .set_pkce_verifier(pkce_verifier) .add_extra_param("audience", &self.0.oauth_config.audience) - .request_async(|req| async move { - let mut conn = http_client - .build_conn(req.method, req.url) - .with_body(req.body) - .with_request_headers(Headers::from(req.headers)) - .await?; - let status_code = conn.status().unwrap().try_into()?; - let body = conn.response_body().read_bytes().await?; - Ok::<_, OauthError>(HttpResponse { - status_code, - headers: conn.response_headers().clone().try_into()?, - body, - }) - }) + .request_async(&ClientWrapper(http_client)) .await?; let mut client_conn = self @@ -190,13 +190,11 @@ impl OauthClient { } pub fn new(config: &Oauth2Config) -> Self { - let oauth2_client = BasicClient::new( - ClientId::new(config.client_id.clone()), - Some(ClientSecret::new(config.client_secret.clone())), - AuthUrl::from_url(config.authorize_url.clone()), - Some(TokenUrl::from_url(config.token_url.clone())), - ) - .set_redirect_uri(RedirectUrl::from_url(config.redirect_url.clone())); + let oauth2_client = BasicClient::new(ClientId::new(config.client_id.clone())) + .set_client_secret(ClientSecret::new(config.client_secret.clone())) + .set_auth_uri(AuthUrl::from_url(config.authorize_url.clone())) + .set_token_uri(TokenUrl::from_url(config.token_url.clone())) + .set_redirect_uri(RedirectUrl::from_url(config.redirect_url.clone())); Self(Arc::new(OauthClientInner { oauth_config: config.clone(), @@ -204,7 +202,7 @@ impl OauthClient { })) } - pub fn oauth2_client(&self) -> &BasicClient { + pub fn oauth2_client(&self) -> &ConfiguredOauthClient { &self.0.oauth2_client } @@ -212,3 +210,38 @@ impl OauthClient { &self.0.oauth_config.http_client } } + +// Wraps a [`trillium_client::Client`] so we can implement [`oauth2::AsyncHttpClient`] on it, as +// otherwise the orphan rule would forbid this. +struct ClientWrapper(Client); + +// Inspired by the impls `oauth2` provides for `reqwest::Client` +// https://github.com/ramosbugs/oauth2-rs/blob/23b952b23e6069525bc7e4c4f2c4924b8d28ce3a/src/reqwest.rs +impl<'c> AsyncHttpClient<'c> for ClientWrapper { + type Error = OauthError; + type Future = Pin> + Send + 'c>>; + + fn call(&'c self, req: HttpRequest) -> Self::Future { + Box::pin(async move { + // Translate the oauth2::http::Request into a Trillium request + let mut conn = self + .0 + .build_conn(req.method(), req.uri().to_string().parse::()?) + .with_body(req.body().clone()) + .with_request_headers(Headers::from(req.headers().clone())) + .await?; + let status_code: oauth2::http::StatusCode = conn.status().unwrap().try_into()?; + let body = conn.response_body().read_bytes().await?; + + // Now transform the Trillium response back into an http::Response + let mut builder = oauth2::http::Response::builder().status(status_code); + let http_headers: oauth2::http::HeaderMap = + conn.response_headers().clone().try_into()?; + builder + .headers_mut() + .ok_or_else(|| OauthError::Other("no headers in builder?".into()))? + .extend(http_headers); + Ok::<_, OauthError>(builder.body(body)?) + }) + } +}