From d305b2fc00e7699d1d224f534bb1b6383989a999 Mon Sep 17 00:00:00 2001 From: Pranav Iyer Date: Sat, 23 Aug 2025 00:20:16 -0700 Subject: [PATCH 01/18] Initial Changes for trust boundary structure without TB enabled check and header value. # Conflicts: # oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java # oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java # oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java # Conflicts: # oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java # oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java # oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java # oauth2_http/java/com/google/auth/oauth2/OAuth2Credentials.java # oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java # oauth2_http/java/com/google/auth/oauth2/TrustBoundary.java --- .../oauth2/DefaultCredentialsProvider.java | 1 + .../auth/oauth2/TrustBoundaryCredentials.java | 46 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 oauth2_http/java/com/google/auth/oauth2/TrustBoundaryCredentials.java diff --git a/oauth2_http/java/com/google/auth/oauth2/DefaultCredentialsProvider.java b/oauth2_http/java/com/google/auth/oauth2/DefaultCredentialsProvider.java index acbfe28af..b501a8d86 100644 --- a/oauth2_http/java/com/google/auth/oauth2/DefaultCredentialsProvider.java +++ b/oauth2_http/java/com/google/auth/oauth2/DefaultCredentialsProvider.java @@ -115,6 +115,7 @@ class DefaultCredentialsProvider { */ final GoogleCredentials getDefaultCredentials(HttpTransportFactory transportFactory) throws IOException { + LOGGER.log(Level.FINE, "ajbhdabjh"); synchronized (this) { if (cachedCredentials == null) { cachedCredentials = getDefaultCredentialsUnsynchronized(transportFactory); diff --git a/oauth2_http/java/com/google/auth/oauth2/TrustBoundaryCredentials.java b/oauth2_http/java/com/google/auth/oauth2/TrustBoundaryCredentials.java new file mode 100644 index 000000000..99ed4c495 --- /dev/null +++ b/oauth2_http/java/com/google/auth/oauth2/TrustBoundaryCredentials.java @@ -0,0 +1,46 @@ +/* + * Copyright 2024, Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.auth.oauth2; + +import com.google.auth.http.HttpTransportFactory; + +/** + * An interface for credentials that support trust boundaries. This is an experimental feature. + */ +public interface TrustBoundaryCredentials { + + /** Returns whether trust boundary is enabled. */ + boolean isTrustBoundaryEnabled(); + + /** Returns the transport factory. */ + HttpTransportFactory getTransportFactory(); +} \ No newline at end of file From c17e26d2d18d0ed132b8bb2e868c73af766b428f Mon Sep 17 00:00:00 2001 From: Pranav Iyer Date: Mon, 25 Aug 2025 13:16:21 -0700 Subject: [PATCH 02/18] Added changes to authenticate TB endpoint, add correct TB headers. --- .../auth/oauth2/TrustBoundaryCredentials.java | 46 ------------------- 1 file changed, 46 deletions(-) delete mode 100644 oauth2_http/java/com/google/auth/oauth2/TrustBoundaryCredentials.java diff --git a/oauth2_http/java/com/google/auth/oauth2/TrustBoundaryCredentials.java b/oauth2_http/java/com/google/auth/oauth2/TrustBoundaryCredentials.java deleted file mode 100644 index 99ed4c495..000000000 --- a/oauth2_http/java/com/google/auth/oauth2/TrustBoundaryCredentials.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2024, Google LLC - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * - * * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.google.auth.oauth2; - -import com.google.auth.http.HttpTransportFactory; - -/** - * An interface for credentials that support trust boundaries. This is an experimental feature. - */ -public interface TrustBoundaryCredentials { - - /** Returns whether trust boundary is enabled. */ - boolean isTrustBoundaryEnabled(); - - /** Returns the transport factory. */ - HttpTransportFactory getTransportFactory(); -} \ No newline at end of file From 2b486bde66cbeff7793bdc0f32a6852e546b02a4 Mon Sep 17 00:00:00 2001 From: Pranav Iyer Date: Sun, 28 Sep 2025 15:17:33 -0700 Subject: [PATCH 03/18] Added unit tests for Trust Boundary for Service accounts. Updated. the trust boundary enabler env variable --- .../java/com/google/auth/oauth2/ServiceAccountCredentials.java | 2 ++ samples/snippets/pom.xml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java index 5307d4d6d..7110a0b5f 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java @@ -1162,6 +1162,7 @@ public static class Builder extends GoogleCredentials.Builder { private int lifetime = DEFAULT_LIFETIME_IN_SECONDS; private boolean useJwtAccessWithScope = false; private boolean defaultRetriesEnabled = true; + private TrustBoundary trustBoundary; protected Builder() {} @@ -1180,6 +1181,7 @@ protected Builder(ServiceAccountCredentials credentials) { this.lifetime = credentials.lifetime; this.useJwtAccessWithScope = credentials.useJwtAccessWithScope; this.defaultRetriesEnabled = credentials.defaultRetriesEnabled; + this.trustBoundary = credentials.getTrustBoundary(); } @CanIgnoreReturnValue diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index e725b2a83..4af02774e 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -43,7 +43,7 @@ com.google.auth google-auth-library-oauth2-http - 1.35.0 + 1.35.0 From 63f508bf4ed5979803e9732532e9e0af327a69d9 Mon Sep 17 00:00:00 2001 From: Pranav Iyer Date: Mon, 29 Sep 2025 14:29:16 -0700 Subject: [PATCH 04/18] Formatting changes --- .../src/main/java/ServiceAccount.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 samples/snippets/src/main/java/ServiceAccount.java diff --git a/samples/snippets/src/main/java/ServiceAccount.java b/samples/snippets/src/main/java/ServiceAccount.java new file mode 100644 index 000000000..5188f1735 --- /dev/null +++ b/samples/snippets/src/main/java/ServiceAccount.java @@ -0,0 +1,38 @@ +import java.util.Arrays; + +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpRequestFactory; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.JsonObjectParser; +import com.google.api.client.json.gson.GsonFactory; +import com.google.auth.http.HttpCredentialsAdapter; +import com.google.auth.oauth2.GoogleCredentials; + +public class ServiceAccount { +public static void main(String[] args) throws Exception { + // String bucketName = "byoid-test-gcs-bucket"; + String bucketName = "oidc-test"; + String url = "https://storage.googleapis.com/storage/v1/b/" + bucketName; + + GoogleCredentials googleCredentials = GoogleCredentials.getApplicationDefault(); + // Add scopes to the credentials. This will force the use of the OAuth2 access token flow + // instead of the self-signed JWT flow. + String[] scopes = new String[] { "https://www.googleapis.com/auth/cloud-platform" }; + + googleCredentials = googleCredentials.createScoped(Arrays.asList(scopes)); + System.out.println(googleCredentials.getClass().getSimpleName()); + + HttpCredentialsAdapter credentialsAdapter = new HttpCredentialsAdapter(googleCredentials); + HttpRequestFactory requestFactory = new NetHttpTransport().createRequestFactory(credentialsAdapter); + HttpRequest request = requestFactory.buildGetRequest(new GenericUrl(url)); + + JsonObjectParser parser = new JsonObjectParser(GsonFactory.getDefaultInstance()); + request.setParser(parser); + + HttpResponse response = request.execute(); + System.out.println(String.format("Success: %s", response.parseAsString())); +} + +} From 5bddb2fa3d5eb4be8ff25cbdd3710cb64dcf4991 Mon Sep 17 00:00:00 2001 From: Pranav Iyer Date: Mon, 29 Sep 2025 16:02:48 -0700 Subject: [PATCH 05/18] Trust Boundary Recovery --- .../main/java/{ServiceAccount.java => ServiceAccountCopy.java} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename samples/snippets/src/main/java/{ServiceAccount.java => ServiceAccountCopy.java} (100%) diff --git a/samples/snippets/src/main/java/ServiceAccount.java b/samples/snippets/src/main/java/ServiceAccountCopy.java similarity index 100% rename from samples/snippets/src/main/java/ServiceAccount.java rename to samples/snippets/src/main/java/ServiceAccountCopy.java From 9fb35474010fc10fd4290c0967ad815a67756f51 Mon Sep 17 00:00:00 2001 From: Pranav Iyer Date: Tue, 30 Sep 2025 12:15:49 -0700 Subject: [PATCH 06/18] Changed key for encodedLocations and changed tests. --- .../java/com/google/auth/oauth2/DefaultCredentialsProvider.java | 1 - 1 file changed, 1 deletion(-) diff --git a/oauth2_http/java/com/google/auth/oauth2/DefaultCredentialsProvider.java b/oauth2_http/java/com/google/auth/oauth2/DefaultCredentialsProvider.java index b501a8d86..acbfe28af 100644 --- a/oauth2_http/java/com/google/auth/oauth2/DefaultCredentialsProvider.java +++ b/oauth2_http/java/com/google/auth/oauth2/DefaultCredentialsProvider.java @@ -115,7 +115,6 @@ class DefaultCredentialsProvider { */ final GoogleCredentials getDefaultCredentials(HttpTransportFactory transportFactory) throws IOException { - LOGGER.log(Level.FINE, "ajbhdabjh"); synchronized (this) { if (cachedCredentials == null) { cachedCredentials = getDefaultCredentialsUnsynchronized(transportFactory); From dab6d3b81a3b88253463609887bee631ff6dfb37 Mon Sep 17 00:00:00 2001 From: Pranav Iyer Date: Tue, 30 Sep 2025 14:49:06 -0700 Subject: [PATCH 07/18] minor fixes --- samples/snippets/pom.xml | 2 +- .../src/main/java/ServiceAccountCopy.java | 38 ------------------- 2 files changed, 1 insertion(+), 39 deletions(-) delete mode 100644 samples/snippets/src/main/java/ServiceAccountCopy.java diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index 4af02774e..e725b2a83 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -43,7 +43,7 @@ com.google.auth google-auth-library-oauth2-http - 1.35.0 + 1.35.0 diff --git a/samples/snippets/src/main/java/ServiceAccountCopy.java b/samples/snippets/src/main/java/ServiceAccountCopy.java deleted file mode 100644 index 5188f1735..000000000 --- a/samples/snippets/src/main/java/ServiceAccountCopy.java +++ /dev/null @@ -1,38 +0,0 @@ -import java.util.Arrays; - -import com.google.api.client.http.GenericUrl; -import com.google.api.client.http.HttpRequest; -import com.google.api.client.http.HttpRequestFactory; -import com.google.api.client.http.HttpResponse; -import com.google.api.client.http.javanet.NetHttpTransport; -import com.google.api.client.json.JsonObjectParser; -import com.google.api.client.json.gson.GsonFactory; -import com.google.auth.http.HttpCredentialsAdapter; -import com.google.auth.oauth2.GoogleCredentials; - -public class ServiceAccount { -public static void main(String[] args) throws Exception { - // String bucketName = "byoid-test-gcs-bucket"; - String bucketName = "oidc-test"; - String url = "https://storage.googleapis.com/storage/v1/b/" + bucketName; - - GoogleCredentials googleCredentials = GoogleCredentials.getApplicationDefault(); - // Add scopes to the credentials. This will force the use of the OAuth2 access token flow - // instead of the self-signed JWT flow. - String[] scopes = new String[] { "https://www.googleapis.com/auth/cloud-platform" }; - - googleCredentials = googleCredentials.createScoped(Arrays.asList(scopes)); - System.out.println(googleCredentials.getClass().getSimpleName()); - - HttpCredentialsAdapter credentialsAdapter = new HttpCredentialsAdapter(googleCredentials); - HttpRequestFactory requestFactory = new NetHttpTransport().createRequestFactory(credentialsAdapter); - HttpRequest request = requestFactory.buildGetRequest(new GenericUrl(url)); - - JsonObjectParser parser = new JsonObjectParser(GsonFactory.getDefaultInstance()); - request.setParser(parser); - - HttpResponse response = request.execute(); - System.out.println(String.format("Success: %s", response.parseAsString())); -} - -} From d58147c72222814259e6a06514530a0b0624d316 Mon Sep 17 00:00:00 2001 From: Pranav Iyer Date: Thu, 2 Oct 2025 17:12:29 -0700 Subject: [PATCH 08/18] External Account Initial Changes. --- ...ernalAccountAuthorizedUserCredentials.java | 39 ++- .../oauth2/ExternalAccountCredentials.java | 54 +++- .../google/auth/oauth2/GoogleCredentials.java | 4 + .../auth/oauth2/AwsCredentialsTest.java | 102 +++++++ ...lAccountAuthorizedUserCredentialsTest.java | 96 +++++- .../ExternalAccountCredentialsTest.java | 279 +++++++++++++++++- .../oauth2/IdentityPoolCredentialsTest.java | 87 ++++++ .../oauth2/PluggableAuthCredentialsTest.java | 85 ++++++ 8 files changed, 724 insertions(+), 22 deletions(-) diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java index e67ddb89d..aaccce572 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java @@ -55,6 +55,8 @@ import java.util.Date; import java.util.Map; import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.annotation.Nullable; /** @@ -75,12 +77,19 @@ * } * */ -public class ExternalAccountAuthorizedUserCredentials extends GoogleCredentials { +public class ExternalAccountAuthorizedUserCredentials extends GoogleCredentials + implements TrustBoundaryProvider { private static final String PARSE_ERROR_PREFIX = "Error parsing token refresh response. "; private static final long serialVersionUID = -2181779590486283287L; + private static final String WORKFORCE_POOL_URL_FORMAT = + "https://iamcredentials.googleapis.com/v1/locations/global/workforcePools/%s/allowedLocations"; + private static final Pattern WORKFORCE_PATTERN = + Pattern.compile( + "^//iam.googleapis.com/locations/(?[^/]+)/workforcePools/(?[^/]+)/providers/(?[^/]+)$"); + private final String transportFactoryClassName; private final String audience; private final String tokenUrl; @@ -210,10 +219,30 @@ public AccessToken refreshAccessToken() throws IOException { this.refreshToken = refreshToken; } - return AccessToken.newBuilder() - .setExpirationTime(expiresAtMilliseconds) - .setTokenValue(accessToken) - .build(); + AccessToken newAccessToken = + AccessToken.newBuilder() + .setExpirationTime(expiresAtMilliseconds) + .setTokenValue(accessToken) + .build(); + + refreshTrustBoundaries(newAccessToken); + return newAccessToken; + } + + @Override + public String getTrustBoundaryUrl() throws IOException { + Matcher matcher = WORKFORCE_PATTERN.matcher(getAudience()); + if (!matcher.matches()) { + throw new IOException( + "The provided audience is not in the correct format for a workforce pool."); + } + String poolId = matcher.group("pool"); + return String.format(WORKFORCE_POOL_URL_FORMAT, poolId); + } + + @Override + public HttpTransportFactory getTransportFactory() { + return transportFactory; } @Nullable diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java index c4268d167..ebd9303aa 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java @@ -55,6 +55,7 @@ import java.util.Locale; import java.util.Map; import java.util.concurrent.Executor; +import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nullable; @@ -64,7 +65,8 @@ *

Handles initializing external credentials, calls to the Security Token Service, and service * account impersonation. */ -public abstract class ExternalAccountCredentials extends GoogleCredentials { +public abstract class ExternalAccountCredentials extends GoogleCredentials + implements TrustBoundaryProvider { private static final long serialVersionUID = 8049126194174465023L; @@ -98,6 +100,18 @@ public abstract class ExternalAccountCredentials extends GoogleCredentials { private EnvironmentProvider environmentProvider; + private static final String WORKFORCE_POOL_URL_FORMAT = + "https://iamcredentials.googleapis.com/v1/locations/global/workforcePools/%s/allowedLocations"; + private static final String WORKLOAD_POOL_URL_FORMAT = + "https://iamcredentials.googleapis.com/v1/projects/%s/locations/global/workloadIdentityPools/%s/allowedLocations"; + + private static final Pattern WORKFORCE_PATTERN = + Pattern.compile( + "^//iam.googleapis.com/locations/(?[^/]+)/workforcePools/(?[^/]+)/providers/(?[^/]+)$"); + private static final Pattern WORKLOAD_PATTERN = + Pattern.compile( + "^//iam.googleapis.com/projects/(?[^/]+)/locations/(?[^/]+)/workloadIdentityPools/(?[^/]+)/providers/(?[^/]+)$"); + /** * Constructor with minimum identifying information and custom HTTP transport. Does not support * workforce credentials. @@ -527,7 +541,11 @@ protected AccessToken exchangeExternalCredentialForAccessToken( this.impersonatedCredentials = this.buildImpersonatedCredentials(); } if (this.impersonatedCredentials != null) { - return this.impersonatedCredentials.refreshAccessToken(); + AccessToken accessToken = this.impersonatedCredentials.refreshAccessToken(); + // After the impersonated credential refreshes, its trust boundary is + // also refreshed. We need to get the refreshed trust boundary. + setTrustBoundary(this.impersonatedCredentials.getTrustBoundary()); + return accessToken; } StsRequestHandler.Builder requestHandler = @@ -556,7 +574,9 @@ protected AccessToken exchangeExternalCredentialForAccessToken( } StsTokenExchangeResponse response = requestHandler.build().exchangeToken(); - return response.getAccessToken(); + AccessToken accessToken = response.getAccessToken(); + refreshTrustBoundaries(accessToken); + return accessToken; } /** @@ -613,6 +633,34 @@ public String getServiceAccountEmail() { return ImpersonatedCredentials.extractTargetPrincipal(serviceAccountImpersonationUrl); } + // todo Add doc comment. + @Override + public String getTrustBoundaryUrl() throws IOException { + if (isWorkforcePoolConfiguration()) { + Matcher matcher = WORKFORCE_PATTERN.matcher(getAudience()); + if (!matcher.matches()) { + throw new IOException( + "The provided audience is not in the correct format for a workforce pool."); + } + String poolId = matcher.group("pool"); + return String.format(WORKFORCE_POOL_URL_FORMAT, poolId); + } else { + Matcher matcher = WORKLOAD_PATTERN.matcher(getAudience()); + if (!matcher.matches()) { + throw new IOException( + "The provided audience is not in the correct format for a workload identity pool."); + } + String projectNumber = matcher.group("project"); + String poolId = matcher.group("pool"); + return String.format(WORKLOAD_POOL_URL_FORMAT, projectNumber, poolId); + } + } + + @Override + public HttpTransportFactory getTransportFactory() { + return transportFactory; + } + @Nullable public String getClientId() { return clientId; diff --git a/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java b/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java index 59afc00d5..62ea3f693 100644 --- a/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java @@ -339,6 +339,10 @@ TrustBoundary getTrustBoundary() { return trustBoundary; } + protected void setTrustBoundary(TrustBoundary trustBoundary) { + this.trustBoundary = trustBoundary; + } + /** * Refreshes the trust boundary by making a call to the trust boundary URL. * diff --git a/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java index e8b401063..46813e65c 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java @@ -1399,4 +1399,106 @@ public AwsSecurityCredentials getCredentials(ExternalAccountSupplierContext cont return credentials; } } + + // @Test + // public void testRefresh_trustBoundarySuccess() throws IOException { + // TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + // TrustBoundary.setEnvironmentProviderForTest(environmentProvider); + // environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); + // + // MockHttpTransport mockHttpTransport = + // new MockHttpTransport.Builder() + // // AWS region call + // .setLowLevelHttpResponse(new MockLowLevelHttpResponse().setContent("us-east-1a")) + // // AWS IAM role name call + // .setLowLevelHttpResponse(new MockLowLevelHttpResponse().setContent("roleName")) + // // AWS credentials call + // .setLowLevelHttpResponse( + // new MockLowLevelHttpResponse() + // .setContent( + // + // "{\"Code\":\"Success\",\"AccessKeyId\":\"accessKeyId\",\"SecretAccessKey\":\"secretAccessKey\",\"Token\":\"token\"}")) + // // STS token call + // .setLowLevelHttpResponse( + // new MockLowLevelHttpResponse() + // .setContentType(Json.MEDIA_TYPE) + // .setContent( + // String.format( + // "{\"access_token\": \"%s\", \"expires_in\": %s, \"token_type\": + // \"Bearer\"}", + // "sts_access_token", 3600))) + // // Trust boundary call + // .setLowLevelHttpResponse( + // new MockLowLevelHttpResponse() + // .setContentType(Json.MEDIA_TYPE) + // .setContent( + // "{\"locations\": [\"us-central1\"], \"encodedLocations\": \"0x1\"}")) + // .build(); + // + // AwsCredentials credentials = + // AwsCredentials.newBuilder() + // .setHttpTransportFactory(() -> mockHttpTransport) + // .setAudience( + // + // "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/pool/providers/provider") + // .setSubjectTokenType("subjectTokenType") + // .setTokenUrl(STS_URL) + // .setCredentialSource(AWS_CREDENTIAL_SOURCE) + // .build(); + // + // credentials.refresh(); + // + // TrustBoundary trustBoundary = credentials.getTrustBoundary(); + // assertNotNull(trustBoundary); + // assertEquals("0x1", trustBoundary.getEncodedLocations()); + // } + // + // @Test + // public void testRefresh_trustBoundaryFails() throws IOException { + // TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + // TrustBoundary.setEnvironmentProviderForTest(environmentProvider); + // environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); + // + // MockHttpTransport mockHttpTransport = + // new MockHttpTransport.Builder() + // .setLowLevelHttpResponse(new MockLowLevelHttpResponse().setContent("us-east-1a")) + // .setLowLevelHttpResponse(new MockLowLevelHttpResponse().setContent("roleName")) + // .setLowLevelHttpResponse( + // new MockLowLevelHttpResponse() + // .setContent( + // + // "{\"Code\":\"Success\",\"AccessKeyId\":\"accessKeyId\",\"SecretAccessKey\":\"secretAccessKey\",\"Token\":\"token\"}")) + // .setLowLevelHttpResponse( + // new MockLowLevelHttpResponse() + // .setContentType(Json.MEDIA_TYPE) + // .setContent( + // String.format( + // "{\"access_token\": \"%s\", \"expires_in\": %s, \"token_type\": + // \"Bearer\"}", + // "sts_access_token", 3600))) + // .setLowLevelHttpResponse( + // new MockLowLevelHttpResponse() + // .setStatusCode(404) + // .setContent("{\"error\": \"not found\"}")) + // .build(); + // + // AwsCredentials credentials = + // AwsCredentials.newBuilder() + // .setHttpTransportFactory(() -> mockHttpTransport) + // .setAudience( + // + // "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/pool/providers/provider") + // .setSubjectTokenType("subjectTokenType") + // .setTokenUrl(STS_URL) + // .setCredentialSource(AWS_CREDENTIAL_SOURCE) + // .build(); + // + // try { + // credentials.refresh(); + // fail("Expected IOException to be thrown."); + // } catch (IOException e) { + // assertEquals( + // "Failed to refresh trust boundary and no cached value is available.", e.getMessage()); + // } + // } } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java index 740cabba5..6ec244592 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java @@ -32,17 +32,14 @@ package com.google.auth.oauth2; import static com.google.auth.Credentials.GOOGLE_DEFAULT_UNIVERSE; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; import com.google.api.client.http.HttpTransport; import com.google.api.client.json.GenericJson; +import com.google.api.client.json.Json; +import com.google.api.client.testing.http.MockHttpTransport; import com.google.api.client.testing.http.MockLowLevelHttpRequest; +import com.google.api.client.testing.http.MockLowLevelHttpResponse; import com.google.api.client.util.Clock; import com.google.auth.TestUtils; import com.google.auth.http.AuthHttpConstants; @@ -62,6 +59,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -132,6 +130,11 @@ public void setup() { transportFactory = new MockExternalAccountAuthorizedUserCredentialsTransportFactory(); } + @After + public void tearDown() { + TrustBoundary.setEnvironmentProviderForTest(null); + } + @Test public void builder_allFields() throws IOException { ExternalAccountAuthorizedUserCredentials credentials = @@ -1216,6 +1219,85 @@ public void toString_expectedFormat() { assertEquals(expectedToString, credentials.toString()); } + @Test + public void testRefresh_trustBoundarySuccess() throws IOException { + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + TrustBoundary.setEnvironmentProviderForTest(environmentProvider); + environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); + + MockHttpTransport mockHttpTransport = + new MockHttpTransport.Builder() + .setLowLevelHttpResponse( + new MockLowLevelHttpResponse() + .setContentType(Json.MEDIA_TYPE) + .setContent( + String.format( + "{\"access_token\": \"%s\", \"expires_in\": %s, \"token_type\": \"Bearer\"}", + "sts_access_token", 3600))) + .setLowLevelHttpResponse( + new MockLowLevelHttpResponse() + .setContentType(Json.MEDIA_TYPE) + .setContent( + "{\"locations\": [\"us-central1\"], \"encodedLocations\": \"0x1\"}")) + .build(); + + ExternalAccountAuthorizedUserCredentials credentials = + ExternalAccountAuthorizedUserCredentials.newBuilder() + .setHttpTransportFactory(() -> mockHttpTransport) + .setAudience(AUDIENCE) + .setClientId(CLIENT_ID) + .setClientSecret(CLIENT_SECRET) + .setRefreshToken(REFRESH_TOKEN) + .setTokenUrl(TOKEN_URL) + .build(); + + credentials.refresh(); + + TrustBoundary trustBoundary = credentials.getTrustBoundary(); + assertNotNull(trustBoundary); + assertEquals("0x1", trustBoundary.getEncodedLocations()); + } + + @Test + public void testRefresh_trustBoundaryFails() throws IOException { + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + TrustBoundary.setEnvironmentProviderForTest(environmentProvider); + environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); + + MockHttpTransport mockHttpTransport = + new MockHttpTransport.Builder() + .setLowLevelHttpResponse( + new MockLowLevelHttpResponse() + .setContentType(Json.MEDIA_TYPE) + .setContent( + String.format( + "{\"access_token\": \"%s\", \"expires_in\": %s, \"token_type\": \"Bearer\"}", + "sts_access_token", 3600))) + .setLowLevelHttpResponse( + new MockLowLevelHttpResponse() + .setStatusCode(404) + .setContent("{\"error\": \"not found\"}")) + .build(); + + ExternalAccountAuthorizedUserCredentials credentials = + ExternalAccountAuthorizedUserCredentials.newBuilder() + .setHttpTransportFactory(() -> mockHttpTransport) + .setAudience(AUDIENCE) + .setClientId(CLIENT_ID) + .setClientSecret(CLIENT_SECRET) + .setRefreshToken(REFRESH_TOKEN) + .setTokenUrl(TOKEN_URL) + .build(); + + try { + credentials.refresh(); + fail("Expected IOException to be thrown."); + } catch (IOException e) { + assertEquals( + "Failed to refresh trust boundary and no cached value is available.", e.getMessage()); + } + } + @Test public void serialize() throws IOException, ClassNotFoundException { ExternalAccountAuthorizedUserCredentials credentials = diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java index 32009f755..ae8a5f552 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java @@ -32,12 +32,7 @@ package com.google.auth.oauth2; import static com.google.auth.oauth2.MockExternalAccountCredentialsTransport.SERVICE_ACCOUNT_IMPERSONATION_URL; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; import com.google.api.client.http.HttpTransport; import com.google.api.client.json.GenericJson; @@ -1233,7 +1228,7 @@ public void validateServiceAccountImpersonationUrls_invalidUrls() { "https://", "http://iamcredentials.googleapis.com", "https:/iamcredentials.googleapis.com", - "https://us-eas\t-1.iamcredentials.googleapis.com", + "https://us-eas\\t-1.iamcredentials.googleapis.com", "testhttps://us-east-1.iamcredentials.googleapis.com", "hhttps://us-east-1.iamcredentials.googleapis.com", "https://us- -1.iamcredentials.googleapis.com"); @@ -1248,6 +1243,276 @@ public void validateServiceAccountImpersonationUrls_invalidUrls() { } } + // @Test + // public void testRefreshAccessToken_workload_trustBoundarySuccess() throws IOException { + // // By default, the trust boundary feature is disabled. + // TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + // TrustBoundary.setEnvironmentProviderForTest(environmentProvider); + // environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); + // + // // Mock STS and IAM endpoints. + // MockHttpTransport mockHttpTransport = + // new MockHttpTransport.Builder() + // .setLowLevelHttpResponse( + // new MockLowLevelHttpResponse() + // .setContentType(Json.MEDIA_TYPE) + // .setContent( + // String.format( + // "{\"access_token\": \"%s\", \"expires_in\": %s, \"token_type\": + // \"Bearer\"}", + // "sts_access_token", 3600))) + // .setLowLevelHttpResponse( + // new MockLowLevelHttpResponse() + // .setContentType(Json.MEDIA_TYPE) + // .setContent( + // "{\"locations\": [\"us-central1\"], \"encodedLocations\": \"0x1\"}")) + // .build(); + // + // HttpTransportFactory testingHttpTransportFactory = () -> mockHttpTransport; + // + // ExternalAccountCredentials credentials = + // IdentityPoolCredentials.newBuilder() + // .setHttpTransportFactory(testingHttpTransportFactory) + // .setAudience( + // + // "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/pool/providers/provider") + // .setSubjectTokenType("subjectTokenType") + // .setTokenUrl(STS_URL) + // .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) + // .build(); + // + // // Refresh the token to trigger trust boundary refresh. + // credentials.refresh(); + // + // // Verify that the trust boundary was set correctly. + // TrustBoundary trustBoundary = credentials.getTrustBoundary(); + // assertNotNull(trustBoundary); + // assertEquals("0x1", trustBoundary.getEncodedLocations()); + // + // // Verify that the header is added to requests. + // Map> headers = credentials.getRequestMetadata(); + // assertTrue(headers.containsKey(TrustBoundary.TRUST_BOUNDARY_KEY)); + // assertEquals("0x1", headers.get(TrustBoundary.TRUST_BOUNDARY_KEY).get(0)); + // } + // + // @Test + // public void testRefreshAccessToken_workforce_trustBoundaryNoOp() throws IOException { + // // By default, the trust boundary feature is disabled. + // TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + // TrustBoundary.setEnvironmentProviderForTest(environmentProvider); + // environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); + // + // // Mock STS and IAM endpoints. + // MockHttpTransport mockHttpTransport = + // new MockHttpTransport.Builder() + // .setLowLevelHttpResponse( + // new MockLowLevelHttpResponse() + // .setContentType(Json.MEDIA_TYPE) + // .setContent( + // String.format( + // "{\"access_token\": \"%s\", \"expires_in\": %s, \"token_type\": + // \"Bearer\"}", + // "sts_access_token", 3600))) + // .setLowLevelHttpResponse( + // new MockLowLevelHttpResponse() + // .setContentType(Json.MEDIA_TYPE) + // .setContent("{\"encodedLocations\": \"0x0\"}")) + // .build(); + // + // HttpTransportFactory testingHttpTransportFactory = () -> mockHttpTransport; + // + // ExternalAccountCredentials credentials = + // IdentityPoolCredentials.newBuilder() + // .setHttpTransportFactory(testingHttpTransportFactory) + // .setAudience( + // "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider") + // .setSubjectTokenType("subjectTokenType") + // .setTokenUrl(STS_URL) + // .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) + // .setWorkforcePoolUserProject("12345") + // .build(); + // + // // Refresh the token to trigger trust boundary refresh. + // credentials.refresh(); + // + // // Verify that the trust boundary was set correctly. + // TrustBoundary trustBoundary = credentials.getTrustBoundary(); + // assertNotNull(trustBoundary); + // assertTrue(trustBoundary.isNoOp()); + // + // // Verify that the header is not added to requests. + // Map> headers = credentials.getRequestMetadata(); + // assertFalse(headers.containsKey(TrustBoundary.TRUST_BOUNDARY_KEY)); + // + // // Refresh again and confirm the lookup is not called again. + // credentials.refresh(); + // // assertEquals(1, mockHttpTransport.getRequests().size()); + // } + // + // @Test + // // public void testRefreshAccessToken_impersonated_trustBoundarySuccess() + // throws + // // IOException { + // // // By default, the trust boundary feature is disabled. + // // TestEnvironmentProvider environmentProvider = new + // TestEnvironmentProvider(); + // // TrustBoundary.setEnvironmentProviderForTest(environmentProvider); + // // + // environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", + // // "1"); + // // + // // // This is the trust boundary that the impersonated credential will have. + // // final TrustBoundary impersonatedTrustBoundary = new TrustBoundary("0x123", + // // null); + // // + // // // Create a mock ImpersonatedCredentials that returns a specific trust + // // boundary. + // // class MockImpersonatedCredentials extends ImpersonatedCredentials { + // // private MockImpersonatedCredentials() throws IOException { + // // super(new ImpersonatedCredentials.Builder() + // // .setSourceCredentials(new + // // TestExternalAccountCredentials.Builder().build()) + // // .setTargetPrincipal("foo@example.com")); + // // } + // // + // // @Override + // // public AccessToken refreshAccessToken() { + // // return new AccessToken("impersonated_token", new Date()); + // // } + // // + // // @Override + // // public TrustBoundary getTrustBoundary() { + // // return impersonatedTrustBoundary; + // // } + // // } + // // + // // final MockImpersonatedCredentials mockImpersonated = new + // // MockImpersonatedCredentials(); + // // + // // // Create a credential that will be configured for impersonation. + // // TestExternalAccountCredentials credentials = new + // // TestExternalAccountCredentials( + // // TestExternalAccountCredentials.newBuilder() + // // .setHttpTransportFactory(transportFactory) + // // .setAudience("audience") + // // .setSubjectTokenType("subjectTokenType") + // // .setTokenUrl(STS_URL) + // // .setCredentialSource(new + // // TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) + // // + // .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL)) + // // { + // // @Override + // // ImpersonatedCredentials buildImpersonatedCredentials() { + // // return mockImpersonated; + // // } + // // }; + // // + // // // Refresh the token. This should trigger the impersonation flow. + // // credentials.refresh(); + // // + // // // Verify that the parent credential's trust boundary is the one from the + // // impersonated credential. + // // assertEquals(impersonatedTrustBoundary, credentials.getTrustBoundary()); + // // } @Test + // public void testRefreshAccessToken_workload_trustBoundaryFails() throws IOException { + // // By default, the trust boundary feature is disabled. + // TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + // TrustBoundary.setEnvironmentProviderForTest(environmentProvider); + // environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); + // + // // Mock STS and IAM endpoints. + // MockHttpTransport mockHttpTransport = + // new MockHttpTransport.Builder() + // .setLowLevelHttpResponse( + // new MockLowLevelHttpResponse() + // .setContentType(Json.MEDIA_TYPE) + // .setContent( + // String.format( + // "{\"access_token\": \"%s\", \"expires_in\": %s, \"token_type\": + // \"Bearer\"}", + // "sts_access_token", 3600))) + // .setLowLevelHttpResponse( + // new MockLowLevelHttpResponse() + // .setStatusCode(404) + // .setContent("{\"error\": \"not found\"}")) + // .build(); + // + // HttpTransportFactory testingHttpTransportFactory = () -> mockHttpTransport; + // + // ExternalAccountCredentials credentials = + // IdentityPoolCredentials.newBuilder() + // .setHttpTransportFactory(testingHttpTransportFactory) + // .setAudience( + // + // "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/pool/providers/provider") + // .setSubjectTokenType("subjectTokenType") + // .setTokenUrl(STS_URL) + // .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) + // .build(); + // + // // Refresh the token to trigger trust boundary refresh. + // try { + // credentials.refresh(); + // fail("Expected IOException to be thrown."); + // } catch (IOException e) { + // assertEquals( + // "Failed to refresh trust boundary and no cached value is available.", e.getMessage()); + // } + // } + // + // @Test + // public void testRefreshAccessToken_workforce_trustBoundarySuccess() throws IOException { + // // By default, the trust boundary feature is disabled. + // TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + // TrustBoundary.setEnvironmentProviderForTest(environmentProvider); + // environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); + // + // // Mock STS and IAM endpoints. + // MockHttpTransport mockHttpTransport = + // new MockHttpTransport.Builder() + // .setLowLevelHttpResponse( + // new MockLowLevelHttpResponse() + // .setContentType(Json.MEDIA_TYPE) + // .setContent( + // String.format( + // "{\"access_token\": \"%s\", \"expires_in\": %s, \"token_type\": + // \"Bearer\"}", + // "sts_access_token", 3600))) + // .setLowLevelHttpResponse( + // new MockLowLevelHttpResponse() + // .setContentType(Json.MEDIA_TYPE) + // .setContent( + // "{\"locations\": [\"us-central1\"], \"encodedLocations\": \"0x1\"}")) + // .build(); + // + // HttpTransportFactory testingHttpTransportFactory = () -> mockHttpTransport; + // + // ExternalAccountCredentials credentials = + // IdentityPoolCredentials.newBuilder() + // .setHttpTransportFactory(testingHttpTransportFactory) + // .setAudience( + // "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider") + // .setSubjectTokenType("subjectTokenType") + // .setTokenUrl(STS_URL) + // .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) + // .setWorkforcePoolUserProject("12345") + // .build(); + // + // // Refresh the token to trigger trust boundary refresh. + // credentials.refresh(); + // + // // Verify that the trust boundary was set correctly. + // TrustBoundary trustBoundary = credentials.getTrustBoundary(); + // assertNotNull(trustBoundary); + // assertEquals("0x1", trustBoundary.getEncodedLocations()); + // + // // Verify that the header is added to requests. + // Map> headers = credentials.getRequestMetadata(); + // assertTrue(headers.containsKey(TrustBoundary.TRUST_BOUNDARY_KEY)); + // assertEquals("0x1", headers.get(TrustBoundary.TRUST_BOUNDARY_KEY).get(0)); + // } + private GenericJson buildJsonIdentityPoolCredential() { GenericJson json = new GenericJson(); json.put( diff --git a/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java index cce03e085..167bb792a 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java @@ -1304,4 +1304,91 @@ void setShouldThrowOnGetCertificatePath(boolean shouldThrow) { this.shouldThrowOnGetCertificatePath = shouldThrow; } } + + // @Test + // public void testRefresh_trustBoundarySuccess() throws IOException { + // TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + // TrustBoundary.setEnvironmentProviderForTest(environmentProvider); + // environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); + // + // MockHttpTransport mockHttpTransport = + // new MockHttpTransport.Builder() + // .setLowLevelHttpResponse( + // new MockLowLevelHttpResponse() + // .setContentType(Json.MEDIA_TYPE) + // .setContent( + // String.format( + // "{\"access_token\": \"%s\", \"expires_in\": %s, \"token_type\": + // \"Bearer\"}", + // "sts_access_token", 3600))) + // .setLowLevelHttpResponse( + // new MockLowLevelHttpResponse() + // .setContentType(Json.MEDIA_TYPE) + // .setContent( + // "{\"locations\": [\"us-central1\"], \"encodedLocations\": \"0x1\"}")) + // .build(); + // + // HttpTransportFactory testingHttpTransportFactory = () -> mockHttpTransport; + // + // IdentityPoolCredentials credentials = + // IdentityPoolCredentials.newBuilder() + // .setHttpTransportFactory(testingHttpTransportFactory) + // .setAudience( + // + // "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/pool/providers/provider") + // .setSubjectTokenType("subjectTokenType") + // .setTokenUrl(STS_URL) + // .setCredentialSource(createFileCredentialSource()) + // .build(); + // + // credentials.refresh(); + // + // TrustBoundary trustBoundary = credentials.getTrustBoundary(); + // assertNotNull(trustBoundary); + // assertEquals("0x1", trustBoundary.getEncodedLocations()); + // } + // + // @Test + // public void testRefresh_trustBoundaryFails() throws IOException { + // TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + // TrustBoundary.setEnvironmentProviderForTest(environmentProvider); + // environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); + // + // MockHttpTransport mockHttpTransport = + // new MockHttpTransport.Builder() + // .setLowLevelHttpResponse( + // new MockLowLevelHttpResponse() + // .setContentType(Json.MEDIA_TYPE) + // .setContent( + // String.format( + // "{\"access_token\": \"%s\", \"expires_in\": %s, \"token_type\": + // \"Bearer\"}", + // "sts_access_token", 3600))) + // .setLowLevelHttpResponse( + // new MockLowLevelHttpResponse() + // .setStatusCode(404) + // .setContent("{\"error\": \"not found\"}")) + // .build(); + // + // HttpTransportFactory testingHttpTransportFactory = () -> mockHttpTransport; + // + // IdentityPoolCredentials credentials = + // IdentityPoolCredentials.newBuilder() + // .setHttpTransportFactory(testingHttpTransportFactory) + // .setAudience( + // + // "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/pool/providers/provider") + // .setSubjectTokenType("subjectTokenType") + // .setTokenUrl(STS_URL) + // .setCredentialSource(createFileCredentialSource()) + // .build(); + // + // try { + // credentials.refresh(); + // fail("Expected IOException to be thrown."); + // } catch (IOException e) { + // assertEquals( + // "Failed to refresh trust boundary and no cached value is available.", e.getMessage()); + // } + // } } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java index cd321daf3..64264f1b5 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java @@ -603,6 +603,91 @@ public void serialize() throws IOException, ClassNotFoundException { assertThrows(NotSerializableException.class, () -> serializeAndDeserialize(testCredentials)); } + // @Test + // public void testRefresh_trustBoundarySuccess() throws IOException { + // TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + // TrustBoundary.setEnvironmentProviderForTest(environmentProvider); + // environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); + // + // MockHttpTransport mockHttpTransport = + // new MockHttpTransport.Builder() + // .setLowLevelHttpResponse( + // new MockLowLevelHttpResponse() + // .setContentType(Json.MEDIA_TYPE) + // .setContent( + // String.format( + // "{\"access_token\": \"%s\", \"expires_in\": %s, \"token_type\": + // \"Bearer\"}", + // "sts_access_token", 3600))) + // .setLowLevelHttpResponse( + // new MockLowLevelHttpResponse() + // .setContentType(Json.MEDIA_TYPE) + // .setContent( + // "{\"locations\": [\"us-central1\"], \"encodedLocations\": \"0x1\"}")) + // .build(); + // + // PluggableAuthCredentials credentials = + // PluggableAuthCredentials.newBuilder() + // .setHttpTransportFactory(() -> mockHttpTransport) + // .setAudience( + // + // "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/pool/providers/provider") + // .setSubjectTokenType("subjectTokenType") + // .setTokenUrl(STS_URL) + // .setCredentialSource(buildCredentialSource()) + // .setExecutableHandler(options -> "pluggableAuthToken") + // .build(); + // + // credentials.refresh(); + // + // TrustBoundary trustBoundary = credentials.getTrustBoundary(); + // assertNotNull(trustBoundary); + // assertEquals("0x1", trustBoundary.getEncodedLocations()); + // } + // + // @Test + // public void testRefresh_trustBoundaryFails() throws IOException { + // TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + // TrustBoundary.setEnvironmentProviderForTest(environmentProvider); + // environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); + // + // MockHttpTransport mockHttpTransport = + // new MockHttpTransport.Builder() + // .setLowLevelHttpResponse( + // new MockLowLevelHttpResponse() + // .setContentType(Json.MEDIA_TYPE) + // .setContent( + // String.format( + // "{\"access_token\": \"%s\", \"expires_in\": %s, \"token_type\": + // \"Bearer\"}", + // "sts_access_token", 3600))) + // .setLowLevelHttpResponse( + // new MockLowLevelHttpResponse() + // .setStatusCode(404) + // .setContent("{\"error\": \"not found\"}")) + // .build(); + // + // PluggableAuthCredentials credentials = + // PluggableAuthCredentials.newBuilder() + // .setHttpTransportFactory(() -> mockHttpTransport) + // .setAudience( + // + // "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/pool/providers/provider") + // .setSubjectTokenType("subjectTokenType") + // .setTokenUrl(STS_URL) + // .setCredentialSource(buildCredentialSource()) + // .setExecutableHandler(options -> "pluggableAuthToken") + // .build(); + // + // try { + // credentials.refresh(); + // fail("Expected IOException to be thrown."); + // } catch (IOException e) { + // assertEquals( + // "Failed to refresh trust boundary and no cached value is available.", e.getMessage()); + // } + // } + private static PluggableAuthCredentialSource buildCredentialSource() { return buildCredentialSource("command", null, null); } From 283d8b52a3a18a7cb14245b8150056882c60d13b Mon Sep 17 00:00:00 2001 From: Pranav Iyer Date: Tue, 7 Oct 2025 01:01:30 -0700 Subject: [PATCH 09/18] Added tests for all excepting ExternalAccountCredentials. --- .../auth/oauth2/AwsCredentialsTest.java | 132 ++++------------- ...lAccountAuthorizedUserCredentialsTest.java | 61 ++------ .../oauth2/IdentityPoolCredentialsTest.java | 114 ++++---------- ...ckExternalAccountCredentialsTransport.java | 26 +++- .../google/auth/oauth2/MockStsTransport.java | 18 ++- .../oauth2/PluggableAuthCredentialsTest.java | 139 +++++++----------- 6 files changed, 164 insertions(+), 326 deletions(-) diff --git a/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java index 46813e65c..27b3ae3c7 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java @@ -40,8 +40,11 @@ import static org.junit.Assert.fail; import com.google.api.client.json.GenericJson; +import com.google.api.client.json.Json; import com.google.api.client.json.JsonParser; +import com.google.api.client.testing.http.MockHttpTransport; import com.google.api.client.testing.http.MockLowLevelHttpRequest; +import com.google.api.client.testing.http.MockLowLevelHttpResponse; import com.google.api.client.util.Clock; import com.google.auth.TestUtils; import com.google.auth.oauth2.ExternalAccountCredentialsTest.MockExternalAccountCredentialsTransportFactory; @@ -1400,105 +1403,32 @@ public AwsSecurityCredentials getCredentials(ExternalAccountSupplierContext cont } } - // @Test - // public void testRefresh_trustBoundarySuccess() throws IOException { - // TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); - // TrustBoundary.setEnvironmentProviderForTest(environmentProvider); - // environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); - // - // MockHttpTransport mockHttpTransport = - // new MockHttpTransport.Builder() - // // AWS region call - // .setLowLevelHttpResponse(new MockLowLevelHttpResponse().setContent("us-east-1a")) - // // AWS IAM role name call - // .setLowLevelHttpResponse(new MockLowLevelHttpResponse().setContent("roleName")) - // // AWS credentials call - // .setLowLevelHttpResponse( - // new MockLowLevelHttpResponse() - // .setContent( - // - // "{\"Code\":\"Success\",\"AccessKeyId\":\"accessKeyId\",\"SecretAccessKey\":\"secretAccessKey\",\"Token\":\"token\"}")) - // // STS token call - // .setLowLevelHttpResponse( - // new MockLowLevelHttpResponse() - // .setContentType(Json.MEDIA_TYPE) - // .setContent( - // String.format( - // "{\"access_token\": \"%s\", \"expires_in\": %s, \"token_type\": - // \"Bearer\"}", - // "sts_access_token", 3600))) - // // Trust boundary call - // .setLowLevelHttpResponse( - // new MockLowLevelHttpResponse() - // .setContentType(Json.MEDIA_TYPE) - // .setContent( - // "{\"locations\": [\"us-central1\"], \"encodedLocations\": \"0x1\"}")) - // .build(); - // - // AwsCredentials credentials = - // AwsCredentials.newBuilder() - // .setHttpTransportFactory(() -> mockHttpTransport) - // .setAudience( - // - // "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/pool/providers/provider") - // .setSubjectTokenType("subjectTokenType") - // .setTokenUrl(STS_URL) - // .setCredentialSource(AWS_CREDENTIAL_SOURCE) - // .build(); - // - // credentials.refresh(); - // - // TrustBoundary trustBoundary = credentials.getTrustBoundary(); - // assertNotNull(trustBoundary); - // assertEquals("0x1", trustBoundary.getEncodedLocations()); - // } - // - // @Test - // public void testRefresh_trustBoundaryFails() throws IOException { - // TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); - // TrustBoundary.setEnvironmentProviderForTest(environmentProvider); - // environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); - // - // MockHttpTransport mockHttpTransport = - // new MockHttpTransport.Builder() - // .setLowLevelHttpResponse(new MockLowLevelHttpResponse().setContent("us-east-1a")) - // .setLowLevelHttpResponse(new MockLowLevelHttpResponse().setContent("roleName")) - // .setLowLevelHttpResponse( - // new MockLowLevelHttpResponse() - // .setContent( - // - // "{\"Code\":\"Success\",\"AccessKeyId\":\"accessKeyId\",\"SecretAccessKey\":\"secretAccessKey\",\"Token\":\"token\"}")) - // .setLowLevelHttpResponse( - // new MockLowLevelHttpResponse() - // .setContentType(Json.MEDIA_TYPE) - // .setContent( - // String.format( - // "{\"access_token\": \"%s\", \"expires_in\": %s, \"token_type\": - // \"Bearer\"}", - // "sts_access_token", 3600))) - // .setLowLevelHttpResponse( - // new MockLowLevelHttpResponse() - // .setStatusCode(404) - // .setContent("{\"error\": \"not found\"}")) - // .build(); - // - // AwsCredentials credentials = - // AwsCredentials.newBuilder() - // .setHttpTransportFactory(() -> mockHttpTransport) - // .setAudience( - // - // "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/pool/providers/provider") - // .setSubjectTokenType("subjectTokenType") - // .setTokenUrl(STS_URL) - // .setCredentialSource(AWS_CREDENTIAL_SOURCE) - // .build(); - // - // try { - // credentials.refresh(); - // fail("Expected IOException to be thrown."); - // } catch (IOException e) { - // assertEquals( - // "Failed to refresh trust boundary and no cached value is available.", e.getMessage()); - // } - // } + @Test + public void testRefresh_trustBoundarySuccess() throws IOException { + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + TrustBoundary.setEnvironmentProviderForTest(environmentProvider); + environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); + + MockExternalAccountCredentialsTransportFactory transportFactory = + new MockExternalAccountCredentialsTransportFactory(); + + AwsSecurityCredentialsSupplier supplier = + new TestAwsSecurityCredentialsSupplier("test", programmaticAwsCreds, null, null); + + AwsCredentials awsCredential = + AwsCredentials.newBuilder() + .setAwsSecurityCredentialsSupplier(supplier) + .setHttpTransportFactory(transportFactory) + .setAudience( + "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/pool/providers/provider") + .setTokenUrl(STS_URL) + .setSubjectTokenType("subjectTokenType") + .build(); + + awsCredential.refreshAccessToken(); + + TrustBoundary trustBoundary = awsCredential.getTrustBoundary(); + assertNotNull(trustBoundary); + assertEquals("0x800000", trustBoundary.getEncodedLocations()); + } } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java index 6ec244592..0ced9a887 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java @@ -1225,25 +1225,9 @@ public void testRefresh_trustBoundarySuccess() throws IOException { TrustBoundary.setEnvironmentProviderForTest(environmentProvider); environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); - MockHttpTransport mockHttpTransport = - new MockHttpTransport.Builder() - .setLowLevelHttpResponse( - new MockLowLevelHttpResponse() - .setContentType(Json.MEDIA_TYPE) - .setContent( - String.format( - "{\"access_token\": \"%s\", \"expires_in\": %s, \"token_type\": \"Bearer\"}", - "sts_access_token", 3600))) - .setLowLevelHttpResponse( - new MockLowLevelHttpResponse() - .setContentType(Json.MEDIA_TYPE) - .setContent( - "{\"locations\": [\"us-central1\"], \"encodedLocations\": \"0x1\"}")) - .build(); - ExternalAccountAuthorizedUserCredentials credentials = ExternalAccountAuthorizedUserCredentials.newBuilder() - .setHttpTransportFactory(() -> mockHttpTransport) + .setHttpTransportFactory(transportFactory) .setAudience(AUDIENCE) .setClientId(CLIENT_ID) .setClientSecret(CLIENT_SECRET) @@ -1259,35 +1243,20 @@ public void testRefresh_trustBoundarySuccess() throws IOException { } @Test - public void testRefresh_trustBoundaryFails() throws IOException { - TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); - TrustBoundary.setEnvironmentProviderForTest(environmentProvider); - environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); - - MockHttpTransport mockHttpTransport = - new MockHttpTransport.Builder() - .setLowLevelHttpResponse( - new MockLowLevelHttpResponse() - .setContentType(Json.MEDIA_TYPE) - .setContent( - String.format( - "{\"access_token\": \"%s\", \"expires_in\": %s, \"token_type\": \"Bearer\"}", - "sts_access_token", 3600))) - .setLowLevelHttpResponse( - new MockLowLevelHttpResponse() - .setStatusCode(404) - .setContent("{\"error\": \"not found\"}")) - .build(); - - ExternalAccountAuthorizedUserCredentials credentials = - ExternalAccountAuthorizedUserCredentials.newBuilder() - .setHttpTransportFactory(() -> mockHttpTransport) - .setAudience(AUDIENCE) - .setClientId(CLIENT_ID) - .setClientSecret(CLIENT_SECRET) - .setRefreshToken(REFRESH_TOKEN) - .setTokenUrl(TOKEN_URL) - .build(); + public void testRefresh_trustBoundaryFails_incorrectAudience() throws IOException { + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + TrustBoundary.setEnvironmentProviderForTest(environmentProvider); + environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); + + ExternalAccountAuthorizedUserCredentials credentials = + ExternalAccountAuthorizedUserCredentials.newBuilder() + .setHttpTransportFactory(transportFactory) + .setAudience("audience") + .setClientId(CLIENT_ID) + .setClientSecret(CLIENT_SECRET) + .setRefreshToken(REFRESH_TOKEN) + .setTokenUrl(TOKEN_URL) + .build(); try { credentials.refresh(); diff --git a/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java index 167bb792a..0d3238ee9 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java @@ -1305,90 +1305,32 @@ void setShouldThrowOnGetCertificatePath(boolean shouldThrow) { } } - // @Test - // public void testRefresh_trustBoundarySuccess() throws IOException { - // TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); - // TrustBoundary.setEnvironmentProviderForTest(environmentProvider); - // environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); - // - // MockHttpTransport mockHttpTransport = - // new MockHttpTransport.Builder() - // .setLowLevelHttpResponse( - // new MockLowLevelHttpResponse() - // .setContentType(Json.MEDIA_TYPE) - // .setContent( - // String.format( - // "{\"access_token\": \"%s\", \"expires_in\": %s, \"token_type\": - // \"Bearer\"}", - // "sts_access_token", 3600))) - // .setLowLevelHttpResponse( - // new MockLowLevelHttpResponse() - // .setContentType(Json.MEDIA_TYPE) - // .setContent( - // "{\"locations\": [\"us-central1\"], \"encodedLocations\": \"0x1\"}")) - // .build(); - // - // HttpTransportFactory testingHttpTransportFactory = () -> mockHttpTransport; - // - // IdentityPoolCredentials credentials = - // IdentityPoolCredentials.newBuilder() - // .setHttpTransportFactory(testingHttpTransportFactory) - // .setAudience( - // - // "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/pool/providers/provider") - // .setSubjectTokenType("subjectTokenType") - // .setTokenUrl(STS_URL) - // .setCredentialSource(createFileCredentialSource()) - // .build(); - // - // credentials.refresh(); - // - // TrustBoundary trustBoundary = credentials.getTrustBoundary(); - // assertNotNull(trustBoundary); - // assertEquals("0x1", trustBoundary.getEncodedLocations()); - // } - // - // @Test - // public void testRefresh_trustBoundaryFails() throws IOException { - // TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); - // TrustBoundary.setEnvironmentProviderForTest(environmentProvider); - // environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); - // - // MockHttpTransport mockHttpTransport = - // new MockHttpTransport.Builder() - // .setLowLevelHttpResponse( - // new MockLowLevelHttpResponse() - // .setContentType(Json.MEDIA_TYPE) - // .setContent( - // String.format( - // "{\"access_token\": \"%s\", \"expires_in\": %s, \"token_type\": - // \"Bearer\"}", - // "sts_access_token", 3600))) - // .setLowLevelHttpResponse( - // new MockLowLevelHttpResponse() - // .setStatusCode(404) - // .setContent("{\"error\": \"not found\"}")) - // .build(); - // - // HttpTransportFactory testingHttpTransportFactory = () -> mockHttpTransport; - // - // IdentityPoolCredentials credentials = - // IdentityPoolCredentials.newBuilder() - // .setHttpTransportFactory(testingHttpTransportFactory) - // .setAudience( - // - // "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/pool/providers/provider") - // .setSubjectTokenType("subjectTokenType") - // .setTokenUrl(STS_URL) - // .setCredentialSource(createFileCredentialSource()) - // .build(); - // - // try { - // credentials.refresh(); - // fail("Expected IOException to be thrown."); - // } catch (IOException e) { - // assertEquals( - // "Failed to refresh trust boundary and no cached value is available.", e.getMessage()); - // } - // } + @Test + public void testRefresh_trustBoundarySuccess() throws IOException { + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + TrustBoundary.setEnvironmentProviderForTest(environmentProvider); + environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); + + MockExternalAccountCredentialsTransportFactory transportFactory = + new MockExternalAccountCredentialsTransportFactory(); + HttpTransportFactory testingHttpTransportFactory = transportFactory; + + IdentityPoolCredentials credentials = + IdentityPoolCredentials.newBuilder() + .setSubjectTokenSupplier(testProvider) + .setHttpTransportFactory(testingHttpTransportFactory) + .setAudience( + "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/pool/providers/provider") + .setSubjectTokenType("subjectTokenType") + .setTokenUrl(STS_URL) + .build(); + + credentials.refresh(); + + TrustBoundary trustBoundary = credentials.getTrustBoundary(); + assertNotNull(trustBoundary); + assertEquals( + "0x800000", + trustBoundary.getEncodedLocations()); + } } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java b/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java index d1bfdaecf..f088678b3 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java @@ -47,12 +47,7 @@ import com.google.auth.TestUtils; import com.google.common.base.Joiner; import java.io.IOException; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Queue; +import java.util.*; /** * Mock transport that handles the necessary steps to exchange an external credential for a GCP @@ -68,6 +63,7 @@ public class MockExternalAccountCredentialsTransport extends MockHttpTransport { private static final String AWS_IMDSV2_SESSION_TOKEN_URL = "https://169.254.169.254/imdsv2"; private static final String METADATA_SERVER_URL = "https://www.metadata.google.com"; private static final String STS_URL = "https://sts.googleapis.com/v1/token"; + private static final String TRUST_BOUNDARY_URL_END = "/allowedLocations"; private static final String SUBJECT_TOKEN = "subjectToken"; private static final String TOKEN_TYPE = "Bearer"; @@ -92,6 +88,7 @@ public class MockExternalAccountCredentialsTransport extends MockHttpTransport { private String expireTime; private String metadataServerContentType; private String stsContent; + private String impersonationTrustBoundary; public void addResponseErrorSequence(IOException... errors) { Collections.addAll(responseErrorSequence, errors); @@ -196,6 +193,19 @@ public LowLevelHttpResponse execute() throws IOException { } if (url.contains(IAM_ENDPOINT)) { + + if (url.endsWith(TRUST_BOUNDARY_URL_END)) { + GenericJson responseJson = new GenericJson(); + responseJson.setFactory(OAuth2Utils.JSON_FACTORY); + String encodedLocations = "0x800000"; + responseJson.put("encodedLocations", encodedLocations); + responseJson.put("locations", Arrays.asList("us-east-1", "us-east-2")); + String content = responseJson.toPrettyString(); + return new MockLowLevelHttpResponse() + .setContentType(Json.MEDIA_TYPE) + .setContent(content); + } + GenericJson query = OAuth2Utils.JSON_FACTORY .createJsonParser(getContentAsString()) @@ -299,4 +309,8 @@ public void setExpireTime(String expireTime) { public void setMetadataServerContentType(String contentType) { this.metadataServerContentType = contentType; } + + public void setImpersonationTrustBoundary(String trustBoundary) { + this.impersonationTrustBoundary = trustBoundary; + } } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/MockStsTransport.java b/oauth2_http/javatests/com/google/auth/oauth2/MockStsTransport.java index 5b1b3fded..e4c51569d 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/MockStsTransport.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/MockStsTransport.java @@ -60,8 +60,9 @@ public final class MockStsTransport extends MockHttpTransport { private static final String EXPECTED_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange"; private static final String ISSUED_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token"; - private static final String VALID_STS_PATTERN = - "https:\\/\\/sts.[a-z-_\\.]+\\/v1\\/(token|oauthtoken)"; + private static final String VALID_STS_PATTERN = "https:\\/\\/sts.[a-z-_\\.]+\\/v1\\/(token|oauthtoken)"; + private static final String VALID_TRUST_BOUNDARY_PATTERN = + "https:\\/\\/iam.[a-z-_\\.]+\\/v1\\/.*\\/allowedLocations"; private static final String ACCESS_TOKEN = "accessToken"; private static final String TOKEN_TYPE = "Bearer"; private static final Long EXPIRES_IN = 3600L; @@ -99,6 +100,17 @@ public LowLevelHttpRequest buildRequest(final String method, final String url) { new MockLowLevelHttpRequest(url) { @Override public LowLevelHttpResponse execute() throws IOException { + Matcher trustBoundaryMatcher = Pattern.compile(VALID_TRUST_BOUNDARY_PATTERN).matcher(url); + if (trustBoundaryMatcher.matches()) { + GenericJson response = new GenericJson(); + response.setFactory(new GsonFactory()); + response.put("locations", Collections.singletonList("us-central1")); + response.put("encodedLocations", "0x1"); + return new MockLowLevelHttpResponse() + .setContentType(Json.MEDIA_TYPE) + .setContent(response.toPrettyString()); + } + // Environment version is prefixed by "aws". e.g. "aws1". Matcher matcher = Pattern.compile(VALID_STS_PATTERN).matcher(url); if (!matcher.matches()) { @@ -201,4 +213,4 @@ public void setReturnAccessBoundarySessionKey(boolean returnAccessBoundarySessio public int getRequestCount() { return requestCount; } -} +} \ No newline at end of file diff --git a/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java index 64264f1b5..4ce34c3f4 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java @@ -603,90 +603,61 @@ public void serialize() throws IOException, ClassNotFoundException { assertThrows(NotSerializableException.class, () -> serializeAndDeserialize(testCredentials)); } - // @Test - // public void testRefresh_trustBoundarySuccess() throws IOException { - // TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); - // TrustBoundary.setEnvironmentProviderForTest(environmentProvider); - // environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); - // - // MockHttpTransport mockHttpTransport = - // new MockHttpTransport.Builder() - // .setLowLevelHttpResponse( - // new MockLowLevelHttpResponse() - // .setContentType(Json.MEDIA_TYPE) - // .setContent( - // String.format( - // "{\"access_token\": \"%s\", \"expires_in\": %s, \"token_type\": - // \"Bearer\"}", - // "sts_access_token", 3600))) - // .setLowLevelHttpResponse( - // new MockLowLevelHttpResponse() - // .setContentType(Json.MEDIA_TYPE) - // .setContent( - // "{\"locations\": [\"us-central1\"], \"encodedLocations\": \"0x1\"}")) - // .build(); - // - // PluggableAuthCredentials credentials = - // PluggableAuthCredentials.newBuilder() - // .setHttpTransportFactory(() -> mockHttpTransport) - // .setAudience( - // - // "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/pool/providers/provider") - // .setSubjectTokenType("subjectTokenType") - // .setTokenUrl(STS_URL) - // .setCredentialSource(buildCredentialSource()) - // .setExecutableHandler(options -> "pluggableAuthToken") - // .build(); - // - // credentials.refresh(); - // - // TrustBoundary trustBoundary = credentials.getTrustBoundary(); - // assertNotNull(trustBoundary); - // assertEquals("0x1", trustBoundary.getEncodedLocations()); - // } - // - // @Test - // public void testRefresh_trustBoundaryFails() throws IOException { - // TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); - // TrustBoundary.setEnvironmentProviderForTest(environmentProvider); - // environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); - // - // MockHttpTransport mockHttpTransport = - // new MockHttpTransport.Builder() - // .setLowLevelHttpResponse( - // new MockLowLevelHttpResponse() - // .setContentType(Json.MEDIA_TYPE) - // .setContent( - // String.format( - // "{\"access_token\": \"%s\", \"expires_in\": %s, \"token_type\": - // \"Bearer\"}", - // "sts_access_token", 3600))) - // .setLowLevelHttpResponse( - // new MockLowLevelHttpResponse() - // .setStatusCode(404) - // .setContent("{\"error\": \"not found\"}")) - // .build(); - // - // PluggableAuthCredentials credentials = - // PluggableAuthCredentials.newBuilder() - // .setHttpTransportFactory(() -> mockHttpTransport) - // .setAudience( - // - // "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/pool/providers/provider") - // .setSubjectTokenType("subjectTokenType") - // .setTokenUrl(STS_URL) - // .setCredentialSource(buildCredentialSource()) - // .setExecutableHandler(options -> "pluggableAuthToken") - // .build(); - // - // try { - // credentials.refresh(); - // fail("Expected IOException to be thrown."); - // } catch (IOException e) { - // assertEquals( - // "Failed to refresh trust boundary and no cached value is available.", e.getMessage()); - // } - // } + @Test + + public void testRefresh_trustBoundarySuccess() throws IOException { + + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + + TrustBoundary.setEnvironmentProviderForTest(environmentProvider); + + environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); + + + + MockExternalAccountCredentialsTransportFactory transportFactory = + + new MockExternalAccountCredentialsTransportFactory(); + + + + transportFactory.transport.setExpireTime(TestUtils.getDefaultExpireTime()); + + + + PluggableAuthCredentials credentials = + + PluggableAuthCredentials.newBuilder() + + .setHttpTransportFactory(transportFactory) + + .setAudience( + + "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/pool/providers/provider") + + .setSubjectTokenType("subjectTokenType") + + .setTokenUrl(transportFactory.transport.getStsUrl()) + + .setCredentialSource(buildCredentialSource()) + + .setExecutableHandler(options -> "pluggableAuthToken") + + .build(); + + + + credentials.refresh(); + + + + TrustBoundary trustBoundary = credentials.getTrustBoundary(); + + assertNotNull(trustBoundary); + + assertEquals("0x800000", trustBoundary.getEncodedLocations()); + + } private static PluggableAuthCredentialSource buildCredentialSource() { return buildCredentialSource("command", null, null); From b4e5199f8f53e5de73e6d80df9e3eb3e05631aff Mon Sep 17 00:00:00 2001 From: Pranav Iyer Date: Mon, 27 Oct 2025 11:09:52 -0700 Subject: [PATCH 10/18] Added tests for ExternalAccountCredentials trust boundary. Added comments regarding a separate mock for trust boundary. --- ...ernalAccountAuthorizedUserCredentials.java | 7 +- .../oauth2/ExternalAccountCredentials.java | 9 +- .../com/google/auth/oauth2/TrustBoundary.java | 4 +- .../javatests/com/google/auth/TestUtils.java | 4 + .../auth/oauth2/AwsCredentialsTest.java | 7 +- .../oauth2/ComputeEngineCredentialsTest.java | 7 +- ...lAccountAuthorizedUserCredentialsTest.java | 32 +- .../ExternalAccountCredentialsTest.java | 508 ++++++++---------- .../oauth2/IdentityPoolCredentialsTest.java | 46 +- .../oauth2/ImpersonatedCredentialsTest.java | 19 +- ...ckExternalAccountCredentialsTransport.java | 8 +- .../oauth2/MockMetadataServerTransport.java | 5 +- .../google/auth/oauth2/MockStsTransport.java | 8 +- .../auth/oauth2/MockTokenServerTransport.java | 5 +- .../oauth2/PluggableAuthCredentialsTest.java | 68 +-- .../oauth2/ServiceAccountCredentialsTest.java | 15 +- 16 files changed, 345 insertions(+), 407 deletions(-) diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java index aaccce572..03cd45b2c 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java @@ -225,7 +225,7 @@ public AccessToken refreshAccessToken() throws IOException { .setTokenValue(accessToken) .build(); - refreshTrustBoundaries(newAccessToken); + refreshTrustBoundary(newAccessToken, transportFactory); return newAccessToken; } @@ -240,11 +240,6 @@ public String getTrustBoundaryUrl() throws IOException { return String.format(WORKFORCE_POOL_URL_FORMAT, poolId); } - @Override - public HttpTransportFactory getTransportFactory() { - return transportFactory; - } - @Nullable public String getAudience() { return audience; diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java index ebd9303aa..356c31206 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java @@ -543,7 +543,7 @@ protected AccessToken exchangeExternalCredentialForAccessToken( if (this.impersonatedCredentials != null) { AccessToken accessToken = this.impersonatedCredentials.refreshAccessToken(); // After the impersonated credential refreshes, its trust boundary is - // also refreshed. We need to get the refreshed trust boundary. + // also refreshed. That is the trust boundary we will use. setTrustBoundary(this.impersonatedCredentials.getTrustBoundary()); return accessToken; } @@ -575,7 +575,7 @@ protected AccessToken exchangeExternalCredentialForAccessToken( StsTokenExchangeResponse response = requestHandler.build().exchangeToken(); AccessToken accessToken = response.getAccessToken(); - refreshTrustBoundaries(accessToken); + refreshTrustBoundary(accessToken, transportFactory); return accessToken; } @@ -656,11 +656,6 @@ public String getTrustBoundaryUrl() throws IOException { } } - @Override - public HttpTransportFactory getTransportFactory() { - return transportFactory; - } - @Nullable public String getClientId() { return clientId; diff --git a/oauth2_http/java/com/google/auth/oauth2/TrustBoundary.java b/oauth2_http/java/com/google/auth/oauth2/TrustBoundary.java index 672941e58..ebbb110de 100644 --- a/oauth2_http/java/com/google/auth/oauth2/TrustBoundary.java +++ b/oauth2_http/java/com/google/auth/oauth2/TrustBoundary.java @@ -184,9 +184,7 @@ static TrustBoundary refresh( // Add the cached trust boundary header, if available. if (cachedTrustBoundary != null) { - String headerValue = - cachedTrustBoundary.isNoOp() ? "" : cachedTrustBoundary.getEncodedLocations(); - request.getHeaders().set(TRUST_BOUNDARY_KEY, headerValue); + request.getHeaders().set(TRUST_BOUNDARY_KEY, cachedTrustBoundary.getEncodedLocations()); } // Add retry logic diff --git a/oauth2_http/javatests/com/google/auth/TestUtils.java b/oauth2_http/javatests/com/google/auth/TestUtils.java index 99d601da8..e933e1044 100644 --- a/oauth2_http/javatests/com/google/auth/TestUtils.java +++ b/oauth2_http/javatests/com/google/auth/TestUtils.java @@ -42,6 +42,7 @@ import com.google.api.client.json.gson.GsonFactory; import com.google.auth.http.AuthHttpConstants; import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -64,6 +65,9 @@ public class TestUtils { URI.create("https://auth.cloud.google/authorize"); public static final URI WORKFORCE_IDENTITY_FEDERATION_TOKEN_SERVER_URI = URI.create("https://sts.googleapis.com/v1/oauthtoken"); + public static final String TRUST_BOUNDARY_ENCODED_LOCATION = "0x800000"; + public static final List TRUST_BOUNDARY_LOCATIONS = + ImmutableList.of("us-central1", "us-central2"); private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance(); diff --git a/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java index 27b3ae3c7..436a785f8 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java @@ -40,11 +40,8 @@ import static org.junit.Assert.fail; import com.google.api.client.json.GenericJson; -import com.google.api.client.json.Json; import com.google.api.client.json.JsonParser; -import com.google.api.client.testing.http.MockHttpTransport; import com.google.api.client.testing.http.MockLowLevelHttpRequest; -import com.google.api.client.testing.http.MockLowLevelHttpResponse; import com.google.api.client.util.Clock; import com.google.auth.TestUtils; import com.google.auth.oauth2.ExternalAccountCredentialsTest.MockExternalAccountCredentialsTransportFactory; @@ -1403,8 +1400,8 @@ public AwsSecurityCredentials getCredentials(ExternalAccountSupplierContext cont } } - @Test - public void testRefresh_trustBoundarySuccess() throws IOException { + @Test + public void testRefresh_trustBoundarySuccess() throws IOException { TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); TrustBoundary.setEnvironmentProviderForTest(environmentProvider); environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ComputeEngineCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ComputeEngineCredentialsTest.java index 275545db2..e13c9ce93 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ComputeEngineCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ComputeEngineCredentialsTest.java @@ -33,6 +33,7 @@ import static com.google.auth.oauth2.ComputeEngineCredentials.METADATA_RESPONSE_EMPTY_CONTENT_ERROR_MESSAGE; import static com.google.auth.oauth2.ImpersonatedCredentialsTest.SA_CLIENT_EMAIL; +import static com.google.auth.oauth2.TrustBoundary.TRUST_BOUNDARY_KEY; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -1159,7 +1160,8 @@ public void refresh_trustBoundarySuccess() throws IOException { String defaultAccountEmail = "default@email.com"; MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory(); TrustBoundary trustBoundary = - new TrustBoundary("0x80000", Collections.singletonList("us-central1")); + new TrustBoundary( + TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION, TestUtils.TRUST_BOUNDARY_LOCATIONS); transportFactory.transport.setTrustBoundary(trustBoundary); transportFactory.transport.setServiceAccountEmail(defaultAccountEmail); @@ -1167,7 +1169,8 @@ public void refresh_trustBoundarySuccess() throws IOException { ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build(); Map> headers = credentials.getRequestMetadata(); - assertEquals(headers.get("x-allowed-locations"), Arrays.asList("0x80000")); + assertEquals( + headers.get(TRUST_BOUNDARY_KEY), Arrays.asList(TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION)); } @Test diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java index 0ced9a887..20c56221a 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java @@ -36,10 +36,7 @@ import com.google.api.client.http.HttpTransport; import com.google.api.client.json.GenericJson; -import com.google.api.client.json.Json; -import com.google.api.client.testing.http.MockHttpTransport; import com.google.api.client.testing.http.MockLowLevelHttpRequest; -import com.google.api.client.testing.http.MockLowLevelHttpResponse; import com.google.api.client.util.Clock; import com.google.auth.TestUtils; import com.google.auth.http.AuthHttpConstants; @@ -1244,26 +1241,27 @@ public void testRefresh_trustBoundarySuccess() throws IOException { @Test public void testRefresh_trustBoundaryFails_incorrectAudience() throws IOException { - TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); - TrustBoundary.setEnvironmentProviderForTest(environmentProvider); - environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); - - ExternalAccountAuthorizedUserCredentials credentials = - ExternalAccountAuthorizedUserCredentials.newBuilder() - .setHttpTransportFactory(transportFactory) - .setAudience("audience") - .setClientId(CLIENT_ID) - .setClientSecret(CLIENT_SECRET) - .setRefreshToken(REFRESH_TOKEN) - .setTokenUrl(TOKEN_URL) - .build(); + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + TrustBoundary.setEnvironmentProviderForTest(environmentProvider); + environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); + + ExternalAccountAuthorizedUserCredentials credentials = + ExternalAccountAuthorizedUserCredentials.newBuilder() + .setHttpTransportFactory(transportFactory) + .setAudience("audience") + .setClientId(CLIENT_ID) + .setClientSecret(CLIENT_SECRET) + .setRefreshToken(REFRESH_TOKEN) + .setTokenUrl(TOKEN_URL) + .build(); try { credentials.refresh(); fail("Expected IOException to be thrown."); } catch (IOException e) { assertEquals( - "Failed to refresh trust boundary and no cached value is available.", e.getMessage()); + "The provided audience is not in the correct format for a workforce pool.", + e.getMessage()); } } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java index ae8a5f552..07f3297cd 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java @@ -1243,276 +1243,6 @@ public void validateServiceAccountImpersonationUrls_invalidUrls() { } } - // @Test - // public void testRefreshAccessToken_workload_trustBoundarySuccess() throws IOException { - // // By default, the trust boundary feature is disabled. - // TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); - // TrustBoundary.setEnvironmentProviderForTest(environmentProvider); - // environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); - // - // // Mock STS and IAM endpoints. - // MockHttpTransport mockHttpTransport = - // new MockHttpTransport.Builder() - // .setLowLevelHttpResponse( - // new MockLowLevelHttpResponse() - // .setContentType(Json.MEDIA_TYPE) - // .setContent( - // String.format( - // "{\"access_token\": \"%s\", \"expires_in\": %s, \"token_type\": - // \"Bearer\"}", - // "sts_access_token", 3600))) - // .setLowLevelHttpResponse( - // new MockLowLevelHttpResponse() - // .setContentType(Json.MEDIA_TYPE) - // .setContent( - // "{\"locations\": [\"us-central1\"], \"encodedLocations\": \"0x1\"}")) - // .build(); - // - // HttpTransportFactory testingHttpTransportFactory = () -> mockHttpTransport; - // - // ExternalAccountCredentials credentials = - // IdentityPoolCredentials.newBuilder() - // .setHttpTransportFactory(testingHttpTransportFactory) - // .setAudience( - // - // "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/pool/providers/provider") - // .setSubjectTokenType("subjectTokenType") - // .setTokenUrl(STS_URL) - // .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) - // .build(); - // - // // Refresh the token to trigger trust boundary refresh. - // credentials.refresh(); - // - // // Verify that the trust boundary was set correctly. - // TrustBoundary trustBoundary = credentials.getTrustBoundary(); - // assertNotNull(trustBoundary); - // assertEquals("0x1", trustBoundary.getEncodedLocations()); - // - // // Verify that the header is added to requests. - // Map> headers = credentials.getRequestMetadata(); - // assertTrue(headers.containsKey(TrustBoundary.TRUST_BOUNDARY_KEY)); - // assertEquals("0x1", headers.get(TrustBoundary.TRUST_BOUNDARY_KEY).get(0)); - // } - // - // @Test - // public void testRefreshAccessToken_workforce_trustBoundaryNoOp() throws IOException { - // // By default, the trust boundary feature is disabled. - // TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); - // TrustBoundary.setEnvironmentProviderForTest(environmentProvider); - // environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); - // - // // Mock STS and IAM endpoints. - // MockHttpTransport mockHttpTransport = - // new MockHttpTransport.Builder() - // .setLowLevelHttpResponse( - // new MockLowLevelHttpResponse() - // .setContentType(Json.MEDIA_TYPE) - // .setContent( - // String.format( - // "{\"access_token\": \"%s\", \"expires_in\": %s, \"token_type\": - // \"Bearer\"}", - // "sts_access_token", 3600))) - // .setLowLevelHttpResponse( - // new MockLowLevelHttpResponse() - // .setContentType(Json.MEDIA_TYPE) - // .setContent("{\"encodedLocations\": \"0x0\"}")) - // .build(); - // - // HttpTransportFactory testingHttpTransportFactory = () -> mockHttpTransport; - // - // ExternalAccountCredentials credentials = - // IdentityPoolCredentials.newBuilder() - // .setHttpTransportFactory(testingHttpTransportFactory) - // .setAudience( - // "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider") - // .setSubjectTokenType("subjectTokenType") - // .setTokenUrl(STS_URL) - // .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) - // .setWorkforcePoolUserProject("12345") - // .build(); - // - // // Refresh the token to trigger trust boundary refresh. - // credentials.refresh(); - // - // // Verify that the trust boundary was set correctly. - // TrustBoundary trustBoundary = credentials.getTrustBoundary(); - // assertNotNull(trustBoundary); - // assertTrue(trustBoundary.isNoOp()); - // - // // Verify that the header is not added to requests. - // Map> headers = credentials.getRequestMetadata(); - // assertFalse(headers.containsKey(TrustBoundary.TRUST_BOUNDARY_KEY)); - // - // // Refresh again and confirm the lookup is not called again. - // credentials.refresh(); - // // assertEquals(1, mockHttpTransport.getRequests().size()); - // } - // - // @Test - // // public void testRefreshAccessToken_impersonated_trustBoundarySuccess() - // throws - // // IOException { - // // // By default, the trust boundary feature is disabled. - // // TestEnvironmentProvider environmentProvider = new - // TestEnvironmentProvider(); - // // TrustBoundary.setEnvironmentProviderForTest(environmentProvider); - // // - // environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", - // // "1"); - // // - // // // This is the trust boundary that the impersonated credential will have. - // // final TrustBoundary impersonatedTrustBoundary = new TrustBoundary("0x123", - // // null); - // // - // // // Create a mock ImpersonatedCredentials that returns a specific trust - // // boundary. - // // class MockImpersonatedCredentials extends ImpersonatedCredentials { - // // private MockImpersonatedCredentials() throws IOException { - // // super(new ImpersonatedCredentials.Builder() - // // .setSourceCredentials(new - // // TestExternalAccountCredentials.Builder().build()) - // // .setTargetPrincipal("foo@example.com")); - // // } - // // - // // @Override - // // public AccessToken refreshAccessToken() { - // // return new AccessToken("impersonated_token", new Date()); - // // } - // // - // // @Override - // // public TrustBoundary getTrustBoundary() { - // // return impersonatedTrustBoundary; - // // } - // // } - // // - // // final MockImpersonatedCredentials mockImpersonated = new - // // MockImpersonatedCredentials(); - // // - // // // Create a credential that will be configured for impersonation. - // // TestExternalAccountCredentials credentials = new - // // TestExternalAccountCredentials( - // // TestExternalAccountCredentials.newBuilder() - // // .setHttpTransportFactory(transportFactory) - // // .setAudience("audience") - // // .setSubjectTokenType("subjectTokenType") - // // .setTokenUrl(STS_URL) - // // .setCredentialSource(new - // // TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) - // // - // .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL)) - // // { - // // @Override - // // ImpersonatedCredentials buildImpersonatedCredentials() { - // // return mockImpersonated; - // // } - // // }; - // // - // // // Refresh the token. This should trigger the impersonation flow. - // // credentials.refresh(); - // // - // // // Verify that the parent credential's trust boundary is the one from the - // // impersonated credential. - // // assertEquals(impersonatedTrustBoundary, credentials.getTrustBoundary()); - // // } @Test - // public void testRefreshAccessToken_workload_trustBoundaryFails() throws IOException { - // // By default, the trust boundary feature is disabled. - // TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); - // TrustBoundary.setEnvironmentProviderForTest(environmentProvider); - // environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); - // - // // Mock STS and IAM endpoints. - // MockHttpTransport mockHttpTransport = - // new MockHttpTransport.Builder() - // .setLowLevelHttpResponse( - // new MockLowLevelHttpResponse() - // .setContentType(Json.MEDIA_TYPE) - // .setContent( - // String.format( - // "{\"access_token\": \"%s\", \"expires_in\": %s, \"token_type\": - // \"Bearer\"}", - // "sts_access_token", 3600))) - // .setLowLevelHttpResponse( - // new MockLowLevelHttpResponse() - // .setStatusCode(404) - // .setContent("{\"error\": \"not found\"}")) - // .build(); - // - // HttpTransportFactory testingHttpTransportFactory = () -> mockHttpTransport; - // - // ExternalAccountCredentials credentials = - // IdentityPoolCredentials.newBuilder() - // .setHttpTransportFactory(testingHttpTransportFactory) - // .setAudience( - // - // "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/pool/providers/provider") - // .setSubjectTokenType("subjectTokenType") - // .setTokenUrl(STS_URL) - // .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) - // .build(); - // - // // Refresh the token to trigger trust boundary refresh. - // try { - // credentials.refresh(); - // fail("Expected IOException to be thrown."); - // } catch (IOException e) { - // assertEquals( - // "Failed to refresh trust boundary and no cached value is available.", e.getMessage()); - // } - // } - // - // @Test - // public void testRefreshAccessToken_workforce_trustBoundarySuccess() throws IOException { - // // By default, the trust boundary feature is disabled. - // TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); - // TrustBoundary.setEnvironmentProviderForTest(environmentProvider); - // environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); - // - // // Mock STS and IAM endpoints. - // MockHttpTransport mockHttpTransport = - // new MockHttpTransport.Builder() - // .setLowLevelHttpResponse( - // new MockLowLevelHttpResponse() - // .setContentType(Json.MEDIA_TYPE) - // .setContent( - // String.format( - // "{\"access_token\": \"%s\", \"expires_in\": %s, \"token_type\": - // \"Bearer\"}", - // "sts_access_token", 3600))) - // .setLowLevelHttpResponse( - // new MockLowLevelHttpResponse() - // .setContentType(Json.MEDIA_TYPE) - // .setContent( - // "{\"locations\": [\"us-central1\"], \"encodedLocations\": \"0x1\"}")) - // .build(); - // - // HttpTransportFactory testingHttpTransportFactory = () -> mockHttpTransport; - // - // ExternalAccountCredentials credentials = - // IdentityPoolCredentials.newBuilder() - // .setHttpTransportFactory(testingHttpTransportFactory) - // .setAudience( - // "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider") - // .setSubjectTokenType("subjectTokenType") - // .setTokenUrl(STS_URL) - // .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) - // .setWorkforcePoolUserProject("12345") - // .build(); - // - // // Refresh the token to trigger trust boundary refresh. - // credentials.refresh(); - // - // // Verify that the trust boundary was set correctly. - // TrustBoundary trustBoundary = credentials.getTrustBoundary(); - // assertNotNull(trustBoundary); - // assertEquals("0x1", trustBoundary.getEncodedLocations()); - // - // // Verify that the header is added to requests. - // Map> headers = credentials.getRequestMetadata(); - // assertTrue(headers.containsKey(TrustBoundary.TRUST_BOUNDARY_KEY)); - // assertEquals("0x1", headers.get(TrustBoundary.TRUST_BOUNDARY_KEY).get(0)); - // } - private GenericJson buildJsonIdentityPoolCredential() { GenericJson json = new GenericJson(); json.put( @@ -1650,4 +1380,242 @@ public String retrieveSubjectToken() { return "subjectToken"; } } + + @Test + public void refresh_workload_trustBoundarySuccess() throws IOException { + // 1. Scenario Setup + String audience = + "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/my-pool/providers/my-provider"; + + // Enable the trust boundary feature via a mock environment provider. + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + TrustBoundary.setEnvironmentProviderForTest(environmentProvider); + environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); + + // Create the credentials, overriding retrieveSubjectToken() to isolate from the filesystem. + // No extra mock setup is needed, as MockExternalAccountCredentialsTransport handles the rest. + ExternalAccountCredentials credentials = + new IdentityPoolCredentials( + IdentityPoolCredentials.newBuilder() + .setHttpTransportFactory(transportFactory) + .setAudience(audience) + .setSubjectTokenType("subject_token_type") + .setTokenUrl(STS_URL) + .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) + .setEnvironmentProvider(environmentProvider)) { + @Override + public String retrieveSubjectToken() throws IOException { + // This override isolates the test from the filesystem. + return "dummy-subject-token"; + } + }; + + // 2. Action: Refresh the credentials. + credentials.refresh(); + + // 3. Assertion: Verify the trust boundary was set from the mock's hardcoded response. + TrustBoundary trustBoundary = credentials.getTrustBoundary(); + assertNotNull(trustBoundary); + assertEquals("0x800000", trustBoundary.getEncodedLocations()); + + // Cleanup + TrustBoundary.setEnvironmentProviderForTest(null); + } + + @Test + public void getTrustBoundaryUrl_workload() throws IOException { + String audience = + "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/my-pool/providers/my-provider"; + ExternalAccountCredentials credentials = + TestExternalAccountCredentials.newBuilder() + .setAudience(audience) + .setSubjectTokenType("subject_token_type") + .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) + .build(); + + String expectedUrl = + "https://iamcredentials.googleapis.com/v1/projects/12345/locations/global/workloadIdentityPools/my-pool/allowedLocations"; + assertEquals(expectedUrl, credentials.getTrustBoundaryUrl()); + } + + @Test + public void getTrustBoundaryUrl_workforce() throws IOException { + String audience = + "//iam.googleapis.com/locations/global/workforcePools/my-pool/providers/my-provider"; + ExternalAccountCredentials credentials = + TestExternalAccountCredentials.newBuilder() + .setAudience(audience) + .setWorkforcePoolUserProject("12345") + .setSubjectTokenType("subject_token_type") + .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) + .build(); + + String expectedUrl = + "https://iamcredentials.googleapis.com/v1/locations/global/workforcePools/my-pool/allowedLocations"; + assertEquals(expectedUrl, credentials.getTrustBoundaryUrl()); + } + + @Test(expected = IOException.class) + public void getTrustBoundaryUrl_invalidAudience_throws() throws IOException { + ExternalAccountCredentials credentials = + TestExternalAccountCredentials.newBuilder() + .setAudience("invalid-audience") + .setSubjectTokenType("subject_token_type") + .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) + .build(); + credentials.getTrustBoundaryUrl(); + } + + @Test + public void refresh_workforce_trustBoundarySuccess() throws IOException { + String audience = + "//iam.googleapis.com/locations/global/workforcePools/my-pool/providers/my-provider"; + + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + TrustBoundary.setEnvironmentProviderForTest(environmentProvider); + environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); + + ExternalAccountCredentials credentials = + new IdentityPoolCredentials( + IdentityPoolCredentials.newBuilder() + .setHttpTransportFactory(transportFactory) + .setAudience(audience) + .setWorkforcePoolUserProject("12345") + .setSubjectTokenType("subject_token_type") + .setTokenUrl(STS_URL) + .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) + .setEnvironmentProvider(environmentProvider)) { + @Override + public String retrieveSubjectToken() throws IOException { + return "dummy-subject-token"; + } + }; + + credentials.refresh(); + + TrustBoundary trustBoundary = credentials.getTrustBoundary(); + assertNotNull(trustBoundary); + assertEquals("0x800000", trustBoundary.getEncodedLocations()); + + TrustBoundary.setEnvironmentProviderForTest(null); + } + + @Test + public void refresh_impersonated_workload_trustBoundarySuccess() throws IOException { + + String audience = + "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/my-pool/providers/my-provider"; + + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + + TrustBoundary.setEnvironmentProviderForTest(environmentProvider); + + environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); + + // Set an expire time on the mock transport for the impersonation call. + + // Manually create a future timestamp in RFC3339 format. + + long distantFutureMillis = System.currentTimeMillis() + 3600 * 1000; + + java.text.SimpleDateFormat rfc3339 = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + + rfc3339.setTimeZone(java.util.TimeZone.getTimeZone("UTC")); + + String expireTime = rfc3339.format(new java.util.Date(distantFutureMillis)); + + transportFactory.transport.setExpireTime(expireTime); + + // Use a URL-based source that the mock transport can handle, to avoid file IO. + + Map urlCredentialSourceMap = new HashMap<>(); + + urlCredentialSourceMap.put("url", "https://www.metadata.google.com"); + + Map headers = new HashMap<>(); + + headers.put("Metadata-Flavor", "Google"); + + urlCredentialSourceMap.put("headers", headers); + + ExternalAccountCredentials credentials = + IdentityPoolCredentials.newBuilder() + .setHttpTransportFactory(transportFactory) + .setAudience(audience) + .setSubjectTokenType("subject_token_type") + .setTokenUrl(STS_URL) + .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) + .setCredentialSource(new IdentityPoolCredentialSource(urlCredentialSourceMap)) + .setEnvironmentProvider(environmentProvider) + .build(); + + credentials.refresh(); + + TrustBoundary trustBoundary = credentials.getTrustBoundary(); + + assertNotNull(trustBoundary); + + assertEquals("0x800000", trustBoundary.getEncodedLocations()); + + TrustBoundary.setEnvironmentProviderForTest(null); + } + + @Test + public void refresh_impersonated_workforce_trustBoundarySuccess() throws IOException { + + String audience = + "//iam.googleapis.com/locations/global/workforcePools/my-pool/providers/my-provider"; + + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + + TrustBoundary.setEnvironmentProviderForTest(environmentProvider); + + environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); + + // Set an expire time on the mock transport for the impersonation call. + + long distantFutureMillis = System.currentTimeMillis() + 3600 * 1000; + + java.text.SimpleDateFormat rfc3339 = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + + rfc3339.setTimeZone(java.util.TimeZone.getTimeZone("UTC")); + + String expireTime = rfc3339.format(new java.util.Date(distantFutureMillis)); + + transportFactory.transport.setExpireTime(expireTime); + + // Use a URL-based source that the mock transport can handle, to avoid file IO. + + Map urlCredentialSourceMap = new HashMap<>(); + + urlCredentialSourceMap.put("url", "https://www.metadata.google.com"); + + Map headers = new HashMap<>(); + + headers.put("Metadata-Flavor", "Google"); + + urlCredentialSourceMap.put("headers", headers); + + ExternalAccountCredentials credentials = + IdentityPoolCredentials.newBuilder() + .setHttpTransportFactory(transportFactory) + .setAudience(audience) + .setWorkforcePoolUserProject("12345") + .setSubjectTokenType("subject_token_type") + .setTokenUrl(STS_URL) + .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) + .setCredentialSource(new IdentityPoolCredentialSource(urlCredentialSourceMap)) + .setEnvironmentProvider(environmentProvider) + .build(); + + credentials.refresh(); + + TrustBoundary trustBoundary = credentials.getTrustBoundary(); + + assertNotNull(trustBoundary); + + assertEquals("0x800000", trustBoundary.getEncodedLocations()); + + TrustBoundary.setEnvironmentProviderForTest(null); + } } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java index 0d3238ee9..be498d3c5 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java @@ -1305,32 +1305,30 @@ void setShouldThrowOnGetCertificatePath(boolean shouldThrow) { } } - @Test - public void testRefresh_trustBoundarySuccess() throws IOException { - TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); - TrustBoundary.setEnvironmentProviderForTest(environmentProvider); - environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); + @Test + public void testRefresh_trustBoundarySuccess() throws IOException { + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + TrustBoundary.setEnvironmentProviderForTest(environmentProvider); + environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); - MockExternalAccountCredentialsTransportFactory transportFactory = - new MockExternalAccountCredentialsTransportFactory(); - HttpTransportFactory testingHttpTransportFactory = transportFactory; + MockExternalAccountCredentialsTransportFactory transportFactory = + new MockExternalAccountCredentialsTransportFactory(); + HttpTransportFactory testingHttpTransportFactory = transportFactory; - IdentityPoolCredentials credentials = - IdentityPoolCredentials.newBuilder() - .setSubjectTokenSupplier(testProvider) - .setHttpTransportFactory(testingHttpTransportFactory) - .setAudience( - "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/pool/providers/provider") - .setSubjectTokenType("subjectTokenType") - .setTokenUrl(STS_URL) - .build(); + IdentityPoolCredentials credentials = + IdentityPoolCredentials.newBuilder() + .setSubjectTokenSupplier(testProvider) + .setHttpTransportFactory(testingHttpTransportFactory) + .setAudience( + "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/pool/providers/provider") + .setSubjectTokenType("subjectTokenType") + .setTokenUrl(STS_URL) + .build(); - credentials.refresh(); + credentials.refresh(); - TrustBoundary trustBoundary = credentials.getTrustBoundary(); - assertNotNull(trustBoundary); - assertEquals( - "0x800000", - trustBoundary.getEncodedLocations()); - } + TrustBoundary trustBoundary = credentials.getTrustBoundary(); + assertNotNull(trustBoundary); + assertEquals("0x800000", trustBoundary.getEncodedLocations()); + } } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java index 87225c464..cd458cd35 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java @@ -31,6 +31,7 @@ package com.google.auth.oauth2; +import static com.google.auth.oauth2.TrustBoundary.TRUST_BOUNDARY_KEY; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -64,13 +65,7 @@ import java.text.DateFormat; import java.text.SimpleDateFormat; import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.TimeZone; +import java.util.*; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -155,7 +150,8 @@ public class ImpersonatedCredentialsTest extends BaseSerializationTest { public static final List DELEGATES = Arrays.asList("sa1@developer.gserviceaccount.com", "sa2@developer.gserviceaccount.com"); public static final TrustBoundary TRUST_BOUNDARY = - new TrustBoundary("0x80000", Arrays.asList("us-central1")); + new TrustBoundary( + TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION, TestUtils.TRUST_BOUNDARY_LOCATIONS); private GoogleCredentials sourceCredentials; private MockIAMCredentialsServiceTransportFactory mockTransportFactory; @@ -1338,7 +1334,9 @@ public void refresh_trustBoundarySuccess() throws IOException { mockTransportFactory); Map> headers = targetCredentials.getRequestMetadata(); - assertEquals(headers.get("x-allowed-locations"), Arrays.asList("0x80000")); + assertEquals( + headers.get(TRUST_BOUNDARY_KEY), + Collections.singletonList(TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION)); } @Test @@ -1347,9 +1345,6 @@ public void refresh_trustBoundaryFails_throwsIOException() throws IOException { TrustBoundary.setEnvironmentProviderForTest(environmentProvider); environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); - // Mock trust boundary response - TrustBoundary trustBoundary = TRUST_BOUNDARY; - mockTransportFactory.getTransport().setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); mockTransportFactory.getTransport().setAccessToken(ACCESS_TOKEN); mockTransportFactory.getTransport().setExpireTime(getDefaultExpireTime()); diff --git a/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java b/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java index f088678b3..8a08be300 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java @@ -195,11 +195,13 @@ public LowLevelHttpResponse execute() throws IOException { if (url.contains(IAM_ENDPOINT)) { if (url.endsWith(TRUST_BOUNDARY_URL_END)) { + // Mocking call to refresh trust boundaries. + // The lookup endpoint is located in the IAM server which is different from the + // token server. GenericJson responseJson = new GenericJson(); responseJson.setFactory(OAuth2Utils.JSON_FACTORY); - String encodedLocations = "0x800000"; - responseJson.put("encodedLocations", encodedLocations); - responseJson.put("locations", Arrays.asList("us-east-1", "us-east-2")); + responseJson.put("encodedLocations", TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION); + responseJson.put("locations", TestUtils.TRUST_BOUNDARY_LOCATIONS); String content = responseJson.toPrettyString(); return new MockLowLevelHttpResponse() .setContentType(Json.MEDIA_TYPE) diff --git a/oauth2_http/javatests/com/google/auth/oauth2/MockMetadataServerTransport.java b/oauth2_http/javatests/com/google/auth/oauth2/MockMetadataServerTransport.java index 057d19b94..3fed30ef4 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/MockMetadataServerTransport.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/MockMetadataServerTransport.java @@ -388,8 +388,9 @@ public LowLevelHttpResponse execute() throws IOException { } protected boolean isIamLookupUrl(String url) { - // This is for mocking the call to IAM lookup when we attempt to refresh trust boundaries - // after refreshing the access token. + // Mocking call to refresh trust boundaries. + // The lookup endpoint is located in the IAM server which is different from the + // metadata server. return url.endsWith("/allowedLocations"); } } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/MockStsTransport.java b/oauth2_http/javatests/com/google/auth/oauth2/MockStsTransport.java index e4c51569d..989bd4dd7 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/MockStsTransport.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/MockStsTransport.java @@ -60,7 +60,8 @@ public final class MockStsTransport extends MockHttpTransport { private static final String EXPECTED_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange"; private static final String ISSUED_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token"; - private static final String VALID_STS_PATTERN = "https:\\/\\/sts.[a-z-_\\.]+\\/v1\\/(token|oauthtoken)"; + private static final String VALID_STS_PATTERN = + "https:\\/\\/sts.[a-z-_\\.]+\\/v1\\/(token|oauthtoken)"; private static final String VALID_TRUST_BOUNDARY_PATTERN = "https:\\/\\/iam.[a-z-_\\.]+\\/v1\\/.*\\/allowedLocations"; private static final String ACCESS_TOKEN = "accessToken"; @@ -100,7 +101,8 @@ public LowLevelHttpRequest buildRequest(final String method, final String url) { new MockLowLevelHttpRequest(url) { @Override public LowLevelHttpResponse execute() throws IOException { - Matcher trustBoundaryMatcher = Pattern.compile(VALID_TRUST_BOUNDARY_PATTERN).matcher(url); + Matcher trustBoundaryMatcher = + Pattern.compile(VALID_TRUST_BOUNDARY_PATTERN).matcher(url); if (trustBoundaryMatcher.matches()) { GenericJson response = new GenericJson(); response.setFactory(new GsonFactory()); @@ -213,4 +215,4 @@ public void setReturnAccessBoundarySessionKey(boolean returnAccessBoundarySessio public int getRequestCount() { return requestCount; } -} \ No newline at end of file +} diff --git a/oauth2_http/javatests/com/google/auth/oauth2/MockTokenServerTransport.java b/oauth2_http/javatests/com/google/auth/oauth2/MockTokenServerTransport.java index c0b9df754..819267102 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/MockTokenServerTransport.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/MockTokenServerTransport.java @@ -327,8 +327,9 @@ public LowLevelHttpResponse execute() throws IOException { }; return request; } else if (urlWithoutQuery.endsWith("/allowedLocations")) { - // This is for mocking the call to IAM lookup when we attempt to refresh trust boundaries - // after refreshing the access token. + // Mocking call to refresh trust boundaries. + // The lookup endpoint is located in the IAM server which is different from the + // token server. request = new MockLowLevelHttpRequest(url) { @Override diff --git a/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java index 4ce34c3f4..56e9c5547 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java @@ -603,61 +603,39 @@ public void serialize() throws IOException, ClassNotFoundException { assertThrows(NotSerializableException.class, () -> serializeAndDeserialize(testCredentials)); } - @Test - - public void testRefresh_trustBoundarySuccess() throws IOException { - - TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); - - TrustBoundary.setEnvironmentProviderForTest(environmentProvider); - - environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); - - - - MockExternalAccountCredentialsTransportFactory transportFactory = - - new MockExternalAccountCredentialsTransportFactory(); - - - - transportFactory.transport.setExpireTime(TestUtils.getDefaultExpireTime()); - - - - PluggableAuthCredentials credentials = - - PluggableAuthCredentials.newBuilder() - - .setHttpTransportFactory(transportFactory) - - .setAudience( - - "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/pool/providers/provider") - - .setSubjectTokenType("subjectTokenType") - - .setTokenUrl(transportFactory.transport.getStsUrl()) + @Test + public void testRefresh_trustBoundarySuccess() throws IOException { - .setCredentialSource(buildCredentialSource()) + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); - .setExecutableHandler(options -> "pluggableAuthToken") + TrustBoundary.setEnvironmentProviderForTest(environmentProvider); - .build(); + environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); - + MockExternalAccountCredentialsTransportFactory transportFactory = + new MockExternalAccountCredentialsTransportFactory(); - credentials.refresh(); + transportFactory.transport.setExpireTime(TestUtils.getDefaultExpireTime()); - + PluggableAuthCredentials credentials = + PluggableAuthCredentials.newBuilder() + .setHttpTransportFactory(transportFactory) + .setAudience( + "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/pool/providers/provider") + .setSubjectTokenType("subjectTokenType") + .setTokenUrl(transportFactory.transport.getStsUrl()) + .setCredentialSource(buildCredentialSource()) + .setExecutableHandler(options -> "pluggableAuthToken") + .build(); - TrustBoundary trustBoundary = credentials.getTrustBoundary(); + credentials.refresh(); - assertNotNull(trustBoundary); + TrustBoundary trustBoundary = credentials.getTrustBoundary(); - assertEquals("0x800000", trustBoundary.getEncodedLocations()); + assertNotNull(trustBoundary); - } + assertEquals("0x800000", trustBoundary.getEncodedLocations()); + } private static PluggableAuthCredentialSource buildCredentialSource() { return buildCredentialSource("command", null, null); diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java index 1407f024f..ca275f1e3 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java @@ -31,6 +31,7 @@ package com.google.auth.oauth2; +import static com.google.auth.oauth2.TrustBoundary.TRUST_BOUNDARY_KEY; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -1816,15 +1817,16 @@ public void refresh_trustBoundarySuccess() throws IOException { // Mock trust boundary response TrustBoundary trustBoundary = - new TrustBoundary("0x80000", Collections.singletonList("us-central1")); + new TrustBoundary( + TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION, TestUtils.TRUST_BOUNDARY_LOCATIONS); MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addServiceAccount("test-client-email@example.com", "test-access-token"); + transport.addServiceAccount(CLIENT_EMAIL, "test-access-token"); transport.setTrustBoundary(trustBoundary); ServiceAccountCredentials credentials = ServiceAccountCredentials.newBuilder() - .setClientEmail("test-client-email@example.com") + .setClientEmail(CLIENT_EMAIL) .setPrivateKey( OAuth2Utils.privateKeyFromPkcs8(ServiceAccountCredentialsTest.PRIVATE_KEY_PKCS8)) .setPrivateKeyId("test-key-id") @@ -1833,7 +1835,8 @@ public void refresh_trustBoundarySuccess() throws IOException { .build(); Map> headers = credentials.getRequestMetadata(); - assertEquals(headers.get("x-allowed-locations"), Arrays.asList("0x80000")); + assertEquals( + headers.get(TRUST_BOUNDARY_KEY), Arrays.asList(TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION)); } @Test @@ -1843,11 +1846,11 @@ public void refresh_trustBoundaryFails_throwsIOException() throws IOException { environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addServiceAccount("test-client-email@example.com", "test-access-token"); + transport.addServiceAccount(CLIENT_EMAIL, "test-access-token"); ServiceAccountCredentials credentials = ServiceAccountCredentials.newBuilder() - .setClientEmail("test-client-email@example.com") + .setClientEmail(CLIENT_EMAIL) .setPrivateKey( OAuth2Utils.privateKeyFromPkcs8(ServiceAccountCredentialsTest.PRIVATE_KEY_PKCS8)) .setPrivateKeyId("test-key-id") From 458bad44f22a4a7a6449f0eccc3e998954ea3691 Mon Sep 17 00:00:00 2001 From: Pranav Iyer Date: Mon, 27 Oct 2025 16:21:33 -0700 Subject: [PATCH 11/18] Some nit fixes. --- .../oauth2/ExternalAccountCredentials.java | 4 - ...lAccountAuthorizedUserCredentialsTest.java | 9 +- .../ExternalAccountCredentialsTest.java | 415 ++++++++---------- .../auth/oauth2/GoogleCredentialsTest.java | 32 +- .../oauth2/ImpersonatedCredentialsTest.java | 9 +- ...ckExternalAccountCredentialsTransport.java | 7 +- 6 files changed, 230 insertions(+), 246 deletions(-) diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java index 356c31206..40e2169d0 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java @@ -638,10 +638,6 @@ public String getServiceAccountEmail() { public String getTrustBoundaryUrl() throws IOException { if (isWorkforcePoolConfiguration()) { Matcher matcher = WORKFORCE_PATTERN.matcher(getAudience()); - if (!matcher.matches()) { - throw new IOException( - "The provided audience is not in the correct format for a workforce pool."); - } String poolId = matcher.group("pool"); return String.format(WORKFORCE_POOL_URL_FORMAT, poolId); } else { diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java index 20c56221a..fa3158806 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java @@ -32,7 +32,14 @@ package com.google.auth.oauth2; import static com.google.auth.Credentials.GOOGLE_DEFAULT_UNIVERSE; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import com.google.api.client.http.HttpTransport; import com.google.api.client.json.GenericJson; diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java index 07f3297cd..d374f896e 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java @@ -32,7 +32,12 @@ package com.google.auth.oauth2; import static com.google.auth.oauth2.MockExternalAccountCredentialsTransport.SERVICE_ACCOUNT_IMPERSONATION_URL; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import com.google.api.client.http.HttpTransport; import com.google.api.client.json.GenericJson; @@ -1228,7 +1233,7 @@ public void validateServiceAccountImpersonationUrls_invalidUrls() { "https://", "http://iamcredentials.googleapis.com", "https:/iamcredentials.googleapis.com", - "https://us-eas\\t-1.iamcredentials.googleapis.com", + "https://us-eas\t-1.iamcredentials.googleapis.com", "testhttps://us-east-1.iamcredentials.googleapis.com", "hhttps://us-east-1.iamcredentials.googleapis.com", "https://us- -1.iamcredentials.googleapis.com"); @@ -1243,142 +1248,48 @@ public void validateServiceAccountImpersonationUrls_invalidUrls() { } } - private GenericJson buildJsonIdentityPoolCredential() { - GenericJson json = new GenericJson(); - json.put( - "audience", - "//iam.googleapis.com/projects/123/locations/global/workloadIdentityPools/pool/providers/provider"); - json.put("subject_token_type", "subjectTokenType"); - json.put("token_url", STS_URL); - json.put("token_info_url", "tokenInfoUrl"); - - Map map = new HashMap<>(); - map.put("file", "file"); - json.put("credential_source", map); - return json; - } - - private GenericJson buildJsonIdentityPoolWorkforceCredential() { - GenericJson json = buildJsonIdentityPoolCredential(); - json.put( - "audience", "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider"); - json.put("workforce_pool_user_project", "userProject"); - return json; - } - - private GenericJson buildJsonAwsCredential() { - GenericJson json = new GenericJson(); - json.put("audience", "audience"); - json.put("subject_token_type", "subjectTokenType"); - json.put("token_url", STS_URL); - json.put("token_info_url", "tokenInfoUrl"); - - Map map = new HashMap<>(); - map.put("environment_id", "aws1"); - map.put("region_url", "https://169.254.169.254/region"); - map.put("url", "https://169.254.169.254/"); - map.put("regional_cred_verification_url", "regionalCredVerificationUrl"); - json.put("credential_source", map); - - return json; - } - - private GenericJson buildJsonPluggableAuthCredential() { - GenericJson json = new GenericJson(); - json.put("audience", "audience"); - json.put("subject_token_type", "subjectTokenType"); - json.put("token_url", STS_URL); - json.put("token_info_url", "tokenInfoUrl"); - - Map> credentialSource = new HashMap<>(); - - Map executableConfig = new HashMap<>(); - executableConfig.put("command", "command"); - - credentialSource.put("executable", executableConfig); - json.put("credential_source", credentialSource); - - return json; - } + @Test + public void getTrustBoundaryUrl_workload() throws IOException { + String audience = + "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/my-pool/providers/my-provider"; + ExternalAccountCredentials credentials = + TestExternalAccountCredentials.newBuilder() + .setAudience(audience) + .setSubjectTokenType("subject_token_type") + .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) + .build(); - private GenericJson buildJsonPluggableAuthWorkforceCredential() { - GenericJson json = buildJsonPluggableAuthCredential(); - json.put( - "audience", "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider"); - json.put("workforce_pool_user_project", "userProject"); - return json; + String expectedUrl = + "https://iamcredentials.googleapis.com/v1/projects/12345/locations/global/workloadIdentityPools/my-pool/allowedLocations"; + assertEquals(expectedUrl, credentials.getTrustBoundaryUrl()); } - static Map buildServiceAccountImpersonationOptions(Integer lifetime) { - Map map = new HashMap(); - map.put("token_lifetime_seconds", lifetime); - - return map; - } + @Test + public void getTrustBoundaryUrl_workforce() throws IOException { + String audience = + "//iam.googleapis.com/locations/global/workforcePools/my-pool/providers/my-provider"; + ExternalAccountCredentials credentials = + TestExternalAccountCredentials.newBuilder() + .setAudience(audience) + .setWorkforcePoolUserProject("12345") + .setSubjectTokenType("subject_token_type") + .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) + .build(); - static void validateMetricsHeader( - Map> headers, - String source, - boolean saImpersonationUsed, - boolean configLifetimeUsed) { - assertTrue(headers.containsKey(MetricsUtils.API_CLIENT_HEADER)); - String actualMetricsValue = headers.get(MetricsUtils.API_CLIENT_HEADER).get(0); - String expectedMetricsValue = - String.format( - "%s google-byoid-sdk source/%s sa-impersonation/%s config-lifetime/%s", - MetricsUtils.getLanguageAndAuthLibraryVersions(), - source, - saImpersonationUsed, - configLifetimeUsed); - assertEquals(expectedMetricsValue, actualMetricsValue); + String expectedUrl = + "https://iamcredentials.googleapis.com/v1/locations/global/workforcePools/my-pool/allowedLocations"; + assertEquals(expectedUrl, credentials.getTrustBoundaryUrl()); } - static class TestExternalAccountCredentials extends ExternalAccountCredentials { - static class TestCredentialSource extends IdentityPoolCredentialSource { - protected TestCredentialSource(Map credentialSourceMap) { - super(credentialSourceMap); - } - } - - @Override - public Builder toBuilder() { - return new Builder(this); - } - - public static Builder newBuilder() { - return new Builder(); - } - - static class Builder extends ExternalAccountCredentials.Builder { - Builder() {} - - Builder(TestExternalAccountCredentials credentials) { - super(credentials); - } - - @Override - public TestExternalAccountCredentials build() { - return new TestExternalAccountCredentials(this); - } - - public HttpTransportFactory getHttpTransportFactory() { - return transportFactory; - } - } - - protected TestExternalAccountCredentials(ExternalAccountCredentials.Builder builder) { - super(builder); - } - - @Override - public AccessToken refreshAccessToken() { - return new AccessToken("accessToken", new Date()); - } - - @Override - public String retrieveSubjectToken() { - return "subjectToken"; - } + @Test(expected = IOException.class) + public void getTrustBoundaryUrl_invalidAudience_throws() throws IOException { + ExternalAccountCredentials credentials = + TestExternalAccountCredentials.newBuilder() + .setAudience("invalid-audience") + .setSubjectTokenType("subject_token_type") + .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) + .build(); + credentials.getTrustBoundaryUrl(); } @Test @@ -1392,8 +1303,6 @@ public void refresh_workload_trustBoundarySuccess() throws IOException { TrustBoundary.setEnvironmentProviderForTest(environmentProvider); environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); - // Create the credentials, overriding retrieveSubjectToken() to isolate from the filesystem. - // No extra mock setup is needed, as MockExternalAccountCredentialsTransport handles the rest. ExternalAccountCredentials credentials = new IdentityPoolCredentials( IdentityPoolCredentials.newBuilder() @@ -1416,56 +1325,12 @@ public String retrieveSubjectToken() throws IOException { // 3. Assertion: Verify the trust boundary was set from the mock's hardcoded response. TrustBoundary trustBoundary = credentials.getTrustBoundary(); assertNotNull(trustBoundary); - assertEquals("0x800000", trustBoundary.getEncodedLocations()); + assertEquals(TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION, trustBoundary.getEncodedLocations()); // Cleanup TrustBoundary.setEnvironmentProviderForTest(null); } - @Test - public void getTrustBoundaryUrl_workload() throws IOException { - String audience = - "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/my-pool/providers/my-provider"; - ExternalAccountCredentials credentials = - TestExternalAccountCredentials.newBuilder() - .setAudience(audience) - .setSubjectTokenType("subject_token_type") - .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) - .build(); - - String expectedUrl = - "https://iamcredentials.googleapis.com/v1/projects/12345/locations/global/workloadIdentityPools/my-pool/allowedLocations"; - assertEquals(expectedUrl, credentials.getTrustBoundaryUrl()); - } - - @Test - public void getTrustBoundaryUrl_workforce() throws IOException { - String audience = - "//iam.googleapis.com/locations/global/workforcePools/my-pool/providers/my-provider"; - ExternalAccountCredentials credentials = - TestExternalAccountCredentials.newBuilder() - .setAudience(audience) - .setWorkforcePoolUserProject("12345") - .setSubjectTokenType("subject_token_type") - .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) - .build(); - - String expectedUrl = - "https://iamcredentials.googleapis.com/v1/locations/global/workforcePools/my-pool/allowedLocations"; - assertEquals(expectedUrl, credentials.getTrustBoundaryUrl()); - } - - @Test(expected = IOException.class) - public void getTrustBoundaryUrl_invalidAudience_throws() throws IOException { - ExternalAccountCredentials credentials = - TestExternalAccountCredentials.newBuilder() - .setAudience("invalid-audience") - .setSubjectTokenType("subject_token_type") - .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) - .build(); - credentials.getTrustBoundaryUrl(); - } - @Test public void refresh_workforce_trustBoundarySuccess() throws IOException { String audience = @@ -1495,47 +1360,26 @@ public String retrieveSubjectToken() throws IOException { TrustBoundary trustBoundary = credentials.getTrustBoundary(); assertNotNull(trustBoundary); - assertEquals("0x800000", trustBoundary.getEncodedLocations()); + assertEquals(TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION, trustBoundary.getEncodedLocations()); TrustBoundary.setEnvironmentProviderForTest(null); } @Test public void refresh_impersonated_workload_trustBoundarySuccess() throws IOException { - String audience = "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/my-pool/providers/my-provider"; - TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); - TrustBoundary.setEnvironmentProviderForTest(environmentProvider); - environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); - // Set an expire time on the mock transport for the impersonation call. - - // Manually create a future timestamp in RFC3339 format. - - long distantFutureMillis = System.currentTimeMillis() + 3600 * 1000; - - java.text.SimpleDateFormat rfc3339 = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - - rfc3339.setTimeZone(java.util.TimeZone.getTimeZone("UTC")); - - String expireTime = rfc3339.format(new java.util.Date(distantFutureMillis)); - - transportFactory.transport.setExpireTime(expireTime); + transportFactory.transport.setExpireTime(TestUtils.getDefaultExpireTime()); // Use a URL-based source that the mock transport can handle, to avoid file IO. - Map urlCredentialSourceMap = new HashMap<>(); - urlCredentialSourceMap.put("url", "https://www.metadata.google.com"); - Map headers = new HashMap<>(); - headers.put("Metadata-Flavor", "Google"); - urlCredentialSourceMap.put("headers", headers); ExternalAccountCredentials credentials = @@ -1552,48 +1396,27 @@ public void refresh_impersonated_workload_trustBoundarySuccess() throws IOExcept credentials.refresh(); TrustBoundary trustBoundary = credentials.getTrustBoundary(); - assertNotNull(trustBoundary); - - assertEquals("0x800000", trustBoundary.getEncodedLocations()); + assertEquals(TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION, trustBoundary.getEncodedLocations()); TrustBoundary.setEnvironmentProviderForTest(null); } @Test public void refresh_impersonated_workforce_trustBoundarySuccess() throws IOException { - String audience = "//iam.googleapis.com/locations/global/workforcePools/my-pool/providers/my-provider"; - TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); - TrustBoundary.setEnvironmentProviderForTest(environmentProvider); - environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); - // Set an expire time on the mock transport for the impersonation call. - - long distantFutureMillis = System.currentTimeMillis() + 3600 * 1000; - - java.text.SimpleDateFormat rfc3339 = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - - rfc3339.setTimeZone(java.util.TimeZone.getTimeZone("UTC")); - - String expireTime = rfc3339.format(new java.util.Date(distantFutureMillis)); - - transportFactory.transport.setExpireTime(expireTime); + transportFactory.transport.setExpireTime(TestUtils.getDefaultExpireTime()); // Use a URL-based source that the mock transport can handle, to avoid file IO. - Map urlCredentialSourceMap = new HashMap<>(); - urlCredentialSourceMap.put("url", "https://www.metadata.google.com"); - Map headers = new HashMap<>(); - headers.put("Metadata-Flavor", "Google"); - urlCredentialSourceMap.put("headers", headers); ExternalAccountCredentials credentials = @@ -1611,11 +1434,147 @@ public void refresh_impersonated_workforce_trustBoundarySuccess() throws IOExcep credentials.refresh(); TrustBoundary trustBoundary = credentials.getTrustBoundary(); - assertNotNull(trustBoundary); - - assertEquals("0x800000", trustBoundary.getEncodedLocations()); + assertEquals(TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION, trustBoundary.getEncodedLocations()); TrustBoundary.setEnvironmentProviderForTest(null); } + + private GenericJson buildJsonIdentityPoolCredential() { + GenericJson json = new GenericJson(); + json.put( + "audience", + "//iam.googleapis.com/projects/123/locations/global/workloadIdentityPools/pool/providers/provider"); + json.put("subject_token_type", "subjectTokenType"); + json.put("token_url", STS_URL); + json.put("token_info_url", "tokenInfoUrl"); + + Map map = new HashMap<>(); + map.put("file", "file"); + json.put("credential_source", map); + return json; + } + + private GenericJson buildJsonIdentityPoolWorkforceCredential() { + GenericJson json = buildJsonIdentityPoolCredential(); + json.put( + "audience", "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider"); + json.put("workforce_pool_user_project", "userProject"); + return json; + } + + private GenericJson buildJsonAwsCredential() { + GenericJson json = new GenericJson(); + json.put("audience", "audience"); + json.put("subject_token_type", "subjectTokenType"); + json.put("token_url", STS_URL); + json.put("token_info_url", "tokenInfoUrl"); + + Map map = new HashMap<>(); + map.put("environment_id", "aws1"); + map.put("region_url", "https://169.254.169.254/region"); + map.put("url", "https://169.254.169.254/"); + map.put("regional_cred_verification_url", "regionalCredVerificationUrl"); + json.put("credential_source", map); + + return json; + } + + private GenericJson buildJsonPluggableAuthCredential() { + GenericJson json = new GenericJson(); + json.put("audience", "audience"); + json.put("subject_token_type", "subjectTokenType"); + json.put("token_url", STS_URL); + json.put("token_info_url", "tokenInfoUrl"); + + Map> credentialSource = new HashMap<>(); + + Map executableConfig = new HashMap<>(); + executableConfig.put("command", "command"); + + credentialSource.put("executable", executableConfig); + json.put("credential_source", credentialSource); + + return json; + } + + private GenericJson buildJsonPluggableAuthWorkforceCredential() { + GenericJson json = buildJsonPluggableAuthCredential(); + json.put( + "audience", "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider"); + json.put("workforce_pool_user_project", "userProject"); + return json; + } + + static Map buildServiceAccountImpersonationOptions(Integer lifetime) { + Map map = new HashMap(); + map.put("token_lifetime_seconds", lifetime); + + return map; + } + + static void validateMetricsHeader( + Map> headers, + String source, + boolean saImpersonationUsed, + boolean configLifetimeUsed) { + assertTrue(headers.containsKey(MetricsUtils.API_CLIENT_HEADER)); + String actualMetricsValue = headers.get(MetricsUtils.API_CLIENT_HEADER).get(0); + String expectedMetricsValue = + String.format( + "%s google-byoid-sdk source/%s sa-impersonation/%s config-lifetime/%s", + MetricsUtils.getLanguageAndAuthLibraryVersions(), + source, + saImpersonationUsed, + configLifetimeUsed); + assertEquals(expectedMetricsValue, actualMetricsValue); + } + + static class TestExternalAccountCredentials extends ExternalAccountCredentials { + static class TestCredentialSource extends IdentityPoolCredentialSource { + protected TestCredentialSource(Map credentialSourceMap) { + super(credentialSourceMap); + } + } + + @Override + public Builder toBuilder() { + return new Builder(this); + } + + public static Builder newBuilder() { + return new Builder(); + } + + static class Builder extends ExternalAccountCredentials.Builder { + Builder() {} + + Builder(TestExternalAccountCredentials credentials) { + super(credentials); + } + + @Override + public TestExternalAccountCredentials build() { + return new TestExternalAccountCredentials(this); + } + + public HttpTransportFactory getHttpTransportFactory() { + return transportFactory; + } + } + + protected TestExternalAccountCredentials(ExternalAccountCredentials.Builder builder) { + super(builder); + } + + @Override + public AccessToken refreshAccessToken() { + return new AccessToken("accessToken", new Date()); + } + + @Override + public String retrieveSubjectToken() { + return "subjectToken"; + } + } } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java index b8f8c76e9..edf8b104a 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java @@ -31,6 +31,7 @@ package com.google.auth.oauth2; +import static com.google.auth.oauth2.TrustBoundary.TRUST_BOUNDARY_KEY; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -977,7 +978,8 @@ public void trustBoundary_shouldFetchAndReturnTrustBoundaryDataSuccessfully() th MockTokenServerTransport transport = new MockTokenServerTransport(); transport.addServiceAccount(SA_CLIENT_EMAIL, ACCESS_TOKEN); TrustBoundary trustBoundary = - new TrustBoundary("0x80000", Collections.singletonList("us-central1")); + new TrustBoundary( + TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION, Collections.singletonList("us-central1")); transport.setTrustBoundary(trustBoundary); ServiceAccountCredentials credentials = @@ -990,7 +992,8 @@ public void trustBoundary_shouldFetchAndReturnTrustBoundaryDataSuccessfully() th .build(); Map> headers = credentials.getRequestMetadata(); - assertEquals(headers.get("x-allowed-locations"), Arrays.asList("0x80000")); + assertEquals( + headers.get(TRUST_BOUNDARY_KEY), Arrays.asList(TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION)); } @Test @@ -1004,7 +1007,8 @@ public void trustBoundary_shouldRetryTrustBoundaryLookupOnFailure() throws IOExc MockTokenServerTransport trustBoundaryTransport = new MockTokenServerTransport(); trustBoundaryTransport.addResponseErrorSequence(new IOException("Service Unavailable")); TrustBoundary trustBoundary = - new TrustBoundary("0x80000", Collections.singletonList("us-central1")); + new TrustBoundary( + TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION, TestUtils.TRUST_BOUNDARY_LOCATIONS); trustBoundaryTransport.setTrustBoundary(trustBoundary); // This transport will be used for the access token refresh. @@ -1034,7 +1038,8 @@ public com.google.api.client.http.LowLevelHttpRequest buildRequest( .build(); Map> headers = credentials.getRequestMetadata(); - assertEquals(headers.get("x-allowed-locations"), Arrays.asList("0x80000")); + assertEquals( + Arrays.asList(TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION), headers.get(TRUST_BOUNDARY_KEY)); } @Test @@ -1135,7 +1140,8 @@ public void trustBoundary_refreshShouldReturnNoOpAndNotCallLookupEndpointWhenCac // Set trust boundary to a valid non No-Op value. transport.setTrustBoundary( - new TrustBoundary("0x80000", Collections.singletonList("us-central1"))); + new TrustBoundary( + TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION, TestUtils.TRUST_BOUNDARY_LOCATIONS)); // Refresh trust boundaries credentials.refresh(); @@ -1153,7 +1159,8 @@ public void trustBoundary_refreshShouldReturnCachedTbIfCallToLookupFails() throw MockTokenServerTransport transport = new MockTokenServerTransport(); transport.addServiceAccount(SA_CLIENT_EMAIL, ACCESS_TOKEN); TrustBoundary trustBoundary = - new TrustBoundary("0x80000", Collections.singletonList("us-central1")); + new TrustBoundary( + TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION, TestUtils.TRUST_BOUNDARY_LOCATIONS); transport.setTrustBoundary(trustBoundary); ServiceAccountCredentials credentials = @@ -1174,7 +1181,8 @@ public void trustBoundary_refreshShouldReturnCachedTbIfCallToLookupFails() throw credentials.refresh(); assertEquals( - trustBoundary.getEncodedLocations(), credentials.getTrustBoundary().getEncodedLocations()); + TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION, + credentials.getTrustBoundary().getEncodedLocations()); } @Test @@ -1239,7 +1247,8 @@ public void trustBoundary_getRequestHeadersShouldAttachTrustBoundaryHeader() thr MockTokenServerTransport transport = new MockTokenServerTransport(); transport.addServiceAccount(SA_CLIENT_EMAIL, ACCESS_TOKEN); TrustBoundary trustBoundary = - new TrustBoundary("0x80000", Collections.singletonList("us-central1")); + new TrustBoundary( + TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION, Collections.singletonList("us-central1")); transport.setTrustBoundary(trustBoundary); ServiceAccountCredentials credentials = @@ -1253,7 +1262,8 @@ public void trustBoundary_getRequestHeadersShouldAttachTrustBoundaryHeader() thr Map> headers = credentials.getRequestMetadata(); - assertEquals(Arrays.asList("0x80000"), headers.get("x-allowed-locations")); + assertEquals( + Arrays.asList(TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION), headers.get(TRUST_BOUNDARY_KEY)); } @Test @@ -1278,7 +1288,7 @@ public void trustBoundary_getRequestHeadersShouldAttachEmptyStringTbHeaderInCase Map> headers = credentials.getRequestMetadata(); - assertEquals(Arrays.asList(""), headers.get("x-allowed-locations")); + assertEquals(Arrays.asList(""), headers.get(TRUST_BOUNDARY_KEY)); } @Test @@ -1303,6 +1313,6 @@ public void trustBoundary_getRequestHeadersShouldNotAttachTbHeaderInCaseOfNonGdu Map> headers = credentials.getRequestMetadata(); - assertNull(headers.get("x-allowed-locations")); + assertNull(headers.get(TRUST_BOUNDARY_KEY)); } } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java index cd458cd35..abe5d4ed7 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java @@ -65,7 +65,14 @@ import java.text.DateFormat; import java.text.SimpleDateFormat; import java.time.temporal.ChronoUnit; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; import org.junit.After; import org.junit.Before; import org.junit.Test; diff --git a/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java b/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java index 8a08be300..07424a724 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java @@ -47,7 +47,12 @@ import com.google.auth.TestUtils; import com.google.common.base.Joiner; import java.io.IOException; -import java.util.*; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Queue; /** * Mock transport that handles the necessary steps to exchange an external credential for a GCP From 8cd8f2fe6dd3d492869b1eddbce636375dda1f51 Mon Sep 17 00:00:00 2001 From: Pranav Iyer Date: Tue, 28 Oct 2025 20:53:21 -0700 Subject: [PATCH 12/18] Fixed test failures. --- .../oauth2/ExternalAccountCredentials.java | 4 ++++ .../javatests/com/google/auth/TestUtils.java | 2 +- .../auth/oauth2/AwsCredentialsTest.java | 3 ++- ...lAccountAuthorizedUserCredentialsTest.java | 5 +++-- .../ExternalAccountCredentialsTest.java | 20 ++++++------------- .../oauth2/IdentityPoolCredentialsTest.java | 3 ++- ...ckExternalAccountCredentialsTransport.java | 5 ----- .../google/auth/oauth2/MockStsTransport.java | 9 +++++---- .../oauth2/PluggableAuthCredentialsTest.java | 10 ++-------- 9 files changed, 25 insertions(+), 36 deletions(-) diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java index 40e2169d0..356c31206 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java @@ -638,6 +638,10 @@ public String getServiceAccountEmail() { public String getTrustBoundaryUrl() throws IOException { if (isWorkforcePoolConfiguration()) { Matcher matcher = WORKFORCE_PATTERN.matcher(getAudience()); + if (!matcher.matches()) { + throw new IOException( + "The provided audience is not in the correct format for a workforce pool."); + } String poolId = matcher.group("pool"); return String.format(WORKFORCE_POOL_URL_FORMAT, poolId); } else { diff --git a/oauth2_http/javatests/com/google/auth/TestUtils.java b/oauth2_http/javatests/com/google/auth/TestUtils.java index e933e1044..75f0d18e4 100644 --- a/oauth2_http/javatests/com/google/auth/TestUtils.java +++ b/oauth2_http/javatests/com/google/auth/TestUtils.java @@ -150,7 +150,7 @@ public static HttpResponseException buildHttpResponseException( public static String getDefaultExpireTime() { Calendar calendar = Calendar.getInstance(); calendar.setTime(new Date()); - calendar.add(Calendar.SECOND, 300); + calendar.add(Calendar.SECOND, 30000); return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(calendar.getTime()); } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java index 436a785f8..795a1207a 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java @@ -1426,6 +1426,7 @@ public void testRefresh_trustBoundarySuccess() throws IOException { TrustBoundary trustBoundary = awsCredential.getTrustBoundary(); assertNotNull(trustBoundary); - assertEquals("0x800000", trustBoundary.getEncodedLocations()); + assertEquals(TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION, trustBoundary.getEncodedLocations()); + TrustBoundary.setEnvironmentProviderForTest(null); } } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java index fa3158806..61d611a49 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java @@ -1240,10 +1240,10 @@ public void testRefresh_trustBoundarySuccess() throws IOException { .build(); credentials.refresh(); - TrustBoundary trustBoundary = credentials.getTrustBoundary(); assertNotNull(trustBoundary); - assertEquals("0x1", trustBoundary.getEncodedLocations()); + assertEquals(TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION, trustBoundary.getEncodedLocations()); + TrustBoundary.setEnvironmentProviderForTest(null); } @Test @@ -1270,6 +1270,7 @@ public void testRefresh_trustBoundaryFails_incorrectAudience() throws IOExceptio "The provided audience is not in the correct format for a workforce pool.", e.getMessage()); } + TrustBoundary.setEnvironmentProviderForTest(null); } @Test diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java index d374f896e..8b6438488 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java @@ -56,6 +56,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -93,6 +94,11 @@ public void setup() { transportFactory = new MockExternalAccountCredentialsTransportFactory(); } + @After + public void tearDown() { + TrustBoundary.setEnvironmentProviderForTest(null); + } + @Test public void fromStream_identityPoolCredentials() throws IOException { GenericJson json = buildJsonIdentityPoolCredential(); @@ -1294,11 +1300,9 @@ public void getTrustBoundaryUrl_invalidAudience_throws() throws IOException { @Test public void refresh_workload_trustBoundarySuccess() throws IOException { - // 1. Scenario Setup String audience = "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/my-pool/providers/my-provider"; - // Enable the trust boundary feature via a mock environment provider. TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); TrustBoundary.setEnvironmentProviderForTest(environmentProvider); environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); @@ -1318,17 +1322,11 @@ public String retrieveSubjectToken() throws IOException { return "dummy-subject-token"; } }; - - // 2. Action: Refresh the credentials. credentials.refresh(); - // 3. Assertion: Verify the trust boundary was set from the mock's hardcoded response. TrustBoundary trustBoundary = credentials.getTrustBoundary(); assertNotNull(trustBoundary); assertEquals(TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION, trustBoundary.getEncodedLocations()); - - // Cleanup - TrustBoundary.setEnvironmentProviderForTest(null); } @Test @@ -1361,8 +1359,6 @@ public String retrieveSubjectToken() throws IOException { TrustBoundary trustBoundary = credentials.getTrustBoundary(); assertNotNull(trustBoundary); assertEquals(TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION, trustBoundary.getEncodedLocations()); - - TrustBoundary.setEnvironmentProviderForTest(null); } @Test @@ -1398,8 +1394,6 @@ public void refresh_impersonated_workload_trustBoundarySuccess() throws IOExcept TrustBoundary trustBoundary = credentials.getTrustBoundary(); assertNotNull(trustBoundary); assertEquals(TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION, trustBoundary.getEncodedLocations()); - - TrustBoundary.setEnvironmentProviderForTest(null); } @Test @@ -1436,8 +1430,6 @@ public void refresh_impersonated_workforce_trustBoundarySuccess() throws IOExcep TrustBoundary trustBoundary = credentials.getTrustBoundary(); assertNotNull(trustBoundary); assertEquals(TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION, trustBoundary.getEncodedLocations()); - - TrustBoundary.setEnvironmentProviderForTest(null); } private GenericJson buildJsonIdentityPoolCredential() { diff --git a/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java index be498d3c5..a48e56c7e 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java @@ -1329,6 +1329,7 @@ public void testRefresh_trustBoundarySuccess() throws IOException { TrustBoundary trustBoundary = credentials.getTrustBoundary(); assertNotNull(trustBoundary); - assertEquals("0x800000", trustBoundary.getEncodedLocations()); + assertEquals(TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION, trustBoundary.getEncodedLocations()); + TrustBoundary.setEnvironmentProviderForTest(null); } } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java b/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java index 07424a724..5bb500fd4 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java @@ -93,7 +93,6 @@ public class MockExternalAccountCredentialsTransport extends MockHttpTransport { private String expireTime; private String metadataServerContentType; private String stsContent; - private String impersonationTrustBoundary; public void addResponseErrorSequence(IOException... errors) { Collections.addAll(responseErrorSequence, errors); @@ -316,8 +315,4 @@ public void setExpireTime(String expireTime) { public void setMetadataServerContentType(String contentType) { this.metadataServerContentType = contentType; } - - public void setImpersonationTrustBoundary(String trustBoundary) { - this.impersonationTrustBoundary = trustBoundary; - } } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/MockStsTransport.java b/oauth2_http/javatests/com/google/auth/oauth2/MockStsTransport.java index 989bd4dd7..d9f99a148 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/MockStsTransport.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/MockStsTransport.java @@ -101,16 +101,17 @@ public LowLevelHttpRequest buildRequest(final String method, final String url) { new MockLowLevelHttpRequest(url) { @Override public LowLevelHttpResponse execute() throws IOException { + // Mocking call to refresh trust boundaries. + // The lookup endpoint is located in the IAM server. Matcher trustBoundaryMatcher = Pattern.compile(VALID_TRUST_BOUNDARY_PATTERN).matcher(url); if (trustBoundaryMatcher.matches()) { GenericJson response = new GenericJson(); - response.setFactory(new GsonFactory()); - response.put("locations", Collections.singletonList("us-central1")); - response.put("encodedLocations", "0x1"); + response.put("locations", TestUtils.TRUST_BOUNDARY_LOCATIONS); + response.put("encodedLocations", TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION); return new MockLowLevelHttpResponse() .setContentType(Json.MEDIA_TYPE) - .setContent(response.toPrettyString()); + .setContent(OAuth2Utils.JSON_FACTORY.toString(response)); } // Environment version is prefixed by "aws". e.g. "aws1". diff --git a/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java index 56e9c5547..fa21acc0e 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java @@ -605,16 +605,12 @@ public void serialize() throws IOException, ClassNotFoundException { @Test public void testRefresh_trustBoundarySuccess() throws IOException { - TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); - TrustBoundary.setEnvironmentProviderForTest(environmentProvider); - environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); MockExternalAccountCredentialsTransportFactory transportFactory = new MockExternalAccountCredentialsTransportFactory(); - transportFactory.transport.setExpireTime(TestUtils.getDefaultExpireTime()); PluggableAuthCredentials credentials = @@ -629,12 +625,10 @@ public void testRefresh_trustBoundarySuccess() throws IOException { .build(); credentials.refresh(); - TrustBoundary trustBoundary = credentials.getTrustBoundary(); - assertNotNull(trustBoundary); - - assertEquals("0x800000", trustBoundary.getEncodedLocations()); + assertEquals(TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION, trustBoundary.getEncodedLocations()); + TrustBoundary.setEnvironmentProviderForTest(null); } private static PluggableAuthCredentialSource buildCredentialSource() { From f737866718527d575270d9c6caa057294d07aab3 Mon Sep 17 00:00:00 2001 From: Pranav Iyer Date: Thu, 30 Oct 2025 15:09:02 -0700 Subject: [PATCH 13/18] TrustBoundaryUrl's universe domain is now configured from the client's universe domain. --- .../oauth2/ExternalAccountCredentials.java | 40 +++++++++---------- .../com/google/auth/oauth2/OAuth2Utils.java | 4 ++ .../oauth2/ServiceAccountCredentials.java | 2 - .../javatests/com/google/auth/TestUtils.java | 2 +- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java index 356c31206..bcd66b653 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java @@ -100,10 +100,6 @@ public abstract class ExternalAccountCredentials extends GoogleCredentials private EnvironmentProvider environmentProvider; - private static final String WORKFORCE_POOL_URL_FORMAT = - "https://iamcredentials.googleapis.com/v1/locations/global/workforcePools/%s/allowedLocations"; - private static final String WORKLOAD_POOL_URL_FORMAT = - "https://iamcredentials.googleapis.com/v1/projects/%s/locations/global/workloadIdentityPools/%s/allowedLocations"; private static final Pattern WORKFORCE_PATTERN = Pattern.compile( @@ -633,26 +629,28 @@ public String getServiceAccountEmail() { return ImpersonatedCredentials.extractTargetPrincipal(serviceAccountImpersonationUrl); } - // todo Add doc comment. @Override public String getTrustBoundaryUrl() throws IOException { - if (isWorkforcePoolConfiguration()) { - Matcher matcher = WORKFORCE_PATTERN.matcher(getAudience()); - if (!matcher.matches()) { - throw new IOException( - "The provided audience is not in the correct format for a workforce pool."); - } - String poolId = matcher.group("pool"); - return String.format(WORKFORCE_POOL_URL_FORMAT, poolId); + Matcher workforceMatcher = WORKFORCE_PATTERN.matcher(getAudience()); + Matcher workloadMatcher = WORKLOAD_PATTERN.matcher(getAudience()); + + if (workforceMatcher.matches()) { + String poolId = workforceMatcher.group("pool"); + return String.format( + OAuth2Utils.IAM_CREDENTIALS_ALLOWED_LOCATIONS_URL_FORMAT_WORKFORCE_POOL, + getUniverseDomain(), + poolId); + } else if (workloadMatcher.matches()) { + String projectNumber = workloadMatcher.group("project"); + String poolId = workloadMatcher.group("pool"); + return String.format( + OAuth2Utils.IAM_CREDENTIALS_ALLOWED_LOCATIONS_URL_FORMAT_WORKLOAD_POOL, + getUniverseDomain(), + projectNumber, + poolId); } else { - Matcher matcher = WORKLOAD_PATTERN.matcher(getAudience()); - if (!matcher.matches()) { - throw new IOException( - "The provided audience is not in the correct format for a workload identity pool."); - } - String projectNumber = matcher.group("project"); - String poolId = matcher.group("pool"); - return String.format(WORKLOAD_POOL_URL_FORMAT, projectNumber, poolId); + throw new IOException( + "The provided audience is not in a valid format for either a workload identity pool or a workforce pool."); } } diff --git a/oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java b/oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java index b69d636c8..42eb5582d 100644 --- a/oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java +++ b/oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java @@ -96,6 +96,10 @@ public class OAuth2Utils { static final String IAM_CREDENTIALS_ALLOWED_LOCATIONS_URL_FORMAT_SERVICE_ACCOUNT = "https://iamcredentials.%s/v1/projects/-/serviceAccounts/%s/allowedLocations"; + static final String IAM_CREDENTIALS_ALLOWED_LOCATIONS_URL_FORMAT_WORKFORCE_POOL = + "https://iamcredentials.%s/v1/locations/global/workforcePools/%s/allowedLocations"; + static final String IAM_CREDENTIALS_ALLOWED_LOCATIONS_URL_FORMAT_WORKLOAD_POOL = + "https://iamcredentials.%s/v1/projects/%s/locations/global/workloadIdentityPools/%s/allowedLocations"; static final URI USER_AUTH_URI = URI.create("https://accounts.google.com/o/oauth2/auth"); public static final String CLOUD_PLATFORM_SCOPE = diff --git a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java index 7110a0b5f..5307d4d6d 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java @@ -1162,7 +1162,6 @@ public static class Builder extends GoogleCredentials.Builder { private int lifetime = DEFAULT_LIFETIME_IN_SECONDS; private boolean useJwtAccessWithScope = false; private boolean defaultRetriesEnabled = true; - private TrustBoundary trustBoundary; protected Builder() {} @@ -1181,7 +1180,6 @@ protected Builder(ServiceAccountCredentials credentials) { this.lifetime = credentials.lifetime; this.useJwtAccessWithScope = credentials.useJwtAccessWithScope; this.defaultRetriesEnabled = credentials.defaultRetriesEnabled; - this.trustBoundary = credentials.getTrustBoundary(); } @CanIgnoreReturnValue diff --git a/oauth2_http/javatests/com/google/auth/TestUtils.java b/oauth2_http/javatests/com/google/auth/TestUtils.java index 75f0d18e4..f683bf7dd 100644 --- a/oauth2_http/javatests/com/google/auth/TestUtils.java +++ b/oauth2_http/javatests/com/google/auth/TestUtils.java @@ -150,7 +150,7 @@ public static HttpResponseException buildHttpResponseException( public static String getDefaultExpireTime() { Calendar calendar = Calendar.getInstance(); calendar.setTime(new Date()); - calendar.add(Calendar.SECOND, 30000); + calendar.add(Calendar.SECOND, 3000); return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(calendar.getTime()); } From fe772d8cb31339353ac85f62e7d57a95044f0de9 Mon Sep 17 00:00:00 2001 From: Pranav Iyer Date: Thu, 30 Oct 2025 15:16:32 -0700 Subject: [PATCH 14/18] Formatted ExternalAccountCredentials. --- .../java/com/google/auth/oauth2/ExternalAccountCredentials.java | 1 - 1 file changed, 1 deletion(-) diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java index bcd66b653..15f3f12e2 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java @@ -100,7 +100,6 @@ public abstract class ExternalAccountCredentials extends GoogleCredentials private EnvironmentProvider environmentProvider; - private static final Pattern WORKFORCE_PATTERN = Pattern.compile( "^//iam.googleapis.com/locations/(?[^/]+)/workforcePools/(?[^/]+)/providers/(?[^/]+)$"); From 1efce2ddd7399457519e107a927a3ed1896f59a8 Mon Sep 17 00:00:00 2001 From: Pranav Iyer Date: Thu, 30 Oct 2025 18:56:47 -0700 Subject: [PATCH 15/18] assertThrows changes. Fixed TestUtils getDefaultExpireTime to represent time in UTC. --- .../javatests/com/google/auth/TestUtils.java | 7 +++++-- ...lAccountAuthorizedUserCredentialsTest.java | 20 +++++++++---------- .../ExternalAccountCredentialsTest.java | 17 +++++++++++++--- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/oauth2_http/javatests/com/google/auth/TestUtils.java b/oauth2_http/javatests/com/google/auth/TestUtils.java index f683bf7dd..b68b49dc5 100644 --- a/oauth2_http/javatests/com/google/auth/TestUtils.java +++ b/oauth2_http/javatests/com/google/auth/TestUtils.java @@ -56,6 +56,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.TimeZone; import javax.annotation.Nullable; /** Utilities for test code under com.google.auth. */ @@ -150,8 +151,10 @@ public static HttpResponseException buildHttpResponseException( public static String getDefaultExpireTime() { Calendar calendar = Calendar.getInstance(); calendar.setTime(new Date()); - calendar.add(Calendar.SECOND, 3000); - return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(calendar.getTime()); + calendar.add(Calendar.SECOND, 300); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + return dateFormat.format(calendar.getTime()); } private TestUtils() {} diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java index 61d611a49..de7e029a6 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java @@ -38,6 +38,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -1243,7 +1244,6 @@ public void testRefresh_trustBoundarySuccess() throws IOException { TrustBoundary trustBoundary = credentials.getTrustBoundary(); assertNotNull(trustBoundary); assertEquals(TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION, trustBoundary.getEncodedLocations()); - TrustBoundary.setEnvironmentProviderForTest(null); } @Test @@ -1262,15 +1262,15 @@ public void testRefresh_trustBoundaryFails_incorrectAudience() throws IOExceptio .setTokenUrl(TOKEN_URL) .build(); - try { - credentials.refresh(); - fail("Expected IOException to be thrown."); - } catch (IOException e) { - assertEquals( - "The provided audience is not in the correct format for a workforce pool.", - e.getMessage()); - } - TrustBoundary.setEnvironmentProviderForTest(null); + IOException exception = + assertThrows( + IOException.class, + () -> { + credentials.refresh(); + }); + assertEquals( + "The provided audience is not in the correct format for a workforce pool.", + exception.getMessage()); } @Test diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java index 8b6438488..5b62c0249 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java @@ -36,6 +36,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -1287,15 +1288,25 @@ public void getTrustBoundaryUrl_workforce() throws IOException { assertEquals(expectedUrl, credentials.getTrustBoundaryUrl()); } - @Test(expected = IOException.class) - public void getTrustBoundaryUrl_invalidAudience_throws() throws IOException { + @Test + public void getTrustBoundaryUrl_invalidAudience_throws() { ExternalAccountCredentials credentials = TestExternalAccountCredentials.newBuilder() .setAudience("invalid-audience") .setSubjectTokenType("subject_token_type") .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) .build(); - credentials.getTrustBoundaryUrl(); + + IOException exception = + assertThrows( + IOException.class, + () -> { + credentials.getTrustBoundaryUrl(); + }); + + assertEquals( + "The provided audience is not in a valid format for either a workload identity pool or a workforce pool.", + exception.getMessage()); } @Test From 6eb249c81ae600b82d0194e1af799ee98b3ed464 Mon Sep 17 00:00:00 2001 From: Pranav Iyer Date: Fri, 31 Oct 2025 14:13:44 -0700 Subject: [PATCH 16/18] Error thrown when audience doesn't match pattern is IllegalStateException and format changes. --- ...ernalAccountAuthorizedUserCredentials.java | 17 +++------- .../oauth2/ExternalAccountCredentials.java | 33 +++++++++---------- .../com/google/auth/oauth2/OAuth2Utils.java | 24 ++++++++++---- ...lAccountAuthorizedUserCredentialsTest.java | 6 ++-- .../ExternalAccountCredentialsTest.java | 4 +-- ...ckExternalAccountCredentialsTransport.java | 3 -- .../oauth2/MockMetadataServerTransport.java | 7 ++-- .../google/auth/oauth2/MockStsTransport.java | 3 ++ .../auth/oauth2/MockTokenServerTransport.java | 7 ++-- 9 files changed, 54 insertions(+), 50 deletions(-) diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java index 03cd45b2c..9cf8c18df 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java @@ -31,7 +31,7 @@ package com.google.auth.oauth2; -import static com.google.auth.oauth2.OAuth2Utils.JSON_FACTORY; +import static com.google.auth.oauth2.OAuth2Utils.*; import static com.google.common.base.Preconditions.checkNotNull; import com.google.api.client.http.GenericUrl; @@ -56,7 +56,6 @@ import java.util.Map; import java.util.Objects; import java.util.regex.Matcher; -import java.util.regex.Pattern; import javax.annotation.Nullable; /** @@ -83,13 +82,6 @@ public class ExternalAccountAuthorizedUserCredentials extends GoogleCredentials private static final String PARSE_ERROR_PREFIX = "Error parsing token refresh response. "; private static final long serialVersionUID = -2181779590486283287L; - - private static final String WORKFORCE_POOL_URL_FORMAT = - "https://iamcredentials.googleapis.com/v1/locations/global/workforcePools/%s/allowedLocations"; - private static final Pattern WORKFORCE_PATTERN = - Pattern.compile( - "^//iam.googleapis.com/locations/(?[^/]+)/workforcePools/(?[^/]+)/providers/(?[^/]+)$"); - private final String transportFactoryClassName; private final String audience; private final String tokenUrl; @@ -231,13 +223,14 @@ public AccessToken refreshAccessToken() throws IOException { @Override public String getTrustBoundaryUrl() throws IOException { - Matcher matcher = WORKFORCE_PATTERN.matcher(getAudience()); + Matcher matcher = WORKFORCE_AUDIENCE_PATTERN.matcher(getAudience()); if (!matcher.matches()) { - throw new IOException( + throw new IllegalStateException( "The provided audience is not in the correct format for a workforce pool."); } String poolId = matcher.group("pool"); - return String.format(WORKFORCE_POOL_URL_FORMAT, poolId); + return String.format( + IAM_CREDENTIALS_ALLOWED_LOCATIONS_URL_FORMAT_WORKFORCE_POOL, getUniverseDomain(), poolId); } @Nullable diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java index 15f3f12e2..0cbacfb40 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java @@ -31,12 +31,15 @@ package com.google.auth.oauth2; +import static com.google.auth.oauth2.OAuth2Utils.WORKFORCE_AUDIENCE_PATTERN; +import static com.google.auth.oauth2.OAuth2Utils.WORKLOAD_AUDIENCE_PATTERN; import static com.google.common.base.Preconditions.checkNotNull; import com.google.api.client.http.HttpHeaders; import com.google.api.client.json.GenericJson; import com.google.api.client.json.JsonObjectParser; import com.google.api.client.util.Data; +import com.google.api.core.InternalApi; import com.google.auth.RequestMetadataCallback; import com.google.auth.http.HttpTransportFactory; import com.google.common.base.MoreObjects; @@ -100,13 +103,6 @@ public abstract class ExternalAccountCredentials extends GoogleCredentials private EnvironmentProvider environmentProvider; - private static final Pattern WORKFORCE_PATTERN = - Pattern.compile( - "^//iam.googleapis.com/locations/(?[^/]+)/workforcePools/(?[^/]+)/providers/(?[^/]+)$"); - private static final Pattern WORKLOAD_PATTERN = - Pattern.compile( - "^//iam.googleapis.com/projects/(?[^/]+)/locations/(?[^/]+)/workloadIdentityPools/(?[^/]+)/providers/(?[^/]+)$"); - /** * Constructor with minimum identifying information and custom HTTP transport. Does not support * workforce credentials. @@ -537,8 +533,9 @@ protected AccessToken exchangeExternalCredentialForAccessToken( } if (this.impersonatedCredentials != null) { AccessToken accessToken = this.impersonatedCredentials.refreshAccessToken(); - // After the impersonated credential refreshes, its trust boundary is - // also refreshed. That is the trust boundary we will use. + // We use the impersonated account's credential as the trust boundary + // since the regional restriction is bounded by the access that the + // impersonated account has. setTrustBoundary(this.impersonatedCredentials.getTrustBoundary()); return accessToken; } @@ -628,18 +625,20 @@ public String getServiceAccountEmail() { return ImpersonatedCredentials.extractTargetPrincipal(serviceAccountImpersonationUrl); } + @InternalApi @Override - public String getTrustBoundaryUrl() throws IOException { - Matcher workforceMatcher = WORKFORCE_PATTERN.matcher(getAudience()); - Matcher workloadMatcher = WORKLOAD_PATTERN.matcher(getAudience()); - + public String getTrustBoundaryUrl() { + Matcher workforceMatcher = WORKFORCE_AUDIENCE_PATTERN.matcher(getAudience()); if (workforceMatcher.matches()) { String poolId = workforceMatcher.group("pool"); return String.format( OAuth2Utils.IAM_CREDENTIALS_ALLOWED_LOCATIONS_URL_FORMAT_WORKFORCE_POOL, getUniverseDomain(), poolId); - } else if (workloadMatcher.matches()) { + } + + Matcher workloadMatcher = WORKLOAD_AUDIENCE_PATTERN.matcher(getAudience()); + if (workloadMatcher.matches()) { String projectNumber = workloadMatcher.group("project"); String poolId = workloadMatcher.group("pool"); return String.format( @@ -647,10 +646,10 @@ public String getTrustBoundaryUrl() throws IOException { getUniverseDomain(), projectNumber, poolId); - } else { - throw new IOException( - "The provided audience is not in a valid format for either a workload identity pool or a workforce pool."); } + + throw new IllegalStateException( + "The provided audience is not in a valid format for either a workload identity pool or a workforce pool."); } @Nullable diff --git a/oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java b/oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java index 42eb5582d..72cab4f01 100644 --- a/oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java +++ b/oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java @@ -68,6 +68,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.regex.Pattern; /** * Internal utilities for the com.google.auth.oauth2 namespace. @@ -93,13 +94,6 @@ public class OAuth2Utils { static final URI TOKEN_SERVER_URI = URI.create("https://oauth2.googleapis.com/token"); static final URI TOKEN_REVOKE_URI = URI.create("https://oauth2.googleapis.com/revoke"); - - static final String IAM_CREDENTIALS_ALLOWED_LOCATIONS_URL_FORMAT_SERVICE_ACCOUNT = - "https://iamcredentials.%s/v1/projects/-/serviceAccounts/%s/allowedLocations"; - static final String IAM_CREDENTIALS_ALLOWED_LOCATIONS_URL_FORMAT_WORKFORCE_POOL = - "https://iamcredentials.%s/v1/locations/global/workforcePools/%s/allowedLocations"; - static final String IAM_CREDENTIALS_ALLOWED_LOCATIONS_URL_FORMAT_WORKLOAD_POOL = - "https://iamcredentials.%s/v1/projects/%s/locations/global/workloadIdentityPools/%s/allowedLocations"; static final URI USER_AUTH_URI = URI.create("https://accounts.google.com/o/oauth2/auth"); public static final String CLOUD_PLATFORM_SCOPE = @@ -124,6 +118,22 @@ public class OAuth2Utils { static final double RETRY_MULTIPLIER = 2; static final int DEFAULT_NUMBER_OF_RETRIES = 3; + static final Pattern WORKFORCE_AUDIENCE_PATTERN = + Pattern.compile( + "^//iam.googleapis.com/locations/(?[^/]+)/workforcePools/(?[^/]+)/providers/(?[^/]+)$"); + static final Pattern WORKLOAD_AUDIENCE_PATTERN = + Pattern.compile( + "^//iam.googleapis.com/projects/(?[^/]+)/locations/(?[^/]+)/workloadIdentityPools/(?[^/]+)/providers/(?[^/]+)$"); + + static final String IAM_CREDENTIALS_ALLOWED_LOCATIONS_URL_FORMAT_SERVICE_ACCOUNT = + "https://iamcredentials.%s/v1/projects/-/serviceAccounts/%s/allowedLocations"; + + static final String IAM_CREDENTIALS_ALLOWED_LOCATIONS_URL_FORMAT_WORKFORCE_POOL = + "https://iamcredentials.%s/v1/locations/global/workforcePools/%s/allowedLocations"; + + static final String IAM_CREDENTIALS_ALLOWED_LOCATIONS_URL_FORMAT_WORKLOAD_POOL = + "https://iamcredentials.%s/v1/projects/%s/locations/global/workloadIdentityPools/%s/allowedLocations"; + // Includes expected server errors from Google token endpoint // Other 5xx codes are either not used or retries are unlikely to succeed public static final Set TOKEN_ENDPOINT_RETRYABLE_STATUS_CODES = diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java index de7e029a6..04673a260 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java @@ -1247,7 +1247,7 @@ public void testRefresh_trustBoundarySuccess() throws IOException { } @Test - public void testRefresh_trustBoundaryFails_incorrectAudience() throws IOException { + public void testRefresh_trustBoundaryFails_incorrectAudience() { TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); TrustBoundary.setEnvironmentProviderForTest(environmentProvider); environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); @@ -1262,9 +1262,9 @@ public void testRefresh_trustBoundaryFails_incorrectAudience() throws IOExceptio .setTokenUrl(TOKEN_URL) .build(); - IOException exception = + IllegalStateException exception = assertThrows( - IOException.class, + IllegalStateException.class, () -> { credentials.refresh(); }); diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java index 5b62c0249..14fe4e90b 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java @@ -1297,9 +1297,9 @@ public void getTrustBoundaryUrl_invalidAudience_throws() { .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) .build(); - IOException exception = + IllegalStateException exception = assertThrows( - IOException.class, + IllegalStateException.class, () -> { credentials.getTrustBoundaryUrl(); }); diff --git a/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java b/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java index 5bb500fd4..7be65adf3 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java @@ -199,9 +199,6 @@ public LowLevelHttpResponse execute() throws IOException { if (url.contains(IAM_ENDPOINT)) { if (url.endsWith(TRUST_BOUNDARY_URL_END)) { - // Mocking call to refresh trust boundaries. - // The lookup endpoint is located in the IAM server which is different from the - // token server. GenericJson responseJson = new GenericJson(); responseJson.setFactory(OAuth2Utils.JSON_FACTORY); responseJson.put("encodedLocations", TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION); diff --git a/oauth2_http/javatests/com/google/auth/oauth2/MockMetadataServerTransport.java b/oauth2_http/javatests/com/google/auth/oauth2/MockMetadataServerTransport.java index 3fed30ef4..3e90d0faa 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/MockMetadataServerTransport.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/MockMetadataServerTransport.java @@ -388,9 +388,10 @@ public LowLevelHttpResponse execute() throws IOException { } protected boolean isIamLookupUrl(String url) { - // Mocking call to refresh trust boundaries. - // The lookup endpoint is located in the IAM server which is different from the - // metadata server. + // Mocking call to the /allowedLocations endpoint for trust boundary refresh. + // For testing convenience, this mock transport handles + // the /allowedLocations endpoint. The actual server for this endpoint + // will be the IAM Credentials API. return url.endsWith("/allowedLocations"); } } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/MockStsTransport.java b/oauth2_http/javatests/com/google/auth/oauth2/MockStsTransport.java index d9f99a148..e3a50d027 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/MockStsTransport.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/MockStsTransport.java @@ -106,6 +106,9 @@ public LowLevelHttpResponse execute() throws IOException { Matcher trustBoundaryMatcher = Pattern.compile(VALID_TRUST_BOUNDARY_PATTERN).matcher(url); if (trustBoundaryMatcher.matches()) { + // Mocking call to the /allowedLocations endpoint for trust boundary refresh. + // For testing convenience, this mock transport handles + // the /allowedLocations endpoint. GenericJson response = new GenericJson(); response.put("locations", TestUtils.TRUST_BOUNDARY_LOCATIONS); response.put("encodedLocations", TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION); diff --git a/oauth2_http/javatests/com/google/auth/oauth2/MockTokenServerTransport.java b/oauth2_http/javatests/com/google/auth/oauth2/MockTokenServerTransport.java index 819267102..76ef3f807 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/MockTokenServerTransport.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/MockTokenServerTransport.java @@ -327,9 +327,10 @@ public LowLevelHttpResponse execute() throws IOException { }; return request; } else if (urlWithoutQuery.endsWith("/allowedLocations")) { - // Mocking call to refresh trust boundaries. - // The lookup endpoint is located in the IAM server which is different from the - // token server. + // Mocking call to the /allowedLocations endpoint for trust boundary refresh. + // For testing convenience, this mock transport handles + // the /allowedLocations endpoint. The actual server for this endpoint + // will be the IAM Credentials API. request = new MockLowLevelHttpRequest(url) { @Override From 2071071e0a0a71057bb621ba331602257c66aa57 Mon Sep 17 00:00:00 2001 From: Pranav Iyer Date: Fri, 31 Oct 2025 14:16:28 -0700 Subject: [PATCH 17/18] Nit fixes. --- .../auth/oauth2/ExternalAccountAuthorizedUserCredentials.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java index 9cf8c18df..3cbb4dedf 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java @@ -44,6 +44,7 @@ import com.google.api.client.json.JsonObjectParser; import com.google.api.client.util.GenericData; import com.google.api.client.util.Preconditions; +import com.google.api.core.InternalApi; import com.google.auth.http.HttpTransportFactory; import com.google.common.base.MoreObjects; import com.google.common.io.BaseEncoding; @@ -221,6 +222,7 @@ public AccessToken refreshAccessToken() throws IOException { return newAccessToken; } + @InternalApi @Override public String getTrustBoundaryUrl() throws IOException { Matcher matcher = WORKFORCE_AUDIENCE_PATTERN.matcher(getAudience()); From f013aa573a3e880a14d647037d2ecabb6aa11c3c Mon Sep 17 00:00:00 2001 From: Pranav Iyer Date: Mon, 3 Nov 2025 22:55:35 -0800 Subject: [PATCH 18/18] Imports are listed. setTrustBoundaries removed. --- .../oauth2/ExternalAccountAuthorizedUserCredentials.java | 4 +++- .../com/google/auth/oauth2/ExternalAccountCredentials.java | 7 +++---- .../java/com/google/auth/oauth2/GoogleCredentials.java | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java index 3cbb4dedf..abb1da71c 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java @@ -31,7 +31,9 @@ package com.google.auth.oauth2; -import static com.google.auth.oauth2.OAuth2Utils.*; +import static com.google.auth.oauth2.OAuth2Utils.IAM_CREDENTIALS_ALLOWED_LOCATIONS_URL_FORMAT_WORKFORCE_POOL; +import static com.google.auth.oauth2.OAuth2Utils.JSON_FACTORY; +import static com.google.auth.oauth2.OAuth2Utils.WORKFORCE_AUDIENCE_PATTERN; import static com.google.common.base.Preconditions.checkNotNull; import com.google.api.client.http.GenericUrl; diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java index 0cbacfb40..33cbb592c 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java @@ -533,10 +533,9 @@ protected AccessToken exchangeExternalCredentialForAccessToken( } if (this.impersonatedCredentials != null) { AccessToken accessToken = this.impersonatedCredentials.refreshAccessToken(); - // We use the impersonated account's credential as the trust boundary - // since the regional restriction is bounded by the access that the - // impersonated account has. - setTrustBoundary(this.impersonatedCredentials.getTrustBoundary()); + // After the impersonated credential refreshes, its trust boundary is + // also refreshed. That is the trust boundary we will use. + this.trustBoundary = this.impersonatedCredentials.getTrustBoundary(); return accessToken; } diff --git a/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java b/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java index 62ea3f693..4f3118bc8 100644 --- a/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java @@ -108,7 +108,7 @@ String getFileType() { private final String universeDomain; private final boolean isExplicitUniverseDomain; - private TrustBoundary trustBoundary; + TrustBoundary trustBoundary; protected final String quotaProjectId;