Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add authorized user authenticator #170

Merged
merged 2 commits into from
Mar 22, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions src/authenticator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -360,6 +361,35 @@ where
InstanceMetadata(AuthenticatorBuilder<C, ApplicationDefaultCredentialsFlowOpts>),
}

/// 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<DefaultHyperClient, AuthorizedUserFlow> {
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<C>(
authorized_user_secret: AuthorizedUserSecret,
client: C,
) -> AuthenticatorBuilder<C, AuthorizedUserFlow> {
AuthenticatorBuilder::new(AuthorizedUserFlow { secret: authorized_user_secret }, client)
}
}

/// ## Methods available when building any Authenticator.
/// ```
/// # #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
Expand Down Expand Up @@ -603,8 +633,25 @@ impl<C> AuthenticatorBuilder<C, ApplicationDefaultCredentialsFlowOpts> {
}
}

/// ## Methods available when building an authorized user flow Authenticator.
impl<C> AuthenticatorBuilder<C, AuthorizedUserFlow> {
/// Create the authenticator.
pub async fn build(self) -> io::Result<Authenticator<C::Connector>>
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;
Expand All @@ -618,6 +665,7 @@ mod private {
#[cfg(feature = "service_account")]
ServiceAccountFlow(ServiceAccountFlow),
ApplicationDefaultCredentialsFlow(ApplicationDefaultCredentialsFlow),
AuthorizedUserFlow(AuthorizedUserFlow),
}

impl AuthFlow {
Expand All @@ -628,6 +676,7 @@ mod private {
#[cfg(feature = "service_account")]
AuthFlow::ServiceAccountFlow(_) => None,
AuthFlow::ApplicationDefaultCredentialsFlow(_) => None,
AuthFlow::AuthorizedUserFlow(_) => None,
}
}

Expand All @@ -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
}
}
}
}
Expand Down
69 changes: 69 additions & 0 deletions src/authorized_user.rs
Original file line number Diff line number Diff line change
@@ -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<C, T>(
&self,
hyper_client: &hyper::Client<C>,
_scopes: &[T],
) -> Result<TokenInfo, Error>
where
T: AsRef<str>,
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)
}
}
18 changes: 17 additions & 1 deletion src/helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
//
// Refer to the project root for licensing information.
#[cfg(feature = "service_account")]
use crate::service_account::ServiceAccountKey;
dermesser marked this conversation as resolved.
Show resolved Hide resolved
use crate::types::{ApplicationSecret, ConsoleApplicationSecret};
use crate::{authorized_user::AuthorizedUserSecret, service_account::ServiceAccountKey};

use std::io;
use std::path::Path;
Expand Down Expand Up @@ -57,6 +57,22 @@ pub fn parse_service_account_key<S: AsRef<[u8]>>(key: S) -> io::Result<ServiceAc
})
}

/// Read an authorized user secret from a JSON file. You can obtain it by running on the client:
/// `gcloud auth application-default login`.
/// The file should be on Windows in: `%APPDATA%/gcloud/application_default_credentials.json`
/// for other systems: `$HOME/.config/gcloud/application_default_credentials.json`.
pub async fn read_authorized_user_secret<P: AsRef<Path>>(
path: P,
) -> io::Result<AuthorizedUserSecret> {
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<T>(pieces: &[T], separator: &str) -> String
where
T: AsRef<str>,
Expand Down
5 changes: 3 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down