From 92a89b726ba3a3a44bfb5d80e541639fba5d4d48 Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Tue, 2 Dec 2025 13:26:22 -0500 Subject: [PATCH 1/4] fix: Add configurable connect and read timeouts to STS requests --- .../oauth2/ExternalAccountCredentials.java | 31 +++++++++++ .../google/auth/oauth2/StsRequestHandler.java | 53 +++++++++++++------ 2 files changed, 67 insertions(+), 17 deletions(-) diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java index 2e95379ba..de0046b20 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java @@ -36,6 +36,7 @@ import com.google.api.client.http.HttpHeaders; import com.google.api.client.json.GenericJson; 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; @@ -97,6 +98,9 @@ public abstract class ExternalAccountCredentials extends GoogleCredentials { private EnvironmentProvider environmentProvider; + private int connectTimeout; + private int readTimeout; + /** * Constructor with minimum identifying information and custom HTTP transport. Does not support * workforce credentials. @@ -271,6 +275,8 @@ protected ExternalAccountCredentials(ExternalAccountCredentials.Builder builder) : builder.metricsHandler; this.name = GoogleCredentialsInfo.EXTERNAL_ACCOUNT_CREDENTIALS.getCredentialName(); + this.connectTimeout = builder.connectTimeout; + this.readTimeout = builder.readTimeout; } ImpersonatedCredentials buildImpersonatedCredentials() { @@ -534,6 +540,8 @@ protected AccessToken exchangeExternalCredentialForAccessToken( StsRequestHandler.Builder requestHandler = StsRequestHandler.newBuilder( tokenUrl, stsTokenExchangeRequest, transportFactory.create().createRequestFactory()); + requestHandler.setConnectTimeout(connectTimeout); + requestHandler.setReadTimeout(readTimeout); // If this credential was initialized with a Workforce configuration then the // workforcePoolUserProject must be passed to the Security Token Service via the internal @@ -771,6 +779,9 @@ public abstract static class Builder extends GoogleCredentials.Builder { @Nullable protected String workforcePoolUserProject; @Nullable protected ServiceAccountImpersonationOptions serviceAccountImpersonationOptions; + private int connectTimeout = 20000; // Default to 20000ms = 20s + private int readTimeout = 20000; // Default to 20000ms = 20s + /* The field is not being used and value not set. Superseded by the same field in the {@link GoogleCredentials.Builder}. */ @@ -796,6 +807,8 @@ protected Builder(ExternalAccountCredentials credentials) { this.workforcePoolUserProject = credentials.workforcePoolUserProject; this.serviceAccountImpersonationOptions = credentials.serviceAccountImpersonationOptions; this.metricsHandler = credentials.metricsHandler; + this.connectTimeout = credentials.connectTimeout; + this.readTimeout = credentials.readTimeout; } /** @@ -988,6 +1001,24 @@ public Builder setUniverseDomain(String universeDomain) { return this; } + /** + * Warning: Not for public use and can be removed at any time. + */ + @InternalApi + public Builder setConnectTimeout(int connectTimeout) { + this.connectTimeout = connectTimeout; + return this; + } + + /** + * Warning: Not for public use and can be removed at any time. + */ + @InternalApi + public Builder setReadTimeout(int readTimeout) { + this.readTimeout = readTimeout; + return this; + } + /** * Sets the optional Environment Provider. * diff --git a/oauth2_http/java/com/google/auth/oauth2/StsRequestHandler.java b/oauth2_http/java/com/google/auth/oauth2/StsRequestHandler.java index bd7a2d21f..958409576 100644 --- a/oauth2_http/java/com/google/auth/oauth2/StsRequestHandler.java +++ b/oauth2_http/java/com/google/auth/oauth2/StsRequestHandler.java @@ -42,6 +42,7 @@ import com.google.api.client.json.JsonObjectParser; import com.google.api.client.json.JsonParser; import com.google.api.client.util.GenericData; +import com.google.api.core.InternalApi; import com.google.common.base.Joiner; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.IOException; @@ -73,26 +74,22 @@ public final class StsRequestHandler { @Nullable private final HttpHeaders headers; @Nullable private final String internalOptions; + private int connectTimeout; + private int readTimeout; + /** * Internal constructor. * - * @param tokenExchangeEndpoint the token exchange endpoint - * @param request the token exchange request - * @param headers optional additional headers to pass along the request - * @param internalOptions optional GCP specific STS options * @return an StsTokenExchangeResponse instance if the request was successful */ - private StsRequestHandler( - String tokenExchangeEndpoint, - StsTokenExchangeRequest request, - HttpRequestFactory httpRequestFactory, - @Nullable HttpHeaders headers, - @Nullable String internalOptions) { - this.tokenExchangeEndpoint = tokenExchangeEndpoint; - this.request = request; - this.httpRequestFactory = httpRequestFactory; - this.headers = headers; - this.internalOptions = internalOptions; + private StsRequestHandler(Builder builder) { + this.tokenExchangeEndpoint = builder.tokenExchangeEndpoint; + this.request = builder.request; + this.httpRequestFactory = builder.httpRequestFactory; + this.headers = builder.headers; + this.internalOptions = builder.internalOptions; + this.connectTimeout = builder.connectTimeout; + this.readTimeout = builder.readTimeout; } /** @@ -120,6 +117,8 @@ public StsTokenExchangeResponse exchangeToken() throws IOException { if (headers != null) { httpRequest.setHeaders(headers); } + httpRequest.setConnectTimeout(connectTimeout); + httpRequest.setReadTimeout(readTimeout); try { HttpResponse response = httpRequest.execute(); @@ -214,6 +213,9 @@ public static class Builder { @Nullable private HttpHeaders headers; @Nullable private String internalOptions; + private int connectTimeout = 20000; // Default to 20000ms = 20s + private int readTimeout = 20000; // Default to 20000ms = 20s + private Builder( String tokenExchangeEndpoint, StsTokenExchangeRequest stsTokenExchangeRequest, @@ -235,9 +237,26 @@ public StsRequestHandler.Builder setInternalOptions(String internalOptions) { return this; } + /** + * Warning: Not for public use and can be removed at any time. + */ + @InternalApi + public StsRequestHandler.Builder setConnectTimeout(int connectTimeout) { + this.connectTimeout = connectTimeout; + return this; + } + + /** + * Warning: Not for public use and can be removed at any time. + */ + @InternalApi + public StsRequestHandler.Builder setReadTimeout(int readTimeout) { + this.readTimeout = readTimeout; + return this; + } + public StsRequestHandler build() { - return new StsRequestHandler( - tokenExchangeEndpoint, request, httpRequestFactory, headers, internalOptions); + return new StsRequestHandler(this); } } } From 6d8d45d8e2ea646b4bd27918ddbfcd275a8ab3c3 Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Tue, 2 Dec 2025 14:16:42 -0500 Subject: [PATCH 2/4] chore: Fix lint issues --- .../google/auth/oauth2/ExternalAccountCredentials.java | 8 ++------ .../java/com/google/auth/oauth2/StsRequestHandler.java | 8 ++------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java index de0046b20..2e7c82987 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java @@ -1001,18 +1001,14 @@ public Builder setUniverseDomain(String universeDomain) { return this; } - /** - * Warning: Not for public use and can be removed at any time. - */ + /** Warning: Not for public use and can be removed at any time. */ @InternalApi public Builder setConnectTimeout(int connectTimeout) { this.connectTimeout = connectTimeout; return this; } - /** - * Warning: Not for public use and can be removed at any time. - */ + /** Warning: Not for public use and can be removed at any time. */ @InternalApi public Builder setReadTimeout(int readTimeout) { this.readTimeout = readTimeout; diff --git a/oauth2_http/java/com/google/auth/oauth2/StsRequestHandler.java b/oauth2_http/java/com/google/auth/oauth2/StsRequestHandler.java index 958409576..3c1061e6c 100644 --- a/oauth2_http/java/com/google/auth/oauth2/StsRequestHandler.java +++ b/oauth2_http/java/com/google/auth/oauth2/StsRequestHandler.java @@ -237,18 +237,14 @@ public StsRequestHandler.Builder setInternalOptions(String internalOptions) { return this; } - /** - * Warning: Not for public use and can be removed at any time. - */ + /** Warning: Not for public use and can be removed at any time. */ @InternalApi public StsRequestHandler.Builder setConnectTimeout(int connectTimeout) { this.connectTimeout = connectTimeout; return this; } - /** - * Warning: Not for public use and can be removed at any time. - */ + /** Warning: Not for public use and can be removed at any time. */ @InternalApi public StsRequestHandler.Builder setReadTimeout(int readTimeout) { this.readTimeout = readTimeout; From 56cae6c37c0b84191f5252b093bf5fffe3ffeab1 Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Tue, 2 Dec 2025 21:33:51 -0500 Subject: [PATCH 3/4] chore: Add connect and read timeout to identity pool creds --- .../auth/oauth2/ImpersonatedCredentials.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java index 77c319e0b..5b3df6fec 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java @@ -44,6 +44,7 @@ import com.google.api.client.json.GenericJson; import com.google.api.client.json.JsonObjectParser; import com.google.api.client.util.GenericData; +import com.google.api.core.InternalApi; import com.google.auth.CredentialTypeForMetrics; import com.google.auth.ServiceAccountSigner; import com.google.auth.http.HttpCredentialsAdapter; @@ -117,6 +118,9 @@ public class ImpersonatedCredentials extends GoogleCredentials private transient Calendar calendar; + private int connectTimeout; + private int readTimeout; + /** * @param sourceCredentials the source credential used to acquire the impersonated credentials. It * should be either a user account credential or a service account credential. @@ -559,6 +563,8 @@ private ImpersonatedCredentials(Builder builder) throws IOException { + "does not match %s universe domain set for impersonated credentials.", this.sourceCredentials.getUniverseDomain(), builder.getUniverseDomain())); } + this.connectTimeout = builder.connectTimeout; + this.readTimeout = builder.readTimeout; } /** @@ -587,6 +593,12 @@ public AccessToken refreshAccessToken() throws IOException { || (isDefaultUniverseDomain() && ((ServiceAccountCredentials) this.sourceCredentials) .shouldUseAssertionFlowForGdu())) { + if (this.sourceCredentials instanceof IdentityPoolCredentials) { + this.sourceCredentials = + ((IdentityPoolCredentials) this.sourceCredentials) + .toBuilder().setConnectTimeout(connectTimeout).setReadTimeout(readTimeout).build(); + } + try { this.sourceCredentials.refreshIfExpired(); } catch (IOException e) { @@ -616,6 +628,8 @@ public AccessToken refreshAccessToken() throws IOException { HttpContent requestContent = new JsonHttpContent(parser.getJsonFactory(), body); HttpRequest request = requestFactory.buildPostRequest(url, requestContent); + request.setConnectTimeout(connectTimeout); + request.setReadTimeout(readTimeout); adapter.initialize(request); request.setParser(parser); MetricsUtils.setMetricsHeader( @@ -746,6 +760,9 @@ public static class Builder extends GoogleCredentials.Builder { private String iamEndpointOverride; private Calendar calendar = Calendar.getInstance(); + private int connectTimeout = 20000; // Default to 20000ms = 20s + private int readTimeout = 20000; // Default to 20000ms = 20s + protected Builder() {} /** @@ -769,6 +786,8 @@ protected Builder(ImpersonatedCredentials credentials) { this.lifetime = credentials.lifetime; this.transportFactory = credentials.transportFactory; this.iamEndpointOverride = credentials.iamEndpointOverride; + this.connectTimeout = credentials.connectTimeout; + this.readTimeout = credentials.readTimeout; } @CanIgnoreReturnValue @@ -860,6 +879,20 @@ public Builder setCalendar(Calendar calendar) { return this; } + /** Warning: Not for public use and can be removed at any time. */ + @InternalApi + public Builder setConnectTimeout(int connectTimeout) { + this.connectTimeout = connectTimeout; + return this; + } + + /** Warning: Not for public use and can be removed at any time. */ + @InternalApi + public Builder setReadTimeout(int readTimeout) { + this.readTimeout = readTimeout; + return this; + } + public Calendar getCalendar() { return this.calendar; } From b1044cfc1726a0e20f22387d3f3a8ee0d25832ac Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Wed, 3 Dec 2025 11:36:48 -0500 Subject: [PATCH 4/4] chore: Propogate connect and read timeouts to all calls --- .../auth/oauth2/ExternalAccountCredentials.java | 12 +++++++----- .../com/google/auth/oauth2/StsRequestHandler.java | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java index 2e7c82987..7f9f0c207 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java @@ -311,6 +311,8 @@ ImpersonatedCredentials buildImpersonatedCredentials() { .setScopes(new ArrayList<>(scopes)) .setLifetime(this.serviceAccountImpersonationOptions.lifetime) .setIamEndpointOverride(serviceAccountImpersonationUrl) + .setConnectTimeout(connectTimeout) + .setReadTimeout(readTimeout) .build(); } @@ -539,9 +541,9 @@ protected AccessToken exchangeExternalCredentialForAccessToken( StsRequestHandler.Builder requestHandler = StsRequestHandler.newBuilder( - tokenUrl, stsTokenExchangeRequest, transportFactory.create().createRequestFactory()); - requestHandler.setConnectTimeout(connectTimeout); - requestHandler.setReadTimeout(readTimeout); + tokenUrl, stsTokenExchangeRequest, transportFactory.create().createRequestFactory()) + .setConnectTimeout(connectTimeout) + .setReadTimeout(readTimeout); // If this credential was initialized with a Workforce configuration then the // workforcePoolUserProject must be passed to the Security Token Service via the internal @@ -779,8 +781,8 @@ public abstract static class Builder extends GoogleCredentials.Builder { @Nullable protected String workforcePoolUserProject; @Nullable protected ServiceAccountImpersonationOptions serviceAccountImpersonationOptions; - private int connectTimeout = 20000; // Default to 20000ms = 20s - private int readTimeout = 20000; // Default to 20000ms = 20s + protected int connectTimeout = 20000; // Default to 20000ms = 20s + protected int readTimeout = 20000; // Default to 20000ms = 20s /* The field is not being used and value not set. Superseded by the same field in the {@link GoogleCredentials.Builder}. diff --git a/oauth2_http/java/com/google/auth/oauth2/StsRequestHandler.java b/oauth2_http/java/com/google/auth/oauth2/StsRequestHandler.java index 3c1061e6c..b2e635524 100644 --- a/oauth2_http/java/com/google/auth/oauth2/StsRequestHandler.java +++ b/oauth2_http/java/com/google/auth/oauth2/StsRequestHandler.java @@ -74,8 +74,8 @@ public final class StsRequestHandler { @Nullable private final HttpHeaders headers; @Nullable private final String internalOptions; - private int connectTimeout; - private int readTimeout; + private final int connectTimeout; + private final int readTimeout; /** * Internal constructor.