diff --git a/src/authenticator.rs b/src/authenticator.rs index 74522130..7c4c8616 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -1,5 +1,6 @@ //! Module contianing the core functionality for OAuth2 Authentication. use crate::authenticator_delegate::{DeviceFlowDelegate, InstalledFlowDelegate}; +use crate::authorized_user::{AuthorizedUserFlow, AuthorizedUserFlowOpts, AuthorizedUserSecret}; use crate::device::DeviceFlow; use crate::error::Error; use crate::installed::{InstalledFlow, InstalledFlowReturnMethod}; @@ -210,6 +211,28 @@ impl ServiceAccountAuthenticator { } } +/// Create an authenticator that uses an authorized user credentials. +/// ``` +/// # async fn foo() { +/// # 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 { + AuthenticatorBuilder::::with_auth_flow(AuthorizedUserFlowOpts { + secret: authorized_user_secret, + }) + } +} + /// ## Methods available when building any Authenticator. /// ``` /// # async fn foo() { @@ -431,7 +454,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, + { + let authorized_user_auth_flow = AuthorizedUserFlow::new(self.auth_flow)?; + Self::common_build( + self.hyper_client_builder, + self.storage_type, + AuthFlow::AuthorizedUserFlow(authorized_user_auth_flow), + ) + .await + } +} + mod private { + use crate::authorized_user::AuthorizedUserFlow; use crate::device::DeviceFlow; use crate::error::Error; use crate::installed::InstalledFlow; @@ -442,6 +483,7 @@ mod private { DeviceFlow(DeviceFlow), InstalledFlow(InstalledFlow), ServiceAccountFlow(ServiceAccountFlow), + AuthorizedUserFlow(AuthorizedUserFlow), } impl AuthFlow { @@ -450,6 +492,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::AuthorizedUserFlow(_) => None, } } @@ -470,6 +513,9 @@ mod private { AuthFlow::ServiceAccountFlow(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 00000000..12a0affe --- /dev/null +++ b/src/authorized_user.rs @@ -0,0 +1,80 @@ +//! 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 std::io; +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, +} + +pub struct AuthorizedUserFlowOpts { + pub(crate) secret: AuthorizedUserSecret, +} + +/// AuthorizedUserFlow can fetch oauth tokens using an authorized user secret. +pub struct AuthorizedUserFlow { + secret: AuthorizedUserSecret, +} + +impl AuthorizedUserFlow { + pub(crate) fn new(opts: AuthorizedUserFlowOpts) -> Result { + Ok(AuthorizedUserFlow { + secret: opts.secret, + }) + } + + /// 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 b143db7f..1c99b817 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -4,8 +4,8 @@ // Copyright (c) 2016 Google Inc (lewinb@google.com). // // Refer to the project root for licensing information. -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; @@ -49,6 +49,22 @@ pub async fn read_service_account_key>(path: P) -> 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 284903e9..c8c4b55b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,6 +71,7 @@ #![deny(missing_docs)] pub mod authenticator; pub mod authenticator_delegate; +mod authorized_user; mod device; pub mod error; mod helper; @@ -86,12 +87,14 @@ mod types; #[doc(inline)] pub use crate::authenticator::{ - DeviceFlowAuthenticator, InstalledFlowAuthenticator, ServiceAccountAuthenticator, + AuthorizedUserAuthenticator, DeviceFlowAuthenticator, InstalledFlowAuthenticator, + ServiceAccountAuthenticator, }; pub use crate::helper::*; pub use crate::installed::InstalledFlowReturnMethod; +pub use crate::authorized_user::AuthorizedUserSecret; pub use crate::service_account::ServiceAccountKey; #[doc(inline)]