diff --git a/src/authenticator.rs b/src/authenticator.rs index 68676b3..6092ded 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,40 @@ 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 +638,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 +670,7 @@ mod private { #[cfg(feature = "service_account")] ServiceAccountFlow(ServiceAccountFlow), ApplicationDefaultCredentialsFlow(ApplicationDefaultCredentialsFlow), + AuthorizedUserFlow(AuthorizedUserFlow), } impl AuthFlow { @@ -628,6 +681,7 @@ mod private { #[cfg(feature = "service_account")] AuthFlow::ServiceAccountFlow(_) => None, AuthFlow::ApplicationDefaultCredentialsFlow(_) => None, + AuthFlow::AuthorizedUserFlow(_) => None, } } @@ -652,6 +706,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..c9de258 --- /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) + } +} diff --git a/src/helper.rs b/src/helper.rs index 6972c07..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. +use crate::authorized_user::AuthorizedUserSecret; +use crate::types::{ApplicationSecret, ConsoleApplicationSecret}; + #[cfg(feature = "service_account")] use crate::service_account::ServiceAccountKey; -use crate::types::{ApplicationSecret, ConsoleApplicationSecret}; use std::io; use std::path::Path; @@ -57,6 +59,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..c6c0997 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; @@ -90,13 +91,13 @@ pub mod storage; mod types; -#[doc(inline)] -pub use crate::authenticator::{ - ApplicationDefaultCredentialsAuthenticator, DeviceFlowAuthenticator, - InstalledFlowAuthenticator -}; #[cfg(feature = "service_account")] pub use crate::authenticator::ServiceAccountAuthenticator; +#[doc(inline)] +pub use crate::authenticator::{ + ApplicationDefaultCredentialsAuthenticator, AuthorizedUserAuthenticator, + DeviceFlowAuthenticator, InstalledFlowAuthenticator, +}; pub use crate::helper::*; pub use crate::installed::InstalledFlowReturnMethod;