From 046ec89cf00ebaca74cb607f7df5e9657023e0ef Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Tue, 14 Oct 2025 15:16:23 -0400 Subject: [PATCH 1/3] feat: Introduce per-credential specific load methods --- ...ernalAccountAuthorizedUserCredentials.java | 31 ++++++---- .../oauth2/ExternalAccountCredentials.java | 32 +++++----- .../google/auth/oauth2/GdchCredentials.java | 55 ++++++++++++++++++ .../google/auth/oauth2/GoogleCredentials.java | 19 +++--- .../auth/oauth2/ImpersonatedCredentials.java | 58 ++++++++++++++++++- .../oauth2/ServiceAccountCredentials.java | 21 ++++--- .../google/auth/oauth2/UserCredentials.java | 12 +--- ...lAccountAuthorizedUserCredentialsTest.java | 4 ++ .../ExternalAccountCredentialsTest.java | 9 +++ .../auth/oauth2/GoogleCredentialsTest.java | 11 ++-- 10 files changed, 192 insertions(+), 60 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..82ac09317 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java @@ -32,7 +32,6 @@ package com.google.auth.oauth2; import static com.google.auth.oauth2.OAuth2Utils.JSON_FACTORY; -import static com.google.common.base.Preconditions.checkNotNull; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpHeaders; @@ -140,7 +139,6 @@ private ExternalAccountAuthorizedUserCredentials(Builder builder) { */ public static ExternalAccountAuthorizedUserCredentials fromStream(InputStream credentialsStream) throws IOException { - checkNotNull(credentialsStream); return fromStream(credentialsStream, OAuth2Utils.HTTP_TRANSPORT_FACTORY); } @@ -162,17 +160,26 @@ public static ExternalAccountAuthorizedUserCredentials fromStream(InputStream cr */ public static ExternalAccountAuthorizedUserCredentials fromStream( InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException { - checkNotNull(credentialsStream); - checkNotNull(transportFactory); - - JsonObjectParser parser = new JsonObjectParser(OAuth2Utils.JSON_FACTORY); - GenericJson fileContents = - parser.parseAndClose(credentialsStream, StandardCharsets.UTF_8, GenericJson.class); - try { - return fromJson(fileContents, transportFactory); - } catch (ClassCastException | IllegalArgumentException e) { - throw new CredentialFormatException("Invalid input stream provided.", e); + GenericJson fileContents = parseJsonInputStream(credentialsStream, transportFactory); + String fileType = (String) fileContents.get("type"); + if (fileType == null) { + throw new IOException("Error reading credentials from stream, 'type' field not specified."); + } + if (fileType.equals( + GoogleCredentialsInfo.EXTERNAL_ACCOUNT_AUTHORIZED_USER_CREDENTIALS.getFileType())) { + try { + return fromJson(fileContents, transportFactory); + } catch (ClassCastException | IllegalArgumentException e) { + throw new CredentialFormatException("Invalid input stream provided.", e); + } } + + throw new IOException( + String.format( + "Error reading credentials from stream, 'type' value '%s' not recognized." + + " Expecting '%s'.", + fileType, + GoogleCredentialsInfo.EXTERNAL_ACCOUNT_AUTHORIZED_USER_CREDENTIALS.getFileType())); } @Override diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java index c4268d167..22396b31b 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java @@ -35,7 +35,6 @@ 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.auth.RequestMetadataCallback; import com.google.auth.http.HttpTransportFactory; @@ -46,7 +45,6 @@ import java.io.ObjectInputStream; import java.math.BigDecimal; import java.net.URI; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -387,17 +385,24 @@ public static ExternalAccountCredentials fromStream(InputStream credentialsStrea */ public static ExternalAccountCredentials fromStream( InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException { - checkNotNull(credentialsStream); - checkNotNull(transportFactory); - - JsonObjectParser parser = new JsonObjectParser(OAuth2Utils.JSON_FACTORY); - GenericJson fileContents = - parser.parseAndClose(credentialsStream, StandardCharsets.UTF_8, GenericJson.class); - try { - return fromJson(fileContents, transportFactory); - } catch (ClassCastException | IllegalArgumentException e) { - throw new CredentialFormatException("An invalid input stream was provided.", e); + GenericJson fileContents = parseJsonInputStream(credentialsStream, transportFactory); + String fileType = (String) fileContents.get("type"); + if (fileType == null) { + throw new IOException("Error reading credentials from stream, 'type' field not specified."); } + if (fileType.equals(GoogleCredentialsInfo.EXTERNAL_ACCOUNT_CREDENTIALS.getFileType())) { + try { + return fromJson(fileContents, transportFactory); + } catch (ClassCastException | IllegalArgumentException e) { + throw new CredentialFormatException("An invalid input stream was provided.", e); + } + } + + throw new IOException( + String.format( + "Error reading credentials from stream, 'type' value '%s' not recognized." + + " Expecting '%s'.", + fileType, GoogleCredentialsInfo.EXTERNAL_ACCOUNT_CREDENTIALS.getFileType())); } /** @@ -417,9 +422,6 @@ public static ExternalAccountCredentials fromStream( @SuppressWarnings("unchecked") static ExternalAccountCredentials fromJson( Map json, HttpTransportFactory transportFactory) { - checkNotNull(json); - checkNotNull(transportFactory); - String audience = (String) json.get("audience"); String subjectTokenType = (String) json.get("subject_token_type"); String tokenUrl = (String) json.get("token_url"); diff --git a/oauth2_http/java/com/google/auth/oauth2/GdchCredentials.java b/oauth2_http/java/com/google/auth/oauth2/GdchCredentials.java index 10825c801..1d211780e 100644 --- a/oauth2_http/java/com/google/auth/oauth2/GdchCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/GdchCredentials.java @@ -39,6 +39,7 @@ import com.google.api.client.http.HttpTransport; import com.google.api.client.http.UrlEncodedContent; import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.GenericJson; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.JsonObjectParser; import com.google.api.client.json.webtoken.JsonWebSignature; @@ -99,6 +100,60 @@ public class GdchCredentials extends GoogleCredentials { this.name = GoogleCredentialsInfo.GDCH_CREDENTIALS.getCredentialName(); } + /** + * Returns credentials defined by a GDCHCredential key file in JSON format from the Google + * Developers Console. + * + *

Important: If you accept a credential configuration (credential JSON/File/Stream) from an + * external source for authentication to Google Cloud Platform, you must validate it before + * providing it to any Google API or library. Providing an unvalidated credential configuration to + * Google APIs can compromise the security of your systems and data. For more information, refer + * to {@see documentation}. + * + * @param credentialsStream the stream with the credential definition. + * @return the credential defined by the credentialsStream. + * @throws IOException if the credential cannot be created from the stream. + */ + public static GdchCredentials fromStream(InputStream credentialsStream) throws IOException { + return fromStream(credentialsStream, OAuth2Utils.HTTP_TRANSPORT_FACTORY); + } + + /** + * Returns credentials defined by a GDCHCredential key file in JSON format from the Google + * Developers Console. + * + *

Important: If you accept a credential configuration (credential JSON/File/Stream) from an + * external source for authentication to Google Cloud Platform, you must validate it before + * providing it to any Google API or library. Providing an unvalidated credential configuration to + * Google APIs can compromise the security of your systems and data. For more information, refer + * to {@see documentation}. + * + * @param credentialsStream the stream with the credential definition. + * @param transportFactory HTTP transport factory, creates the transport used to get access + * tokens. + * @return the credential defined by the credentialsStream. + * @throws IOException if the credential cannot be created from the stream. + */ + public static GdchCredentials fromStream( + InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException { + GenericJson fileContents = parseJsonInputStream(credentialsStream, transportFactory); + String fileType = (String) fileContents.get("type"); + if (fileType == null) { + throw new IOException("Error reading credentials from stream, 'type' field not specified."); + } + if (fileType.equals(GoogleCredentialsInfo.GDCH_CREDENTIALS.getFileType())) { + return fromJson(fileContents, transportFactory); + } + + throw new IOException( + String.format( + "Error reading credentials from stream, 'type' value '%s' not recognized." + + " Expecting '%s'.", + fileType, GoogleCredentialsInfo.GDCH_CREDENTIALS.getFileType())); + } + /** * Create GDCH service account credentials defined by JSON. * diff --git a/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java b/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java index fbfd147f2..82bee5184 100644 --- a/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java @@ -235,6 +235,16 @@ public static GoogleCredentials fromStream(InputStream credentialsStream) throws return fromStream(credentialsStream, OAuth2Utils.HTTP_TRANSPORT_FACTORY); } + static GenericJson parseJsonInputStream( + InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException { + Preconditions.checkNotNull(credentialsStream); + Preconditions.checkNotNull(transportFactory); + + JsonFactory jsonFactory = OAuth2Utils.JSON_FACTORY; + JsonObjectParser parser = new JsonObjectParser(jsonFactory); + return parser.parseAndClose(credentialsStream, StandardCharsets.UTF_8, GenericJson.class); + } + /** * This method is obsolete because of a potential security risk. Use the credential specific load * method instead @@ -276,14 +286,7 @@ public static GoogleCredentials fromStream(InputStream credentialsStream) throws "This method is obsolete because of a potential security risk. Use the credential specific load method instead") public static GoogleCredentials fromStream( InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException { - Preconditions.checkNotNull(credentialsStream); - Preconditions.checkNotNull(transportFactory); - - JsonFactory jsonFactory = OAuth2Utils.JSON_FACTORY; - JsonObjectParser parser = new JsonObjectParser(jsonFactory); - GenericJson fileContents = - parser.parseAndClose(credentialsStream, StandardCharsets.UTF_8, GenericJson.class); - + GenericJson fileContents = parseJsonInputStream(credentialsStream, transportFactory); String fileType = (String) fileContents.get("type"); if (fileType == null) { throw new IOException("Error reading credentials from stream, 'type' field not specified."); diff --git a/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java index 18d7cd0f8..58cbd64d1 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java @@ -41,6 +41,7 @@ import com.google.api.client.http.HttpResponse; import com.google.api.client.http.HttpTransport; import com.google.api.client.http.json.JsonHttpContent; +import com.google.api.client.json.GenericJson; import com.google.api.client.json.JsonObjectParser; import com.google.api.client.util.GenericData; import com.google.auth.CredentialTypeForMetrics; @@ -55,6 +56,7 @@ import com.google.common.collect.ImmutableMap; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.IOException; +import java.io.InputStream; import java.io.ObjectInputStream; import java.text.DateFormat; import java.text.ParseException; @@ -360,6 +362,61 @@ public byte[] sign(byte[] toSign) { } } + /** + * Returns credentials defined by a ImpersonatedCredential key file in JSON format from the Google + * Developers Console. + * + *

Important: If you accept a credential configuration (credential JSON/File/Stream) from an + * external source for authentication to Google Cloud Platform, you must validate it before + * providing it to any Google API or library. Providing an unvalidated credential configuration to + * Google APIs can compromise the security of your systems and data. For more information, refer + * to {@see documentation}. + * + * @param credentialsStream the stream with the credential definition. + * @return the credential defined by the credentialsStream. + * @throws IOException if the credential cannot be created from the stream. + */ + public static ImpersonatedCredentials fromStream(InputStream credentialsStream) + throws IOException { + return fromStream(credentialsStream, OAuth2Utils.HTTP_TRANSPORT_FACTORY); + } + + /** + * Returns credentials defined by a ImpersonatedCredential key file in JSON format from the Google + * Developers Console. + * + *

Important: If you accept a credential configuration (credential JSON/File/Stream) from an + * external source for authentication to Google Cloud Platform, you must validate it before + * providing it to any Google API or library. Providing an unvalidated credential configuration to + * Google APIs can compromise the security of your systems and data. For more information, refer + * to {@see documentation}. + * + * @param credentialsStream the stream with the credential definition. + * @param transportFactory HTTP transport factory, creates the transport used to get access + * tokens. + * @return the credential defined by the credentialsStream. + * @throws IOException if the credential cannot be created from the stream. + */ + public static ImpersonatedCredentials fromStream( + InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException { + GenericJson fileContents = parseJsonInputStream(credentialsStream, transportFactory); + String fileType = (String) fileContents.get("type"); + if (fileType == null) { + throw new IOException("Error reading credentials from stream, 'type' field not specified."); + } + if (fileType.equals(GoogleCredentialsInfo.IMPERSONATED_CREDENTIALS.getFileType())) { + return fromJson(fileContents, transportFactory); + } + + throw new IOException( + String.format( + "Error reading credentials from stream, 'type' value '%s' not recognized." + + " Expecting '%s'.", + fileType, GoogleCredentialsInfo.IMPERSONATED_CREDENTIALS.getFileType())); + } + /** * Returns impersonation account credentials defined by JSON using the format generated by gCloud. * The source credentials in the JSON should be either user account credentials or service account @@ -380,7 +437,6 @@ public byte[] sign(byte[] toSign) { @SuppressWarnings("unchecked") static ImpersonatedCredentials fromJson( Map json, HttpTransportFactory transportFactory) throws IOException { - checkNotNull(json); checkNotNull(transportFactory); diff --git a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java index 5628a5add..9224c0f16 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java @@ -43,6 +43,7 @@ import com.google.api.client.http.HttpResponse; import com.google.api.client.http.HttpResponseException; import com.google.api.client.http.UrlEncodedContent; +import com.google.api.client.json.GenericJson; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.JsonObjectParser; import com.google.api.client.json.webtoken.JsonWebSignature; @@ -485,14 +486,20 @@ public static ServiceAccountCredentials fromStream(InputStream credentialsStream */ public static ServiceAccountCredentials fromStream( InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException { - ServiceAccountCredentials credential = - (ServiceAccountCredentials) - GoogleCredentials.fromStream(credentialsStream, transportFactory); - if (credential == null) { - throw new IOException( - "Error reading credentials from stream, ServiceAccountCredentials type is not recognized."); + GenericJson fileContents = parseJsonInputStream(credentialsStream, transportFactory); + String fileType = (String) fileContents.get("type"); + if (fileType == null) { + throw new IOException("Error reading credentials from stream, 'type' field not specified."); + } + if (fileType.equals(GoogleCredentialsInfo.SERVICE_ACCOUNT_CREDENTIALS.getFileType())) { + return fromJson(fileContents, transportFactory); } - return credential; + + throw new IOException( + String.format( + "Error reading credentials from stream, 'type' value '%s' not recognized." + + " Expecting '%s'.", + fileType, GoogleCredentialsInfo.SERVICE_ACCOUNT_CREDENTIALS.getFileType())); } /** Returns whether the scopes are empty, meaning createScoped must be called before use. */ diff --git a/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java b/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java index 084d24c8c..019e1b803 100644 --- a/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java @@ -41,7 +41,6 @@ import com.google.api.client.http.HttpResponseException; import com.google.api.client.http.UrlEncodedContent; import com.google.api.client.json.GenericJson; -import com.google.api.client.json.JsonFactory; import com.google.api.client.json.JsonObjectParser; import com.google.api.client.util.GenericData; import com.google.api.client.util.Preconditions; @@ -173,19 +172,12 @@ public static UserCredentials fromStream(InputStream credentialsStream) throws I */ public static UserCredentials fromStream( InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException { - Preconditions.checkNotNull(credentialsStream); - Preconditions.checkNotNull(transportFactory); - - JsonFactory jsonFactory = JSON_FACTORY; - JsonObjectParser parser = new JsonObjectParser(jsonFactory); - GenericJson fileContents = - parser.parseAndClose(credentialsStream, StandardCharsets.UTF_8, GenericJson.class); - + GenericJson fileContents = parseJsonInputStream(credentialsStream, transportFactory); String fileType = (String) fileContents.get("type"); if (fileType == null) { throw new IOException("Error reading credentials from stream, 'type' field not specified."); } - if (GoogleCredentialsInfo.USER_CREDENTIALS.getFileType().equals(fileType)) { + if (fileType.equals(GoogleCredentialsInfo.USER_CREDENTIALS.getFileType())) { return fromJson(fileContents, transportFactory); } throw new IOException( diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java index 740cabba5..8328ac54d 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentialsTest.java @@ -542,6 +542,8 @@ public void fromStream_allFields() throws IOException { @Test public void fromStream_minimumRequiredFieldsForRefresh() throws IOException { GenericJson json = new GenericJson(); + json.put( + "type", GoogleCredentialsInfo.EXTERNAL_ACCOUNT_AUTHORIZED_USER_CREDENTIALS.getFileType()); json.put("client_id", CLIENT_ID); json.put("client_secret", CLIENT_SECRET); json.put("refresh_token", REFRESH_TOKEN); @@ -565,6 +567,8 @@ public void fromStream_minimumRequiredFieldsForRefresh() throws IOException { public void fromStream_accessTokenOnly_notSupported() throws IOException { GenericJson json = new GenericJson(); json.put("access_token", ACCESS_TOKEN); + json.put( + "type", GoogleCredentialsInfo.EXTERNAL_ACCOUNT_AUTHORIZED_USER_CREDENTIALS.getFileType()); try { ExternalAccountAuthorizedUserCredentials.fromStream(TestUtils.jsonToInputStream(json)); fail("Should not be able to continue without exception."); diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java index 32009f755..497ead12a 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java @@ -154,6 +154,7 @@ public void fromStream_nullOptionalField() throws IOException { ExternalAccountCredentials.fromStream( new ByteArrayInputStream( ("{" + + "\"type\": \"external_account\"," + "\"service_account_impersonation_url\": null," + // required @@ -1250,6 +1251,8 @@ public void validateServiceAccountImpersonationUrls_invalidUrls() { private GenericJson buildJsonIdentityPoolCredential() { GenericJson json = new GenericJson(); + json.put( + "type", GoogleCredentials.GoogleCredentialsInfo.EXTERNAL_ACCOUNT_CREDENTIALS.getFileType()); json.put( "audience", "//iam.googleapis.com/projects/123/locations/global/workloadIdentityPools/pool/providers/provider"); @@ -1273,6 +1276,8 @@ private GenericJson buildJsonIdentityPoolWorkforceCredential() { private GenericJson buildJsonAwsCredential() { GenericJson json = new GenericJson(); + json.put( + "type", GoogleCredentials.GoogleCredentialsInfo.EXTERNAL_ACCOUNT_CREDENTIALS.getFileType()); json.put("audience", "audience"); json.put("subject_token_type", "subjectTokenType"); json.put("token_url", STS_URL); @@ -1290,6 +1295,8 @@ private GenericJson buildJsonAwsCredential() { private GenericJson buildJsonPluggableAuthCredential() { GenericJson json = new GenericJson(); + json.put( + "type", GoogleCredentials.GoogleCredentialsInfo.EXTERNAL_ACCOUNT_CREDENTIALS.getFileType()); json.put("audience", "audience"); json.put("subject_token_type", "subjectTokenType"); json.put("token_url", STS_URL); @@ -1308,6 +1315,8 @@ private GenericJson buildJsonPluggableAuthCredential() { private GenericJson buildJsonPluggableAuthWorkforceCredential() { GenericJson json = buildJsonPluggableAuthCredential(); + json.put( + "type", GoogleCredentials.GoogleCredentialsInfo.EXTERNAL_ACCOUNT_CREDENTIALS.getFileType()); json.put( "audience", "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider"); json.put("workforce_pool_user_project", "userProject"); diff --git a/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java index 5004fd6b6..0f74c4c20 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java @@ -153,14 +153,11 @@ public void fromStream_noType_throws() throws IOException { } @Test - public void fromStream_nullStream_throws() throws IOException { + public void fromStream_nullStream_throws() { MockHttpTransportFactory transportFactory = new MockHttpTransportFactory(); - try { - GoogleCredentials.fromStream(null, transportFactory); - fail("Should throw if InputStream is null"); - } catch (NullPointerException expected) { - // Expected - } + assertThrows( + NullPointerException.class, + () -> GoogleCredentials.parseJsonInputStream(null, transportFactory)); } @Test From e203c111231ac9f77bfdd74d267b5a7cce6c26d8 Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Tue, 14 Oct 2025 15:49:13 -0400 Subject: [PATCH 2/3] chore: Refactor parseJsonInputStream to only accept credentialStream --- ...ExternalAccountAuthorizedUserCredentials.java | 3 ++- .../auth/oauth2/ExternalAccountCredentials.java | 4 +++- .../com/google/auth/oauth2/GdchCredentials.java | 7 ++++--- .../google/auth/oauth2/GoogleCredentials.java | 16 ++++++++-------- .../auth/oauth2/ImpersonatedCredentials.java | 3 ++- .../auth/oauth2/ServiceAccountCredentials.java | 3 ++- .../com/google/auth/oauth2/UserCredentials.java | 3 ++- .../auth/oauth2/GoogleCredentialsTest.java | 4 +--- 8 files changed, 24 insertions(+), 19 deletions(-) diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java index 82ac09317..c1c459365 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java @@ -160,7 +160,8 @@ public static ExternalAccountAuthorizedUserCredentials fromStream(InputStream cr */ public static ExternalAccountAuthorizedUserCredentials fromStream( InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException { - GenericJson fileContents = parseJsonInputStream(credentialsStream, transportFactory); + Preconditions.checkNotNull(transportFactory); + GenericJson fileContents = parseJsonInputStream(credentialsStream); String fileType = (String) fileContents.get("type"); if (fileType == null) { throw new IOException("Error reading credentials from stream, 'type' field not specified."); diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java index 22396b31b..bc2cbbbb2 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java @@ -39,6 +39,7 @@ import com.google.auth.RequestMetadataCallback; import com.google.auth.http.HttpTransportFactory; import com.google.common.base.MoreObjects; +import com.google.common.base.Preconditions; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.IOException; import java.io.InputStream; @@ -385,7 +386,8 @@ public static ExternalAccountCredentials fromStream(InputStream credentialsStrea */ public static ExternalAccountCredentials fromStream( InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException { - GenericJson fileContents = parseJsonInputStream(credentialsStream, transportFactory); + Preconditions.checkNotNull(transportFactory); + GenericJson fileContents = parseJsonInputStream(credentialsStream); String fileType = (String) fileContents.get("type"); if (fileType == null) { throw new IOException("Error reading credentials from stream, 'type' field not specified."); diff --git a/oauth2_http/java/com/google/auth/oauth2/GdchCredentials.java b/oauth2_http/java/com/google/auth/oauth2/GdchCredentials.java index 1d211780e..6eebcbe1b 100644 --- a/oauth2_http/java/com/google/auth/oauth2/GdchCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/GdchCredentials.java @@ -101,7 +101,7 @@ public class GdchCredentials extends GoogleCredentials { } /** - * Returns credentials defined by a GDCHCredential key file in JSON format from the Google + * Returns credentials defined by a GdchCredentials key file in JSON format from the Google * Developers Console. * *

Important: If you accept a credential configuration (credential JSON/File/Stream) from an @@ -120,7 +120,7 @@ public static GdchCredentials fromStream(InputStream credentialsStream) throws I } /** - * Returns credentials defined by a GDCHCredential key file in JSON format from the Google + * Returns credentials defined by a GdchCredentials key file in JSON format from the Google * Developers Console. * *

Important: If you accept a credential configuration (credential JSON/File/Stream) from an @@ -138,7 +138,8 @@ public static GdchCredentials fromStream(InputStream credentialsStream) throws I */ public static GdchCredentials fromStream( InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException { - GenericJson fileContents = parseJsonInputStream(credentialsStream, transportFactory); + Preconditions.checkNotNull(transportFactory); + GenericJson fileContents = parseJsonInputStream(credentialsStream); String fileType = (String) fileContents.get("type"); if (fileType == null) { throw new IOException("Error reading credentials from stream, 'type' field not specified."); diff --git a/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java b/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java index 82bee5184..2a73fa26f 100644 --- a/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java @@ -32,7 +32,6 @@ package com.google.auth.oauth2; import com.google.api.client.json.GenericJson; -import com.google.api.client.json.JsonFactory; import com.google.api.client.json.JsonObjectParser; import com.google.api.client.util.Preconditions; import com.google.api.core.ObsoleteApi; @@ -235,13 +234,13 @@ public static GoogleCredentials fromStream(InputStream credentialsStream) throws return fromStream(credentialsStream, OAuth2Utils.HTTP_TRANSPORT_FACTORY); } - static GenericJson parseJsonInputStream( - InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException { + /** + * Parses the Credential InputStream into JSON for each credential subclass to consume. The + * Credential InputStream must be non-null and valid. + */ + static GenericJson parseJsonInputStream(InputStream credentialsStream) throws IOException { Preconditions.checkNotNull(credentialsStream); - Preconditions.checkNotNull(transportFactory); - - JsonFactory jsonFactory = OAuth2Utils.JSON_FACTORY; - JsonObjectParser parser = new JsonObjectParser(jsonFactory); + JsonObjectParser parser = new JsonObjectParser(OAuth2Utils.JSON_FACTORY); return parser.parseAndClose(credentialsStream, StandardCharsets.UTF_8, GenericJson.class); } @@ -286,7 +285,8 @@ static GenericJson parseJsonInputStream( "This method is obsolete because of a potential security risk. Use the credential specific load method instead") public static GoogleCredentials fromStream( InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException { - GenericJson fileContents = parseJsonInputStream(credentialsStream, transportFactory); + Preconditions.checkNotNull(transportFactory); + GenericJson fileContents = parseJsonInputStream(credentialsStream); String fileType = (String) fileContents.get("type"); if (fileType == null) { throw new IOException("Error reading credentials from stream, 'type' field not specified."); diff --git a/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java index 58cbd64d1..ff9140c66 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java @@ -401,7 +401,8 @@ public static ImpersonatedCredentials fromStream(InputStream credentialsStream) */ public static ImpersonatedCredentials fromStream( InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException { - GenericJson fileContents = parseJsonInputStream(credentialsStream, transportFactory); + Preconditions.checkNotNull(transportFactory); + GenericJson fileContents = parseJsonInputStream(credentialsStream); String fileType = (String) fileContents.get("type"); if (fileType == null) { throw new IOException("Error reading credentials from stream, 'type' field not specified."); diff --git a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java index 9224c0f16..2fbad9f6b 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java @@ -486,7 +486,8 @@ public static ServiceAccountCredentials fromStream(InputStream credentialsStream */ public static ServiceAccountCredentials fromStream( InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException { - GenericJson fileContents = parseJsonInputStream(credentialsStream, transportFactory); + Preconditions.checkNotNull(transportFactory); + GenericJson fileContents = parseJsonInputStream(credentialsStream); String fileType = (String) fileContents.get("type"); if (fileType == null) { throw new IOException("Error reading credentials from stream, 'type' field not specified."); diff --git a/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java b/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java index 019e1b803..c0f30366f 100644 --- a/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java @@ -172,7 +172,8 @@ public static UserCredentials fromStream(InputStream credentialsStream) throws I */ public static UserCredentials fromStream( InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException { - GenericJson fileContents = parseJsonInputStream(credentialsStream, transportFactory); + Preconditions.checkNotNull(transportFactory); + GenericJson fileContents = parseJsonInputStream(credentialsStream); String fileType = (String) fileContents.get("type"); if (fileType == null) { throw new IOException("Error reading credentials from stream, 'type' field not specified."); diff --git a/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java index 0f74c4c20..cd577bbf5 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java @@ -155,9 +155,7 @@ public void fromStream_noType_throws() throws IOException { @Test public void fromStream_nullStream_throws() { MockHttpTransportFactory transportFactory = new MockHttpTransportFactory(); - assertThrows( - NullPointerException.class, - () -> GoogleCredentials.parseJsonInputStream(null, transportFactory)); + assertThrows(NullPointerException.class, () -> GoogleCredentials.parseJsonInputStream(null)); } @Test From e9df028ffa86a86396520fe06b7083f7cb39c1fa Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Tue, 14 Oct 2025 16:03:21 -0400 Subject: [PATCH 3/3] chore: Create helper method to extract the type field --- .../ExternalAccountAuthorizedUserCredentials.java | 5 +---- .../auth/oauth2/ExternalAccountCredentials.java | 5 +---- .../com/google/auth/oauth2/GdchCredentials.java | 5 +---- .../com/google/auth/oauth2/GoogleCredentials.java | 13 +++++++++++++ .../google/auth/oauth2/ImpersonatedCredentials.java | 5 +---- .../auth/oauth2/ServiceAccountCredentials.java | 5 +---- .../com/google/auth/oauth2/UserCredentials.java | 5 +---- 7 files changed, 19 insertions(+), 24 deletions(-) diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java index c1c459365..eb2d64e09 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java @@ -162,10 +162,7 @@ public static ExternalAccountAuthorizedUserCredentials fromStream( InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException { Preconditions.checkNotNull(transportFactory); GenericJson fileContents = parseJsonInputStream(credentialsStream); - String fileType = (String) fileContents.get("type"); - if (fileType == null) { - throw new IOException("Error reading credentials from stream, 'type' field not specified."); - } + String fileType = extractFromJson(fileContents, "type"); if (fileType.equals( GoogleCredentialsInfo.EXTERNAL_ACCOUNT_AUTHORIZED_USER_CREDENTIALS.getFileType())) { try { diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java index bc2cbbbb2..2e95379ba 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java @@ -388,10 +388,7 @@ public static ExternalAccountCredentials fromStream( InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException { Preconditions.checkNotNull(transportFactory); GenericJson fileContents = parseJsonInputStream(credentialsStream); - String fileType = (String) fileContents.get("type"); - if (fileType == null) { - throw new IOException("Error reading credentials from stream, 'type' field not specified."); - } + String fileType = extractFromJson(fileContents, "type"); if (fileType.equals(GoogleCredentialsInfo.EXTERNAL_ACCOUNT_CREDENTIALS.getFileType())) { try { return fromJson(fileContents, transportFactory); diff --git a/oauth2_http/java/com/google/auth/oauth2/GdchCredentials.java b/oauth2_http/java/com/google/auth/oauth2/GdchCredentials.java index 6eebcbe1b..c5e8bd576 100644 --- a/oauth2_http/java/com/google/auth/oauth2/GdchCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/GdchCredentials.java @@ -140,10 +140,7 @@ public static GdchCredentials fromStream( InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException { Preconditions.checkNotNull(transportFactory); GenericJson fileContents = parseJsonInputStream(credentialsStream); - String fileType = (String) fileContents.get("type"); - if (fileType == null) { - throw new IOException("Error reading credentials from stream, 'type' field not specified."); - } + String fileType = extractFromJson(fileContents, "type"); if (fileType.equals(GoogleCredentialsInfo.GDCH_CREDENTIALS.getFileType())) { return fromJson(fileContents, transportFactory); } diff --git a/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java b/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java index 2a73fa26f..7395274c4 100644 --- a/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java @@ -244,6 +244,19 @@ static GenericJson parseJsonInputStream(InputStream credentialsStream) throws IO return parser.parseAndClose(credentialsStream, StandardCharsets.UTF_8, GenericJson.class); } + /** + * Internal helper method to try and extract a field from the json stream and throw an exception + * if it doesn't exist. + */ + static String extractFromJson(Map json, String field) throws IOException { + String fileType = (String) json.get(field); + if (fileType == null) { + throw new IOException( + "Error reading credentials from stream, '" + field + "' field not specified."); + } + return fileType; + } + /** * This method is obsolete because of a potential security risk. Use the credential specific load * method instead diff --git a/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java index ff9140c66..77c319e0b 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java @@ -403,10 +403,7 @@ public static ImpersonatedCredentials fromStream( InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException { Preconditions.checkNotNull(transportFactory); GenericJson fileContents = parseJsonInputStream(credentialsStream); - String fileType = (String) fileContents.get("type"); - if (fileType == null) { - throw new IOException("Error reading credentials from stream, 'type' field not specified."); - } + String fileType = extractFromJson(fileContents, "type"); if (fileType.equals(GoogleCredentialsInfo.IMPERSONATED_CREDENTIALS.getFileType())) { return fromJson(fileContents, transportFactory); } diff --git a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java index 2fbad9f6b..15700dd7f 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java @@ -488,10 +488,7 @@ public static ServiceAccountCredentials fromStream( InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException { Preconditions.checkNotNull(transportFactory); GenericJson fileContents = parseJsonInputStream(credentialsStream); - String fileType = (String) fileContents.get("type"); - if (fileType == null) { - throw new IOException("Error reading credentials from stream, 'type' field not specified."); - } + String fileType = extractFromJson(fileContents, "type"); if (fileType.equals(GoogleCredentialsInfo.SERVICE_ACCOUNT_CREDENTIALS.getFileType())) { return fromJson(fileContents, transportFactory); } diff --git a/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java b/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java index c0f30366f..fa4399765 100644 --- a/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java @@ -174,10 +174,7 @@ public static UserCredentials fromStream( InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException { Preconditions.checkNotNull(transportFactory); GenericJson fileContents = parseJsonInputStream(credentialsStream); - String fileType = (String) fileContents.get("type"); - if (fileType == null) { - throw new IOException("Error reading credentials from stream, 'type' field not specified."); - } + String fileType = extractFromJson(fileContents, "type"); if (fileType.equals(GoogleCredentialsInfo.USER_CREDENTIALS.getFileType())) { return fromJson(fileContents, transportFactory); }