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

@@ -4,7 +4,7 @@ use yup_oauth2::ServiceAccountAuthenticator;
#[tokio::main]
async fn main() {
let creds = yup_oauth2::service_account_key_from_file("serviceaccount.json").unwrap();
let sa = ServiceAccountAuthenticator::builder(creds).build().unwrap();
let sa = ServiceAccountAuthenticator::builder(creds).build().await.unwrap();
let scopes = &["https://www.googleapis.com/auth/pubsub"];
let tok = sa.token(scopes).await.unwrap();

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;

View File

@@ -22,6 +22,7 @@ pub const GOOGLE_GRANT_TYPE: &str = "http://oauth.net/grant_type/device/1.0";
/// * obtain a code to show to the user
// * (repeatedly) poll for the user to authenticate your application
pub struct DeviceFlow {
pub(crate) app_secret: ApplicationSecret,
pub(crate) device_code_url: Cow<'static, str>,
pub(crate) flow_delegate: Box<dyn FlowDelegate>,
pub(crate) wait_duration: Duration,
@@ -31,8 +32,9 @@ pub struct DeviceFlow {
impl DeviceFlow {
/// Create a new DeviceFlow. The default FlowDelegate will be used and the
/// default wait time is 120 seconds.
pub(crate) fn new() -> Self {
pub(crate) fn new(app_secret: ApplicationSecret) -> Self {
DeviceFlow {
app_secret,
device_code_url: GOOGLE_DEVICE_CODE_URL.into(),
flow_delegate: Box::new(DefaultFlowDelegate),
wait_duration: Duration::from_secs(120),
@@ -43,20 +45,24 @@ impl DeviceFlow {
pub(crate) async fn token<C, T>(
&self,
hyper_client: &hyper::Client<C>,
app_secret: &ApplicationSecret,
scopes: &[T],
) -> Result<Token, Error>
where
T: AsRef<str>,
C: hyper::client::connect::Connect + 'static,
{
let (pollinf, device_code) =
Self::request_code(app_secret, hyper_client, &self.device_code_url, scopes).await?;
let (pollinf, device_code) = Self::request_code(
&self.app_secret,
hyper_client,
&self.device_code_url,
scopes,
)
.await?;
self.flow_delegate.present_user_code(&pollinf);
tokio::timer::Timeout::new(
self.wait_for_device_token(
hyper_client,
app_secret,
&self.app_secret,
&pollinf,
&device_code,
&self.grant_type,
@@ -296,6 +302,7 @@ mod tests {
.build::<_, hyper::Body>(https);
let flow = DeviceFlow {
app_secret,
device_code_url: device_code_url.into(),
flow_delegate: Box::new(FD),
wait_duration: Duration::from_secs(5),
@@ -333,11 +340,7 @@ mod tests {
.create();
let token = flow
.token(
&client,
&app_secret,
&["https://www.googleapis.com/scope/1"],
)
.token(&client, &["https://www.googleapis.com/scope/1"])
.await
.expect("token failed");
assert_eq!("accesstoken", token.access_token);
@@ -373,11 +376,7 @@ mod tests {
.create();
let res = flow
.token(
&client,
&app_secret,
&["https://www.googleapis.com/scope/1"],
)
.token(&client, &["https://www.googleapis.com/scope/1"])
.await;
assert!(res.is_err());
assert!(format!("{}", res.unwrap_err()).contains("invalid_client_id"));
@@ -411,11 +410,7 @@ mod tests {
.create();
let res = flow
.token(
&client,
&app_secret,
&["https://www.googleapis.com/scope/1"],
)
.token(&client, &["https://www.googleapis.com/scope/1"])
.await;
assert!(res.is_err());
assert!(format!("{}", res.unwrap_err()).contains("Access denied by user"));

View File

@@ -66,14 +66,19 @@ pub enum InstalledFlowReturnMethod {
/// https://www.oauth.com/oauth2-servers/authorization/,
/// https://developers.google.com/identity/protocols/OAuth2InstalledApp).
pub struct InstalledFlow {
pub(crate) app_secret: ApplicationSecret,
pub(crate) method: InstalledFlowReturnMethod,
pub(crate) flow_delegate: Box<dyn FlowDelegate>,
}
impl InstalledFlow {
/// Create a new InstalledFlow with the provided secret and method.
pub(crate) fn new(method: InstalledFlowReturnMethod) -> InstalledFlow {
pub(crate) fn new(
app_secret: ApplicationSecret,
method: InstalledFlowReturnMethod,
) -> InstalledFlow {
InstalledFlow {
app_secret,
method,
flow_delegate: Box::new(DefaultFlowDelegate),
}
@@ -88,7 +93,6 @@ impl InstalledFlow {
pub(crate) async fn token<C, T>(
&self,
hyper_client: &hyper::Client<C>,
app_secret: &ApplicationSecret,
scopes: &[T],
) -> Result<Token, Error>
where
@@ -97,11 +101,11 @@ impl InstalledFlow {
{
match self.method {
InstalledFlowReturnMethod::HTTPRedirect => {
self.ask_auth_code_via_http(hyper_client, app_secret, scopes)
self.ask_auth_code_via_http(hyper_client, &self.app_secret, scopes)
.await
}
InstalledFlowReturnMethod::Interactive => {
self.ask_auth_code_interactively(hyper_client, app_secret, scopes)
self.ask_auth_code_interactively(hyper_client, &self.app_secret, scopes)
.await
}
}
@@ -470,6 +474,7 @@ mod tests {
let fd = FD("authorizationcode".to_string(), client.clone());
let inf = InstalledFlow {
app_secret: app_secret.clone(),
method: InstalledFlowReturnMethod::Interactive,
flow_delegate: Box::new(fd),
};
@@ -493,7 +498,7 @@ mod tests {
.create();
let tok = inf
.token(&client, &app_secret, &["https://googleapis.com/some/scope"])
.token(&client, &["https://googleapis.com/some/scope"])
.await
.expect("failed to get token");
assert_eq!("accesstoken", tok.access_token);
@@ -505,6 +510,7 @@ mod tests {
// Successful path with HTTP redirect.
{
let inf = InstalledFlow {
app_secret: app_secret.clone(),
method: InstalledFlowReturnMethod::HTTPRedirect,
flow_delegate: Box::new(FD(
"authorizationcodefromlocalserver".to_string(),
@@ -528,7 +534,7 @@ mod tests {
.create();
let tok = inf
.token(&client, &app_secret, &["https://googleapis.com/some/scope"])
.token(&client, &["https://googleapis.com/some/scope"])
.await
.expect("failed to get token");
assert_eq!("accesstoken", tok.access_token);
@@ -549,7 +555,7 @@ mod tests {
.create();
let tokr = inf
.token(&client, &app_secret, &["https://googleapis.com/some/scope"])
.token(&client, &["https://googleapis.com/some/scope"])
.await;
assert!(tokr.is_err());
assert!(format!("{}", tokr.unwrap_err()).contains("invalid_code"));

View File

@@ -76,18 +76,19 @@ pub mod error;
mod helper;
mod installed;
mod refresh;
pub mod service_account;
mod service_account;
mod storage;
mod types;
#[doc(inline)]
pub use crate::authenticator::{DeviceFlowAuthenticator, InstalledFlowAuthenticator};
pub use crate::authenticator::{
DeviceFlowAuthenticator, InstalledFlowAuthenticator, ServiceAccountAuthenticator,
};
pub use crate::helper::*;
pub use crate::installed::InstalledFlowReturnMethod;
#[doc(inline)]
pub use crate::service_account::{ServiceAccountAuthenticator, ServiceAccountKey};
pub use crate::service_account::ServiceAccountKey;
#[doc(inline)]
pub use crate::error::Error;

View File

@@ -11,13 +11,10 @@
//! Copyright (c) 2016 Google Inc (lewinb@google.com).
//!
use crate::authenticator::{DefaultHyperClient, HyperClientBuilder};
use crate::error::{Error, JsonErrorOr};
use crate::storage::{self, Storage};
use crate::types::Token;
use std::io;
use std::sync::Mutex;
use futures::prelude::*;
use hyper::header;
@@ -124,7 +121,7 @@ impl<'a> Claims<'a> {
}
/// A JSON Web Token ready for signing.
struct JWTSigner {
pub(crate) struct JWTSigner {
signer: Box<dyn rustls::sign::Signer>,
}
@@ -160,139 +157,40 @@ impl JWTSigner {
}
}
/// Create an authenticator that uses a service account.
/// ```
/// # async fn foo() {
/// # let service_key = yup_oauth2::service_account_key_from_file("/tmp/foo").unwrap();
/// let authenticator = yup_oauth2::ServiceAccountAuthenticator::builder(service_key)
/// .build()
/// .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(key: ServiceAccountKey) -> Builder<DefaultHyperClient> {
Builder {
client: DefaultHyperClient,
key,
subject: None,
}
}
pub struct ServiceAccountFlowOpts {
pub(crate) key: ServiceAccountKey,
pub(crate) subject: Option<String>,
}
/// Configure a service account authenticator using the builder pattern.
pub struct Builder<C> {
client: C,
/// ServiceAccountFlow can fetch oauth tokens using a service account.
pub struct ServiceAccountFlow {
key: ServiceAccountKey,
subject: Option<String>,
}
/// Methods available when building a service account authenticator.
/// ```
/// # async fn foo() {
/// # let custom_hyper_client = hyper::Client::new();
/// # let service_key = yup_oauth2::service_account_key_from_file("/tmp/foo").unwrap();
/// let authenticator = yup_oauth2::ServiceAccountAuthenticator::builder(service_key)
/// .hyper_client(custom_hyper_client)
/// .subject("foo")
/// .build()
/// .expect("failed to create authenticator");
/// # }
/// ```
impl<C> Builder<C> {
/// Use the provided hyper client.
pub fn hyper_client<NewC: HyperClientBuilder>(self, hyper_client: NewC) -> Builder<NewC> {
Builder {
client: hyper_client,
key: self.key,
subject: self.subject,
}
}
/// Use the provided subject.
pub fn subject(self, subject: impl Into<String>) -> Self {
Builder {
subject: Some(subject.into()),
..self
}
}
/// Build the configured ServiceAccountAccess.
pub fn build(self) -> Result<ServiceAccountAccess<C::Connector>, io::Error>
where
C: HyperClientBuilder,
{
ServiceAccountAccess::new(self.client.build_hyper_client(), self.key, self.subject)
}
}
/// ServiceAccountAccess can fetch oauth tokens using a service account.
pub struct ServiceAccountAccess<C> {
client: hyper::Client<C>,
key: ServiceAccountKey,
cache: Storage,
subject: Option<String>,
signer: JWTSigner,
}
impl<C> ServiceAccountAccess<C>
where
C: hyper::client::connect::Connect + 'static,
{
fn new(
client: hyper::Client<C>,
key: ServiceAccountKey,
subject: Option<String>,
) -> Result<Self, io::Error> {
let signer = JWTSigner::new(&key.private_key)?;
Ok(ServiceAccountAccess {
client,
key,
cache: Storage::Memory {
tokens: Mutex::new(storage::JSONTokens::new()),
},
subject,
impl ServiceAccountFlow {
pub(crate) fn new(opts: ServiceAccountFlowOpts) -> Result<Self, io::Error> {
let signer = JWTSigner::new(&opts.key.private_key)?;
Ok(ServiceAccountFlow {
key: opts.key,
subject: opts.subject,
signer,
})
}
/// Return the current token for the provided scopes.
pub async fn token<T>(&self, scopes: &[T]) -> Result<Token, Error>
where
T: AsRef<str>,
{
let hashed_scopes = storage::ScopesAndFilter::from(scopes);
let cache = &self.cache;
match cache.get(hashed_scopes) {
Some(token) if !token.expired() => return Ok(token),
_ => {}
}
let token = Self::request_token(
&self.client,
&self.signer,
self.subject.as_ref().map(|x| x.as_str()),
&self.key,
scopes,
)
.await?;
cache.set(hashed_scopes, token.clone()).await;
Ok(token)
}
/// Send a request for a new Bearer token to the OAuth provider.
async fn request_token<T>(
client: &hyper::client::Client<C>,
signer: &JWTSigner,
subject: Option<&str>,
key: &ServiceAccountKey,
pub(crate) async fn token<C, T>(
&self,
hyper_client: &hyper::Client<C>,
scopes: &[T],
) -> Result<Token, Error>
where
T: AsRef<str>,
C: hyper::client::connect::Connect + 'static,
{
let claims = Claims::new(key, scopes, subject);
let signed = signer.sign_claims(&claims).map_err(|_| {
let claims = Claims::new(&self.key, scopes, self.subject.as_ref().map(|x| x.as_str()));
let signed = self.signer.sign_claims(&claims).map_err(|_| {
Error::LowLevelError(io::Error::new(
io::ErrorKind::Other,
"unable to sign claims",
@@ -301,11 +199,14 @@ where
let rqbody = form_urlencoded::Serializer::new(String::new())
.extend_pairs(&[("grant_type", GRANT_TYPE), ("assertion", signed.as_str())])
.finish();
let request = hyper::Request::post(&key.token_uri)
let request = hyper::Request::post(&self.key.token_uri)
.header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
.body(hyper::Body::from(rqbody))
.unwrap();
let response = client.request(request).await.map_err(Error::ClientError)?;
let response = hyper_client
.request(request)
.await
.map_err(Error::ClientError)?;
let body = response
.into_body()
.try_concat()
@@ -349,12 +250,17 @@ mod tests {
use super::*;
use crate::helper::service_account_key_from_file;
use crate::parse_json;
use hyper_rustls::HttpsConnector;
use mockito::mock;
#[tokio::test]
async fn test_mocked_http() {
env_logger::try_init().unwrap();
let https = HttpsConnector::new();
let client = hyper::Client::builder()
.keep_alive(false)
.build::<_, hyper::Body>(https);
let server_url = &mockito::server_url();
let key: ServiceAccountKey = parse_json!({
"type": "service_account",
@@ -387,25 +293,13 @@ mod tests {
.with_body(json_response.to_string())
.expect(1)
.create();
let acc = ServiceAccountAuthenticator::builder(key.clone())
.build()
.unwrap();
let acc = ServiceAccountFlow::new(ServiceAccountFlowOpts {
key: key.clone(),
subject: None,
})
.unwrap();
let tok = acc
.token(&["https://www.googleapis.com/auth/pubsub"])
.await
.expect("token failed");
assert!(tok.access_token.contains("ya29.c.ElouBywiys0Ly"));
assert_eq!(Some(3600), tok.expires_in);
assert!(acc
.cache
.get(storage::ScopesAndFilter::from(&[
"https://www.googleapis.com/auth/pubsub"
]))
.is_some());
// Test that token is in cache (otherwise mock will tell us)
let tok = acc
.token(&["https://www.googleapis.com/auth/pubsub"])
.token(&client, &["https://www.googleapis.com/auth/pubsub"])
.await
.expect("token failed");
assert!(tok.access_token.contains("ya29.c.ElouBywiys0Ly"));
@@ -419,10 +313,14 @@ mod tests {
.with_header("content-type", "text/json")
.with_body(bad_json_response.to_string())
.create();
let acc = ServiceAccountAuthenticator::builder(key.clone())
.build()
.unwrap();
let result = acc.token(&["https://www.googleapis.com/auth/pubsub"]).await;
let acc = ServiceAccountFlow::new(ServiceAccountFlowOpts {
key: key.clone(),
subject: None,
})
.unwrap();
let result = acc
.token(&client, &["https://www.googleapis.com/auth/pubsub"])
.await;
assert!(result.is_err());
_m.assert();
}
@@ -436,10 +334,15 @@ mod tests {
#[allow(dead_code)]
async fn test_service_account_e2e() {
let key = service_account_key_from_file(&TEST_PRIVATE_KEY_PATH.to_string()).unwrap();
let acc = ServiceAccountAuthenticator::builder(key).build().unwrap();
let acc = ServiceAccountFlow::new(ServiceAccountFlowOpts { key, subject: None }).unwrap();
let https = HttpsConnector::new();
let client = hyper::Client::builder()
.keep_alive(false)
.build::<_, hyper::Body>(https);
println!(
"{:?}",
acc.token(&["https://www.googleapis.com/auth/pubsub"]).await
acc.token(&client, &["https://www.googleapis.com/auth/pubsub"])
.await
);
}

View File

@@ -178,27 +178,24 @@ impl JSONTokens {
// application will provide the same set of scopes repeatedly. If a
// token exists for the exact scope list requested a lookup of the
// ScopeFilter will return a list that would contain it.
if let Some(tokens) = self.token_map.get(&filter) {
for t in tokens {
if requested_scopes_are_subset_of(t.scopes.as_slice()) {
return Some(t.token.clone());
}
}
if let Some(t) = self
.token_map
.get(&filter)
.into_iter()
.flat_map(|tokens_matching_filter| tokens_matching_filter.iter())
.find(|js_token: &&JSONToken| requested_scopes_are_subset_of(&js_token.scopes))
{
return Some(t.token.clone());
}
// No exact match for the scopes provided. Search for any tokens that
// exist for a superset of the scopes requested.
for t in self
.token_map
self.token_map
.iter()
.filter(|(k, _v)| filter.is_subset_of(**k) == FilterResponse::Maybe)
.flat_map(|(_, v)| v.iter())
{
if requested_scopes_are_subset_of(&t.scopes) {
return Some(t.token.clone());
}
}
None
.filter(|(k, _)| filter.is_subset_of(**k) == FilterResponse::Maybe)
.flat_map(|(_, tokens_matching_filter)| tokens_matching_filter.iter())
.find(|v: &&JSONToken| requested_scopes_are_subset_of(&v.scopes))
.map(|t: &JSONToken| t.token.clone())
}
fn set<T>(&mut self, ScopesAndFilter { filter, scopes }: ScopesAndFilter<T>, token: Token)