From b4c5ef852756b053f8b5648932692131a9305a9b Mon Sep 17 00:00:00 2001 From: Federico Cergol Date: Sat, 5 Mar 2022 16:31:12 +0100 Subject: [PATCH 1/2] feat: add authorized user authenticator --- src/authenticator.rs | 52 +++++++++++++++++++++++++++++++ src/authorized_user.rs | 69 ++++++++++++++++++++++++++++++++++++++++++ src/helper.rs | 18 ++++++++++- src/lib.rs | 5 +-- 4 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 src/authorized_user.rs diff --git a/src/authenticator.rs b/src/authenticator.rs index 68676b3..bfba7c9 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -3,6 +3,7 @@ use crate::application_default_credentials::{ ApplicationDefaultCredentialsFlow, ApplicationDefaultCredentialsFlowOpts, }; use crate::authenticator_delegate::{DeviceFlowDelegate, InstalledFlowDelegate}; +use crate::authorized_user::{AuthorizedUserFlow, AuthorizedUserSecret}; use crate::device::DeviceFlow; use crate::error::Error; use crate::installed::{InstalledFlow, InstalledFlowReturnMethod}; @@ -360,6 +361,35 @@ where InstanceMetadata(AuthenticatorBuilder), } +/// Create an authenticator that uses an authorized user credentials. +/// ``` +/// # async fn foo() { +/// # use yup_oauth2::authenticator::AuthorizedUserAuthenticator; +/// # let secret = yup_oauth2::read_authorized_user_secret("/tmp/foo").await.unwrap(); +/// let authenticator = yup_oauth2::AuthorizedUserAuthenticator::builder(secret) +/// .build() +/// .await +/// .expect("failed to create authenticator"); +/// # } +/// ``` +pub struct AuthorizedUserAuthenticator; +impl AuthorizedUserAuthenticator { + /// Use the builder pattern to create an Authenticator that uses an authorized user. + pub fn builder( + authorized_user_secret: AuthorizedUserSecret, + ) -> AuthenticatorBuilder { + Self::with_client(authorized_user_secret, DefaultHyperClient) + } + + /// Construct a new Authenticator that uses the installed flow and the provided http client. + pub fn with_client( + authorized_user_secret: AuthorizedUserSecret, + client: C, + ) -> AuthenticatorBuilder { + AuthenticatorBuilder::new(AuthorizedUserFlow { secret: authorized_user_secret }, client) + } +} + /// ## Methods available when building any Authenticator. /// ``` /// # #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))] @@ -603,8 +633,25 @@ impl AuthenticatorBuilder { } } +/// ## Methods available when building an authorized user flow Authenticator. +impl AuthenticatorBuilder { + /// Create the authenticator. + pub async fn build(self) -> io::Result> + where + C: HyperClientBuilder, + { + Self::common_build( + self.hyper_client_builder, + self.storage_type, + AuthFlow::AuthorizedUserFlow(self.auth_flow), + ) + .await + } +} + mod private { use crate::application_default_credentials::ApplicationDefaultCredentialsFlow; + use crate::authorized_user::AuthorizedUserFlow; use crate::device::DeviceFlow; use crate::error::Error; use crate::installed::InstalledFlow; @@ -618,6 +665,7 @@ mod private { #[cfg(feature = "service_account")] ServiceAccountFlow(ServiceAccountFlow), ApplicationDefaultCredentialsFlow(ApplicationDefaultCredentialsFlow), + AuthorizedUserFlow(AuthorizedUserFlow), } impl AuthFlow { @@ -628,6 +676,7 @@ mod private { #[cfg(feature = "service_account")] AuthFlow::ServiceAccountFlow(_) => None, AuthFlow::ApplicationDefaultCredentialsFlow(_) => None, + AuthFlow::AuthorizedUserFlow(_) => None, } } @@ -652,6 +701,9 @@ mod private { AuthFlow::ApplicationDefaultCredentialsFlow(service_account_flow) => { service_account_flow.token(hyper_client, scopes).await } + AuthFlow::AuthorizedUserFlow(authorized_user_flow) => { + authorized_user_flow.token(hyper_client, scopes).await + } } } } diff --git a/src/authorized_user.rs b/src/authorized_user.rs new file mode 100644 index 0000000..b0e5116 --- /dev/null +++ b/src/authorized_user.rs @@ -0,0 +1,69 @@ +//! This module provides a token source (`GetToken`) that obtains tokens using user credentials +//! for use by software (i.e., non-human actors) to get access to Google services. +//! +//! Resources: +//! - [gcloud auth application-default login](https://cloud.google.com/sdk/gcloud/reference/auth/application-default/login) +//! +use crate::error::Error; +use crate::types::TokenInfo; +use hyper::header; +use serde::{Deserialize, Serialize}; +use url::form_urlencoded; + +const TOKEN_URI: &'static str = "https://accounts.google.com/o/oauth2/token"; + +/// JSON schema of authorized user secret. You can obtain it by +/// running on the client: `gcloud auth application-default login`. +/// +/// You can use `helpers::read_authorized_user_secret()` to read a JSON file +/// into a `AuthorizedUserSecret`. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct AuthorizedUserSecret { + /// client_id + pub client_id: String, + /// client_secret + pub client_secret: String, + /// refresh_token + pub refresh_token: String, + #[serde(rename = "type")] + /// key_type + pub key_type: String, +} + +/// AuthorizedUserFlow can fetch oauth tokens using an authorized user secret. +pub struct AuthorizedUserFlow { + pub(crate) secret: AuthorizedUserSecret, +} + +impl AuthorizedUserFlow { + /// Send a request for a new Bearer token to the OAuth provider. + 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 req = form_urlencoded::Serializer::new(String::new()) + .extend_pairs(&[ + ("client_id", self.secret.client_id.as_str()), + ("client_secret", self.secret.client_secret.as_str()), + ("refresh_token", self.secret.refresh_token.as_str()), + ("grant_type", "refresh_token"), + ]) + .finish(); + + let request = hyper::Request::post(TOKEN_URI) + .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded") + .body(hyper::Body::from(req)) + .unwrap(); + + log::debug!("requesting token from authorized user: {:?}", 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) + } +} \ No newline at end of file diff --git a/src/helper.rs b/src/helper.rs index 6972c07..b826eaa 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -5,8 +5,8 @@ // // Refer to the project root for licensing information. #[cfg(feature = "service_account")] -use crate::service_account::ServiceAccountKey; use crate::types::{ApplicationSecret, ConsoleApplicationSecret}; +use crate::{authorized_user::AuthorizedUserSecret, service_account::ServiceAccountKey}; use std::io; use std::path::Path; @@ -57,6 +57,22 @@ pub fn parse_service_account_key>(key: S) -> io::Result>( + path: P, +) -> io::Result { + let key = tokio::fs::read(path).await?; + serde_json::from_slice(&key).map_err(|e| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("Bad authorized user secret: {}", e), + ) + }) +} + pub(crate) fn join(pieces: &[T], separator: &str) -> String where T: AsRef, diff --git a/src/lib.rs b/src/lib.rs index 722f17a..9feb87e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,6 +75,7 @@ mod application_default_credentials; pub mod authenticator; pub mod authenticator_delegate; +mod authorized_user; mod device; pub mod error; mod helper; @@ -92,8 +93,8 @@ mod types; #[doc(inline)] pub use crate::authenticator::{ - ApplicationDefaultCredentialsAuthenticator, DeviceFlowAuthenticator, - InstalledFlowAuthenticator + ApplicationDefaultCredentialsAuthenticator, AuthorizedUserAuthenticator, + DeviceFlowAuthenticator, InstalledFlowAuthenticator }; #[cfg(feature = "service_account")] pub use crate::authenticator::ServiceAccountAuthenticator; From e9cb1e43ebede74ebdeb481f74315d4d18d263a3 Mon Sep 17 00:00:00 2001 From: Federico Cergol Date: Wed, 9 Mar 2022 18:31:10 +0100 Subject: [PATCH 2/2] fix: service_account feature flag placement --- src/authenticator.rs | 7 ++++++- src/authorized_user.rs | 2 +- src/helper.rs | 6 ++++-- src/lib.rs | 6 +++--- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/authenticator.rs b/src/authenticator.rs index bfba7c9..6092ded 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -386,7 +386,12 @@ impl AuthorizedUserAuthenticator { authorized_user_secret: AuthorizedUserSecret, client: C, ) -> AuthenticatorBuilder { - AuthenticatorBuilder::new(AuthorizedUserFlow { secret: authorized_user_secret }, client) + AuthenticatorBuilder::new( + AuthorizedUserFlow { + secret: authorized_user_secret, + }, + client, + ) } } diff --git a/src/authorized_user.rs b/src/authorized_user.rs index b0e5116..c9de258 100644 --- a/src/authorized_user.rs +++ b/src/authorized_user.rs @@ -66,4 +66,4 @@ impl AuthorizedUserFlow { log::debug!("received response; head: {:?}, body: {:?}", head, body); TokenInfo::from_json(&body) } -} \ No newline at end of file +} diff --git a/src/helper.rs b/src/helper.rs index b826eaa..00efa4e 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -4,9 +4,11 @@ // Copyright (c) 2016 Google Inc (lewinb@google.com). // // Refer to the project root for licensing information. -#[cfg(feature = "service_account")] +use crate::authorized_user::AuthorizedUserSecret; use crate::types::{ApplicationSecret, ConsoleApplicationSecret}; -use crate::{authorized_user::AuthorizedUserSecret, service_account::ServiceAccountKey}; + +#[cfg(feature = "service_account")] +use crate::service_account::ServiceAccountKey; use std::io; use std::path::Path; diff --git a/src/lib.rs b/src/lib.rs index 9feb87e..c6c0997 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -91,13 +91,13 @@ pub mod storage; mod types; +#[cfg(feature = "service_account")] +pub use crate::authenticator::ServiceAccountAuthenticator; #[doc(inline)] pub use crate::authenticator::{ ApplicationDefaultCredentialsAuthenticator, AuthorizedUserAuthenticator, - DeviceFlowAuthenticator, InstalledFlowAuthenticator + DeviceFlowAuthenticator, InstalledFlowAuthenticator, }; -#[cfg(feature = "service_account")] -pub use crate::authenticator::ServiceAccountAuthenticator; pub use crate::helper::*; pub use crate::installed::InstalledFlowReturnMethod;