Tie ServiceAccount's into Authenticator.

Prior to this change DeviceFlow and InstalledFlow were used within
Authenticator, while ServiceAccountAccess was used on it's own. AFAICT
this was the case because ServiceAccountAccess never used refresh tokens
and Authenticator assumed all tokens contained refresh tokens.
Authenticator was recently modified to handle the case where a token
does not contain a refresh token so I don't see any reason to keep the
service account access separate anymore. Folding it into the
authenticator provides a nice consistent interface, and the service
account implementation no longer needs to provide it's own caching since
it is now handled by Authenticator.
This commit is contained in:
Glenn Griffin
2019-11-20 14:01:17 -08:00
parent d4b80a0c5c
commit 5256f642d7
7 changed files with 233 additions and 250 deletions

View File

@@ -6,6 +6,7 @@ use crate::device::DeviceFlow;
use crate::error::Error;
use crate::installed::{InstalledFlow, InstalledFlowReturnMethod};
use crate::refresh::RefreshFlow;
use crate::service_account::{ServiceAccountFlow, ServiceAccountFlowOpts, ServiceAccountKey};
use crate::storage::{self, Storage};
use crate::types::{ApplicationSecret, Token};
use private::AuthFlow;
@@ -20,7 +21,6 @@ use std::time::Duration;
/// and optionally persisting tokens to disk.
pub struct Authenticator<C> {
hyper_client: hyper::Client<C>,
app_secret: ApplicationSecret,
auth_delegate: Box<dyn AuthenticatorDelegate>,
storage: Storage,
auth_flow: AuthFlow,
@@ -36,19 +36,22 @@ where
T: AsRef<str>,
{
let hashed_scopes = storage::ScopesAndFilter::from(scopes);
match self.storage.get(hashed_scopes) {
Some(t) if !t.expired() => {
match (self.storage.get(hashed_scopes), self.auth_flow.app_secret()) {
(Some(t), _) if !t.expired() => {
// unexpired token found
Ok(t)
}
Some(Token {
refresh_token: Some(refresh_token),
..
}) => {
(
Some(Token {
refresh_token: Some(refresh_token),
..
}),
Some(app_secret),
) => {
// token is expired but has a refresh token.
let token = match RefreshFlow::refresh_token(
&self.hyper_client,
&self.app_secret,
app_secret,
&refresh_token,
)
.await
@@ -62,16 +65,9 @@ where
self.storage.set(hashed_scopes, token.clone()).await;
Ok(token)
}
None
| Some(Token {
refresh_token: None,
..
}) => {
// no token in the cache or the token returned does not contain a refresh token.
let t = self
.auth_flow
.token(&self.hyper_client, &self.app_secret, scopes)
.await?;
_ => {
// no token in the cache or the token returned can't be refreshed.
let t = self.auth_flow.token(&self.hyper_client, scopes).await?;
self.storage.set(hashed_scopes, t.clone()).await;
Ok(t)
}
@@ -82,7 +78,6 @@ where
/// Configure an Authenticator using the builder pattern.
pub struct AuthenticatorBuilder<C, F> {
hyper_client_builder: C,
app_secret: ApplicationSecret,
auth_delegate: Box<dyn AuthenticatorDelegate>,
storage_type: StorageType,
auth_flow: F,
@@ -110,10 +105,9 @@ impl InstalledFlowAuthenticator {
app_secret: ApplicationSecret,
method: InstalledFlowReturnMethod,
) -> AuthenticatorBuilder<DefaultHyperClient, InstalledFlow> {
AuthenticatorBuilder::<DefaultHyperClient, _>::with_auth_flow(
app_secret,
InstalledFlow::new(method),
)
AuthenticatorBuilder::<DefaultHyperClient, _>::with_auth_flow(InstalledFlow::new(
app_secret, method,
))
}
}
@@ -133,7 +127,30 @@ impl DeviceFlowAuthenticator {
pub fn builder(
app_secret: ApplicationSecret,
) -> AuthenticatorBuilder<DefaultHyperClient, DeviceFlow> {
AuthenticatorBuilder::<DefaultHyperClient, _>::with_auth_flow(app_secret, DeviceFlow::new())
AuthenticatorBuilder::<DefaultHyperClient, _>::with_auth_flow(DeviceFlow::new(app_secret))
}
}
/// Create an authenticator that uses a service account.
/// ```
/// # async fn foo() {
/// # let service_account_key = yup_oauth2::service_account_key_from_file("/tmp/foo").unwrap();
/// let authenticator = yup_oauth2::ServiceAccountAuthenticator::builder(service_account_key)
/// .build()
/// .await
/// .expect("failed to create authenticator");
/// # }
/// ```
pub struct ServiceAccountAuthenticator;
impl ServiceAccountAuthenticator {
/// Use the builder pattern to create an Authenticator that uses a service account.
pub fn builder(
service_account_key: ServiceAccountKey,
) -> AuthenticatorBuilder<DefaultHyperClient, ServiceAccountFlowOpts> {
AuthenticatorBuilder::<DefaultHyperClient, _>::with_auth_flow(ServiceAccountFlowOpts {
key: service_account_key,
subject: None,
})
}
}
@@ -153,14 +170,17 @@ impl DeviceFlowAuthenticator {
/// # }
/// ```
impl<C, F> AuthenticatorBuilder<C, F> {
/// Create the authenticator.
pub async fn build(self) -> io::Result<Authenticator<C::Connector>>
async fn common_build(
hyper_client_builder: C,
storage_type: StorageType,
auth_delegate: Box<dyn AuthenticatorDelegate>,
auth_flow: AuthFlow,
) -> io::Result<Authenticator<C::Connector>>
where
C: HyperClientBuilder,
F: Into<AuthFlow>,
{
let hyper_client = self.hyper_client_builder.build_hyper_client();
let storage = match self.storage_type {
let hyper_client = hyper_client_builder.build_hyper_client();
let storage = match storage_type {
StorageType::Memory => Storage::Memory {
tokens: Mutex::new(storage::JSONTokens::new()),
},
@@ -169,20 +189,15 @@ impl<C, F> AuthenticatorBuilder<C, F> {
Ok(Authenticator {
hyper_client,
app_secret: self.app_secret,
storage,
auth_delegate: self.auth_delegate,
auth_flow: self.auth_flow.into(),
auth_delegate,
auth_flow,
})
}
fn with_auth_flow(
app_secret: ApplicationSecret,
auth_flow: F,
) -> AuthenticatorBuilder<DefaultHyperClient, F> {
fn with_auth_flow(auth_flow: F) -> AuthenticatorBuilder<DefaultHyperClient, F> {
AuthenticatorBuilder {
hyper_client_builder: DefaultHyperClient,
app_secret,
auth_delegate: Box::new(DefaultAuthenticatorDelegate),
storage_type: StorageType::Memory,
auth_flow,
@@ -196,7 +211,6 @@ impl<C, F> AuthenticatorBuilder<C, F> {
) -> AuthenticatorBuilder<hyper::Client<NewC>, F> {
AuthenticatorBuilder {
hyper_client_builder: hyper_client,
app_secret: self.app_secret,
auth_delegate: self.auth_delegate,
storage_type: self.storage_type,
auth_flow: self.auth_flow,
@@ -282,6 +296,20 @@ impl<C> AuthenticatorBuilder<C, DeviceFlow> {
..self
}
}
/// Create the authenticator.
pub async fn build(self) -> io::Result<Authenticator<C::Connector>>
where
C: HyperClientBuilder,
{
Self::common_build(
self.hyper_client_builder,
self.storage_type,
self.auth_delegate,
AuthFlow::DeviceFlow(self.auth_flow),
)
.await
}
}
/// ## Methods available when building an installed flow Authenticator.
@@ -311,36 +339,88 @@ impl<C> AuthenticatorBuilder<C, InstalledFlow> {
..self
}
}
/// Create the authenticator.
pub async fn build(self) -> io::Result<Authenticator<C::Connector>>
where
C: HyperClientBuilder,
{
Self::common_build(
self.hyper_client_builder,
self.storage_type,
self.auth_delegate,
AuthFlow::InstalledFlow(self.auth_flow),
)
.await
}
}
/// ## Methods available when building a service account authenticator.
/// ```
/// # async fn foo() {
/// # let service_account_key = yup_oauth2::service_account_key_from_file("/tmp/foo").unwrap();
/// let authenticator = yup_oauth2::ServiceAccountAuthenticator::builder(
/// service_account_key,
/// )
/// .subject("mysubject")
/// .build()
/// .await
/// .expect("failed to create authenticator");
/// # }
/// ```
impl<C> AuthenticatorBuilder<C, ServiceAccountFlowOpts> {
/// Use the provided subject.
pub fn subject(self, subject: impl Into<String>) -> Self {
AuthenticatorBuilder {
auth_flow: ServiceAccountFlowOpts {
subject: Some(subject.into()),
..self.auth_flow
},
..self
}
}
/// Create the authenticator.
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,
self.auth_delegate,
AuthFlow::ServiceAccountFlow(service_account_auth_flow),
)
.await
}
}
mod private {
use crate::device::DeviceFlow;
use crate::error::Error;
use crate::installed::InstalledFlow;
use crate::service_account::ServiceAccountFlow;
use crate::types::{ApplicationSecret, Token};
pub enum AuthFlow {
DeviceFlow(DeviceFlow),
InstalledFlow(InstalledFlow),
}
impl From<DeviceFlow> for AuthFlow {
fn from(device_flow: DeviceFlow) -> AuthFlow {
AuthFlow::DeviceFlow(device_flow)
}
}
impl From<InstalledFlow> for AuthFlow {
fn from(installed_flow: InstalledFlow) -> AuthFlow {
AuthFlow::InstalledFlow(installed_flow)
}
ServiceAccountFlow(ServiceAccountFlow),
}
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),
AuthFlow::ServiceAccountFlow(_) => None,
}
}
pub(crate) async fn token<'a, C, T>(
&'a self,
hyper_client: &'a hyper::Client<C>,
app_secret: &'a ApplicationSecret,
scopes: &'a [T],
) -> Result<Token, Error>
where
@@ -348,18 +428,19 @@ mod private {
C: hyper::client::connect::Connect + 'static,
{
match self {
AuthFlow::DeviceFlow(device_flow) => {
device_flow.token(hyper_client, app_secret, scopes).await
}
AuthFlow::DeviceFlow(device_flow) => device_flow.token(hyper_client, scopes).await,
AuthFlow::InstalledFlow(installed_flow) => {
installed_flow.token(hyper_client, app_secret, scopes).await
installed_flow.token(hyper_client, scopes).await
}
AuthFlow::ServiceAccountFlow(service_account_flow) => {
service_account_flow.token(hyper_client, scopes).await
}
}
}
}
}
/// A trait implemented for any hyper::Client as well as teh DefaultHyperClient.
/// 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: hyper::client::connect::Connect + 'static;