Skip to content
Open
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
6 changes: 6 additions & 0 deletions src/config_default_credentials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ impl TokenProvider for ConfigDefaultCredentials {
Ok(token)
}

async fn email(&self) -> Result<String, Error> {
let token = self.token(&[]).await?;
let info = self.client.token_info(&token).await?;
Ok(info.email)
}

async fn project_id(&self) -> Result<Arc<str>, Error> {
self.credentials
.quota_project_id
Expand Down
4 changes: 4 additions & 0 deletions src/custom_service_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ impl TokenProvider for CustomServiceAccount {
return Ok(token);
}

async fn email(&self) -> Result<String, Error> {
Ok(self.credentials.client_email.clone())
}

async fn project_id(&self) -> Result<Arc<str>, Error> {
match &self.credentials.project_id {
Some(pid) => Ok(pid.clone()),
Expand Down
4 changes: 4 additions & 0 deletions src/gcloud_authorized_user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ impl TokenProvider for GCloudAuthorizedUser {
Ok(token)
}

async fn email(&self) -> Result<String, Error> {
run(&["auth", "print-identity-token"])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this needs additional steps as it only returns the token but not the email.

}

async fn project_id(&self) -> Result<Arc<str>, Error> {
self.project_id
.clone()
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ pub trait TokenProvider: Send + Sync {
/// the current token (for the given scopes) has expired.
async fn token(&self, scopes: &[&str]) -> Result<Arc<Token>, Error>;

async fn email(&self) -> Result<String, Error>;

/// Get the project ID for the authentication context
async fn project_id(&self) -> Result<Arc<str>, Error>;
}
Expand Down
11 changes: 11 additions & 0 deletions src/metadata_service_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ impl TokenProvider for MetadataServiceAccount {
Ok(token)
}

async fn email(&self) -> Result<String, Error> {
let email = self
.client
.request(metadata_request(DEFAULT_SERVICE_ACCOUNT_EMAIL_URI))
.await?;

String::from_utf8(email.to_vec()).map_err(|_| Error::Str("invalid UTF-8 email"))
}

async fn project_id(&self) -> Result<Arc<str>, Error> {
Ok(self.project_id.clone())
}
Expand All @@ -97,3 +106,5 @@ const DEFAULT_PROJECT_ID_GCP_URI: &str =
"http://metadata.google.internal/computeMetadata/v1/project/project-id";
const DEFAULT_TOKEN_GCP_URI: &str =
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token";
const DEFAULT_SERVICE_ACCOUNT_EMAIL_URI: &str =
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email";
25 changes: 25 additions & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::{env, fmt};

use bytes::Buf;
use chrono::{DateTime, Utc};
use http::Method;
use http_body_util::{BodyExt, Full};
use hyper::body::Bytes;
use hyper::Request;
Expand Down Expand Up @@ -69,6 +70,25 @@ impl HttpClient {
.map_err(|err| Error::Json("failed to deserialize token from response", err))
}

pub(crate) async fn token_info(&self, token: &Token) -> Result<TokenInfo, Error> {
let req = Request::builder()
.method(Method::GET)
.uri(format!(
"https://oauth2.googleapis.com/tokeninfo?access_token={}",
token.as_str()
))
.body(Full::from(Bytes::new()))
.map_err(|err| Error::Other("failed to build HTTP request", Box::new(err)))?;

let body = self
.request(req)
.await
.map_err(|err| Error::Other("failed to fetch token info", Box::new(err)))?;

serde_json::from_slice(&body)
.map_err(|err| Error::Json("failed to deserialize token info from response", err))
}

pub(crate) async fn request(&self, req: Request<Full<Bytes>>) -> Result<Bytes, Error> {
debug!(url = ?req.uri(), "requesting token");
let (parts, body) = self
Expand Down Expand Up @@ -296,6 +316,11 @@ impl fmt::Debug for AuthorizedUserRefreshToken {
}
}

#[derive(Deserialize)]
pub(crate) struct TokenInfo {
pub(crate) email: String,
}

/// How many times to attempt to fetch a token from the set credentials token endpoint.
const RETRY_COUNT: u8 = 5;

Expand Down