From 7638946508f087e95382a57faa6b00d937f6d8a8 Mon Sep 17 00:00:00 2001 From: Antti Peltonen Date: Wed, 9 Jun 2021 11:44:04 +0000 Subject: [PATCH 1/9] work started on adc implementation --- Cargo.toml | 2 +- examples/test-adc/Cargo.toml | 9 ++++ examples/test-adc/src/main.rs | 14 ++++++ src/application_default_credentials.rs | 36 ++++++++++++++++ src/authenticator.rs | 60 ++++++++++++++++++++++++++ src/lib.rs | 4 +- tests/tests.rs | 37 +++++++++++++++- 7 files changed, 158 insertions(+), 4 deletions(-) create mode 100644 examples/test-adc/Cargo.toml create mode 100644 examples/test-adc/src/main.rs create mode 100644 src/application_default_credentials.rs diff --git a/Cargo.toml b/Cargo.toml index b0d7cd8..fd1f99d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ webbrowser = "0.5" hyper-rustls = "0.22.1" [workspace] -members = ["examples/test-installed/", "examples/test-svc-acct/", "examples/test-device/", "examples/service_account", "examples/drive_example"] +members = ["examples/test-installed/", "examples/test-svc-acct/", "examples/test-device/", "examples/service_account", "examples/drive_example", "examples/test-adc"] [package.metadata.docs.rs] all-features = true diff --git a/examples/test-adc/Cargo.toml b/examples/test-adc/Cargo.toml new file mode 100644 index 0000000..a5a023e --- /dev/null +++ b/examples/test-adc/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "test-adc" +version = "0.1.0" +authors = ["Antti Peltonen "] +edition = "2018" + +[dependencies] +yup-oauth2 = { path = "../../" } +tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } diff --git a/examples/test-adc/src/main.rs b/examples/test-adc/src/main.rs new file mode 100644 index 0000000..01137ef --- /dev/null +++ b/examples/test-adc/src/main.rs @@ -0,0 +1,14 @@ +use yup_oauth2::ApplicationDefaultCredentialsAuthenticator; + +#[tokio::main] +async fn main() { + let auth = ApplicationDefaultCredentialsAuthenticator::builder() + .await + .build() + .await + .unwrap(); + let scopes = &["https://www.googleapis.com/auth/pubsub"]; + + let tok = auth.token(scopes).await.unwrap(); + println!("token is: {:?}", tok); +} diff --git a/src/application_default_credentials.rs b/src/application_default_credentials.rs new file mode 100644 index 0000000..f052df2 --- /dev/null +++ b/src/application_default_credentials.rs @@ -0,0 +1,36 @@ +use crate::error::Error; +use crate::types::TokenInfo; + +pub struct ApplicationDefaultCredentialsFlowOpts; + +/// ServiceAccountFlow can fetch oauth tokens using a service account. +pub struct ApplicationDefaultCredentialsFlow; +impl ApplicationDefaultCredentialsFlow { + pub(crate) fn new(_opts: ApplicationDefaultCredentialsFlowOpts) -> Self { + ApplicationDefaultCredentialsFlow {} + } + + pub(crate) async fn token( + &self, + hyper_client: &hyper::Client, + scopes: &[T], + ) -> Result + where + T: AsRef, + C: hyper::client::connect::Connect + Clone + Send + Sync + 'static, + { + let scope = crate::helper::join(scopes, ","); + let token_uri = format!("http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token?scopes={}", scope); + let request = hyper::Request::get(token_uri) + .header("Metadata-Flavor", "Google") + .body(hyper::Body::from(String::new())) // why body is needed? + .unwrap(); + log::debug!("requesting token from metadata server: {:?}", request); + let (head, body) = hyper_client.request(request).await?.into_parts(); + let body = hyper::body::to_bytes(body).await?; + log::debug!("received response; head: {:?}, body: {:?}", head, body); + TokenInfo::from_json(&body) + } +} + +// eof diff --git a/src/authenticator.rs b/src/authenticator.rs index d3d72e4..7b0970a 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -1,4 +1,7 @@ //! Module contianing the core functionality for OAuth2 Authentication. +use crate::application_default_credentials::{ + ApplicationDefaultCredentialsFlow, ApplicationDefaultCredentialsFlowOpts, +}; use crate::authenticator_delegate::{DeviceFlowDelegate, InstalledFlowDelegate}; use crate::device::DeviceFlow; use crate::error::Error; @@ -254,6 +257,40 @@ impl ServiceAccountAuthenticator { } } +/// Create an authenticator that uses a application default credentials. +/// ``` +/// # async fn foo() { +/// let authenticator = yup_oauth2::ApplicationDefaultCredentialsAuthenticator::builder() +/// .await +/// .build() +/// .await +/// .expect("failed to create authenticator"); +/// # } +/// ``` +pub struct ApplicationDefaultCredentialsAuthenticator; +impl ApplicationDefaultCredentialsAuthenticator { + /// Use the builder pattern to create an Authenticator that uses a service account. + pub async fn builder( + ) -> AuthenticatorBuilder { + match std::env::var("GOOGLE_APPLICATION_CREDENTIALS") { + Ok(_path) => { + todo!() + // # here we would need to do something like this: + // let service_account_key = crate::read_service_account_key(path).await.unwrap(); + // AuthenticatorBuilder::::with_auth_flow( + // ServiceAccountFlowOpts { + // key: service_account_key, + // subject: None, + // }, + // ) + } + Err(_e) => AuthenticatorBuilder::::with_auth_flow( + ApplicationDefaultCredentialsFlowOpts {}, + ), + } + } +} + /// ## Methods available when building any Authenticator. /// ``` /// # #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] @@ -479,7 +516,25 @@ impl AuthenticatorBuilder { } } +impl AuthenticatorBuilder { + /// Create the authenticator. + pub async fn build(self) -> io::Result> + 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; @@ -490,6 +545,7 @@ mod private { DeviceFlow(DeviceFlow), InstalledFlow(InstalledFlow), ServiceAccountFlow(ServiceAccountFlow), + ApplicationDefaultCredentialsFlow(ApplicationDefaultCredentialsFlow), } impl AuthFlow { @@ -498,6 +554,7 @@ mod private { AuthFlow::DeviceFlow(device_flow) => Some(&device_flow.app_secret), AuthFlow::InstalledFlow(installed_flow) => Some(&installed_flow.app_secret), AuthFlow::ServiceAccountFlow(_) => None, + AuthFlow::ApplicationDefaultCredentialsFlow(_) => None, } } @@ -518,6 +575,9 @@ mod private { 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 + } } } } diff --git a/src/lib.rs b/src/lib.rs index 78d7491..a82f6ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,6 +72,7 @@ #![deny(missing_docs)] #![cfg_attr(yup_oauth2_docsrs, feature(doc_cfg))] +mod application_default_credentials; pub mod authenticator; pub mod authenticator_delegate; mod device; @@ -89,7 +90,8 @@ mod types; #[doc(inline)] pub use crate::authenticator::{ - DeviceFlowAuthenticator, InstalledFlowAuthenticator, ServiceAccountAuthenticator, + ApplicationDefaultCredentialsAuthenticator, DeviceFlowAuthenticator, + InstalledFlowAuthenticator, ServiceAccountAuthenticator, }; pub use crate::helper::*; diff --git a/tests/tests.rs b/tests/tests.rs index 75f0ab5..3b10967 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -2,8 +2,9 @@ use yup_oauth2::{ authenticator::{DefaultAuthenticator, DefaultHyperClient, HyperClientBuilder}, authenticator_delegate::{DeviceAuthResponse, DeviceFlowDelegate, InstalledFlowDelegate}, error::{AuthError, AuthErrorCode}, - ApplicationSecret, DeviceFlowAuthenticator, Error, InstalledFlowAuthenticator, - InstalledFlowReturnMethod, ServiceAccountAuthenticator, ServiceAccountKey, + ApplicationDefaultCredentialsAuthenticator, ApplicationSecret, DeviceFlowAuthenticator, Error, + InstalledFlowAuthenticator, InstalledFlowReturnMethod, ServiceAccountAuthenticator, + ServiceAccountKey, }; use std::future::Future; @@ -596,3 +597,35 @@ async fn test_disk_storage() { assert_eq!(token1.as_str(), "accesstoken"); assert_eq!(token1, token2); } + +#[tokio::test] +async fn test_default_application_credentials() { + let _ = env_logger::try_init(); + let server = Server::run(); + server.expect( + // TODO: this does not work. + Expectation::matching(all_of![ + request::method_path("GET", "/token"), + request::query(url_decoded(all_of![contains(( + "scopes", + "https://googleapis.com/some/scope" + ))])) + ]) + .respond_with(json_encoded(serde_json::json!({ + "access_token": "accesstoken", + "refresh_token": "refreshtoken", + "token_type": "Bearer", + "expires_in": 12345678, + }))), + ); + let authenticator = ApplicationDefaultCredentialsAuthenticator::builder() + .await + .build() + .await + .unwrap(); + let token = authenticator + .token(&["https://googleapis.com/some/scope"]) + .await + .unwrap(); + assert_ne!(token.as_str(), "accesstoken"); +} From 921f1c71900440dfadaca735363c295e665b236a Mon Sep 17 00:00:00 2001 From: Antti Peltonen Date: Thu, 10 Jun 2021 05:49:41 +0000 Subject: [PATCH 2/9] builder pattern for adc struct --- examples/test-adc/src/main.rs | 16 +++++--- src/authenticator.rs | 72 +++++++++++++++++++++++++---------- tests/tests.rs | 12 +++--- 3 files changed, 68 insertions(+), 32 deletions(-) diff --git a/examples/test-adc/src/main.rs b/examples/test-adc/src/main.rs index 01137ef..cac39c9 100644 --- a/examples/test-adc/src/main.rs +++ b/examples/test-adc/src/main.rs @@ -1,12 +1,18 @@ +use yup_oauth2::authenticator::ApplicationDefaultCredentialsTypes; use yup_oauth2::ApplicationDefaultCredentialsAuthenticator; #[tokio::main] async fn main() { - let auth = ApplicationDefaultCredentialsAuthenticator::builder() - .await - .build() - .await - .unwrap(); + let auth = match ApplicationDefaultCredentialsAuthenticator::builder().await { + ApplicationDefaultCredentialsTypes::InstanceMetadata(auth) => auth + .build() + .await + .expect("Unable to create instance metadata authenticator"), + ApplicationDefaultCredentialsTypes::ServiceAccount(auth) => auth + .build() + .await + .expect("Unable to create service account authenticator"), + }; let scopes = &["https://www.googleapis.com/auth/pubsub"]; let tok = auth.token(scopes).await.unwrap(); diff --git a/src/authenticator.rs b/src/authenticator.rs index 7b0970a..101709c 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -260,36 +260,66 @@ impl ServiceAccountAuthenticator { /// Create an authenticator that uses a application default credentials. /// ``` /// # async fn foo() { -/// let authenticator = yup_oauth2::ApplicationDefaultCredentialsAuthenticator::builder() -/// .await -/// .build() -/// .await -/// .expect("failed to create authenticator"); +/// let authenticator = match ApplicationDefaultCredentialsAuthenticator::builder().await { +/// ApplicationDefaultCredentialsTypes::InstanceMetadata(auth) => auth +/// .build() +/// .await +/// .expect("Unable to create instance metadata authenticator"), +/// ApplicationDefaultCredentialsTypes::ServiceAccount(auth) => auth +/// .build() +/// .await +/// .expect("Unable to create service account authenticator"), +/// }; /// # } /// ``` pub struct ApplicationDefaultCredentialsAuthenticator; impl ApplicationDefaultCredentialsAuthenticator { - /// Use the builder pattern to create an Authenticator that uses a service account. - pub async fn builder( + /// Use modified builder pattern to create an Authenticator that uses GCE instance metadata server + /// to provide tokens. + pub fn from_instance_metadata( ) -> AuthenticatorBuilder { - match std::env::var("GOOGLE_APPLICATION_CREDENTIALS") { - Ok(_path) => { - todo!() - // # here we would need to do something like this: - // let service_account_key = crate::read_service_account_key(path).await.unwrap(); - // AuthenticatorBuilder::::with_auth_flow( - // ServiceAccountFlowOpts { - // key: service_account_key, - // subject: None, - // }, - // ) - } - Err(_e) => AuthenticatorBuilder::::with_auth_flow( - ApplicationDefaultCredentialsFlowOpts {}, + AuthenticatorBuilder::::with_auth_flow( + ApplicationDefaultCredentialsFlowOpts {}, + ) + } + + /// Use modified builder pattern to create an Authenticator that pulls default application credentials + /// service account file name from os environment variable. + pub async fn from_environment( + ) -> Result, std::env::VarError> + { + let service_account_key = + crate::read_service_account_key(std::env::var("GOOGLE_APPLICATION_CREDENTIALS")?) + .await + .unwrap(); + Ok( + AuthenticatorBuilder::::with_auth_flow(ServiceAccountFlowOpts { + key: service_account_key, + subject: None, + }), + ) + } + + /// Use the builder pattern to deduce which model of authenticator should be used: + /// Service account one or GCE instance metadata kind + pub async fn builder() -> ApplicationDefaultCredentialsTypes { + match ApplicationDefaultCredentialsAuthenticator::from_environment().await { + Ok(builder) => ApplicationDefaultCredentialsTypes::ServiceAccount(builder), + Err(_) => ApplicationDefaultCredentialsTypes::InstanceMetadata( + ApplicationDefaultCredentialsAuthenticator::from_instance_metadata(), ), } } } +/// Types of authenticators provided by ApplicationDefaultCredentialsAuthenticator +pub enum ApplicationDefaultCredentialsTypes { + /// Service account based authenticator signature + ServiceAccount(AuthenticatorBuilder), + /// GCE Instance Metadata based authenticator signature + InstanceMetadata( + AuthenticatorBuilder, + ), +} /// ## Methods available when building any Authenticator. /// ``` diff --git a/tests/tests.rs b/tests/tests.rs index 3b10967..fab7e6e 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -599,7 +599,8 @@ async fn test_disk_storage() { } #[tokio::test] -async fn test_default_application_credentials() { +async fn test_default_application_credentials_from_metadata_server() { + use yup_oauth2::authenticator::ApplicationDefaultCredentialsTypes; let _ = env_logger::try_init(); let server = Server::run(); server.expect( @@ -618,11 +619,10 @@ async fn test_default_application_credentials() { "expires_in": 12345678, }))), ); - let authenticator = ApplicationDefaultCredentialsAuthenticator::builder() - .await - .build() - .await - .unwrap(); + let authenticator = match ApplicationDefaultCredentialsAuthenticator::builder().await { + ApplicationDefaultCredentialsTypes::InstanceMetadata(auth) => auth.build().await.unwrap(), + _ => panic!("We are not testing service account adc model"), + }; let token = authenticator .token(&["https://googleapis.com/some/scope"]) .await From 7818c6a4600343cb7206e7ef304aff36c1b1eea9 Mon Sep 17 00:00:00 2001 From: Lukas Winkler Date: Thu, 25 Nov 2021 16:45:26 +0100 Subject: [PATCH 3/9] Make work with new structure after rebase --- src/authenticator.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/authenticator.rs b/src/authenticator.rs index 101709c..097627e 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -278,9 +278,7 @@ impl ApplicationDefaultCredentialsAuthenticator { /// to provide tokens. pub fn from_instance_metadata( ) -> AuthenticatorBuilder { - AuthenticatorBuilder::::with_auth_flow( - ApplicationDefaultCredentialsFlowOpts {}, - ) + AuthenticatorBuilder::new(ApplicationDefaultCredentialsFlowOpts {}, DefaultHyperClient) } /// Use modified builder pattern to create an Authenticator that pulls default application credentials @@ -292,12 +290,14 @@ impl ApplicationDefaultCredentialsAuthenticator { crate::read_service_account_key(std::env::var("GOOGLE_APPLICATION_CREDENTIALS")?) .await .unwrap(); - Ok( - AuthenticatorBuilder::::with_auth_flow(ServiceAccountFlowOpts { + + Ok(AuthenticatorBuilder::new( + ServiceAccountFlowOpts { key: service_account_key, subject: None, - }), - ) + }, + DefaultHyperClient, + )) } /// Use the builder pattern to deduce which model of authenticator should be used: From 792cc0469418386b5049998f55eb9f2bf6b1666e Mon Sep 17 00:00:00 2001 From: Lukas Winkler Date: Thu, 25 Nov 2021 17:36:39 +0100 Subject: [PATCH 4/9] Extend to provide with_client as well --- src/authenticator.rs | 56 ++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/src/authenticator.rs b/src/authenticator.rs index 097627e..ec2dc2b 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -276,49 +276,59 @@ pub struct ApplicationDefaultCredentialsAuthenticator; impl ApplicationDefaultCredentialsAuthenticator { /// Use modified builder pattern to create an Authenticator that uses GCE instance metadata server /// to provide tokens. - pub fn from_instance_metadata( - ) -> AuthenticatorBuilder { - AuthenticatorBuilder::new(ApplicationDefaultCredentialsFlowOpts {}, DefaultHyperClient) + pub fn from_instance_metadata() -> ApplicationDefaultCredentialsFlowOpts { + ApplicationDefaultCredentialsFlowOpts {} } /// Use modified builder pattern to create an Authenticator that pulls default application credentials /// service account file name from os environment variable. - pub async fn from_environment( - ) -> Result, std::env::VarError> - { + pub async fn from_environment() -> Result { let service_account_key = crate::read_service_account_key(std::env::var("GOOGLE_APPLICATION_CREDENTIALS")?) .await .unwrap(); - Ok(AuthenticatorBuilder::new( - ServiceAccountFlowOpts { - key: service_account_key, - subject: None, - }, - DefaultHyperClient, - )) + Ok(ServiceAccountFlowOpts { + key: service_account_key, + subject: None, + }) } /// Use the builder pattern to deduce which model of authenticator should be used: /// Service account one or GCE instance metadata kind - pub async fn builder() -> ApplicationDefaultCredentialsTypes { + pub async fn builder() -> ApplicationDefaultCredentialsTypes { + Self::with_client(DefaultHyperClient).await + } + + /// Use the builder pattern to deduce which model of authenticator should be used and allow providing a hyper client + pub async fn with_client(client: C) -> ApplicationDefaultCredentialsTypes + where + C: HyperClientBuilder, + { match ApplicationDefaultCredentialsAuthenticator::from_environment().await { - Ok(builder) => ApplicationDefaultCredentialsTypes::ServiceAccount(builder), - Err(_) => ApplicationDefaultCredentialsTypes::InstanceMetadata( - ApplicationDefaultCredentialsAuthenticator::from_instance_metadata(), - ), + Ok(flow_opts) => { + let builder = AuthenticatorBuilder::new(flow_opts, client); + + ApplicationDefaultCredentialsTypes::ServiceAccount(builder) + } + Err(_) => { + ApplicationDefaultCredentialsTypes::InstanceMetadata(AuthenticatorBuilder::new( + ApplicationDefaultCredentialsAuthenticator::from_instance_metadata(), + client, + )) + } } } } /// Types of authenticators provided by ApplicationDefaultCredentialsAuthenticator -pub enum ApplicationDefaultCredentialsTypes { +pub enum ApplicationDefaultCredentialsTypes +where + C: HyperClientBuilder, +{ /// Service account based authenticator signature - ServiceAccount(AuthenticatorBuilder), + ServiceAccount(AuthenticatorBuilder), /// GCE Instance Metadata based authenticator signature - InstanceMetadata( - AuthenticatorBuilder, - ), + InstanceMetadata(AuthenticatorBuilder), } /// ## Methods available when building any Authenticator. From 169e5ff1c06244e6db0221bb9b656a93e310aa92 Mon Sep 17 00:00:00 2001 From: Lukas Winkler Date: Thu, 25 Nov 2021 19:37:11 +0100 Subject: [PATCH 5/9] Allow overriding metadata url used during testing --- examples/test-adc/src/main.rs | 4 +++- src/application_default_credentials.rs | 24 +++++++++++++++------ src/authenticator.rs | 29 +++++++++++--------------- src/lib.rs | 1 + tests/tests.rs | 14 ++++++++----- 5 files changed, 43 insertions(+), 29 deletions(-) diff --git a/examples/test-adc/src/main.rs b/examples/test-adc/src/main.rs index cac39c9..0b63f4f 100644 --- a/examples/test-adc/src/main.rs +++ b/examples/test-adc/src/main.rs @@ -1,9 +1,11 @@ use yup_oauth2::authenticator::ApplicationDefaultCredentialsTypes; use yup_oauth2::ApplicationDefaultCredentialsAuthenticator; +use yup_oauth2::ApplicationDefaultCredentialsFlowOpts; #[tokio::main] async fn main() { - let auth = match ApplicationDefaultCredentialsAuthenticator::builder().await { + let opts = ApplicationDefaultCredentialsFlowOpts::default(); + let auth = match ApplicationDefaultCredentialsAuthenticator::builder(opts).await { ApplicationDefaultCredentialsTypes::InstanceMetadata(auth) => auth .build() .await diff --git a/src/application_default_credentials.rs b/src/application_default_credentials.rs index f052df2..5caa05c 100644 --- a/src/application_default_credentials.rs +++ b/src/application_default_credentials.rs @@ -1,13 +1,25 @@ use crate::error::Error; use crate::types::TokenInfo; -pub struct ApplicationDefaultCredentialsFlowOpts; +/// Provide options for the Application Default Credential Flow, mostly used for testing +pub struct ApplicationDefaultCredentialsFlowOpts { + /// Used as base to build the url during token request from GCP metadata server + pub metadata_url: Option, +} +impl Default for ApplicationDefaultCredentialsFlowOpts { + fn default() -> Self { + Self { metadata_url: None } + } +} + +pub struct ApplicationDefaultCredentialsFlow { + metadata_url: String, +} -/// ServiceAccountFlow can fetch oauth tokens using a service account. -pub struct ApplicationDefaultCredentialsFlow; impl ApplicationDefaultCredentialsFlow { - pub(crate) fn new(_opts: ApplicationDefaultCredentialsFlowOpts) -> Self { - ApplicationDefaultCredentialsFlow {} + pub(crate) fn new(opts: ApplicationDefaultCredentialsFlowOpts) -> Self { + let metadata_url = opts.metadata_url.unwrap_or_else(|| "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token".to_string()); + ApplicationDefaultCredentialsFlow { metadata_url } } pub(crate) async fn token( @@ -20,7 +32,7 @@ impl ApplicationDefaultCredentialsFlow { C: hyper::client::connect::Connect + Clone + Send + Sync + 'static, { let scope = crate::helper::join(scopes, ","); - let token_uri = format!("http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token?scopes={}", scope); + let token_uri = format!("{}?scopes={}", self.metadata_url, scope); // TODO: This feels jank, can it be done better? let request = hyper::Request::get(token_uri) .header("Metadata-Flavor", "Google") .body(hyper::Body::from(String::new())) // why body is needed? diff --git a/src/authenticator.rs b/src/authenticator.rs index ec2dc2b..2564650 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -274,14 +274,7 @@ impl ServiceAccountAuthenticator { /// ``` pub struct ApplicationDefaultCredentialsAuthenticator; impl ApplicationDefaultCredentialsAuthenticator { - /// Use modified builder pattern to create an Authenticator that uses GCE instance metadata server - /// to provide tokens. - pub fn from_instance_metadata() -> ApplicationDefaultCredentialsFlowOpts { - ApplicationDefaultCredentialsFlowOpts {} - } - - /// Use modified builder pattern to create an Authenticator that pulls default application credentials - /// service account file name from os environment variable. + /// Try to build ServiceAccountFlowOpts from the environment pub async fn from_environment() -> Result { let service_account_key = crate::read_service_account_key(std::env::var("GOOGLE_APPLICATION_CREDENTIALS")?) @@ -296,12 +289,17 @@ impl ApplicationDefaultCredentialsAuthenticator { /// Use the builder pattern to deduce which model of authenticator should be used: /// Service account one or GCE instance metadata kind - pub async fn builder() -> ApplicationDefaultCredentialsTypes { - Self::with_client(DefaultHyperClient).await + pub async fn builder( + opts: ApplicationDefaultCredentialsFlowOpts, + ) -> ApplicationDefaultCredentialsTypes { + Self::with_client(DefaultHyperClient, opts).await } /// Use the builder pattern to deduce which model of authenticator should be used and allow providing a hyper client - pub async fn with_client(client: C) -> ApplicationDefaultCredentialsTypes + pub async fn with_client( + client: C, + opts: ApplicationDefaultCredentialsFlowOpts, + ) -> ApplicationDefaultCredentialsTypes where C: HyperClientBuilder, { @@ -311,12 +309,9 @@ impl ApplicationDefaultCredentialsAuthenticator { ApplicationDefaultCredentialsTypes::ServiceAccount(builder) } - Err(_) => { - ApplicationDefaultCredentialsTypes::InstanceMetadata(AuthenticatorBuilder::new( - ApplicationDefaultCredentialsAuthenticator::from_instance_metadata(), - client, - )) - } + Err(_) => ApplicationDefaultCredentialsTypes::InstanceMetadata( + AuthenticatorBuilder::new(opts, client), + ), } } } diff --git a/src/lib.rs b/src/lib.rs index a82f6ff..f4c298a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,6 +97,7 @@ pub use crate::authenticator::{ pub use crate::helper::*; pub use crate::installed::InstalledFlowReturnMethod; +pub use crate::application_default_credentials::ApplicationDefaultCredentialsFlowOpts; pub use crate::service_account::ServiceAccountKey; #[doc(inline)] diff --git a/tests/tests.rs b/tests/tests.rs index fab7e6e..0711bd7 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -2,9 +2,9 @@ use yup_oauth2::{ authenticator::{DefaultAuthenticator, DefaultHyperClient, HyperClientBuilder}, authenticator_delegate::{DeviceAuthResponse, DeviceFlowDelegate, InstalledFlowDelegate}, error::{AuthError, AuthErrorCode}, - ApplicationDefaultCredentialsAuthenticator, ApplicationSecret, DeviceFlowAuthenticator, Error, - InstalledFlowAuthenticator, InstalledFlowReturnMethod, ServiceAccountAuthenticator, - ServiceAccountKey, + ApplicationDefaultCredentialsAuthenticator, ApplicationDefaultCredentialsFlowOpts, + ApplicationSecret, DeviceFlowAuthenticator, Error, InstalledFlowAuthenticator, + InstalledFlowReturnMethod, ServiceAccountAuthenticator, ServiceAccountKey, }; use std::future::Future; @@ -619,7 +619,11 @@ async fn test_default_application_credentials_from_metadata_server() { "expires_in": 12345678, }))), ); - let authenticator = match ApplicationDefaultCredentialsAuthenticator::builder().await { + + let opts = ApplicationDefaultCredentialsFlowOpts { + metadata_url: Some(server.url("/token").to_string()), + }; + let authenticator = match ApplicationDefaultCredentialsAuthenticator::builder(opts).await { ApplicationDefaultCredentialsTypes::InstanceMetadata(auth) => auth.build().await.unwrap(), _ => panic!("We are not testing service account adc model"), }; @@ -627,5 +631,5 @@ async fn test_default_application_credentials_from_metadata_server() { .token(&["https://googleapis.com/some/scope"]) .await .unwrap(); - assert_ne!(token.as_str(), "accesstoken"); + assert_eq!(token.as_str(), "accesstoken"); } From cd821f575de094a195eeee491e4dbd4727d4d3db Mon Sep 17 00:00:00 2001 From: Lukas Winkler Date: Thu, 25 Nov 2021 19:45:08 +0100 Subject: [PATCH 6/9] Make doc tests pass --- src/authenticator.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/authenticator.rs b/src/authenticator.rs index 2564650..8b67d2c 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -257,10 +257,16 @@ impl ServiceAccountAuthenticator { } } +// TODO: Can those use statements be cleaned up? /// Create an authenticator that uses a application default credentials. /// ``` /// # async fn foo() { -/// let authenticator = match ApplicationDefaultCredentialsAuthenticator::builder().await { +/// use yup_oauth2::ApplicationDefaultCredentialsAuthenticator; +/// use yup_oauth2::ApplicationDefaultCredentialsFlowOpts; +/// use yup_oauth2::authenticator::ApplicationDefaultCredentialsTypes; +/// +/// let opts = ApplicationDefaultCredentialsFlowOpts::default(); +/// let authenticator = match ApplicationDefaultCredentialsAuthenticator::builder(opts).await { /// ApplicationDefaultCredentialsTypes::InstanceMetadata(auth) => auth /// .build() /// .await From 7e90b28cf208809b07f49ea4b2dcdbed3fc2b8e1 Mon Sep 17 00:00:00 2001 From: Lukas Winkler Date: Thu, 25 Nov 2021 19:54:12 +0100 Subject: [PATCH 7/9] Add myself as adc sample author --- examples/test-adc/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/test-adc/Cargo.toml b/examples/test-adc/Cargo.toml index a5a023e..e2fa1f0 100644 --- a/examples/test-adc/Cargo.toml +++ b/examples/test-adc/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "test-adc" version = "0.1.0" -authors = ["Antti Peltonen "] +authors = ["Antti Peltonen ", "Lukas Winkler "] edition = "2018" [dependencies] From c7793063e4368b1327bad0bbdf8e289e2d5801cc Mon Sep 17 00:00:00 2001 From: Lukas Winkler Date: Thu, 25 Nov 2021 20:01:48 +0100 Subject: [PATCH 8/9] Add feature gates where required --- src/authenticator.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/authenticator.rs b/src/authenticator.rs index 8b67d2c..5983995 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -260,10 +260,11 @@ impl ServiceAccountAuthenticator { // TODO: Can those use statements be cleaned up? /// Create an authenticator that uses a application default credentials. /// ``` +/// # #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] /// # async fn foo() { -/// use yup_oauth2::ApplicationDefaultCredentialsAuthenticator; -/// use yup_oauth2::ApplicationDefaultCredentialsFlowOpts; -/// use yup_oauth2::authenticator::ApplicationDefaultCredentialsTypes; +/// # use yup_oauth2::ApplicationDefaultCredentialsAuthenticator; +/// # use yup_oauth2::ApplicationDefaultCredentialsFlowOpts; +/// # use yup_oauth2::authenticator::ApplicationDefaultCredentialsTypes; /// /// let opts = ApplicationDefaultCredentialsFlowOpts::default(); /// let authenticator = match ApplicationDefaultCredentialsAuthenticator::builder(opts).await { @@ -295,6 +296,11 @@ impl ApplicationDefaultCredentialsAuthenticator { /// Use the builder pattern to deduce which model of authenticator should be used: /// Service account one or GCE instance metadata kind + #[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 { From 39c712dfab7ba037d207e9ee8dead792faa041e8 Mon Sep 17 00:00:00 2001 From: Lukas Winkler Date: Wed, 1 Dec 2021 13:47:29 +0100 Subject: [PATCH 9/9] Remove no longer applicatble TODO's --- src/application_default_credentials.rs | 2 +- src/authenticator.rs | 1 - tests/tests.rs | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/application_default_credentials.rs b/src/application_default_credentials.rs index 5caa05c..5b5f2f5 100644 --- a/src/application_default_credentials.rs +++ b/src/application_default_credentials.rs @@ -32,7 +32,7 @@ impl ApplicationDefaultCredentialsFlow { C: hyper::client::connect::Connect + Clone + Send + Sync + 'static, { let scope = crate::helper::join(scopes, ","); - let token_uri = format!("{}?scopes={}", self.metadata_url, scope); // TODO: This feels jank, can it be done better? + let token_uri = format!("{}?scopes={}", self.metadata_url, scope); let request = hyper::Request::get(token_uri) .header("Metadata-Flavor", "Google") .body(hyper::Body::from(String::new())) // why body is needed? diff --git a/src/authenticator.rs b/src/authenticator.rs index 5983995..cc9cc1c 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -257,7 +257,6 @@ impl ServiceAccountAuthenticator { } } -// TODO: Can those use statements be cleaned up? /// Create an authenticator that uses a application default credentials. /// ``` /// # #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] diff --git a/tests/tests.rs b/tests/tests.rs index 0711bd7..846e51e 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -604,7 +604,6 @@ async fn test_default_application_credentials_from_metadata_server() { let _ = env_logger::try_init(); let server = Server::run(); server.expect( - // TODO: this does not work. Expectation::matching(all_of![ request::method_path("GET", "/token"), request::query(url_decoded(all_of![contains((