diff --git a/examples/test-svc-acct/src/main.rs b/examples/test-svc-acct/src/main.rs index 8ff8db0..1b73a69 100644 --- a/examples/test-svc-acct/src/main.rs +++ b/examples/test-svc-acct/src/main.rs @@ -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(); diff --git a/src/authenticator.rs b/src/authenticator.rs index 1402448..b74a3c8 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -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 { hyper_client: hyper::Client, - app_secret: ApplicationSecret, auth_delegate: Box, storage: Storage, auth_flow: AuthFlow, @@ -36,19 +36,22 @@ where T: AsRef, { 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 { hyper_client_builder: C, - app_secret: ApplicationSecret, auth_delegate: Box, storage_type: StorageType, auth_flow: F, @@ -110,10 +105,9 @@ impl InstalledFlowAuthenticator { app_secret: ApplicationSecret, method: InstalledFlowReturnMethod, ) -> AuthenticatorBuilder { - AuthenticatorBuilder::::with_auth_flow( - app_secret, - InstalledFlow::new(method), - ) + AuthenticatorBuilder::::with_auth_flow(InstalledFlow::new( + app_secret, method, + )) } } @@ -133,7 +127,30 @@ impl DeviceFlowAuthenticator { pub fn builder( app_secret: ApplicationSecret, ) -> AuthenticatorBuilder { - AuthenticatorBuilder::::with_auth_flow(app_secret, DeviceFlow::new()) + AuthenticatorBuilder::::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 { + AuthenticatorBuilder::::with_auth_flow(ServiceAccountFlowOpts { + key: service_account_key, + subject: None, + }) } } @@ -153,14 +170,17 @@ impl DeviceFlowAuthenticator { /// # } /// ``` impl AuthenticatorBuilder { - /// Create the authenticator. - pub async fn build(self) -> io::Result> + async fn common_build( + hyper_client_builder: C, + storage_type: StorageType, + auth_delegate: Box, + auth_flow: AuthFlow, + ) -> io::Result> where C: HyperClientBuilder, - F: Into, { - 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 AuthenticatorBuilder { 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 { + fn with_auth_flow(auth_flow: F) -> AuthenticatorBuilder { AuthenticatorBuilder { hyper_client_builder: DefaultHyperClient, - app_secret, auth_delegate: Box::new(DefaultAuthenticatorDelegate), storage_type: StorageType::Memory, auth_flow, @@ -196,7 +211,6 @@ impl AuthenticatorBuilder { ) -> AuthenticatorBuilder, 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 AuthenticatorBuilder { ..self } } + + /// Create the authenticator. + pub async fn build(self) -> io::Result> + 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 AuthenticatorBuilder { ..self } } + + /// Create the authenticator. + pub async fn build(self) -> io::Result> + 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 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)?; + 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 for AuthFlow { - fn from(device_flow: DeviceFlow) -> AuthFlow { - AuthFlow::DeviceFlow(device_flow) - } - } - - impl From 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, - app_secret: &'a ApplicationSecret, scopes: &'a [T], ) -> Result 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; diff --git a/src/device.rs b/src/device.rs index 6815963..d2758a6 100644 --- a/src/device.rs +++ b/src/device.rs @@ -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, 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( &self, hyper_client: &hyper::Client, - app_secret: &ApplicationSecret, scopes: &[T], ) -> Result where T: AsRef, 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")); diff --git a/src/installed.rs b/src/installed.rs index d0805d0..81528f9 100644 --- a/src/installed.rs +++ b/src/installed.rs @@ -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, } 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( &self, hyper_client: &hyper::Client, - app_secret: &ApplicationSecret, scopes: &[T], ) -> Result 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")); diff --git a/src/lib.rs b/src/lib.rs index 824eb9f..81520f9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/service_account.rs b/src/service_account.rs index 211357d..abb4eda 100644 --- a/src/service_account.rs +++ b/src/service_account.rs @@ -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, } @@ -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 { - Builder { - client: DefaultHyperClient, - key, - subject: None, - } - } +pub struct ServiceAccountFlowOpts { + pub(crate) key: ServiceAccountKey, + pub(crate) subject: Option, } -/// Configure a service account authenticator using the builder pattern. -pub struct Builder { - client: C, +/// ServiceAccountFlow can fetch oauth tokens using a service account. +pub struct ServiceAccountFlow { key: ServiceAccountKey, subject: Option, -} - -/// 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 Builder { - /// Use the provided hyper client. - pub fn hyper_client(self, hyper_client: NewC) -> Builder { - Builder { - client: hyper_client, - key: self.key, - subject: self.subject, - } - } - - /// Use the provided subject. - pub fn subject(self, subject: impl Into) -> Self { - Builder { - subject: Some(subject.into()), - ..self - } - } - - /// Build the configured ServiceAccountAccess. - pub fn build(self) -> Result, 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 { - client: hyper::Client, - key: ServiceAccountKey, - cache: Storage, - subject: Option, signer: JWTSigner, } -impl ServiceAccountAccess -where - C: hyper::client::connect::Connect + 'static, -{ - fn new( - client: hyper::Client, - key: ServiceAccountKey, - subject: Option, - ) -> Result { - 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 { + 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(&self, scopes: &[T]) -> Result - where - T: AsRef, - { - 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( - client: &hyper::client::Client, - signer: &JWTSigner, - subject: Option<&str>, - key: &ServiceAccountKey, + pub(crate) async fn token( + &self, + hyper_client: &hyper::Client, scopes: &[T], ) -> Result where T: AsRef, + 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 ); } diff --git a/src/storage.rs b/src/storage.rs index 35e123a..3172c85 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -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(&mut self, ScopesAndFilter { filter, scopes }: ScopesAndFilter, token: Token)