use std::path::PathBuf;
use hyper::client::Client;
use crate::Auth;
const SERVICE_ACCOUNT_ENV_VAR: &str = "GOOGLE_APPLICATION_CREDENTIALS";
#[derive(Debug, Clone, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ServiceAccountAuth {
EnvVar,
Path(PathBuf),
ApplicationDefault,
}
impl Default for ServiceAccountAuth {
fn default() -> Self {
Self::EnvVar
}
}
#[derive(Debug, Clone, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum AuthFlow {
ServiceAccount(ServiceAccountAuth),
UserAccount(PathBuf),
ServiceAccountImpersonation {
user: PathBuf,
email: String,
},
NoAuth,
}
impl Default for AuthFlow {
fn default() -> Self {
AuthFlow::ServiceAccount(ServiceAccountAuth::default())
}
}
config_default! {
#[derive(Debug, Clone, serde::Deserialize)]
#[non_exhaustive]
pub struct ClientBuilderConfig {
@default(AuthFlow::ServiceAccount(ServiceAccountAuth::EnvVar), "ClientBuilderConfig::default_auth_flow")
pub auth_flow: AuthFlow,
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum CreateBuilderError {
#[error("failed to read service account key {}", _1.display())]
ReadServiceAccountKey(#[source] std::io::Error, PathBuf),
#[error("failed to read user account secrets {}", _1.display())]
ReadUserSecrets(#[source] std::io::Error, PathBuf),
#[error(
"environment variable {SERVICE_ACCOUNT_ENV_VAR} isn't set. Consider either setting the \
variable, or specifying the path with ServiceAccountAuth::Path(...)"
)]
CredentialsVarMissing,
#[error("failed to initialize authenticator")]
Authenticator(#[source] std::io::Error),
#[error("failed to initialize HTTP connector")]
Connector(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
}
cfg_if::cfg_if! {
if #[cfg(feature="openssl")] {
pub type DefaultConnector =
hyper_openssl::HttpsConnector<hyper::client::connect::HttpConnector>;
impl ClientBuilder {
#[cfg_attr(docsrs, doc(cfg(any(feature="rustls", feature="openssl"))))]
pub async fn new(config: ClientBuilderConfig) -> Result<Self, CreateBuilderError> {
let connector = hyper_openssl::HttpsConnector::new()
.map_err(|e| CreateBuilderError::Connector(e.into()))?;
Self::with_connector(config, connector).await
}
}
}
else if #[cfg(feature="rustls")] {
pub type DefaultConnector =
hyper_rustls::HttpsConnector<hyper::client::connect::HttpConnector>;
impl ClientBuilder {
#[cfg_attr(docsrs, doc(cfg(any(feature="rustls", feature="openssl"))))]
pub async fn new(config: ClientBuilderConfig) -> Result<Self, CreateBuilderError> {
let connector = hyper_rustls::HttpsConnector::with_native_roots();
Self::with_connector(config, connector).await
}
}
}
else {
#[doc(hidden)]
pub struct NoConnectorFeaturesEnabled;
pub type DefaultConnector = NoConnectorFeaturesEnabled;
}
}
pub struct ClientBuilder<Connector = DefaultConnector> {
#[allow(unused)]
pub(crate) connector: Connector,
#[allow(unused)]
pub(crate) auth: Option<Auth<Connector>>,
#[allow(unused)]
pub(crate) client: Client<Connector>,
}
impl<C> ClientBuilder<C> {
pub async fn with_connector(
config: ClientBuilderConfig,
connector: C,
) -> Result<Self, CreateBuilderError>
where
C: hyper::service::Service<http::Uri> + Clone + Send + Sync + 'static,
C::Response: hyper::client::connect::Connection
+ tokio::io::AsyncRead
+ tokio::io::AsyncWrite
+ Send
+ Unpin
+ 'static,
C::Future: Send + Unpin + 'static,
C::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
{
use AuthFlow::{NoAuth, ServiceAccount, ServiceAccountImpersonation, UserAccount};
let client = hyper::client::Client::builder().build(connector.clone());
let auth = match config.auth_flow {
NoAuth => None,
ServiceAccount(service_config) => Some(
create_service_auth(
match service_config {
ServiceAccountAuth::Path(path) => Some(path.into_os_string()),
ServiceAccountAuth::EnvVar => Some(
std::env::var_os(SERVICE_ACCOUNT_ENV_VAR)
.ok_or(CreateBuilderError::CredentialsVarMissing)?,
),
ServiceAccountAuth::ApplicationDefault => None,
},
client.clone(),
)
.await?,
),
ServiceAccountImpersonation { user, email } => Some(
create_service_impersonation_auth(user.into_os_string(), email, client.clone())
.await?,
),
UserAccount(path) => {
Some(create_user_auth(path.into_os_string(), client.clone()).await?)
}
};
Ok(Self {
connector,
client,
auth,
})
}
pub async fn with_connector_and_auth_builder<F>(
connector: C,
auth_builder: impl FnOnce(Client<C>) -> F,
) -> Result<Self, CreateBuilderError>
where
C: crate::Connect + Clone + Send + Sync + 'static,
F: futures::Future<Output = std::io::Result<Auth<C>>>,
{
let client = hyper::client::Client::builder().build(connector.clone());
let auth = Some(
auth_builder(client.clone())
.await
.map_err(CreateBuilderError::Authenticator)?,
);
Ok(Self {
connector,
client,
auth,
})
}
}
async fn create_service_auth<C>(
service_account_key_path: Option<impl AsRef<std::path::Path>>,
client: Client<C>,
) -> Result<Auth<C>, CreateBuilderError>
where
C: hyper::service::Service<http::Uri> + Clone + Send + Sync + 'static,
C::Response: hyper::client::connect::Connection
+ tokio::io::AsyncRead
+ tokio::io::AsyncWrite
+ Send
+ Unpin
+ 'static,
C::Future: Send + Unpin + 'static,
C::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
{
match service_account_key_path {
Some(service_account_key_path) => {
let service_account_key =
yup_oauth2::read_service_account_key(service_account_key_path.as_ref())
.await
.map_err(|e| {
CreateBuilderError::ReadServiceAccountKey(
e,
service_account_key_path.as_ref().to_owned(),
)
})?;
yup_oauth2::ServiceAccountAuthenticator::builder(service_account_key)
.hyper_client(client)
.build()
.await
.map_err(CreateBuilderError::Authenticator)
}
None => match yup_oauth2::ApplicationDefaultCredentialsAuthenticator::with_client(
yup_oauth2::ApplicationDefaultCredentialsFlowOpts::default(),
client,
)
.await
{
yup_oauth2::authenticator::ApplicationDefaultCredentialsTypes::ServiceAccount(auth) => {
auth.build()
.await
.map_err(CreateBuilderError::Authenticator)
}
yup_oauth2::authenticator::ApplicationDefaultCredentialsTypes::InstanceMetadata(
auth,
) => auth
.build()
.await
.map_err(CreateBuilderError::Authenticator),
},
}
}
async fn create_user_auth<C>(
user_secrets_path: impl AsRef<std::path::Path>,
client: Client<C>,
) -> Result<Auth<C>, CreateBuilderError>
where
C: hyper::service::Service<http::Uri> + Clone + Send + Sync + 'static,
C::Response: hyper::client::connect::Connection
+ tokio::io::AsyncRead
+ tokio::io::AsyncWrite
+ Send
+ Unpin
+ 'static,
C::Future: Send + Unpin + 'static,
C::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
{
let user_secret = yup_oauth2::read_authorized_user_secret(user_secrets_path.as_ref())
.await
.map_err(|e| {
CreateBuilderError::ReadUserSecrets(e, user_secrets_path.as_ref().to_owned())
})?;
yup_oauth2::AuthorizedUserAuthenticator::with_client(user_secret, client)
.build()
.await
.map_err(CreateBuilderError::Authenticator)
}
async fn create_service_impersonation_auth<C>(
user_secrets_path: impl AsRef<std::path::Path>,
email: String,
client: Client<C>,
) -> Result<Auth<C>, CreateBuilderError>
where
C: hyper::service::Service<http::Uri> + Clone + Send + Sync + 'static,
C::Response: hyper::client::connect::Connection
+ tokio::io::AsyncRead
+ tokio::io::AsyncWrite
+ Send
+ Unpin
+ 'static,
C::Future: Send + Unpin + 'static,
C::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
{
let user_secret = yup_oauth2::read_authorized_user_secret(user_secrets_path.as_ref())
.await
.map_err(|e| {
CreateBuilderError::ReadUserSecrets(e, user_secrets_path.as_ref().to_owned())
})?;
yup_oauth2::ServiceAccountImpersonationAuthenticator::with_client(user_secret, &email, client)
.build()
.await
.map_err(CreateBuilderError::Authenticator)
}