-
Notifications
You must be signed in to change notification settings - Fork 94
impl(auth): id token adc flow should allow including email #3763
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
Merged
alvarowolfx
merged 11 commits into
googleapis:main
from
alvarowolfx:impl-auth-id-token-adc-with-email
Nov 14, 2025
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
a47dd56
impl(auth): id token adc flow should include email
alvarowolfx 0b4fe4c
fix: fmt all the things
alvarowolfx 240867b
feat: add option to ID Token credentials builder and update integrat…
alvarowolfx c988e86
fix: forgot to update unit tests
alvarowolfx 2e24a12
test: add unit test for include email
alvarowolfx 5f70517
fix: only allow to enable including emaiL
alvarowolfx 1b439b1
Merge branch 'main' into impl-auth-id-token-adc-with-email
alvarowolfx d5123c7
fix: update with new enum and include_email method
alvarowolfx 3c3aeef
test: internal changes to better test passing include_email
alvarowolfx 6b46c45
docs: comment on the adc_impersonated test
alvarowolfx 39abba2
fix: fmt all the things
alvarowolfx File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -180,6 +180,7 @@ pub(crate) mod dynamic { | |
| /// [AIP-4110]: https://google.aip.dev/auth/4110 | ||
| pub struct Builder { | ||
| target_audience: String, | ||
| include_email: bool, | ||
| } | ||
|
|
||
| impl Builder { | ||
|
|
@@ -193,9 +194,20 @@ impl Builder { | |
| pub fn new<S: Into<String>>(target_audience: S) -> Self { | ||
| Self { | ||
| target_audience: target_audience.into(), | ||
| include_email: false, | ||
| } | ||
| } | ||
|
|
||
| /// Sets whether the ID token should include the `email` claim of the user in the token. | ||
| /// | ||
| /// For some credentials sources like Metadata Server and Impersonated Credentials, the default is | ||
| /// to not include the `email` claim. For other sources, they always include it. | ||
| /// This option is only relevant for credentials sources that do not include the `email` claim by default. | ||
| pub fn with_include_email(mut self) -> Self { | ||
| self.include_email = true; | ||
| self | ||
| } | ||
|
|
||
| /// Returns a [IDTokenCredentials] instance with the configured settings. | ||
| /// | ||
| /// # Errors | ||
|
|
@@ -214,30 +226,62 @@ impl Builder { | |
| AdcContents::FallbackToMds => None, | ||
| }; | ||
|
|
||
| build_id_token_credentials(self.target_audience, json_data) | ||
| build_id_token_credentials(self.target_audience, self.include_email, json_data) | ||
| } | ||
| } | ||
| enum IDTokenBuilder { | ||
| Mds(mds::Builder), | ||
| ServiceAccount(service_account::Builder), | ||
| Impersonated(impersonated::Builder), | ||
| } | ||
|
|
||
| fn build_id_token_credentials( | ||
| audience: String, | ||
| include_email: bool, | ||
| json: Option<Value>, | ||
| ) -> BuildResult<IDTokenCredentials> { | ||
| let builder = build_id_token_credentials_internal(audience, include_email, json)?; | ||
| match builder { | ||
| IDTokenBuilder::Mds(builder) => builder.build(), | ||
| IDTokenBuilder::ServiceAccount(builder) => builder.build(), | ||
| IDTokenBuilder::Impersonated(builder) => builder.build(), | ||
| } | ||
| } | ||
|
|
||
| fn build_id_token_credentials_internal( | ||
| audience: String, | ||
| include_email: bool, | ||
| json: Option<Value>, | ||
| ) -> BuildResult<IDTokenBuilder> { | ||
| match json { | ||
| None => { | ||
| // TODO(#3587): pass context that is being built from ADC flow. | ||
| mds::Builder::new(audience) | ||
| .with_format(mds::Format::Full) | ||
| .build() | ||
| let format = if include_email { | ||
| mds::Format::Full | ||
| } else { | ||
| mds::Format::Standard | ||
| }; | ||
| Ok(IDTokenBuilder::Mds( | ||
| mds::Builder::new(audience).with_format(format), | ||
| )) | ||
| } | ||
| Some(json) => { | ||
| let cred_type = extract_credential_type(&json)?; | ||
| match cred_type { | ||
| "authorized_user" => Err(BuilderError::not_supported(format!( | ||
| "{cred_type}, use idtoken::user_account::Builder directly." | ||
| ))), | ||
| "service_account" => service_account::Builder::new(audience, json).build(), | ||
| "service_account" => Ok(IDTokenBuilder::ServiceAccount( | ||
| service_account::Builder::new(audience, json), | ||
| )), | ||
| "impersonated_service_account" => { | ||
| impersonated::Builder::new(audience, json).build() | ||
| let builder = impersonated::Builder::new(audience, json); | ||
| let builder = if include_email { | ||
| builder.with_include_email() | ||
| } else { | ||
| builder | ||
| }; | ||
| Ok(IDTokenBuilder::Impersonated(builder)) | ||
| } | ||
| "external_account" => { | ||
alvarowolfx marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // never gonna be supported for id tokens | ||
|
|
@@ -289,7 +333,9 @@ fn instant_from_epoch_seconds(secs: u64, now: SystemTime) -> Option<Instant> { | |
| pub(crate) mod tests { | ||
| use super::*; | ||
| use jsonwebtoken::{Algorithm, EncodingKey, Header}; | ||
| use mds::Format; | ||
| use rsa::pkcs1::EncodeRsaPrivateKey; | ||
| use serde_json::json; | ||
| use serial_test::parallel; | ||
| use std::collections::HashMap; | ||
| use std::time::{Duration, SystemTime, UNIX_EPOCH}; | ||
|
|
@@ -380,7 +426,7 @@ pub(crate) mod tests { | |
| "refresh_token": "test_refresh_token", | ||
| }); | ||
|
|
||
| let result = build_id_token_credentials(audience, Some(json)); | ||
| let result = build_id_token_credentials(audience, false, Some(json)); | ||
| assert!(result.is_err()); | ||
| let err = result.unwrap_err(); | ||
| assert!(err.is_not_supported()); | ||
|
|
@@ -408,7 +454,7 @@ pub(crate) mod tests { | |
| } | ||
| }); | ||
|
|
||
| let result = build_id_token_credentials(audience, Some(json)); | ||
| let result = build_id_token_credentials(audience, false, Some(json)); | ||
| assert!(result.is_err()); | ||
| let err = result.unwrap_err(); | ||
| assert!(err.is_not_supported()); | ||
|
|
@@ -424,11 +470,72 @@ pub(crate) mod tests { | |
| "type": "unknown_credential_type", | ||
| }); | ||
|
|
||
| let result = build_id_token_credentials(audience, Some(json)); | ||
| let result = build_id_token_credentials(audience, false, Some(json)); | ||
| assert!(result.is_err()); | ||
| let err = result.unwrap_err(); | ||
| assert!(err.is_unknown_type()); | ||
| assert!(err.to_string().contains("unknown_credential_type")); | ||
| Ok(()) | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| #[parallel] | ||
| async fn test_build_id_token_include_email_mds() -> TestResult { | ||
| let audience = "test_audience".to_string(); | ||
|
|
||
| // Test with include_email = true and no source credentials (MDS Fallback) | ||
| let creds = build_id_token_credentials_internal(audience.clone(), true, None)?; | ||
| assert!(matches!(creds, IDTokenBuilder::Mds(_))); | ||
| if let IDTokenBuilder::Mds(builder) = creds { | ||
| assert!(matches!(builder.format, Some(Format::Full))); | ||
| } | ||
|
|
||
| // Test with include_email = false and no source credentials (MDS Fallback) | ||
| let creds = build_id_token_credentials_internal(audience.clone(), false, None)?; | ||
| assert!(matches!(creds, IDTokenBuilder::Mds(_))); | ||
| if let IDTokenBuilder::Mds(builder) = creds { | ||
| assert!(matches!(builder.format, Some(Format::Standard))); | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| #[parallel] | ||
| async fn test_build_id_token_include_email_impersonated() -> TestResult { | ||
| let audience = "test_audience".to_string(); | ||
| let json = json!({ | ||
| "type": "impersonated_service_account", | ||
| "source_credentials": { | ||
| "type": "service_account", | ||
| "project_id": "test-project", | ||
| "private_key_id": "test-key-id", | ||
| "private_key": "-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----", | ||
| "client_email": "[email protected]", | ||
| "client_id": "test-client-id", | ||
| "auth_uri": "https://accounts.google.com/o/oauth2/auth", | ||
| "token_uri": "https://oauth2.googleapis.com/token", | ||
| "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", | ||
| "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/source%40test-project.iam.gserviceaccount.com" | ||
| }, | ||
| "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/[email protected]:generateIdToken" | ||
| }); | ||
|
|
||
| // Test with include_email = true and impersonated source credentials | ||
| let creds = | ||
| build_id_token_credentials_internal(audience.clone(), true, Some(json.clone()))?; | ||
| assert!(matches!(creds, IDTokenBuilder::Impersonated(_))); | ||
| if let IDTokenBuilder::Impersonated(builder) = creds { | ||
| assert_eq!(builder.include_email, Some(true)); | ||
| } | ||
|
|
||
| // Test with include_email = false and impersonated source credentials | ||
| let creds = build_id_token_credentials_internal(audience.clone(), false, Some(json))?; | ||
| assert!(matches!(creds, IDTokenBuilder::Impersonated(_))); | ||
| if let IDTokenBuilder::Impersonated(builder) = creds { | ||
| assert_eq!(builder.include_email, None); | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.