From 7638946508f087e95382a57faa6b00d937f6d8a8 Mon Sep 17 00:00:00 2001 From: Antti Peltonen Date: Wed, 9 Jun 2021 11:44:04 +0000 Subject: [PATCH] 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"); +}