use crate::application_default_credentials::{
ApplicationDefaultCredentialsFlow, ApplicationDefaultCredentialsFlowOpts,
};
use crate::authenticator_delegate::{DeviceFlowDelegate, InstalledFlowDelegate};
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::{ServiceAccountFlow, ServiceAccountFlowOpts, ServiceAccountKey};
use crate::storage::{self, Storage, TokenStorage};
use crate::types::{AccessToken, ApplicationSecret, TokenInfo};
use private::AuthFlow;
use futures::lock::Mutex;
use std::borrow::Cow;
use std::fmt;
use std::io;
use std::path::PathBuf;
use std::sync::Arc;
struct InnerAuthenticator<C> {
hyper_client: hyper::Client<C>,
storage: Storage,
auth_flow: AuthFlow,
}
#[derive(Clone)]
pub struct Authenticator<C> {
inner: Arc<InnerAuthenticator<C>>,
}
struct DisplayScopes<'a, T>(&'a [T]);
impl<'a, T> fmt::Display for DisplayScopes<'a, T>
where
T: AsRef<str>,
{
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<C> Authenticator<C>
where
C: hyper::client::connect::Connect + Clone + Send + Sync + 'static,
{
pub async fn token<'a, T>(&'a self, scopes: &'a [T]) -> Result<AccessToken, Error>
where
T: AsRef<str>,
{
self.find_token_info(scopes, false)
.await
.map(|info| info.into())
}
pub async fn force_refreshed_token<'a, T>(
&'a self,
scopes: &'a [T],
) -> Result<AccessToken, Error>
where
T: AsRef<str>,
{
self.find_token_info(scopes, true)
.await
.map(|info| info.into())
}
pub async fn id_token<'a, T>(&'a self, scopes: &'a [T]) -> Result<Option<String>, Error>
where
T: AsRef<str>,
{
self.find_token_info(scopes, false)
.await
.map(|info| info.id_token)
}
async fn find_token_info<'a, T>(
&'a self,
scopes: &'a [T],
force_refresh: bool,
) -> Result<TokenInfo, Error>
where
T: AsRef<str>,
{
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 => {
log::debug!("found valid token in cache: {:?}", t);
Ok(t.into())
}
(
Some(TokenInfo {
refresh_token: Some(refresh_token),
..
}),
Some(app_secret),
) => {
let token_info = RefreshFlow::refresh_token(
&self.inner.hyper_client,
app_secret,
&refresh_token,
)
.await?;
self.inner
.storage
.set(hashed_scopes, token_info.clone())
.await?;
Ok(token_info)
}
_ => {
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)
}
}
}
}
pub struct AuthenticatorBuilder<C, F> {
hyper_client_builder: C,
storage_type: StorageType,
auth_flow: F,
}
pub struct InstalledFlowAuthenticator;
impl InstalledFlowAuthenticator {
#[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<DefaultHyperClient, InstalledFlow> {
Self::with_client(app_secret, method, DefaultHyperClient)
}
pub fn with_client<C>(
app_secret: ApplicationSecret,
method: InstalledFlowReturnMethod,
client: C,
) -> AuthenticatorBuilder<C, InstalledFlow> {
AuthenticatorBuilder::new(InstalledFlow::new(app_secret, method), client)
}
}
pub struct DeviceFlowAuthenticator;
impl DeviceFlowAuthenticator {
#[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<DefaultHyperClient, DeviceFlow> {
Self::with_client(app_secret, DefaultHyperClient)
}
pub fn with_client<C>(
app_secret: ApplicationSecret,
client: C,
) -> AuthenticatorBuilder<C, DeviceFlow> {
AuthenticatorBuilder::new(DeviceFlow::new(app_secret), client)
}
}
#[cfg(feature = "service_account")]
pub struct ServiceAccountAuthenticator;
#[cfg(feature = "service_account")]
impl ServiceAccountAuthenticator {
#[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<DefaultHyperClient, ServiceAccountFlowOpts> {
Self::with_client(service_account_key, DefaultHyperClient)
}
pub fn with_client<C>(
service_account_key: ServiceAccountKey,
client: C,
) -> AuthenticatorBuilder<C, ServiceAccountFlowOpts> {
AuthenticatorBuilder::new(
ServiceAccountFlowOpts {
key: service_account_key,
subject: None,
},
client,
)
}
}
pub struct ApplicationDefaultCredentialsAuthenticator;
impl ApplicationDefaultCredentialsAuthenticator {
#[cfg(feature = "service_account")]
pub async fn from_environment() -> Result<ServiceAccountFlowOpts, std::env::VarError> {
let service_account_key =
crate::read_service_account_key(std::env::var("GOOGLE_APPLICATION_CREDENTIALS")?)
.await
.unwrap();
Ok(ServiceAccountFlowOpts {
key: service_account_key,
subject: None,
})
}
#[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<DefaultHyperClient> {
Self::with_client(DefaultHyperClient, opts).await
}
#[cfg(feature = "service_account")]
pub async fn with_client<C>(
client: C,
opts: ApplicationDefaultCredentialsFlowOpts,
) -> ApplicationDefaultCredentialsTypes<C>
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),
),
}
}
}
pub enum ApplicationDefaultCredentialsTypes<C>
where
C: HyperClientBuilder,
{
#[cfg(feature = "service_account")]
ServiceAccount(AuthenticatorBuilder<C, ServiceAccountFlowOpts>),
InstanceMetadata(AuthenticatorBuilder<C, ApplicationDefaultCredentialsFlowOpts>),
}
impl<C, F> AuthenticatorBuilder<C, F> {
async fn common_build(
hyper_client_builder: C,
storage_type: StorageType,
auth_flow: AuthFlow,
) -> io::Result<Authenticator<C::Connector>>
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<C, F> {
AuthenticatorBuilder {
hyper_client_builder,
storage_type: StorageType::Memory,
auth_flow,
}
}
pub fn with_storage(self, storage: Box<dyn TokenStorage>) -> Self {
AuthenticatorBuilder {
storage_type: StorageType::Custom(storage),
..self
}
}
pub fn hyper_client<NewC>(
self,
hyper_client: hyper::Client<NewC>,
) -> AuthenticatorBuilder<hyper::Client<NewC>, F> {
AuthenticatorBuilder {
hyper_client_builder: hyper_client,
storage_type: self.storage_type,
auth_flow: self.auth_flow,
}
}
pub fn persist_tokens_to_disk<P: Into<PathBuf>>(self, path: P) -> AuthenticatorBuilder<C, F> {
AuthenticatorBuilder {
storage_type: StorageType::Disk(path.into()),
..self
}
}
}
impl<C> AuthenticatorBuilder<C, DeviceFlow> {
pub fn device_code_url(self, url: impl Into<Cow<'static, str>>) -> Self {
AuthenticatorBuilder {
auth_flow: DeviceFlow {
device_code_url: url.into(),
..self.auth_flow
},
..self
}
}
pub fn flow_delegate(self, flow_delegate: Box<dyn DeviceFlowDelegate>) -> Self {
AuthenticatorBuilder {
auth_flow: DeviceFlow {
flow_delegate,
..self.auth_flow
},
..self
}
}
pub fn grant_type(self, grant_type: impl Into<Cow<'static, str>>) -> Self {
AuthenticatorBuilder {
auth_flow: DeviceFlow {
grant_type: grant_type.into(),
..self.auth_flow
},
..self
}
}
pub async fn build(self) -> io::Result<Authenticator<C::Connector>>
where
C: HyperClientBuilder,
{
Self::common_build(
self.hyper_client_builder,
self.storage_type,
AuthFlow::DeviceFlow(self.auth_flow),
)
.await
}
}
impl<C> AuthenticatorBuilder<C, InstalledFlow> {
pub fn flow_delegate(self, flow_delegate: Box<dyn InstalledFlowDelegate>) -> Self {
AuthenticatorBuilder {
auth_flow: InstalledFlow {
flow_delegate,
..self.auth_flow
},
..self
}
}
pub async fn build(self) -> io::Result<Authenticator<C::Connector>>
where
C: HyperClientBuilder,
{
Self::common_build(
self.hyper_client_builder,
self.storage_type,
AuthFlow::InstalledFlow(self.auth_flow),
)
.await
}
}
#[cfg(feature = "service_account")]
impl<C> AuthenticatorBuilder<C, ServiceAccountFlowOpts> {
pub fn subject(self, subject: impl Into<String>) -> Self {
AuthenticatorBuilder {
auth_flow: ServiceAccountFlowOpts {
subject: Some(subject.into()),
..self.auth_flow
},
..self
}
}
pub async fn build(self) -> io::Result<Authenticator<C::Connector>>
where
C: HyperClientBuilder,
{
let service_account_auth_flow = ServiceAccountFlow::new(self.auth_flow)?;
Self::common_build(
self.hyper_client_builder,
self.storage_type,
AuthFlow::ServiceAccountFlow(service_account_auth_flow),
)
.await
}
}
impl<C> AuthenticatorBuilder<C, ApplicationDefaultCredentialsFlowOpts> {
pub async fn build(self) -> io::Result<Authenticator<C::Connector>>
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
}
}
mod private {
use crate::application_default_credentials::ApplicationDefaultCredentialsFlow;
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),
}
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,
}
}
pub(crate) async fn token<'a, C, T>(
&'a self,
hyper_client: &'a hyper::Client<C>,
scopes: &'a [T],
) -> Result<TokenInfo, Error>
where
T: AsRef<str>,
C: hyper::client::connect::Connect + Clone + Send + Sync + 'static,
{
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
}
}
}
}
}
pub trait HyperClientBuilder {
type Connector: hyper::client::connect::Connect + Clone + Send + Sync + 'static;
fn build_hyper_client(self) -> hyper::Client<Self::Connector>;
}
#[cfg(feature = "hyper-rustls")]
#[cfg_attr(
yup_oauth2_docsrs,
doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls")))
)]
pub type DefaultAuthenticator =
Authenticator<hyper_rustls::HttpsConnector<hyper::client::HttpConnector>>;
#[cfg(all(not(feature = "hyper-rustls"), feature = "hyper-tls"))]
#[cfg_attr(
yup_oauth2_docsrs,
doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls")))
)]
pub type DefaultAuthenticator =
Authenticator<hyper_tls::HttpsConnector<hyper::client::HttpConnector>>;
#[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<hyper::client::connect::HttpConnector>;
#[cfg(all(not(feature = "hyper-rustls"), feature = "hyper-tls"))]
type Connector = hyper_tls::HttpsConnector<hyper::client::connect::HttpConnector>;
fn build_hyper_client(self) -> hyper::Client<Self::Connector> {
#[cfg(feature = "hyper-rustls")]
let connector = hyper_rustls::HttpsConnector::with_native_roots();
#[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<C> HyperClientBuilder for hyper::Client<C>
where
C: hyper::client::connect::Connect + Clone + Send + Sync + 'static,
{
type Connector = C;
fn build_hyper_client(self) -> hyper::Client<C> {
self
}
}
enum StorageType {
Memory,
Disk(PathBuf),
Custom(Box<dyn TokenStorage>),
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
fn ensure_send_sync() {
use super::*;
fn is_send_sync<T: Send + Sync>() {}
is_send_sync::<Authenticator<<DefaultHyperClient as HyperClientBuilder>::Connector>>()
}
}