diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java index e67ddb89d..eb2d64e09 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,24 @@ 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); + Preconditions.checkNotNull(transportFactory); + GenericJson fileContents = parseJsonInputStream(credentialsStream); + String fileType = extractFromJson(fileContents, "type"); + 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..2e95379ba 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java @@ -35,18 +35,17 @@ 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; 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; 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 +386,22 @@ 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); + Preconditions.checkNotNull(transportFactory); + GenericJson fileContents = parseJsonInputStream(credentialsStream); + String fileType = extractFromJson(fileContents, "type"); + 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 +421,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..c5e8bd576 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,58 @@ public class GdchCredentials extends GoogleCredentials { this.name = GoogleCredentialsInfo.GDCH_CREDENTIALS.getCredentialName(); } + /** + * 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 + * 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 GdchCredentials 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 { + Preconditions.checkNotNull(transportFactory); + GenericJson fileContents = parseJsonInputStream(credentialsStream); + String fileType = extractFromJson(fileContents, "type"); + 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..7395274c4 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,6 +234,29 @@ public static GoogleCredentials fromStream(InputStream credentialsStream) throws return fromStream(credentialsStream, OAuth2Utils.HTTP_TRANSPORT_FACTORY); } + /** + * 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); + JsonObjectParser parser = new JsonObjectParser(OAuth2Utils.JSON_FACTORY); + 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 @@ -276,14 +298,8 @@ 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); 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..77c319e0b 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,59 @@ 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 { + Preconditions.checkNotNull(transportFactory); + GenericJson fileContents = parseJsonInputStream(credentialsStream); + String fileType = extractFromJson(fileContents, "type"); + 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 +435,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..15700dd7f 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,18 @@ 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."); + Preconditions.checkNotNull(transportFactory); + GenericJson fileContents = parseJsonInputStream(credentialsStream); + String fileType = extractFromJson(fileContents, "type"); + 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..fa4399765 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,10 @@ 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); - - 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)) { + GenericJson fileContents = parseJsonInputStream(credentialsStream); + String fileType = extractFromJson(fileContents, "type"); + 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..cd577bbf5 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java @@ -153,14 +153,9 @@ 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)); } @Test