mirror of
https://github.com/OMGeeky/yup-oauth2.git
synced 2025-12-26 16:27:25 +01:00
Merge pull request #164 from ingwinlu/gcp_instance_metadata
(GCP) Default Application Credentials flow/support (rebased and make tests pass)
This commit is contained in:
@@ -53,7 +53,7 @@ webbrowser = "0.5"
|
||||
hyper-rustls = "0.22.1"
|
||||
|
||||
[workspace]
|
||||
members = ["examples/test-installed/", "examples/test-svc-acct/", "examples/test-device/", "examples/service_account", "examples/drive_example"]
|
||||
members = ["examples/test-installed/", "examples/test-svc-acct/", "examples/test-device/", "examples/service_account", "examples/drive_example", "examples/test-adc"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
9
examples/test-adc/Cargo.toml
Normal file
9
examples/test-adc/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "test-adc"
|
||||
version = "0.1.0"
|
||||
authors = ["Antti Peltonen <antti.peltonen@iki.fi>", "Lukas Winkler <derwinlu@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
yup-oauth2 = { path = "../../" }
|
||||
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
|
||||
22
examples/test-adc/src/main.rs
Normal file
22
examples/test-adc/src/main.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use yup_oauth2::authenticator::ApplicationDefaultCredentialsTypes;
|
||||
use yup_oauth2::ApplicationDefaultCredentialsAuthenticator;
|
||||
use yup_oauth2::ApplicationDefaultCredentialsFlowOpts;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let opts = ApplicationDefaultCredentialsFlowOpts::default();
|
||||
let auth = match ApplicationDefaultCredentialsAuthenticator::builder(opts).await {
|
||||
ApplicationDefaultCredentialsTypes::InstanceMetadata(auth) => auth
|
||||
.build()
|
||||
.await
|
||||
.expect("Unable to create instance metadata authenticator"),
|
||||
ApplicationDefaultCredentialsTypes::ServiceAccount(auth) => auth
|
||||
.build()
|
||||
.await
|
||||
.expect("Unable to create service account authenticator"),
|
||||
};
|
||||
let scopes = &["https://www.googleapis.com/auth/pubsub"];
|
||||
|
||||
let tok = auth.token(scopes).await.unwrap();
|
||||
println!("token is: {:?}", tok);
|
||||
}
|
||||
48
src/application_default_credentials.rs
Normal file
48
src/application_default_credentials.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use crate::error::Error;
|
||||
use crate::types::TokenInfo;
|
||||
|
||||
/// Provide options for the Application Default Credential Flow, mostly used for testing
|
||||
pub struct ApplicationDefaultCredentialsFlowOpts {
|
||||
/// Used as base to build the url during token request from GCP metadata server
|
||||
pub metadata_url: Option<String>,
|
||||
}
|
||||
impl Default for ApplicationDefaultCredentialsFlowOpts {
|
||||
fn default() -> Self {
|
||||
Self { metadata_url: None }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ApplicationDefaultCredentialsFlow {
|
||||
metadata_url: String,
|
||||
}
|
||||
|
||||
impl ApplicationDefaultCredentialsFlow {
|
||||
pub(crate) fn new(opts: ApplicationDefaultCredentialsFlowOpts) -> Self {
|
||||
let metadata_url = opts.metadata_url.unwrap_or_else(|| "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token".to_string());
|
||||
ApplicationDefaultCredentialsFlow { metadata_url }
|
||||
}
|
||||
|
||||
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 scope = crate::helper::join(scopes, ",");
|
||||
let token_uri = format!("{}?scopes={}", self.metadata_url, scope);
|
||||
let request = hyper::Request::get(token_uri)
|
||||
.header("Metadata-Flavor", "Google")
|
||||
.body(hyper::Body::from(String::new())) // why body is needed?
|
||||
.unwrap();
|
||||
log::debug!("requesting token from metadata server: {:?}", 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)
|
||||
}
|
||||
}
|
||||
|
||||
// eof
|
||||
@@ -1,4 +1,7 @@
|
||||
//! Module contianing the core functionality for OAuth2 Authentication.
|
||||
use crate::application_default_credentials::{
|
||||
ApplicationDefaultCredentialsFlow, ApplicationDefaultCredentialsFlowOpts,
|
||||
};
|
||||
use crate::authenticator_delegate::{DeviceFlowDelegate, InstalledFlowDelegate};
|
||||
use crate::device::DeviceFlow;
|
||||
use crate::error::Error;
|
||||
@@ -254,6 +257,86 @@ impl ServiceAccountAuthenticator {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an authenticator that uses a application default credentials.
|
||||
/// ```
|
||||
/// # #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
|
||||
/// # async fn foo() {
|
||||
/// # use yup_oauth2::ApplicationDefaultCredentialsAuthenticator;
|
||||
/// # use yup_oauth2::ApplicationDefaultCredentialsFlowOpts;
|
||||
/// # use yup_oauth2::authenticator::ApplicationDefaultCredentialsTypes;
|
||||
///
|
||||
/// let opts = ApplicationDefaultCredentialsFlowOpts::default();
|
||||
/// let authenticator = match ApplicationDefaultCredentialsAuthenticator::builder(opts).await {
|
||||
/// ApplicationDefaultCredentialsTypes::InstanceMetadata(auth) => auth
|
||||
/// .build()
|
||||
/// .await
|
||||
/// .expect("Unable to create instance metadata authenticator"),
|
||||
/// ApplicationDefaultCredentialsTypes::ServiceAccount(auth) => auth
|
||||
/// .build()
|
||||
/// .await
|
||||
/// .expect("Unable to create service account authenticator"),
|
||||
/// };
|
||||
/// # }
|
||||
/// ```
|
||||
pub struct ApplicationDefaultCredentialsAuthenticator;
|
||||
impl ApplicationDefaultCredentialsAuthenticator {
|
||||
/// Try to build ServiceAccountFlowOpts from the environment
|
||||
pub async fn from_environment() -> Result<ServiceAccountFlowOpts, std::env::VarError> {
|
||||
let service_account_key =
|
||||
crate::read_service_account_key(std::env::var("GOOGLE_APPLICATION_CREDENTIALS")?)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Ok(ServiceAccountFlowOpts {
|
||||
key: service_account_key,
|
||||
subject: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Use the builder pattern to deduce which model of authenticator should be used:
|
||||
/// Service account one or GCE instance metadata kind
|
||||
#[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
|
||||
#[cfg_attr(
|
||||
yup_oauth2_docsrs,
|
||||
doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls")))
|
||||
)]
|
||||
pub async fn builder(
|
||||
opts: ApplicationDefaultCredentialsFlowOpts,
|
||||
) -> ApplicationDefaultCredentialsTypes<DefaultHyperClient> {
|
||||
Self::with_client(DefaultHyperClient, opts).await
|
||||
}
|
||||
|
||||
/// Use the builder pattern to deduce which model of authenticator should be used and allow providing a hyper client
|
||||
pub async fn with_client<C>(
|
||||
client: C,
|
||||
opts: ApplicationDefaultCredentialsFlowOpts,
|
||||
) -> ApplicationDefaultCredentialsTypes<C>
|
||||
where
|
||||
C: HyperClientBuilder,
|
||||
{
|
||||
match ApplicationDefaultCredentialsAuthenticator::from_environment().await {
|
||||
Ok(flow_opts) => {
|
||||
let builder = AuthenticatorBuilder::new(flow_opts, client);
|
||||
|
||||
ApplicationDefaultCredentialsTypes::ServiceAccount(builder)
|
||||
}
|
||||
Err(_) => ApplicationDefaultCredentialsTypes::InstanceMetadata(
|
||||
AuthenticatorBuilder::new(opts, client),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Types of authenticators provided by ApplicationDefaultCredentialsAuthenticator
|
||||
pub enum ApplicationDefaultCredentialsTypes<C>
|
||||
where
|
||||
C: HyperClientBuilder,
|
||||
{
|
||||
/// Service account based authenticator signature
|
||||
ServiceAccount(AuthenticatorBuilder<C, ServiceAccountFlowOpts>),
|
||||
/// GCE Instance Metadata based authenticator signature
|
||||
InstanceMetadata(AuthenticatorBuilder<C, ApplicationDefaultCredentialsFlowOpts>),
|
||||
}
|
||||
|
||||
/// ## Methods available when building any Authenticator.
|
||||
/// ```
|
||||
/// # #[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
|
||||
@@ -479,7 +562,25 @@ impl<C> AuthenticatorBuilder<C, ServiceAccountFlowOpts> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> AuthenticatorBuilder<C, ApplicationDefaultCredentialsFlowOpts> {
|
||||
/// Create the authenticator.
|
||||
pub async fn build(self) -> io::Result<Authenticator<C::Connector>>
|
||||
where
|
||||
C: HyperClientBuilder,
|
||||
{
|
||||
let application_default_credential_flow =
|
||||
ApplicationDefaultCredentialsFlow::new(self.auth_flow);
|
||||
Self::common_build(
|
||||
self.hyper_client_builder,
|
||||
self.storage_type,
|
||||
AuthFlow::ApplicationDefaultCredentialsFlow(application_default_credential_flow),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
mod private {
|
||||
use crate::application_default_credentials::ApplicationDefaultCredentialsFlow;
|
||||
use crate::device::DeviceFlow;
|
||||
use crate::error::Error;
|
||||
use crate::installed::InstalledFlow;
|
||||
@@ -490,6 +591,7 @@ mod private {
|
||||
DeviceFlow(DeviceFlow),
|
||||
InstalledFlow(InstalledFlow),
|
||||
ServiceAccountFlow(ServiceAccountFlow),
|
||||
ApplicationDefaultCredentialsFlow(ApplicationDefaultCredentialsFlow),
|
||||
}
|
||||
|
||||
impl AuthFlow {
|
||||
@@ -498,6 +600,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::ApplicationDefaultCredentialsFlow(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -518,6 +621,9 @@ mod private {
|
||||
AuthFlow::ServiceAccountFlow(service_account_flow) => {
|
||||
service_account_flow.token(hyper_client, scopes).await
|
||||
}
|
||||
AuthFlow::ApplicationDefaultCredentialsFlow(service_account_flow) => {
|
||||
service_account_flow.token(hyper_client, scopes).await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
#![deny(missing_docs)]
|
||||
#![cfg_attr(yup_oauth2_docsrs, feature(doc_cfg))]
|
||||
|
||||
mod application_default_credentials;
|
||||
pub mod authenticator;
|
||||
pub mod authenticator_delegate;
|
||||
mod device;
|
||||
@@ -89,12 +90,14 @@ mod types;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use crate::authenticator::{
|
||||
DeviceFlowAuthenticator, InstalledFlowAuthenticator, ServiceAccountAuthenticator,
|
||||
ApplicationDefaultCredentialsAuthenticator, DeviceFlowAuthenticator,
|
||||
InstalledFlowAuthenticator, ServiceAccountAuthenticator,
|
||||
};
|
||||
|
||||
pub use crate::helper::*;
|
||||
pub use crate::installed::InstalledFlowReturnMethod;
|
||||
|
||||
pub use crate::application_default_credentials::ApplicationDefaultCredentialsFlowOpts;
|
||||
pub use crate::service_account::ServiceAccountKey;
|
||||
|
||||
#[doc(inline)]
|
||||
|
||||
@@ -2,6 +2,7 @@ use yup_oauth2::{
|
||||
authenticator::{DefaultAuthenticator, DefaultHyperClient, HyperClientBuilder},
|
||||
authenticator_delegate::{DeviceAuthResponse, DeviceFlowDelegate, InstalledFlowDelegate},
|
||||
error::{AuthError, AuthErrorCode},
|
||||
ApplicationDefaultCredentialsAuthenticator, ApplicationDefaultCredentialsFlowOpts,
|
||||
ApplicationSecret, DeviceFlowAuthenticator, Error, InstalledFlowAuthenticator,
|
||||
InstalledFlowReturnMethod, ServiceAccountAuthenticator, ServiceAccountKey,
|
||||
};
|
||||
@@ -596,3 +597,38 @@ async fn test_disk_storage() {
|
||||
assert_eq!(token1.as_str(), "accesstoken");
|
||||
assert_eq!(token1, token2);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_default_application_credentials_from_metadata_server() {
|
||||
use yup_oauth2::authenticator::ApplicationDefaultCredentialsTypes;
|
||||
let _ = env_logger::try_init();
|
||||
let server = Server::run();
|
||||
server.expect(
|
||||
Expectation::matching(all_of![
|
||||
request::method_path("GET", "/token"),
|
||||
request::query(url_decoded(all_of![contains((
|
||||
"scopes",
|
||||
"https://googleapis.com/some/scope"
|
||||
))]))
|
||||
])
|
||||
.respond_with(json_encoded(serde_json::json!({
|
||||
"access_token": "accesstoken",
|
||||
"refresh_token": "refreshtoken",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 12345678,
|
||||
}))),
|
||||
);
|
||||
|
||||
let opts = ApplicationDefaultCredentialsFlowOpts {
|
||||
metadata_url: Some(server.url("/token").to_string()),
|
||||
};
|
||||
let authenticator = match ApplicationDefaultCredentialsAuthenticator::builder(opts).await {
|
||||
ApplicationDefaultCredentialsTypes::InstanceMetadata(auth) => auth.build().await.unwrap(),
|
||||
_ => panic!("We are not testing service account adc model"),
|
||||
};
|
||||
let token = authenticator
|
||||
.token(&["https://googleapis.com/some/scope"])
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(token.as_str(), "accesstoken");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user