Merge pull request #170 from FEC-bendingspoons/master

Add authorized user authenticator
This commit is contained in:
Lewin Bormann
2022-03-22 18:18:08 +01:00
committed by GitHub
4 changed files with 151 additions and 6 deletions

View File

@@ -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<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"))]
@@ -603,8 +638,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;
@@ -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
}
}
}
}

69
src/authorized_user.rs Normal file
View File

@@ -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)
}
}

View File

@@ -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<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>,

View File

@@ -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;