From 82ce134bd8965bc9f7c69018fa4f0e21dfc780d3 Mon Sep 17 00:00:00 2001 From: threema-donat <129288638+threema-donat@users.noreply.github.com> Date: Tue, 2 Jul 2024 12:01:05 +0200 Subject: [PATCH 01/11] Add timeout to hyper client --- examples/custom_client.rs | 15 ++- src/access_token.rs | 7 +- src/application_default_credentials.rs | 7 +- src/authenticator.rs | 172 +++++++++++++++++-------- src/authorized_user.rs | 7 +- src/client.rs | 75 +++++++++++ src/device.rs | 29 ++--- src/error.rs | 4 +- src/external_account.rs | 7 +- src/installed.rs | 36 +++--- src/lib.rs | 6 +- src/refresh.rs | 10 +- src/service_account.rs | 13 +- src/service_account_impersonator.rs | 12 +- tests/tests.rs | 86 ++++++++++--- 15 files changed, 334 insertions(+), 152 deletions(-) create mode 100644 src/client.rs diff --git a/examples/custom_client.rs b/examples/custom_client.rs index 7b7b89444..6ec08776f 100644 --- a/examples/custom_client.rs +++ b/examples/custom_client.rs @@ -5,7 +5,10 @@ //! //! It is also a better use of resources (memory, sockets, etc.) +use std::time::Duration; + use hyper_util::client::legacy::connect::Connect; +use yup_oauth2::authenticator::HyperClientBuilder; async fn r#use( client: hyper_util::client::legacy::Client, @@ -43,11 +46,13 @@ async fn main() { .enable_http2() .build(), ); - let authenticator = - yup_oauth2::ServiceAccountAuthenticator::with_client(secret, client.clone()) - .build() - .await - .expect("could not create an authenticator"); + let authenticator = yup_oauth2::ServiceAccountAuthenticator::with_client( + secret, + yup_oauth2::CustomHyperClient::from(client.clone()).with_timeout(Duration::from_secs(10)), + ) + .build() + .await + .expect("could not create an authenticator"); r#use(client, authenticator) .await .expect("use is successful!"); diff --git a/src/access_token.rs b/src/access_token.rs index 418f6c65c..9332b4f92 100644 --- a/src/access_token.rs +++ b/src/access_token.rs @@ -5,9 +5,9 @@ //! The intention behind this is that if two services using the //! same refresh token then each service will invalitate the //! access token of the other service by generating a new token. +use crate::client::SendRequest; use crate::error::Error; use crate::types::TokenInfo; -use hyper_util::client::legacy::connect::Connect; /// the flow for the access token authenticator pub struct AccessTokenFlow { @@ -16,14 +16,13 @@ pub struct AccessTokenFlow { impl AccessTokenFlow { /// just return the access token - pub(crate) async fn token( + pub(crate) async fn token( &self, - _hyper_client: &hyper_util::client::legacy::Client, + _hyper_client: &impl SendRequest, _scopes: &[T], ) -> Result where T: AsRef, - C: Connect + Clone + Send + Sync + 'static, { Ok(TokenInfo { access_token: Some(self.access_token.clone()), diff --git a/src/application_default_credentials.rs b/src/application_default_credentials.rs index 41924c615..b280305c1 100644 --- a/src/application_default_credentials.rs +++ b/src/application_default_credentials.rs @@ -1,7 +1,7 @@ +use crate::client::SendRequest; use crate::error::Error; use crate::types::TokenInfo; use http_body_util::BodyExt; -use hyper_util::client::legacy::connect::Connect; /// Provide options for the Application Default Credential Flow, mostly used for testing #[derive(Default, Clone, Debug)] @@ -20,14 +20,13 @@ impl ApplicationDefaultCredentialsFlow { ApplicationDefaultCredentialsFlow { metadata_url } } - pub(crate) async fn token( + pub(crate) async fn token( &self, - hyper_client: &hyper_util::client::legacy::Client, + hyper_client: &impl SendRequest, scopes: &[T], ) -> Result where T: AsRef, - C: Connect + Clone + Send + Sync + 'static, { let scope = crate::helper::join(scopes, ","); let token_uri = format!("{}?scopes={}", self.metadata_url, scope); diff --git a/src/authenticator.rs b/src/authenticator.rs index 7edf2ebd3..015e0e7af 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -4,6 +4,7 @@ use crate::application_default_credentials::{ }; use crate::authenticator_delegate::{DeviceFlowDelegate, InstalledFlowDelegate}; use crate::authorized_user::{AuthorizedUserFlow, AuthorizedUserSecret}; +use crate::client::{HttpClient, LegacyClient}; use crate::device::DeviceFlow; use crate::error::Error; use crate::external_account::{ExternalAccountFlow, ExternalAccountSecret}; @@ -30,9 +31,13 @@ use std::fmt; use std::io; use std::path::PathBuf; use std::sync::Arc; +use std::time::Duration; -struct InnerAuthenticator { - hyper_client: hyper_util::client::legacy::Client, +struct InnerAuthenticator +where + C: Connect + Clone + Send + Sync + 'static, +{ + hyper_client: HttpClient, storage: Storage, auth_flow: AuthFlow, } @@ -40,7 +45,10 @@ struct InnerAuthenticator { /// Authenticator is responsible for fetching tokens, handling refreshing tokens, /// and optionally persisting tokens to disk. #[derive(Clone)] -pub struct Authenticator { +pub struct Authenticator +where + C: Connect + Clone + Send + Sync + 'static, +{ inner: Arc>, } @@ -205,7 +213,7 @@ impl InstalledFlowAuthenticator { app_secret: ApplicationSecret, method: InstalledFlowReturnMethod, ) -> AuthenticatorBuilder { - Self::with_client(app_secret, method, DefaultHyperClient) + Self::with_client(app_secret, method, DefaultHyperClient::default()) } /// Construct a new Authenticator that uses the installed flow and the provided http client. @@ -240,7 +248,7 @@ impl DeviceFlowAuthenticator { pub fn builder( app_secret: ApplicationSecret, ) -> AuthenticatorBuilder { - Self::with_client(app_secret, DefaultHyperClient) + Self::with_client(app_secret, DefaultHyperClient::default()) } /// Construct a new Authenticator that uses the installed flow and the provided http client. @@ -277,7 +285,7 @@ impl ServiceAccountAuthenticator { pub fn builder( service_account_key: ServiceAccountKey, ) -> AuthenticatorBuilder { - Self::with_client(service_account_key, DefaultHyperClient) + Self::with_client(service_account_key, DefaultHyperClient::default()) } /// Construct a new Authenticator that uses the installed flow and the provided http client. @@ -340,7 +348,7 @@ impl ApplicationDefaultCredentialsAuthenticator { pub async fn builder( opts: ApplicationDefaultCredentialsFlowOpts, ) -> ApplicationDefaultCredentialsTypes { - Self::with_client(opts, DefaultHyperClient).await + Self::with_client(opts, DefaultHyperClient::default()).await } /// Use the builder pattern to deduce which model of authenticator should be used and allow providing a hyper client @@ -399,7 +407,7 @@ impl AuthorizedUserAuthenticator { pub fn builder( authorized_user_secret: AuthorizedUserSecret, ) -> AuthenticatorBuilder { - Self::with_client(authorized_user_secret, DefaultHyperClient) + Self::with_client(authorized_user_secret, DefaultHyperClient::default()) } /// Construct a new Authenticator that uses the installed flow and the provided http client. @@ -439,7 +447,7 @@ impl ExternalAccountAuthenticator { pub fn builder( external_account_secret: ExternalAccountSecret, ) -> AuthenticatorBuilder { - Self::with_client(external_account_secret, DefaultHyperClient) + Self::with_client(external_account_secret, DefaultHyperClient::default()) } /// Construct a new Authenticator that uses the installed flow and the provided http client. @@ -476,7 +484,7 @@ impl AccessTokenAuthenticator { pub fn builder( access_token: String, ) -> AuthenticatorBuilder { - Self::with_client(access_token, DefaultHyperClient) + Self::with_client(access_token, DefaultHyperClient::default()) } /// Construct a new Authenticator that uses the installed flow and the provided http client. /// the client itself is not used @@ -518,7 +526,7 @@ impl ServiceAccountImpersonationAuthenticator { Self::with_client( authorized_user_secret, service_account_email, - DefaultHyperClient, + DefaultHyperClient::default(), ) } @@ -537,12 +545,10 @@ impl ServiceAccountImpersonationAuthenticator { /// ## Methods available when building any Authenticator. /// ``` -/// # #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] /// # async fn foo() { -/// # let custom_hyper_client = hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build_http::(); +/// # let client = hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build_http::(); /// # let app_secret = yup_oauth2::read_application_secret("/tmp/foo").await.unwrap(); -/// let authenticator = yup_oauth2::DeviceFlowAuthenticator::builder(app_secret) -/// .hyper_client(custom_hyper_client) +/// let authenticator = yup_oauth2::DeviceFlowAuthenticator::with_client(app_secret, yup_oauth2::CustomHyperClient::from(client)) /// .persist_tokens_to_disk("/tmp/tokenfile.json") /// .build() /// .await @@ -598,22 +604,23 @@ impl AuthenticatorBuilder { } } - /// Use the provided hyper client. - pub fn hyper_client( - self, - hyper_client: hyper_util::client::legacy::Client, - ) -> AuthenticatorBuilder, F> { + /// Persist tokens to disk in the provided filename. + pub fn persist_tokens_to_disk>(self, path: P) -> AuthenticatorBuilder { AuthenticatorBuilder { - hyper_client_builder: hyper_client, - storage_type: self.storage_type, - auth_flow: self.auth_flow, + storage_type: StorageType::Disk(path.into()), + ..self } } +} - /// Persist tokens to disk in the provided filename. - pub fn persist_tokens_to_disk>(self, path: P) -> AuthenticatorBuilder { +impl AuthenticatorBuilder +where + C: HyperClientBuilder, +{ + /// Sets the duration after which a HTTP request times out + pub fn with_timeout(self, timeout: Duration) -> Self { AuthenticatorBuilder { - storage_type: StorageType::Disk(path.into()), + hyper_client_builder: self.hyper_client_builder.with_timeout(timeout), ..self } } @@ -869,8 +876,8 @@ impl AuthenticatorBuilder { mod private { use crate::access_token::AccessTokenFlow; use crate::application_default_credentials::ApplicationDefaultCredentialsFlow; - use crate::authenticator::Connect; use crate::authorized_user::AuthorizedUserFlow; + use crate::client::SendRequest; use crate::device::DeviceFlow; use crate::error::Error; use crate::external_account::ExternalAccountFlow; @@ -908,14 +915,13 @@ mod private { } } - pub(crate) async fn token<'a, C, T>( + pub(crate) async fn token<'a, T>( &'a self, - hyper_client: &'a hyper_util::client::legacy::Client, + hyper_client: &'a impl SendRequest, scopes: &'a [T], ) -> Result where T: AsRef, - C: Connect + Clone + Send + Sync + 'static, { match self { AuthFlow::DeviceFlow(device_flow) => device_flow.token(hyper_client, scopes).await, @@ -953,15 +959,15 @@ pub trait HyperClientBuilder { /// The hyper connector that the resulting hyper client will use. type Connector: Connect + Clone + Send + Sync + 'static; + /// Sets duration after which a request times out + fn with_timeout(self, timeout: Duration) -> Self; + /// Create a hyper::Client - fn build_hyper_client( - self, - ) -> Result, Error>; + fn build_hyper_client(self) -> Result, Error>; /// Create a `hyper_util::client::legacy::Client` for tests (HTTPS not required) #[doc(hidden)] - fn build_test_hyper_client(self) - -> hyper_util::client::legacy::Client; + fn build_test_hyper_client(self) -> HttpClient; } #[cfg(feature = "hyper-rustls")] @@ -988,7 +994,61 @@ pub type DefaultAuthenticator = yup_oauth2_docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))) )] -pub struct DefaultHyperClient; +#[derive(Default)] +pub struct DefaultHyperClient { + timeout: Option, +} + +/// Intended for using an existing hyper client with `yup-oauth2`. Instantiate +/// with [`CustomHyperClient::from`] +pub struct CustomHyperClient +where + C: Connect + Clone + Send + Sync + 'static, +{ + client: HttpClient, + timeout: Option, +} + +impl From> for CustomHyperClient +where + C: Connect + Clone + Send + Sync + 'static, +{ + fn from(client: LegacyClient) -> Self { + Self { + client: HttpClient::new(client, None), + timeout: None, + } + } +} + +impl HyperClientBuilder for CustomHyperClient +where + C: Connect + Clone + Send + Sync + 'static, +{ + type Connector = C; + + fn with_timeout(mut self, timeout: Duration) -> Self { + self.timeout = Some(timeout); + self + } + + fn build_hyper_client(self) -> Result, Error> { + Ok(self.client) + } + + fn build_test_hyper_client(self) -> HttpClient { + self.client + } +} + +#[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] +impl DefaultHyperClient { + /// Set the duration after which a request times out + pub fn with_timeout(mut self, timeout: Duration) -> Self { + self.timeout = Some(timeout); + self + } +} #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] #[cfg_attr( @@ -1002,9 +1062,12 @@ impl HyperClientBuilder for DefaultHyperClient { #[cfg(all(not(feature = "hyper-rustls"), feature = "hyper-tls"))] type Connector = hyper_tls::HttpsConnector; - fn build_hyper_client( - self, - ) -> Result, Error> { + fn with_timeout(mut self, timeout: Duration) -> Self { + self.timeout = Some(timeout); + self + } + + fn build_hyper_client(self) -> Result, Error> { #[cfg(feature = "hyper-rustls")] let connector = hyper_rustls::HttpsConnectorBuilder::new() .with_provider_and_native_roots(default_crypto_provider())? @@ -1015,16 +1078,15 @@ impl HyperClientBuilder for DefaultHyperClient { #[cfg(all(not(feature = "hyper-rustls"), feature = "hyper-tls"))] let connector = hyper_tls::HttpsConnector::new(); - Ok( + Ok(HttpClient::new( hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()) .pool_max_idle_per_host(0) .build::<_, String>(connector), - ) + self.timeout, + )) } - fn build_test_hyper_client( - self, - ) -> hyper_util::client::legacy::Client { + fn build_test_hyper_client(self) -> HttpClient { #[cfg(feature = "hyper-rustls")] let connector = hyper_rustls::HttpsConnectorBuilder::new() .with_provider_and_native_roots(default_crypto_provider()) @@ -1036,25 +1098,31 @@ impl HyperClientBuilder for DefaultHyperClient { #[cfg(all(not(feature = "hyper-rustls"), feature = "hyper-tls"))] let connector = hyper_tls::HttpsConnector::new(); - hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()) - .pool_max_idle_per_host(0) - .build::<_, String>(connector) + HttpClient::new( + hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()) + .pool_max_idle_per_host(0) + .build::<_, String>(connector), + self.timeout, + ) } } -impl HyperClientBuilder for hyper_util::client::legacy::Client +impl HyperClientBuilder for HttpClient where C: Connect + Clone + Send + Sync + 'static, { type Connector = C; - fn build_hyper_client(self) -> Result, Error> { + fn with_timeout(mut self, timeout: Duration) -> Self { + self.set_timeout(timeout); + self + } + + fn build_hyper_client(self) -> Result, Error> { Ok(self) } - fn build_test_hyper_client( - self, - ) -> hyper_util::client::legacy::Client { + fn build_test_hyper_client(self) -> HttpClient { self } } diff --git a/src/authorized_user.rs b/src/authorized_user.rs index 9f8476d8e..bfda9eab1 100644 --- a/src/authorized_user.rs +++ b/src/authorized_user.rs @@ -4,11 +4,11 @@ //! Resources: //! - [gcloud auth application-default login](https://cloud.google.com/sdk/gcloud/reference/auth/application-default/login) //! +use crate::client::SendRequest; use crate::error::Error; use crate::types::TokenInfo; use http::header; use http_body_util::BodyExt; -use hyper_util::client::legacy::connect::Connect; use serde::{Deserialize, Serialize}; use url::form_urlencoded; @@ -40,14 +40,13 @@ pub struct AuthorizedUserFlow { impl AuthorizedUserFlow { /// Send a request for a new Bearer token to the OAuth provider. - pub(crate) async fn token( + pub(crate) async fn token( &self, - hyper_client: &hyper_util::client::legacy::Client, + hyper_client: &impl SendRequest, _scopes: &[T], ) -> Result where T: AsRef, - C: Connect + Clone + Send + Sync + 'static, { let req = form_urlencoded::Serializer::new(String::new()) .extend_pairs(&[ diff --git a/src/client.rs b/src/client.rs new file mode 100644 index 000000000..4a4f92d27 --- /dev/null +++ b/src/client.rs @@ -0,0 +1,75 @@ +use std::time::Duration; + +use futures::TryFutureExt; +use http::Uri; +use hyper_util::client::legacy::{connect::Connect, Error as LegacyHyperError}; +use thiserror::Error as ThisError; + +type HyperResponse = http::Response; +pub(crate) type LegacyClient = hyper_util::client::legacy::Client; + +#[derive(Debug, ThisError)] +/// Errors that can happen when a request is sent +pub enum SendError { + /// Request could not complete before timeout elapsed + #[error("Request timed out")] + Timeout, + /// Wrapper for hyper errors + #[error("Hyper error: {0}")] + Hyper(#[source] LegacyHyperError), +} + +/// Client that can be configured that a request will timeout after a specified +/// duration. +#[derive(Clone)] +pub struct HttpClient +where + C: Connect + Clone + Send + Sync + 'static, +{ + client: LegacyClient, + timeout: Option, +} + +impl HttpClient +where + C: Connect + Clone + Send + Sync + 'static, +{ + pub(super) fn new(hyper_client: LegacyClient, timeout: Option) -> Self { + Self { + client: hyper_client, + timeout, + } + } + + pub(super) fn set_timeout(&mut self, timeout: Duration) { + self.timeout = Some(timeout); + } + + /// Execute a get request with the underlying hyper client + #[doc(hidden)] + pub async fn get(&self, uri: Uri) -> Result { + self.client.get(uri).await + } +} + +impl SendRequest for HttpClient +where + C: Connect + Clone + Send + Sync + 'static, +{ + async fn request(&self, payload: http::Request) -> Result { + let future = self.client.request(payload); + match self.timeout { + Some(duration) => { + tokio::time::timeout(duration, future) + .map_err(|_| SendError::Timeout) + .await? + } + None => future.await, + } + .map_err(SendError::Hyper) + } +} + +pub(super) trait SendRequest { + async fn request(&self, payload: http::Request) -> Result; +} diff --git a/src/device.rs b/src/device.rs index ea2c489c0..f7affe826 100644 --- a/src/device.rs +++ b/src/device.rs @@ -1,6 +1,7 @@ use crate::authenticator_delegate::{ DefaultDeviceFlowDelegate, DeviceAuthResponse, DeviceFlowDelegate, }; +use crate::client::SendRequest; use crate::error::{AuthError, Error}; use crate::types::{ApplicationSecret, TokenInfo}; @@ -9,7 +10,6 @@ use std::time::Duration; use http::header; use http_body_util::BodyExt; -use hyper_util::client::legacy::connect::Connect; use url::form_urlencoded; pub const GOOGLE_DEVICE_CODE_URL: &str = "https://accounts.google.com/o/oauth2/device/code"; @@ -40,14 +40,13 @@ impl DeviceFlow { } } - pub(crate) async fn token( + pub(crate) async fn token( &self, - hyper_client: &hyper_util::client::legacy::Client, + hyper_client: &impl SendRequest, scopes: &[T], ) -> Result where T: AsRef, - C: Connect + Clone + Send + Sync + 'static, { let device_auth_resp = Self::request_code( &self.app_secret, @@ -69,16 +68,13 @@ impl DeviceFlow { .await } - async fn wait_for_device_token( + async fn wait_for_device_token( &self, - hyper_client: &hyper_util::client::legacy::Client, + hyper_client: &impl SendRequest, app_secret: &ApplicationSecret, device_auth_resp: &DeviceAuthResponse, grant_type: &str, - ) -> Result - where - C: Connect + Clone + Send + Sync + 'static, - { + ) -> Result { let mut interval = device_auth_resp.interval; log::debug!("Polling every {:?} for device token", interval); loop { @@ -126,15 +122,14 @@ impl DeviceFlow { /// * If called after a successful result was returned at least once. /// # Examples /// See test-cases in source code for a more complete example. - async fn request_code( + async fn request_code( application_secret: &ApplicationSecret, - client: &hyper_util::client::legacy::Client, + client: &impl SendRequest, device_code_url: &str, scopes: &[T], ) -> Result where T: AsRef, - C: Connect + Clone + Send + Sync + 'static, { let req = form_urlencoded::Serializer::new(String::new()) .extend_pairs(&[ @@ -174,15 +169,13 @@ impl DeviceFlow { /// /// # Examples /// See test-cases in source code for a more complete example. - async fn poll_token<'a, C>( + async fn poll_token<'a>( application_secret: &ApplicationSecret, - client: &hyper_util::client::legacy::Client, + client: &impl SendRequest, device_code: &str, grant_type: &str, ) -> Result - where - C: Connect + Clone + Send + Sync + 'static, - { +where { // We should be ready for a new request let req = form_urlencoded::Serializer::new(String::new()) .extend_pairs(&[ diff --git a/src/error.rs b/src/error.rs index 0144511bd..f3961c97b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,10 +6,10 @@ use std::fmt; use std::io; use hyper::Error as HyperError; -use hyper_util::client::legacy::Error as LegacyHyperError; use serde::Deserialize; use thiserror::Error as ThisError; +pub use crate::client::SendError; pub use crate::external_account::CredentialSourceError; pub use crate::storage::TokenStorageError; @@ -155,7 +155,7 @@ pub enum Error { HttpError(#[from] HyperError), /// Indicates connection failure #[error("Connection failure: {0}")] - HttpClientError(#[from] LegacyHyperError), + HttpClientError(#[from] SendError), /// The server returned an error. #[error("Server error: {0}")] AuthError(#[from] AuthError), diff --git a/src/external_account.rs b/src/external_account.rs index 033098559..802c29cee 100644 --- a/src/external_account.rs +++ b/src/external_account.rs @@ -5,11 +5,11 @@ //! - [Workload identity federation](https://cloud.google.com/iam/docs/workload-identity-federation) //! - [External Account Credentials (Workload Identity Federation)](https://google.aip.dev/auth/4117) //! +use crate::client::SendRequest; use crate::error::Error; use crate::types::TokenInfo; use http::header; use http_body_util::BodyExt; -use hyper_util::client::legacy::connect::Connect; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use thiserror::Error; @@ -102,14 +102,13 @@ pub struct ExternalAccountFlow { impl ExternalAccountFlow { /// Send a request for a new Bearer token to the OAuth provider. - pub(crate) async fn token( + pub(crate) async fn token( &self, - hyper_client: &hyper_util::client::legacy::Client, + hyper_client: &impl SendRequest, scopes: &[T], ) -> Result where T: AsRef, - C: Connect + Clone + Send + Sync + 'static, { let subject_token = match &self.secret.credential_source { CredentialSource::File { file } => tokio::fs::read_to_string(file).await?, diff --git a/src/installed.rs b/src/installed.rs index cfa864e80..4afab09b3 100644 --- a/src/installed.rs +++ b/src/installed.rs @@ -3,6 +3,7 @@ // Refer to the project root for licensing information. // use crate::authenticator_delegate::{DefaultInstalledFlowDelegate, InstalledFlowDelegate}; +use crate::client::SendRequest; use crate::error::Error; use crate::types::{ApplicationSecret, TokenInfo}; @@ -13,7 +14,6 @@ use std::net::SocketAddr; use std::sync::Arc; use http::header; -use hyper_util::client::legacy::connect::Connect; use percent_encoding::{percent_encode, AsciiSet, CONTROLS}; use tokio::sync::oneshot; use url::form_urlencoded; @@ -119,14 +119,13 @@ impl InstalledFlow { /// . Return that token /// /// It's recommended not to use the DefaultInstalledFlowDelegate, but a specialized one. - pub(crate) async fn token( + pub(crate) async fn token( &self, - hyper_client: &hyper_util::client::legacy::Client, + hyper_client: &impl SendRequest, scopes: &[T], ) -> Result where T: AsRef, - C: Connect + Clone + Send + Sync + 'static, { match self.method { InstalledFlowReturnMethod::HTTPRedirect => { @@ -144,15 +143,14 @@ impl InstalledFlow { } } - async fn ask_auth_code_interactively( + async fn ask_auth_code_interactively( &self, - hyper_client: &hyper_util::client::legacy::Client, + hyper_client: &impl SendRequest, app_secret: &ApplicationSecret, scopes: &[T], ) -> Result where T: AsRef, - C: Connect + Clone + Send + Sync + 'static, { let url = build_authentication_request_url( &app_secret.auth_uri, @@ -172,16 +170,15 @@ impl InstalledFlow { .await } - async fn ask_auth_code_via_http( + async fn ask_auth_code_via_http( &self, - hyper_client: &hyper_util::client::legacy::Client, + hyper_client: &impl SendRequest, port: Option, app_secret: &ApplicationSecret, scopes: &[T], ) -> Result where T: AsRef, - C: Connect + Clone + Send + Sync + 'static, { use std::borrow::Cow; let server = InstalledFlowServer::run(port)?; @@ -211,16 +208,13 @@ impl InstalledFlow { .await } - async fn exchange_auth_code( + async fn exchange_auth_code( &self, authcode: &str, - hyper_client: &hyper_util::client::legacy::Client, + hyper_client: &impl SendRequest, app_secret: &ApplicationSecret, server_addr: Option, - ) -> Result - where - C: Connect + Clone + Send + Sync + 'static, - { + ) -> Result { let redirect_uri = self.flow_delegate.redirect_uri(); let request = Self::request_token(app_secret, authcode, redirect_uri, server_addr); log::debug!("Sending request: {:?}", request); @@ -419,6 +413,8 @@ mod installed_flow_server { #[cfg(test)] mod tests { + use crate::client::LegacyClient; + use super::*; use http::Uri; @@ -479,11 +475,9 @@ mod tests { #[tokio::test] async fn test_server() { - let client: hyper_util::client::legacy::Client< - hyper_util::client::legacy::connect::HttpConnector, - String, - > = hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()) - .build_http(); + let client: LegacyClient = + hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()) + .build_http(); let server = InstalledFlowServer::run(None).unwrap(); let response = client diff --git a/src/lib.rs b/src/lib.rs index 082aa1ad7..252ed9ced 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,6 +77,7 @@ mod application_default_credentials; pub mod authenticator; pub mod authenticator_delegate; pub mod authorized_user; +mod client; mod device; pub mod error; pub mod external_account; @@ -106,9 +107,12 @@ pub use crate::authenticator::ServiceAccountAuthenticator; #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] pub use crate::authenticator::AccessTokenAuthenticator; +#[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] +pub use crate::client::HttpClient; + #[doc(inline)] pub use crate::authenticator::{ - ApplicationDefaultCredentialsAuthenticator, AuthorizedUserAuthenticator, + ApplicationDefaultCredentialsAuthenticator, AuthorizedUserAuthenticator, CustomHyperClient, DeviceFlowAuthenticator, ExternalAccountAuthenticator, InstalledFlowAuthenticator, ServiceAccountImpersonationAuthenticator, }; diff --git a/src/refresh.rs b/src/refresh.rs index 0c8b14a8c..fef563092 100644 --- a/src/refresh.rs +++ b/src/refresh.rs @@ -1,9 +1,9 @@ +use crate::client::SendRequest; use crate::error::Error; use crate::types::{ApplicationSecret, TokenInfo}; use http::header; use http_body_util::BodyExt; -use hyper_util::client::legacy::connect::Connect; use url::form_urlencoded; /// Implements the [OAuth2 Refresh Token Flow](https://developers.google.com/youtube/v3/guides/authentication#devices). @@ -28,14 +28,12 @@ impl RefreshFlow { /// /// # Examples /// Please see the crate landing page for an example. - pub(crate) async fn refresh_token( - client: &hyper_util::client::legacy::Client, + pub(crate) async fn refresh_token( + client: &impl SendRequest, client_secret: &ApplicationSecret, refresh_token: &str, ) -> Result - where - C: Connect + Clone + Send + Sync + 'static, - { +where { log::debug!( "refreshing access token with refresh token: {}", refresh_token diff --git a/src/service_account.rs b/src/service_account.rs index 3b66f4ff4..0706f5d9d 100644 --- a/src/service_account.rs +++ b/src/service_account.rs @@ -13,6 +13,7 @@ //! //! Copyright (c) 2016 Google Inc (lewinb@google.com). +use crate::client::SendRequest; use crate::error::Error; use crate::types::TokenInfo; @@ -22,7 +23,6 @@ use base64::Engine as _; use http::header; use http_body_util::BodyExt; -use hyper_util::client::legacy::connect::Connect; #[cfg(all(feature = "aws-lc-rs", not(feature = "ring")))] use rustls::crypto::aws_lc_rs as crypto_provider; #[cfg(feature = "ring")] @@ -194,14 +194,13 @@ impl ServiceAccountFlow { } /// Send a request for a new Bearer token to the OAuth provider. - pub(crate) async fn token( + pub(crate) async fn token( &self, - hyper_client: &hyper_util::client::legacy::Client, + hyper_client: &impl SendRequest, scopes: &[T], ) -> Result where T: AsRef, - C: Connect + Clone + Send + Sync + 'static, { let claims = Claims::new(&self.key, scopes, self.subject.as_deref()); let signed = self.signer.sign_claims(&claims).map_err(|_| { @@ -244,7 +243,7 @@ mod tests { }) .await .unwrap(); - let client = + let client = crate::client::HttpClient::new( hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()) .build( hyper_rustls::HttpsConnectorBuilder::new() @@ -254,7 +253,9 @@ mod tests { .enable_http1() .enable_http2() .build(), - ); + ), + None, + ); println!( "{:?}", acc.token(&client, &["https://www.googleapis.com/auth/pubsub"]) diff --git a/src/service_account_impersonator.rs b/src/service_account_impersonator.rs index 0273dd346..e3902dd56 100644 --- a/src/service_account_impersonator.rs +++ b/src/service_account_impersonator.rs @@ -6,11 +6,11 @@ use http::header; use http_body_util::BodyExt; -use hyper_util::client::legacy::connect::Connect; use serde::Serialize; use crate::{ authorized_user::{AuthorizedUserFlow, AuthorizedUserSecret}, + client::SendRequest, storage::TokenInfo, Error, }; @@ -119,14 +119,13 @@ impl ServiceAccountImpersonationFlow { } } - pub(crate) async fn token( + pub(crate) async fn token( &self, - hyper_client: &hyper_util::client::legacy::Client, + hyper_client: &impl SendRequest, scopes: &[T], ) -> Result where T: AsRef, - C: Connect + Clone + Send + Sync + 'static, { let inner_token = self .inner_flow @@ -188,8 +187,8 @@ fn id_request( .unwrap()) } -pub(crate) async fn token_impl( - hyper_client: &hyper_util::client::legacy::Client, +pub(crate) async fn token_impl( + hyper_client: &impl SendRequest, uri: &str, access_token: bool, inner_token: &str, @@ -197,7 +196,6 @@ pub(crate) async fn token_impl( ) -> Result where T: AsRef, - C: Connect + Clone + Send + Sync + 'static, { let scopes: Vec<_> = scopes.iter().map(|s| s.as_ref()).collect(); let request = if access_token { diff --git a/tests/tests.rs b/tests/tests.rs index 845cd2a80..eb9bedab8 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,18 +1,23 @@ use yup_oauth2::{ authenticator::{DefaultAuthenticator, DefaultHyperClient, HyperClientBuilder}, authenticator_delegate::{DeviceAuthResponse, DeviceFlowDelegate, InstalledFlowDelegate}, + error::SendError, AccessTokenAuthenticator, ApplicationDefaultCredentialsAuthenticator, - ApplicationDefaultCredentialsFlowOpts, ApplicationSecret, DeviceFlowAuthenticator, + ApplicationDefaultCredentialsFlowOpts, ApplicationSecret, DeviceFlowAuthenticator, HttpClient, InstalledFlowAuthenticator, InstalledFlowReturnMethod, ServiceAccountAuthenticator, ServiceAccountKey, }; -use std::future::Future; use std::path::PathBuf; use std::pin::Pin; +use std::{future::Future, time::Duration}; use http::Uri; -use httptest::{matchers::*, responders::json_encoded, Expectation, Server}; +use httptest::{ + matchers::*, + responders::{delay_and_then, json_encoded}, + Expectation, Server, +}; use url::form_urlencoded; /// Utility function for parsing json. Useful in unit tests. Simply wrap the @@ -24,6 +29,13 @@ macro_rules! parse_json { } async fn create_device_flow_auth(server: &Server) -> DefaultAuthenticator { + create_device_flow_auth_with_timeout(server, None).await +} + +async fn create_device_flow_auth_with_timeout( + server: &Server, + timeout: Option, +) -> DefaultAuthenticator { let app_secret: ApplicationSecret = parse_json!({ "client_id": "902216714886-k2v9uei3p1dk6h686jbsn9mo96tnbvto.apps.googleusercontent.com", "project_id": "yup-test-243420", @@ -44,7 +56,12 @@ async fn create_device_flow_auth(server: &Server) -> DefaultAuthenticator { } } - DeviceFlowAuthenticator::with_client(app_secret, DefaultHyperClient.build_test_hyper_client()) + let mut client = DefaultHyperClient::default(); + if let Some(duration) = timeout { + client = client.with_timeout(duration); + } + + DeviceFlowAuthenticator::with_client(app_secret, client.build_test_hyper_client()) .flow_delegate(Box::new(FD)) .device_code_url(server.url_str("/code")) .build() @@ -88,7 +105,7 @@ async fn test_device_success() { }))), ); - let auth = create_device_flow_auth(&server).await; + let auth = create_device_flow_auth_with_timeout(&server, Some(Duration::from_secs(1))).await; let token = auth .token(&["https://www.googleapis.com/scope/1"]) .await @@ -99,6 +116,41 @@ async fn test_device_success() { ); } +#[tokio::test] +async fn test_device_delay() { + let _ = env_logger::try_init(); + let server = Server::run(); + let delay = Duration::from_micros(500); + + server.expect( + Expectation::matching(all_of![ + request::method_path("POST", "/code"), + request::body(url_decoded(contains(( + "client_id", + matches("902216714886") + )))), + ]) + .respond_with(delay_and_then(delay, || { + json_encoded(serde_json::json!({ + "device_code": "devicecode", + "user_code": "usercode", + "verification_url": "https://example.com/verify", + "expires_in": 1234567, + "interval": 1 + })) + })), + ); + + let auth = + create_device_flow_auth_with_timeout(&server, Some(delay - Duration::from_micros(1))).await; + let result = auth.token(&["https://www.googleapis.com/scope/1"]).await; + + assert!(matches!( + result, + Err(yup_oauth2::Error::HttpClientError(SendError::Timeout)) + )) +} + #[tokio::test] async fn test_device_no_code() { let _ = env_logger::try_init(); @@ -175,12 +227,7 @@ async fn create_installed_flow_auth( "client_secret": "iuMPN6Ne1PD7cos29Tk9rlqH", "redirect_uris": ["urn:ietf:wg:oauth:2.0:oob","http://localhost"], }); - struct FD( - hyper_util::client::legacy::Client< - ::Connector, - String, - >, - ); + struct FD(HttpClient<::Connector>); impl InstalledFlowDelegate for FD { /// Depending on need_code, return the pre-set code or send the code to the server at /// the redirect_uri given in the url. @@ -223,7 +270,7 @@ async fn create_installed_flow_auth( } } - let client = DefaultHyperClient.build_test_hyper_client(); + let client = DefaultHyperClient::default().build_test_hyper_client(); let mut builder = InstalledFlowAuthenticator::with_client(app_secret, method, client.clone()) .flow_delegate(Box::new(FD(client))); @@ -341,10 +388,13 @@ async fn create_service_account_auth(server: &Server) -> DefaultAuthenticator { "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/yup-test-sa-1%40yup-test-243420.iam.gserviceaccount.com" }); - ServiceAccountAuthenticator::with_client(key, DefaultHyperClient.build_test_hyper_client()) - .build() - .await - .unwrap() + ServiceAccountAuthenticator::with_client( + key, + DefaultHyperClient::default().build_test_hyper_client(), + ) + .build() + .await + .unwrap() } #[tokio::test] @@ -673,7 +723,7 @@ async fn test_default_application_credentials_from_metadata_server() { }; let authenticator = match ApplicationDefaultCredentialsAuthenticator::with_client( opts, - DefaultHyperClient.build_test_hyper_client(), + DefaultHyperClient::default().build_test_hyper_client(), ) .await { @@ -693,7 +743,7 @@ async fn test_default_application_credentials_from_metadata_server() { #[tokio::test] async fn test_token() { let authenticator = - AccessTokenAuthenticator::with_client("0815".to_string(), DefaultHyperClient) + AccessTokenAuthenticator::with_client("0815".to_string(), DefaultHyperClient::default()) .build() .await .unwrap(); From ab22243e260b9513d6206f57bb00952f15120461 Mon Sep 17 00:00:00 2001 From: threema-donat <129288638+threema-donat@users.noreply.github.com> Date: Wed, 3 Jul 2024 08:49:44 +0200 Subject: [PATCH 02/11] Fix clippy warning of expect followed by fn call --- src/storage.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/storage.rs b/src/storage.rs index 010d169ce..0a8a326b1 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -491,10 +491,7 @@ mod tests { tokio::time::timeout(Duration::from_secs(1), find_file(&filename)) .await - .expect(&format!( - "File not created at {}", - filename.to_string_lossy() - )); + .unwrap_or_else(|_| panic!("File not created at {}", filename.to_string_lossy())); { // Create a new DiskStorage instance and verify the tokens were read from disk correctly. From a519510b20fdaf29b89cf33218a61dccfd48a69d Mon Sep 17 00:00:00 2001 From: threema-donat <129288638+threema-donat@users.noreply.github.com> Date: Wed, 3 Jul 2024 08:50:46 +0200 Subject: [PATCH 03/11] Remove function that is not needed anymore Since the test and actual fn definition are equal and HTTP is allowed in every case, the test function is not needed anymore --- src/authenticator.rs | 32 -------------------------------- tests/tests.rs | 29 ++++++++++++++++++++--------- 2 files changed, 20 insertions(+), 41 deletions(-) diff --git a/src/authenticator.rs b/src/authenticator.rs index 015e0e7af..f1e8ae7cf 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -964,10 +964,6 @@ pub trait HyperClientBuilder { /// Create a hyper::Client fn build_hyper_client(self) -> Result, Error>; - - /// Create a `hyper_util::client::legacy::Client` for tests (HTTPS not required) - #[doc(hidden)] - fn build_test_hyper_client(self) -> HttpClient; } #[cfg(feature = "hyper-rustls")] @@ -1035,10 +1031,6 @@ where fn build_hyper_client(self) -> Result, Error> { Ok(self.client) } - - fn build_test_hyper_client(self) -> HttpClient { - self.client - } } #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] @@ -1085,26 +1077,6 @@ impl HyperClientBuilder for DefaultHyperClient { self.timeout, )) } - - fn build_test_hyper_client(self) -> HttpClient { - #[cfg(feature = "hyper-rustls")] - let connector = hyper_rustls::HttpsConnectorBuilder::new() - .with_provider_and_native_roots(default_crypto_provider()) - .unwrap() - .https_or_http() - .enable_http1() - .enable_http2() - .build(); - #[cfg(all(not(feature = "hyper-rustls"), feature = "hyper-tls"))] - let connector = hyper_tls::HttpsConnector::new(); - - HttpClient::new( - hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()) - .pool_max_idle_per_host(0) - .build::<_, String>(connector), - self.timeout, - ) - } } impl HyperClientBuilder for HttpClient @@ -1121,10 +1093,6 @@ where fn build_hyper_client(self) -> Result, Error> { Ok(self) } - - fn build_test_hyper_client(self) -> HttpClient { - self - } } /// How should the acquired tokens be stored? diff --git a/tests/tests.rs b/tests/tests.rs index eb9bedab8..26971c152 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -61,12 +61,17 @@ async fn create_device_flow_auth_with_timeout( client = client.with_timeout(duration); } - DeviceFlowAuthenticator::with_client(app_secret, client.build_test_hyper_client()) - .flow_delegate(Box::new(FD)) - .device_code_url(server.url_str("/code")) - .build() - .await - .unwrap() + DeviceFlowAuthenticator::with_client( + app_secret, + client + .build_hyper_client() + .expect("Hyper client to be built"), + ) + .flow_delegate(Box::new(FD)) + .device_code_url(server.url_str("/code")) + .build() + .await + .unwrap() } #[tokio::test] @@ -270,7 +275,9 @@ async fn create_installed_flow_auth( } } - let client = DefaultHyperClient::default().build_test_hyper_client(); + let client = DefaultHyperClient::default() + .build_hyper_client() + .expect("Hyper client to be built"); let mut builder = InstalledFlowAuthenticator::with_client(app_secret, method, client.clone()) .flow_delegate(Box::new(FD(client))); @@ -390,7 +397,9 @@ async fn create_service_account_auth(server: &Server) -> DefaultAuthenticator { ServiceAccountAuthenticator::with_client( key, - DefaultHyperClient::default().build_test_hyper_client(), + DefaultHyperClient::default() + .build_hyper_client() + .expect("Hyper client to be built"), ) .build() .await @@ -723,7 +732,9 @@ async fn test_default_application_credentials_from_metadata_server() { }; let authenticator = match ApplicationDefaultCredentialsAuthenticator::with_client( opts, - DefaultHyperClient::default().build_test_hyper_client(), + DefaultHyperClient::default() + .build_hyper_client() + .expect("Hyper client to be built"), ) .await { From 89f8d30123a9aeb53901c5e6707bb971fc7d016c Mon Sep 17 00:00:00 2001 From: threema-donat <129288638+threema-donat@users.noreply.github.com> Date: Wed, 3 Jul 2024 08:53:06 +0200 Subject: [PATCH 04/11] Remove code that did not fix the flaky test --- src/storage.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/storage.rs b/src/storage.rs index 0a8a326b1..9a2af3026 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -462,10 +462,8 @@ mod tests { let scope_set = ScopeSet::from(&["myscope"]); let tempdir = tempfile::Builder::new() - .prefix("yup-oauth2-tests_") - .rand_bytes(15) .tempdir() - .unwrap(); + .expect("Tempdir to be created"); let filename = tempdir.path().join("tokenstorage.json"); From ebd79e6ac1b50c9bee15c3854be317c3a3224a7a Mon Sep 17 00:00:00 2001 From: threema-donat <129288638+threema-donat@users.noreply.github.com> Date: Wed, 3 Jul 2024 08:58:53 +0200 Subject: [PATCH 05/11] Add clippy step and rename jobs --- .github/workflows/test.yml | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7bae34d34..843a47cbc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,7 +4,7 @@ name: Actions CI jobs: build_and_test: - name: yup-oauth2 + name: Run tests runs-on: ubuntu-latest strategy: fail-fast: false @@ -21,7 +21,7 @@ jobs: - run: cargo test --no-default-features --features ${{ matrix.features }} doc: - name: yup-oauth2 + name: Create docs runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -33,7 +33,7 @@ jobs: RUSTDOCFLAGS: --cfg yup_oauth2_docsrs fmt: - name: yup-oauth2 + name: Check formatting runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -41,3 +41,14 @@ jobs: with: toolchain: stable - run: cargo fmt -- --check + + clippy: + name: Run clippy lints + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + components: clippy + - run: cargo clippy --all-features --all-targets -- -D warnings From e556f911423cec6d2661e2316913bc1c43cb1e83 Mon Sep 17 00:00:00 2001 From: threema-donat <129288638+threema-donat@users.noreply.github.com> Date: Wed, 3 Jul 2024 09:21:47 +0200 Subject: [PATCH 06/11] Remove duplicate conditional compilation --- src/service_account.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/service_account.rs b/src/service_account.rs index 0706f5d9d..f05f42116 100644 --- a/src/service_account.rs +++ b/src/service_account.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "service-account")] - //! This module provides a flow that obtains tokens for service accounts. //! //! Service accounts are usually used by software (i.e., non-human actors) to get access to From 750cdba494e5886540fb794b5cd598b4313ccbb6 Mon Sep 17 00:00:00 2001 From: threema-donat <129288638+threema-donat@users.noreply.github.com> Date: Wed, 3 Jul 2024 09:45:50 +0200 Subject: [PATCH 07/11] Rename yup_oauth2_docsrs config to docsrs Resolves warnings when creating docs and therefore makes failure on warning possible --- .github/workflows/test.yml | 2 +- Cargo.toml | 2 +- src/authenticator.rs | 55 ++++++++------------------------------ src/lib.rs | 2 +- 4 files changed, 14 insertions(+), 47 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 843a47cbc..f4c0d2cf4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,7 +30,7 @@ jobs: toolchain: nightly - run: cargo doc --no-deps --all-features env: - RUSTDOCFLAGS: --cfg yup_oauth2_docsrs + RUSTDOCFLAGS: --cfg docsrs -D warnings fmt: name: Check formatting diff --git a/Cargo.toml b/Cargo.toml index 0998f0ca4..6347800a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,4 +71,4 @@ members = ["examples/test-installed/", "examples/test-svc-acct/", "examples/test [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "yup_oauth2_docsrs"] +rustdoc-args = ["--cfg", "docsrs"] diff --git a/src/authenticator.rs b/src/authenticator.rs index f1e8ae7cf..b2adf6ab2 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -205,10 +205,7 @@ pub struct InstalledFlowAuthenticator; impl InstalledFlowAuthenticator { /// Use the builder pattern to create an Authenticator that uses the installed flow. #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] - #[cfg_attr( - yup_oauth2_docsrs, - doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))) - )] + #[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))] pub fn builder( app_secret: ApplicationSecret, method: InstalledFlowReturnMethod, @@ -241,10 +238,7 @@ pub struct DeviceFlowAuthenticator; impl DeviceFlowAuthenticator { /// Use the builder pattern to create an Authenticator that uses the device flow. #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] - #[cfg_attr( - yup_oauth2_docsrs, - doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))) - )] + #[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))] pub fn builder( app_secret: ApplicationSecret, ) -> AuthenticatorBuilder { @@ -278,10 +272,7 @@ pub struct ServiceAccountAuthenticator; impl ServiceAccountAuthenticator { /// Use the builder pattern to create an Authenticator that uses a service account. #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] - #[cfg_attr( - yup_oauth2_docsrs, - doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))) - )] + #[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))] pub fn builder( service_account_key: ServiceAccountKey, ) -> AuthenticatorBuilder { @@ -341,10 +332,7 @@ impl ApplicationDefaultCredentialsAuthenticator { /// Service account one or GCE instance metadata kind #[cfg(feature = "service-account")] #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] - #[cfg_attr( - yup_oauth2_docsrs, - doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))) - )] + #[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))] pub async fn builder( opts: ApplicationDefaultCredentialsFlowOpts, ) -> ApplicationDefaultCredentialsTypes { @@ -400,10 +388,7 @@ pub struct AuthorizedUserAuthenticator; impl AuthorizedUserAuthenticator { /// Use the builder pattern to create an Authenticator that uses an authorized user. #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] - #[cfg_attr( - yup_oauth2_docsrs, - doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))) - )] + #[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))] pub fn builder( authorized_user_secret: AuthorizedUserSecret, ) -> AuthenticatorBuilder { @@ -440,10 +425,7 @@ pub struct ExternalAccountAuthenticator; impl ExternalAccountAuthenticator { /// Use the builder pattern to create an Authenticator that uses an external account. #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] - #[cfg_attr( - yup_oauth2_docsrs, - doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))) - )] + #[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))] pub fn builder( external_account_secret: ExternalAccountSecret, ) -> AuthenticatorBuilder { @@ -515,10 +497,7 @@ pub struct ServiceAccountImpersonationAuthenticator; impl ServiceAccountImpersonationAuthenticator { /// Use the builder pattern to create an Authenticator that uses the device flow. #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] - #[cfg_attr( - yup_oauth2_docsrs, - doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))) - )] + #[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))] pub fn builder( authorized_user_secret: AuthorizedUserSecret, service_account_email: &str, @@ -967,29 +946,20 @@ pub trait HyperClientBuilder { } #[cfg(feature = "hyper-rustls")] -#[cfg_attr( - yup_oauth2_docsrs, - doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))) -)] +#[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))] /// Default authenticator type pub type DefaultAuthenticator = Authenticator>; #[cfg(all(not(feature = "hyper-rustls"), feature = "hyper-tls"))] -#[cfg_attr( - yup_oauth2_docsrs, - doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))) -)] +#[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))] /// Default authenticator type pub type DefaultAuthenticator = Authenticator>; /// The builder value used when the default hyper client should be used. #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] -#[cfg_attr( - yup_oauth2_docsrs, - doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))) -)] +#[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))] #[derive(Default)] pub struct DefaultHyperClient { timeout: Option, @@ -1043,10 +1013,7 @@ impl DefaultHyperClient { } #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] -#[cfg_attr( - yup_oauth2_docsrs, - doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))) -)] +#[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))] impl HyperClientBuilder for DefaultHyperClient { #[cfg(feature = "hyper-rustls")] type Connector = diff --git a/src/lib.rs b/src/lib.rs index 252ed9ced..1a7d2b786 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,7 +70,7 @@ //! ``` //! #![deny(missing_docs)] -#![cfg_attr(yup_oauth2_docsrs, feature(doc_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] pub mod access_token; mod application_default_credentials; From 9d09d5b2e322824b6f0988ba4fb4480a0a489e04 Mon Sep 17 00:00:00 2001 From: threema-donat <129288638+threema-donat@users.noreply.github.com> Date: Wed, 3 Jul 2024 09:46:49 +0200 Subject: [PATCH 08/11] Fix formatting of markdown link --- src/external_account.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/external_account.rs b/src/external_account.rs index 802c29cee..10e0cfe05 100644 --- a/src/external_account.rs +++ b/src/external_account.rs @@ -47,8 +47,7 @@ pub enum CredentialSource { file: String, }, - //// [Microsoft Azure and URL-sourced - ///credentials](https://google.aip.dev/auth/4117#determining-the-subject-token-in-microsoft-azure-and-url-sourced-credentials) + /// [Microsoft Azure and URL-sourced credentials](https://google.aip.dev/auth/4117#determining-the-subject-token-in-microsoft-azure-and-url-sourced-credentials) Url { /// This defines the local metadata server to retrieve the external credentials from. For /// Azure, this should be the Azure Instance Metadata Service (IMDS) URL used to retrieve From ff1ffe9b1915eebb168883c05616457e4b706812 Mon Sep 17 00:00:00 2001 From: threema-donat <129288638+threema-donat@users.noreply.github.com> Date: Mon, 5 Aug 2024 11:54:10 +0200 Subject: [PATCH 09/11] Move code related to client to its own module --- examples/custom_client.rs | 2 +- src/authenticator.rs | 125 +----------------------------------- src/client.rs | 130 +++++++++++++++++++++++++++++++++++++- src/lib.rs | 7 +- tests/tests.rs | 5 +- 5 files changed, 138 insertions(+), 131 deletions(-) diff --git a/examples/custom_client.rs b/examples/custom_client.rs index 6ec08776f..1d39ba34a 100644 --- a/examples/custom_client.rs +++ b/examples/custom_client.rs @@ -8,7 +8,7 @@ use std::time::Duration; use hyper_util::client::legacy::connect::Connect; -use yup_oauth2::authenticator::HyperClientBuilder; +use yup_oauth2::HyperClientBuilder; async fn r#use( client: hyper_util::client::legacy::Client, diff --git a/src/authenticator.rs b/src/authenticator.rs index b2adf6ab2..935d57700 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -4,7 +4,9 @@ use crate::application_default_credentials::{ }; use crate::authenticator_delegate::{DeviceFlowDelegate, InstalledFlowDelegate}; use crate::authorized_user::{AuthorizedUserFlow, AuthorizedUserSecret}; -use crate::client::{HttpClient, LegacyClient}; +#[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] +use crate::client::DefaultHyperClient; +use crate::client::{HttpClient, HyperClientBuilder}; use crate::device::DeviceFlow; use crate::error::Error; use crate::external_account::{ExternalAccountFlow, ExternalAccountSecret}; @@ -17,10 +19,6 @@ use crate::service_account::{self, ServiceAccountFlow, ServiceAccountFlowOpts, S use crate::storage::{self, Storage, TokenStorage}; use crate::types::{AccessToken, ApplicationSecret, TokenInfo}; use private::AuthFlow; -#[cfg(all(feature = "aws-lc-rs", feature = "hyper-rustls", not(feature = "ring")))] -use rustls::crypto::aws_lc_rs::default_provider as default_crypto_provider; -#[cfg(all(feature = "ring", feature = "hyper-rustls"))] -use rustls::crypto::ring::default_provider as default_crypto_provider; use crate::access_token::AccessTokenFlow; @@ -933,18 +931,6 @@ mod private { } } -/// A trait implemented for any hyper_util::client::legacy::Client as well as the DefaultHyperClient. -pub trait HyperClientBuilder { - /// The hyper connector that the resulting hyper client will use. - type Connector: Connect + Clone + Send + Sync + 'static; - - /// Sets duration after which a request times out - fn with_timeout(self, timeout: Duration) -> Self; - - /// Create a hyper::Client - fn build_hyper_client(self) -> Result, Error>; -} - #[cfg(feature = "hyper-rustls")] #[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))] /// Default authenticator type @@ -957,111 +943,6 @@ pub type DefaultAuthenticator = pub type DefaultAuthenticator = Authenticator>; -/// The builder value used when the default hyper client should be used. -#[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] -#[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))] -#[derive(Default)] -pub struct DefaultHyperClient { - timeout: Option, -} - -/// Intended for using an existing hyper client with `yup-oauth2`. Instantiate -/// with [`CustomHyperClient::from`] -pub struct CustomHyperClient -where - C: Connect + Clone + Send + Sync + 'static, -{ - client: HttpClient, - timeout: Option, -} - -impl From> for CustomHyperClient -where - C: Connect + Clone + Send + Sync + 'static, -{ - fn from(client: LegacyClient) -> Self { - Self { - client: HttpClient::new(client, None), - timeout: None, - } - } -} - -impl HyperClientBuilder for CustomHyperClient -where - C: Connect + Clone + Send + Sync + 'static, -{ - type Connector = C; - - fn with_timeout(mut self, timeout: Duration) -> Self { - self.timeout = Some(timeout); - self - } - - fn build_hyper_client(self) -> Result, Error> { - Ok(self.client) - } -} - -#[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] -impl DefaultHyperClient { - /// Set the duration after which a request times out - pub fn with_timeout(mut self, timeout: Duration) -> Self { - self.timeout = Some(timeout); - self - } -} - -#[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] -#[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))] -impl HyperClientBuilder for DefaultHyperClient { - #[cfg(feature = "hyper-rustls")] - type Connector = - hyper_rustls::HttpsConnector; - #[cfg(all(not(feature = "hyper-rustls"), feature = "hyper-tls"))] - type Connector = hyper_tls::HttpsConnector; - - fn with_timeout(mut self, timeout: Duration) -> Self { - self.timeout = Some(timeout); - self - } - - fn build_hyper_client(self) -> Result, Error> { - #[cfg(feature = "hyper-rustls")] - let connector = hyper_rustls::HttpsConnectorBuilder::new() - .with_provider_and_native_roots(default_crypto_provider())? - .https_or_http() - .enable_http1() - .enable_http2() - .build(); - #[cfg(all(not(feature = "hyper-rustls"), feature = "hyper-tls"))] - let connector = hyper_tls::HttpsConnector::new(); - - Ok(HttpClient::new( - hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()) - .pool_max_idle_per_host(0) - .build::<_, String>(connector), - self.timeout, - )) - } -} - -impl HyperClientBuilder for HttpClient -where - C: Connect + Clone + Send + Sync + 'static, -{ - type Connector = C; - - fn with_timeout(mut self, timeout: Duration) -> Self { - self.set_timeout(timeout); - self - } - - fn build_hyper_client(self) -> Result, Error> { - Ok(self) - } -} - /// How should the acquired tokens be stored? enum StorageType { /// Store tokens in memory (and always log in again to acquire a new token on startup) diff --git a/src/client.rs b/src/client.rs index 4a4f92d27..fb3f3cfbc 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,10 +1,17 @@ +//! Module containing the HTTP client used for sending requests use std::time::Duration; use futures::TryFutureExt; use http::Uri; use hyper_util::client::legacy::{connect::Connect, Error as LegacyHyperError}; +#[cfg(all(feature = "aws-lc-rs", feature = "hyper-rustls", not(feature = "ring")))] +use rustls::crypto::aws_lc_rs::default_provider as default_crypto_provider; +#[cfg(all(feature = "ring", feature = "hyper-rustls"))] +use rustls::crypto::ring::default_provider as default_crypto_provider; use thiserror::Error as ThisError; +use crate::Error; + type HyperResponse = http::Response; pub(crate) type LegacyClient = hyper_util::client::legacy::Client; @@ -19,6 +26,18 @@ pub enum SendError { Hyper(#[source] LegacyHyperError), } +/// A trait implemented for any hyper_util::client::legacy::Client as well as the DefaultHyperClient. +pub trait HyperClientBuilder { + /// The hyper connector that the resulting hyper client will use. + type Connector: Connect + Clone + Send + Sync + 'static; + + /// Sets duration after which a request times out + fn with_timeout(self, timeout: Duration) -> Self; + + /// Create a hyper::Client + fn build_hyper_client(self) -> Result, Error>; +} + /// Client that can be configured that a request will timeout after a specified /// duration. #[derive(Clone)] @@ -34,14 +53,14 @@ impl HttpClient where C: Connect + Clone + Send + Sync + 'static, { - pub(super) fn new(hyper_client: LegacyClient, timeout: Option) -> Self { + pub(crate) fn new(hyper_client: LegacyClient, timeout: Option) -> Self { Self { client: hyper_client, timeout, } } - pub(super) fn set_timeout(&mut self, timeout: Duration) { + pub(crate) fn set_timeout(&mut self, timeout: Duration) { self.timeout = Some(timeout); } @@ -52,6 +71,22 @@ where } } +impl HyperClientBuilder for HttpClient +where + C: Connect + Clone + Send + Sync + 'static, +{ + type Connector = C; + + fn with_timeout(mut self, timeout: Duration) -> Self { + self.set_timeout(timeout); + self + } + + fn build_hyper_client(self) -> Result, Error> { + Ok(self) + } +} + impl SendRequest for HttpClient where C: Connect + Clone + Send + Sync + 'static, @@ -70,6 +105,95 @@ where } } -pub(super) trait SendRequest { +pub(crate) trait SendRequest { async fn request(&self, payload: http::Request) -> Result; } + +/// The builder value used when the default hyper client should be used. +#[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))] +#[derive(Default)] +pub struct DefaultHyperClient { + timeout: Option, +} + +#[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] +impl DefaultHyperClient { + /// Set the duration after which a request times out + pub fn with_timeout(mut self, timeout: Duration) -> Self { + self.timeout = Some(timeout); + self + } +} + +#[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))] +impl HyperClientBuilder for DefaultHyperClient { + #[cfg(feature = "hyper-rustls")] + type Connector = + hyper_rustls::HttpsConnector; + #[cfg(all(not(feature = "hyper-rustls"), feature = "hyper-tls"))] + type Connector = hyper_tls::HttpsConnector; + + fn with_timeout(mut self, timeout: Duration) -> Self { + self.timeout = Some(timeout); + self + } + + fn build_hyper_client(self) -> Result, Error> { + #[cfg(feature = "hyper-rustls")] + let connector = hyper_rustls::HttpsConnectorBuilder::new() + .with_provider_and_native_roots(default_crypto_provider())? + .https_or_http() + .enable_http1() + .enable_http2() + .build(); + #[cfg(all(not(feature = "hyper-rustls"), feature = "hyper-tls"))] + let connector = hyper_tls::HttpsConnector::new(); + + Ok(HttpClient::new( + hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()) + .pool_max_idle_per_host(0) + .build::<_, String>(connector), + self.timeout, + )) + } +} + +/// Intended for using an existing hyper client with `yup-oauth2`. Instantiate +/// with [`CustomHyperClient::from`] +pub struct CustomHyperClient +where + C: Connect + Clone + Send + Sync + 'static, +{ + client: HttpClient, + timeout: Option, +} + +impl From> for CustomHyperClient +where + C: Connect + Clone + Send + Sync + 'static, +{ + fn from(client: LegacyClient) -> Self { + Self { + client: HttpClient::new(client, None), + timeout: None, + } + } +} + +impl HyperClientBuilder for CustomHyperClient +where + C: Connect + Clone + Send + Sync + 'static, +{ + type Connector = C; + + fn with_timeout(mut self, timeout: Duration) -> Self { + self.timeout = Some(timeout); + self + } + + fn build_hyper_client(self) -> Result, Error> { + Ok(self.client) + } +} diff --git a/src/lib.rs b/src/lib.rs index 1a7d2b786..9157f01af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,7 +77,7 @@ mod application_default_credentials; pub mod authenticator; pub mod authenticator_delegate; pub mod authorized_user; -mod client; +pub mod client; mod device; pub mod error; pub mod external_account; @@ -108,11 +108,12 @@ pub use crate::authenticator::ServiceAccountAuthenticator; pub use crate::authenticator::AccessTokenAuthenticator; #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] -pub use crate::client::HttpClient; +pub use crate::client::DefaultHyperClient; +pub use crate::client::{CustomHyperClient, HttpClient, HyperClientBuilder}; #[doc(inline)] pub use crate::authenticator::{ - ApplicationDefaultCredentialsAuthenticator, AuthorizedUserAuthenticator, CustomHyperClient, + ApplicationDefaultCredentialsAuthenticator, AuthorizedUserAuthenticator, DeviceFlowAuthenticator, ExternalAccountAuthenticator, InstalledFlowAuthenticator, ServiceAccountImpersonationAuthenticator, }; diff --git a/tests/tests.rs b/tests/tests.rs index 26971c152..f8160fc0e 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,9 +1,10 @@ use yup_oauth2::{ - authenticator::{DefaultAuthenticator, DefaultHyperClient, HyperClientBuilder}, + authenticator::DefaultAuthenticator, authenticator_delegate::{DeviceAuthResponse, DeviceFlowDelegate, InstalledFlowDelegate}, + client::{DefaultHyperClient, HttpClient, HyperClientBuilder}, error::SendError, AccessTokenAuthenticator, ApplicationDefaultCredentialsAuthenticator, - ApplicationDefaultCredentialsFlowOpts, ApplicationSecret, DeviceFlowAuthenticator, HttpClient, + ApplicationDefaultCredentialsFlowOpts, ApplicationSecret, DeviceFlowAuthenticator, InstalledFlowAuthenticator, InstalledFlowReturnMethod, ServiceAccountAuthenticator, ServiceAccountKey, }; From e05320a799a69895ace8e832f04197b5447f4303 Mon Sep 17 00:00:00 2001 From: threema-donat <129288638+threema-donat@users.noreply.github.com> Date: Mon, 5 Aug 2024 12:13:17 +0200 Subject: [PATCH 10/11] Fix indentation of docs --- src/service_account.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/service_account.rs b/src/service_account.rs index f05f42116..02b047df1 100644 --- a/src/service_account.rs +++ b/src/service_account.rs @@ -6,7 +6,7 @@ //! //! Resources: //! - [Using OAuth 2.0 for Server to Server -//! Applications](https://developers.google.com/identity/protocols/OAuth2ServiceAccount) +//! Applications](https://developers.google.com/identity/protocols/OAuth2ServiceAccount) //! - [JSON Web Tokens](https://jwt.io/) //! //! Copyright (c) 2016 Google Inc (lewinb@google.com). From d61b4cb880e5164a1d5a35512984e728251ad7c8 Mon Sep 17 00:00:00 2001 From: threema-donat <129288638+threema-donat@users.noreply.github.com> Date: Mon, 5 Aug 2024 12:15:25 +0200 Subject: [PATCH 11/11] Rename the client builders to match their function --- examples/custom_client.rs | 3 ++- src/authenticator.rs | 42 +++++++++++++++++++++------------------ src/client.rs | 14 ++++++------- src/lib.rs | 4 ++-- tests/tests.rs | 24 ++++++++++++---------- 5 files changed, 47 insertions(+), 40 deletions(-) diff --git a/examples/custom_client.rs b/examples/custom_client.rs index 1d39ba34a..441ff1a50 100644 --- a/examples/custom_client.rs +++ b/examples/custom_client.rs @@ -48,7 +48,8 @@ async fn main() { ); let authenticator = yup_oauth2::ServiceAccountAuthenticator::with_client( secret, - yup_oauth2::CustomHyperClient::from(client.clone()).with_timeout(Duration::from_secs(10)), + yup_oauth2::CustomHyperClientBuilder::from(client.clone()) + .with_timeout(Duration::from_secs(10)), ) .build() .await diff --git a/src/authenticator.rs b/src/authenticator.rs index 935d57700..4e153b4c0 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -5,7 +5,7 @@ use crate::application_default_credentials::{ use crate::authenticator_delegate::{DeviceFlowDelegate, InstalledFlowDelegate}; use crate::authorized_user::{AuthorizedUserFlow, AuthorizedUserSecret}; #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] -use crate::client::DefaultHyperClient; +use crate::client::DefaultHyperClientBuilder; use crate::client::{HttpClient, HyperClientBuilder}; use crate::device::DeviceFlow; use crate::error::Error; @@ -207,8 +207,8 @@ impl InstalledFlowAuthenticator { pub fn builder( app_secret: ApplicationSecret, method: InstalledFlowReturnMethod, - ) -> AuthenticatorBuilder { - Self::with_client(app_secret, method, DefaultHyperClient::default()) + ) -> AuthenticatorBuilder { + Self::with_client(app_secret, method, DefaultHyperClientBuilder::default()) } /// Construct a new Authenticator that uses the installed flow and the provided http client. @@ -239,8 +239,8 @@ impl DeviceFlowAuthenticator { #[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))] pub fn builder( app_secret: ApplicationSecret, - ) -> AuthenticatorBuilder { - Self::with_client(app_secret, DefaultHyperClient::default()) + ) -> AuthenticatorBuilder { + Self::with_client(app_secret, DefaultHyperClientBuilder::default()) } /// Construct a new Authenticator that uses the installed flow and the provided http client. @@ -273,8 +273,8 @@ impl ServiceAccountAuthenticator { #[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))] pub fn builder( service_account_key: ServiceAccountKey, - ) -> AuthenticatorBuilder { - Self::with_client(service_account_key, DefaultHyperClient::default()) + ) -> AuthenticatorBuilder { + Self::with_client(service_account_key, DefaultHyperClientBuilder::default()) } /// Construct a new Authenticator that uses the installed flow and the provided http client. @@ -333,8 +333,8 @@ impl ApplicationDefaultCredentialsAuthenticator { #[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))] pub async fn builder( opts: ApplicationDefaultCredentialsFlowOpts, - ) -> ApplicationDefaultCredentialsTypes { - Self::with_client(opts, DefaultHyperClient::default()).await + ) -> ApplicationDefaultCredentialsTypes { + Self::with_client(opts, DefaultHyperClientBuilder::default()).await } /// Use the builder pattern to deduce which model of authenticator should be used and allow providing a hyper client @@ -389,8 +389,8 @@ impl AuthorizedUserAuthenticator { #[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))] pub fn builder( authorized_user_secret: AuthorizedUserSecret, - ) -> AuthenticatorBuilder { - Self::with_client(authorized_user_secret, DefaultHyperClient::default()) + ) -> AuthenticatorBuilder { + Self::with_client(authorized_user_secret, DefaultHyperClientBuilder::default()) } /// Construct a new Authenticator that uses the installed flow and the provided http client. @@ -426,8 +426,11 @@ impl ExternalAccountAuthenticator { #[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))] pub fn builder( external_account_secret: ExternalAccountSecret, - ) -> AuthenticatorBuilder { - Self::with_client(external_account_secret, DefaultHyperClient::default()) + ) -> AuthenticatorBuilder { + Self::with_client( + external_account_secret, + DefaultHyperClientBuilder::default(), + ) } /// Construct a new Authenticator that uses the installed flow and the provided http client. @@ -463,8 +466,8 @@ impl AccessTokenAuthenticator { /// the builder pattern for the authenticator pub fn builder( access_token: String, - ) -> AuthenticatorBuilder { - Self::with_client(access_token, DefaultHyperClient::default()) + ) -> AuthenticatorBuilder { + Self::with_client(access_token, DefaultHyperClientBuilder::default()) } /// Construct a new Authenticator that uses the installed flow and the provided http client. /// the client itself is not used @@ -499,11 +502,11 @@ impl ServiceAccountImpersonationAuthenticator { pub fn builder( authorized_user_secret: AuthorizedUserSecret, service_account_email: &str, - ) -> AuthenticatorBuilder { + ) -> AuthenticatorBuilder { Self::with_client( authorized_user_secret, service_account_email, - DefaultHyperClient::default(), + DefaultHyperClientBuilder::default(), ) } @@ -525,7 +528,7 @@ impl ServiceAccountImpersonationAuthenticator { /// # async fn foo() { /// # let client = hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build_http::(); /// # let app_secret = yup_oauth2::read_application_secret("/tmp/foo").await.unwrap(); -/// let authenticator = yup_oauth2::DeviceFlowAuthenticator::with_client(app_secret, yup_oauth2::CustomHyperClient::from(client)) +/// let authenticator = yup_oauth2::DeviceFlowAuthenticator::with_client(app_secret, yup_oauth2::CustomHyperClientBuilder::from(client)) /// .persist_tokens_to_disk("/tmp/tokenfile.json") /// .build() /// .await @@ -960,6 +963,7 @@ mod tests { fn ensure_send_sync() { use super::*; fn is_send_sync() {} - is_send_sync::::Connector>>() + is_send_sync::::Connector>>( + ) } } diff --git a/src/client.rs b/src/client.rs index fb3f3cfbc..5f76544cf 100644 --- a/src/client.rs +++ b/src/client.rs @@ -113,12 +113,12 @@ pub(crate) trait SendRequest { #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))] #[derive(Default)] -pub struct DefaultHyperClient { +pub struct DefaultHyperClientBuilder { timeout: Option, } #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] -impl DefaultHyperClient { +impl DefaultHyperClientBuilder { /// Set the duration after which a request times out pub fn with_timeout(mut self, timeout: Duration) -> Self { self.timeout = Some(timeout); @@ -128,7 +128,7 @@ impl DefaultHyperClient { #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))] -impl HyperClientBuilder for DefaultHyperClient { +impl HyperClientBuilder for DefaultHyperClientBuilder { #[cfg(feature = "hyper-rustls")] type Connector = hyper_rustls::HttpsConnector; @@ -161,8 +161,8 @@ impl HyperClientBuilder for DefaultHyperClient { } /// Intended for using an existing hyper client with `yup-oauth2`. Instantiate -/// with [`CustomHyperClient::from`] -pub struct CustomHyperClient +/// with [`CustomHyperClientBuilder::from`] +pub struct CustomHyperClientBuilder where C: Connect + Clone + Send + Sync + 'static, { @@ -170,7 +170,7 @@ where timeout: Option, } -impl From> for CustomHyperClient +impl From> for CustomHyperClientBuilder where C: Connect + Clone + Send + Sync + 'static, { @@ -182,7 +182,7 @@ where } } -impl HyperClientBuilder for CustomHyperClient +impl HyperClientBuilder for CustomHyperClientBuilder where C: Connect + Clone + Send + Sync + 'static, { diff --git a/src/lib.rs b/src/lib.rs index 9157f01af..46b940b4e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -108,8 +108,8 @@ pub use crate::authenticator::ServiceAccountAuthenticator; pub use crate::authenticator::AccessTokenAuthenticator; #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] -pub use crate::client::DefaultHyperClient; -pub use crate::client::{CustomHyperClient, HttpClient, HyperClientBuilder}; +pub use crate::client::DefaultHyperClientBuilder; +pub use crate::client::{CustomHyperClientBuilder, HttpClient, HyperClientBuilder}; #[doc(inline)] pub use crate::authenticator::{ diff --git a/tests/tests.rs b/tests/tests.rs index f8160fc0e..7766dff60 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,7 +1,7 @@ use yup_oauth2::{ authenticator::DefaultAuthenticator, authenticator_delegate::{DeviceAuthResponse, DeviceFlowDelegate, InstalledFlowDelegate}, - client::{DefaultHyperClient, HttpClient, HyperClientBuilder}, + client::{DefaultHyperClientBuilder, HttpClient, HyperClientBuilder}, error::SendError, AccessTokenAuthenticator, ApplicationDefaultCredentialsAuthenticator, ApplicationDefaultCredentialsFlowOpts, ApplicationSecret, DeviceFlowAuthenticator, @@ -57,7 +57,7 @@ async fn create_device_flow_auth_with_timeout( } } - let mut client = DefaultHyperClient::default(); + let mut client = DefaultHyperClientBuilder::default(); if let Some(duration) = timeout { client = client.with_timeout(duration); } @@ -233,7 +233,7 @@ async fn create_installed_flow_auth( "client_secret": "iuMPN6Ne1PD7cos29Tk9rlqH", "redirect_uris": ["urn:ietf:wg:oauth:2.0:oob","http://localhost"], }); - struct FD(HttpClient<::Connector>); + struct FD(HttpClient<::Connector>); impl InstalledFlowDelegate for FD { /// Depending on need_code, return the pre-set code or send the code to the server at /// the redirect_uri given in the url. @@ -276,7 +276,7 @@ async fn create_installed_flow_auth( } } - let client = DefaultHyperClient::default() + let client = DefaultHyperClientBuilder::default() .build_hyper_client() .expect("Hyper client to be built"); let mut builder = InstalledFlowAuthenticator::with_client(app_secret, method, client.clone()) @@ -398,7 +398,7 @@ async fn create_service_account_auth(server: &Server) -> DefaultAuthenticator { ServiceAccountAuthenticator::with_client( key, - DefaultHyperClient::default() + DefaultHyperClientBuilder::default() .build_hyper_client() .expect("Hyper client to be built"), ) @@ -733,7 +733,7 @@ async fn test_default_application_credentials_from_metadata_server() { }; let authenticator = match ApplicationDefaultCredentialsAuthenticator::with_client( opts, - DefaultHyperClient::default() + DefaultHyperClientBuilder::default() .build_hyper_client() .expect("Hyper client to be built"), ) @@ -754,11 +754,13 @@ async fn test_default_application_credentials_from_metadata_server() { #[tokio::test] async fn test_token() { - let authenticator = - AccessTokenAuthenticator::with_client("0815".to_string(), DefaultHyperClient::default()) - .build() - .await - .unwrap(); + let authenticator = AccessTokenAuthenticator::with_client( + "0815".to_string(), + DefaultHyperClientBuilder::default(), + ) + .build() + .await + .unwrap(); let access_token = authenticator .token(&["https://googleapis.com/some/scope"]) .await