//! Module containing the core functionality for OAuth2 Authentication. use crate::application_default_credentials::{ ApplicationDefaultCredentialsFlow, ApplicationDefaultCredentialsFlowOpts, }; use crate::authenticator_delegate::{DeviceFlowDelegate, InstalledFlowDelegate}; use crate::authorized_user::{AuthorizedUserFlow, AuthorizedUserSecret}; use crate::device::DeviceFlow; use crate::error::Error; use crate::installed::{InstalledFlow, InstalledFlowReturnMethod}; use crate::refresh::RefreshFlow; #[cfg(feature = "service_account")] use crate::service_account::{self, ServiceAccountFlow, ServiceAccountFlowOpts, ServiceAccountKey}; use crate::storage::{self, Storage, TokenStorage}; use crate::types::{AccessToken, ApplicationSecret, TokenInfo}; use private::AuthFlow; use futures::lock::Mutex; use http::{Uri}; use hyper::client::connect::Connection; use std::borrow::Cow; use std::error::Error as StdError; use std::fmt; use std::io; use std::path::PathBuf; use std::sync::Arc; use tokio::io::{AsyncRead, AsyncWrite}; use tower_service::Service; struct InnerAuthenticator { hyper_client: hyper::Client, storage: Storage, auth_flow: AuthFlow, } /// Authenticator is responsible for fetching tokens, handling refreshing tokens, /// and optionally persisting tokens to disk. #[derive(Clone)] pub struct Authenticator { inner: Arc>, } struct DisplayScopes<'a, T>(&'a [T]); impl<'a, T> fmt::Display for DisplayScopes<'a, T> where T: AsRef, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("[")?; let mut iter = self.0.iter(); if let Some(first) = iter.next() { f.write_str(first.as_ref())?; for scope in iter { f.write_str(", ")?; f.write_str(scope.as_ref())?; } } f.write_str("]") } } impl Authenticator where S: Service + Clone + Send + Sync + 'static, S::Response: Connection + AsyncRead + AsyncWrite + Send + Unpin + 'static, S::Future: Send + Unpin + 'static, S::Error: Into>, { /// Return the current token for the provided scopes. pub async fn token<'a, T>(&'a self, scopes: &'a [T]) -> Result where T: AsRef, { self.find_token_info(scopes, /* force_refresh = */ false) .await .map(|info| info.into()) } /// Return a token for the provided scopes, but don't reuse cached tokens. Instead, /// always fetch a new token from the OAuth server. pub async fn force_refreshed_token<'a, T>( &'a self, scopes: &'a [T], ) -> Result where T: AsRef, { self.find_token_info(scopes, /* force_refresh = */ true) .await .map(|info| info.into()) } /// Return the current ID token for the provided scopes, if any pub async fn id_token<'a, T>(&'a self, scopes: &'a [T]) -> Result, Error> where T: AsRef, { self.find_token_info(scopes, /* force_refresh = */ false) .await .map(|info| info.id_token) } /// Return a cached token or fetch a new one from the server. async fn find_token_info<'a, T>( &'a self, scopes: &'a [T], force_refresh: bool, ) -> Result where T: AsRef, { log::debug!( "access token requested for scopes: {}", DisplayScopes(scopes) ); let hashed_scopes = storage::ScopeSet::from(scopes); match ( self.inner.storage.get(hashed_scopes).await, self.inner.auth_flow.app_secret(), ) { (Some(t), _) if !t.is_expired() && !force_refresh => { // unexpired token found log::debug!("found valid token in cache: {:?}", t); Ok(t) } ( Some(TokenInfo { refresh_token: Some(refresh_token), .. }), Some(app_secret), ) => { // token is expired but has a refresh token. let token_info_result = RefreshFlow::refresh_token( &self.inner.hyper_client, app_secret, &refresh_token, ) .await; let token_info = if let Ok(token_info) = token_info_result { token_info } else { // token refresh failed. self.inner .auth_flow .token(&self.inner.hyper_client, scopes) .await? }; self.inner .storage .set(hashed_scopes, token_info.clone()) .await?; Ok(token_info) } _ => { // no token in the cache or the token returned can't be refreshed. let token_info = self .inner .auth_flow .token(&self.inner.hyper_client, scopes) .await?; self.inner .storage .set(hashed_scopes, token_info.clone()) .await?; Ok(token_info) } } } } /// Configure an Authenticator using the builder pattern. pub struct AuthenticatorBuilder { hyper_client_builder: C, storage_type: StorageType, auth_flow: F, } /// Create an authenticator that uses the installed flow. /// ``` /// # #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] /// # async fn foo() { /// # use yup_oauth2::InstalledFlowReturnMethod; /// # let custom_flow_delegate = yup_oauth2::authenticator_delegate::DefaultInstalledFlowDelegate; /// # let app_secret = yup_oauth2::read_application_secret("/tmp/foo").await.unwrap(); /// let authenticator = yup_oauth2::InstalledFlowAuthenticator::builder( /// app_secret, /// InstalledFlowReturnMethod::HTTPRedirect, /// ) /// .build() /// .await /// .expect("failed to create authenticator"); /// # } /// ``` 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"))) )] pub fn builder( app_secret: ApplicationSecret, method: InstalledFlowReturnMethod, ) -> AuthenticatorBuilder { Self::with_client(app_secret, method, DefaultHyperClient) } /// Construct a new Authenticator that uses the installed flow and the provided http client. pub fn with_client( app_secret: ApplicationSecret, method: InstalledFlowReturnMethod, client: C, ) -> AuthenticatorBuilder { AuthenticatorBuilder::new(InstalledFlow::new(app_secret, method), client) } } /// Create an authenticator that uses the device flow. /// ``` /// # #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] /// # async fn foo() { /// # let app_secret = yup_oauth2::read_application_secret("/tmp/foo").await.unwrap(); /// let authenticator = yup_oauth2::DeviceFlowAuthenticator::builder(app_secret) /// .build() /// .await /// .expect("failed to create authenticator"); /// # } /// ``` 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"))) )] pub fn builder( app_secret: ApplicationSecret, ) -> AuthenticatorBuilder { Self::with_client(app_secret, DefaultHyperClient) } /// Construct a new Authenticator that uses the installed flow and the provided http client. pub fn with_client( app_secret: ApplicationSecret, client: C, ) -> AuthenticatorBuilder { AuthenticatorBuilder::new(DeviceFlow::new(app_secret), client) } } /// Create an authenticator that uses a service account. /// ``` /// # #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] /// # async fn foo() { /// # let service_account_key = yup_oauth2::read_service_account_key("/tmp/foo").await.unwrap(); /// let authenticator = yup_oauth2::ServiceAccountAuthenticator::builder(service_account_key) /// .build() /// .await /// .expect("failed to create authenticator"); /// # } /// ``` #[cfg(feature = "service_account")] pub struct ServiceAccountAuthenticator; #[cfg(feature = "service_account")] 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"))) )] pub fn builder( service_account_key: ServiceAccountKey, ) -> AuthenticatorBuilder { Self::with_client(service_account_key, DefaultHyperClient) } /// Construct a new Authenticator that uses the installed flow and the provided http client. pub fn with_client( service_account_key: ServiceAccountKey, client: C, ) -> AuthenticatorBuilder { AuthenticatorBuilder::new( ServiceAccountFlowOpts { key: service_account::FlowOptsKey::Key(service_account_key), subject: None, }, client, ) } } /// Create an authenticator that uses a application default credentials. /// ``` /// # #[cfg(all(any(feature = "hyper-rustls", feature = "hyper-tls"), feature = "service_account"))] /// # async fn foo() { /// # use yup_oauth2::ApplicationDefaultCredentialsAuthenticator; /// # use yup_oauth2::ApplicationDefaultCredentialsFlowOpts; /// # use yup_oauth2::authenticator::ApplicationDefaultCredentialsTypes; /// /// let opts = ApplicationDefaultCredentialsFlowOpts::default(); /// let authenticator = match ApplicationDefaultCredentialsAuthenticator::builder(opts).await { /// ApplicationDefaultCredentialsTypes::InstanceMetadata(auth) => auth /// .build() /// .await /// .expect("Unable to create instance metadata authenticator"), /// ApplicationDefaultCredentialsTypes::ServiceAccount(auth) => auth /// .build() /// .await /// .expect("Unable to create service account authenticator"), /// }; /// # } /// ``` pub struct ApplicationDefaultCredentialsAuthenticator; impl ApplicationDefaultCredentialsAuthenticator { /// Try to build ServiceAccountFlowOpts from the environment #[cfg(feature = "service_account")] pub async fn from_environment() -> Result { let key_path = std::env::var("GOOGLE_APPLICATION_CREDENTIALS")?; Ok(ServiceAccountFlowOpts { key: service_account::FlowOptsKey::Path(key_path.into()), subject: None, }) } /// Use the builder pattern to deduce which model of authenticator should be used: /// 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"))) )] pub async fn builder( opts: ApplicationDefaultCredentialsFlowOpts, ) -> ApplicationDefaultCredentialsTypes { Self::with_client(opts, DefaultHyperClient).await } /// Use the builder pattern to deduce which model of authenticator should be used and allow providing a hyper client #[cfg(feature = "service_account")] pub async fn with_client( opts: ApplicationDefaultCredentialsFlowOpts, client: C, ) -> ApplicationDefaultCredentialsTypes where C: HyperClientBuilder, { match ApplicationDefaultCredentialsAuthenticator::from_environment().await { Ok(flow_opts) => { let builder = AuthenticatorBuilder::new(flow_opts, client); ApplicationDefaultCredentialsTypes::ServiceAccount(builder) } Err(_) => ApplicationDefaultCredentialsTypes::InstanceMetadata( AuthenticatorBuilder::new(opts, client), ), } } } /// Types of authenticators provided by ApplicationDefaultCredentialsAuthenticator pub enum ApplicationDefaultCredentialsTypes where C: HyperClientBuilder, { /// Service account based authenticator signature #[cfg(feature = "service_account")] ServiceAccount(AuthenticatorBuilder), /// GCE Instance Metadata based authenticator signature InstanceMetadata(AuthenticatorBuilder), } /// Create an authenticator that uses an authorized user credentials. /// ``` /// # #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] /// # async fn foo() { /// # use yup_oauth2::authenticator::AuthorizedUserAuthenticator; /// # let secret = yup_oauth2::read_authorized_user_secret("/tmp/foo").await.unwrap(); /// let authenticator = yup_oauth2::AuthorizedUserAuthenticator::builder(secret) /// .build() /// .await /// .expect("failed to create authenticator"); /// # } /// ``` 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"))) )] pub fn builder( authorized_user_secret: AuthorizedUserSecret, ) -> AuthenticatorBuilder { Self::with_client(authorized_user_secret, DefaultHyperClient) } /// Construct a new Authenticator that uses the installed flow and the provided http client. pub fn with_client( authorized_user_secret: AuthorizedUserSecret, client: C, ) -> AuthenticatorBuilder { AuthenticatorBuilder::new( AuthorizedUserFlow { secret: authorized_user_secret, }, client, ) } } /// ## Methods available when building any Authenticator. /// ``` /// # #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] /// # async fn foo() { /// # let custom_hyper_client = hyper::Client::new(); /// # 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) /// .persist_tokens_to_disk("/tmp/tokenfile.json") /// .build() /// .await /// .expect("failed to create authenticator"); /// # } /// ``` impl AuthenticatorBuilder { async fn common_build( hyper_client_builder: C, storage_type: StorageType, auth_flow: AuthFlow, ) -> io::Result> where C: HyperClientBuilder, { let hyper_client = hyper_client_builder.build_hyper_client(); let storage = match storage_type { StorageType::Memory => Storage::Memory { tokens: Mutex::new(storage::JSONTokens::new()), }, StorageType::Disk(path) => Storage::Disk(storage::DiskStorage::new(path).await?), StorageType::Custom(custom_store) => Storage::Custom(custom_store), }; Ok(Authenticator { inner: Arc::new(InnerAuthenticator { hyper_client, storage, auth_flow, }), }) } fn new(auth_flow: F, hyper_client_builder: C) -> AuthenticatorBuilder { AuthenticatorBuilder { hyper_client_builder, storage_type: StorageType::Memory, auth_flow, } } /// Use the provided token storage mechanism pub fn with_storage(self, storage: Box) -> Self { AuthenticatorBuilder { storage_type: StorageType::Custom(storage), ..self } } /// Use the provided hyper client. pub fn hyper_client( self, hyper_client: hyper::Client, ) -> AuthenticatorBuilder, F> { AuthenticatorBuilder { hyper_client_builder: hyper_client, storage_type: self.storage_type, auth_flow: self.auth_flow, } } /// Persist tokens to disk in the provided filename. pub fn persist_tokens_to_disk>(self, path: P) -> AuthenticatorBuilder { AuthenticatorBuilder { storage_type: StorageType::Disk(path.into()), ..self } } } /// ## Methods available when building a device flow Authenticator. /// ``` /// # #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] /// # async fn foo() { /// # let custom_flow_delegate = yup_oauth2::authenticator_delegate::DefaultDeviceFlowDelegate; /// # let app_secret = yup_oauth2::read_application_secret("/tmp/foo").await.unwrap(); /// let authenticator = yup_oauth2::DeviceFlowAuthenticator::builder(app_secret) /// .device_code_url("foo") /// .flow_delegate(Box::new(custom_flow_delegate)) /// .grant_type("foo") /// .build() /// .await /// .expect("failed to create authenticator"); /// # } /// ``` impl AuthenticatorBuilder { /// Use the provided device code url. pub fn device_code_url(self, url: impl Into>) -> Self { AuthenticatorBuilder { auth_flow: DeviceFlow { device_code_url: url.into(), ..self.auth_flow }, ..self } } /// Use the provided DeviceFlowDelegate. pub fn flow_delegate(self, flow_delegate: Box) -> Self { AuthenticatorBuilder { auth_flow: DeviceFlow { flow_delegate, ..self.auth_flow }, ..self } } /// Use the provided grant type. pub fn grant_type(self, grant_type: impl Into>) -> Self { AuthenticatorBuilder { auth_flow: DeviceFlow { grant_type: grant_type.into(), ..self.auth_flow }, ..self } } /// Create the authenticator. pub async fn build(self) -> io::Result> where C: HyperClientBuilder, { Self::common_build( self.hyper_client_builder, self.storage_type, AuthFlow::DeviceFlow(self.auth_flow), ) .await } } /// ## Methods available when building an installed flow Authenticator. /// ``` /// # #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] /// # async fn foo() { /// # use yup_oauth2::InstalledFlowReturnMethod; /// # let custom_flow_delegate = yup_oauth2::authenticator_delegate::DefaultInstalledFlowDelegate; /// # let app_secret = yup_oauth2::read_application_secret("/tmp/foo").await.unwrap(); /// let authenticator = yup_oauth2::InstalledFlowAuthenticator::builder( /// app_secret, /// InstalledFlowReturnMethod::HTTPRedirect, /// ) /// .flow_delegate(Box::new(custom_flow_delegate)) /// .build() /// .await /// .expect("failed to create authenticator"); /// # } /// ``` impl AuthenticatorBuilder { /// Use the provided InstalledFlowDelegate. pub fn flow_delegate(self, flow_delegate: Box) -> Self { AuthenticatorBuilder { auth_flow: InstalledFlow { flow_delegate, ..self.auth_flow }, ..self } } /// Create the authenticator. pub async fn build(self) -> io::Result> where C: HyperClientBuilder, { Self::common_build( self.hyper_client_builder, self.storage_type, AuthFlow::InstalledFlow(self.auth_flow), ) .await } } /// ## Methods available when building a service account authenticator. /// ``` /// # #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] /// # async fn foo() { /// # let service_account_key = yup_oauth2::read_service_account_key("/tmp/foo").await.unwrap(); /// let authenticator = yup_oauth2::ServiceAccountAuthenticator::builder( /// service_account_key, /// ) /// .subject("mysubject") /// .build() /// .await /// .expect("failed to create authenticator"); /// # } /// ``` #[cfg(feature = "service_account")] impl AuthenticatorBuilder { /// Use the provided subject. pub fn subject(self, subject: impl Into) -> Self { AuthenticatorBuilder { auth_flow: ServiceAccountFlowOpts { subject: Some(subject.into()), ..self.auth_flow }, ..self } } /// Create the authenticator. pub async fn build(self) -> io::Result> where C: HyperClientBuilder, { let service_account_auth_flow = ServiceAccountFlow::new(self.auth_flow).await?; Self::common_build( self.hyper_client_builder, self.storage_type, AuthFlow::ServiceAccountFlow(service_account_auth_flow), ) .await } } impl AuthenticatorBuilder { /// Create the authenticator. pub async fn build(self) -> io::Result> where C: HyperClientBuilder, { let application_default_credential_flow = ApplicationDefaultCredentialsFlow::new(self.auth_flow); Self::common_build( self.hyper_client_builder, self.storage_type, AuthFlow::ApplicationDefaultCredentialsFlow(application_default_credential_flow), ) .await } } /// ## Methods available when building an authorized user flow Authenticator. impl AuthenticatorBuilder { /// Create the authenticator. pub async fn build(self) -> io::Result> where C: HyperClientBuilder, { Self::common_build( self.hyper_client_builder, self.storage_type, AuthFlow::AuthorizedUserFlow(self.auth_flow), ) .await } } mod private { use crate::application_default_credentials::ApplicationDefaultCredentialsFlow; use crate::authorized_user::AuthorizedUserFlow; use crate::authenticator::{AsyncRead, AsyncWrite, Connection, Service, StdError, Uri}; use crate::device::DeviceFlow; use crate::error::Error; use crate::installed::InstalledFlow; #[cfg(feature = "service_account")] use crate::service_account::ServiceAccountFlow; use crate::types::{ApplicationSecret, TokenInfo}; pub enum AuthFlow { DeviceFlow(DeviceFlow), InstalledFlow(InstalledFlow), #[cfg(feature = "service_account")] ServiceAccountFlow(ServiceAccountFlow), ApplicationDefaultCredentialsFlow(ApplicationDefaultCredentialsFlow), AuthorizedUserFlow(AuthorizedUserFlow), } impl AuthFlow { pub(crate) fn app_secret(&self) -> Option<&ApplicationSecret> { match self { AuthFlow::DeviceFlow(device_flow) => Some(&device_flow.app_secret), AuthFlow::InstalledFlow(installed_flow) => Some(&installed_flow.app_secret), #[cfg(feature = "service_account")] AuthFlow::ServiceAccountFlow(_) => None, AuthFlow::ApplicationDefaultCredentialsFlow(_) => None, AuthFlow::AuthorizedUserFlow(_) => None, } } pub(crate) async fn token<'a, S, T>( &'a self, hyper_client: &'a hyper::Client, scopes: &'a [T], ) -> Result where T: AsRef, S: Service + Clone + Send + Sync + 'static, S::Response: Connection + AsyncRead + AsyncWrite + Send + Unpin + 'static, S::Future: Send + Unpin + 'static, S::Error: Into>, { match self { AuthFlow::DeviceFlow(device_flow) => device_flow.token(hyper_client, scopes).await, AuthFlow::InstalledFlow(installed_flow) => { installed_flow.token(hyper_client, scopes).await } #[cfg(feature = "service_account")] AuthFlow::ServiceAccountFlow(service_account_flow) => { service_account_flow.token(hyper_client, scopes).await } AuthFlow::ApplicationDefaultCredentialsFlow(service_account_flow) => { service_account_flow.token(hyper_client, scopes).await } AuthFlow::AuthorizedUserFlow(authorized_user_flow) => { authorized_user_flow.token(hyper_client, scopes).await } } } } } /// A trait implemented for any hyper::Client as well as the DefaultHyperClient. pub trait HyperClientBuilder { /// The hyper connector that the resulting hyper client will use. type Connector: Service + Clone + Send + Sync + 'static; /// Create a hyper::Client fn build_hyper_client(self) -> hyper::Client; /// Create a `hyper::Client` for tests (HTTPS not required) #[doc(hidden)] fn build_test_hyper_client(self) -> hyper::Client; } #[cfg(feature = "hyper-rustls")] #[cfg_attr( yup_oauth2_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"))) )] /// 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"))) )] pub struct DefaultHyperClient; #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] #[cfg_attr( yup_oauth2_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 build_hyper_client(self) -> hyper::Client { #[cfg(feature = "hyper-rustls")] let connector = hyper_rustls::HttpsConnectorBuilder::new() .with_native_roots() .https_only() .enable_http1() .enable_http2() .build(); #[cfg(all(not(feature = "hyper-rustls"), feature = "hyper-tls"))] let connector = hyper_tls::HttpsConnector::new(); hyper::Client::builder() .pool_max_idle_per_host(0) .build::<_, hyper::Body>(connector) } fn build_test_hyper_client(self) -> hyper::Client { #[cfg(feature = "hyper-rustls")] let connector = hyper_rustls::HttpsConnectorBuilder::new() .with_native_roots() .https_or_http() .enable_http1() .enable_http2() .build(); #[cfg(all(not(feature = "hyper-rustls"), feature = "hyper-tls"))] let connector = hyper_tls::HttpsConnector::new(); hyper::Client::builder() .pool_max_idle_per_host(0) .build::<_, hyper::Body>(connector) } } impl HyperClientBuilder for hyper::Client where S: Service + Clone + Send + Sync + 'static, S::Response: Connection + AsyncRead + AsyncWrite + Send + Unpin + 'static, S::Future: Send + Unpin + 'static, S::Error: Into>, { type Connector = S; fn build_hyper_client(self) -> hyper::Client { self } fn build_test_hyper_client(self) -> hyper::Client { 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) Memory, /// Store tokens to disk in the given file. Warning, this may be insecure unless you configure your operating system to restrict read access to the file. Disk(PathBuf), /// Implement your own storage provider Custom(Box), } #[cfg(test)] mod tests { #[test] #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] fn ensure_send_sync() { use super::*; fn is_send_sync() {} is_send_sync::::Connector>>() } }