Skip to content

Commit 39fdc64

Browse files
authored
feat: Introduce per-credential specific load methods (#1827)
* feat: Introduce per-credential specific load methods * chore: Refactor parseJsonInputStream to only accept credentialStream * chore: Create helper method to extract the type field
1 parent 73e9e44 commit 39fdc64

10 files changed

+195
-63
lines changed

oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
package com.google.auth.oauth2;
3333

3434
import static com.google.auth.oauth2.OAuth2Utils.JSON_FACTORY;
35-
import static com.google.common.base.Preconditions.checkNotNull;
3635

3736
import com.google.api.client.http.GenericUrl;
3837
import com.google.api.client.http.HttpHeaders;
@@ -140,7 +139,6 @@ private ExternalAccountAuthorizedUserCredentials(Builder builder) {
140139
*/
141140
public static ExternalAccountAuthorizedUserCredentials fromStream(InputStream credentialsStream)
142141
throws IOException {
143-
checkNotNull(credentialsStream);
144142
return fromStream(credentialsStream, OAuth2Utils.HTTP_TRANSPORT_FACTORY);
145143
}
146144

@@ -162,17 +160,24 @@ public static ExternalAccountAuthorizedUserCredentials fromStream(InputStream cr
162160
*/
163161
public static ExternalAccountAuthorizedUserCredentials fromStream(
164162
InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException {
165-
checkNotNull(credentialsStream);
166-
checkNotNull(transportFactory);
167-
168-
JsonObjectParser parser = new JsonObjectParser(OAuth2Utils.JSON_FACTORY);
169-
GenericJson fileContents =
170-
parser.parseAndClose(credentialsStream, StandardCharsets.UTF_8, GenericJson.class);
171-
try {
172-
return fromJson(fileContents, transportFactory);
173-
} catch (ClassCastException | IllegalArgumentException e) {
174-
throw new CredentialFormatException("Invalid input stream provided.", e);
163+
Preconditions.checkNotNull(transportFactory);
164+
GenericJson fileContents = parseJsonInputStream(credentialsStream);
165+
String fileType = extractFromJson(fileContents, "type");
166+
if (fileType.equals(
167+
GoogleCredentialsInfo.EXTERNAL_ACCOUNT_AUTHORIZED_USER_CREDENTIALS.getFileType())) {
168+
try {
169+
return fromJson(fileContents, transportFactory);
170+
} catch (ClassCastException | IllegalArgumentException e) {
171+
throw new CredentialFormatException("Invalid input stream provided.", e);
172+
}
175173
}
174+
175+
throw new IOException(
176+
String.format(
177+
"Error reading credentials from stream, 'type' value '%s' not recognized."
178+
+ " Expecting '%s'.",
179+
fileType,
180+
GoogleCredentialsInfo.EXTERNAL_ACCOUNT_AUTHORIZED_USER_CREDENTIALS.getFileType()));
176181
}
177182

178183
@Override

oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,17 @@
3535

3636
import com.google.api.client.http.HttpHeaders;
3737
import com.google.api.client.json.GenericJson;
38-
import com.google.api.client.json.JsonObjectParser;
3938
import com.google.api.client.util.Data;
4039
import com.google.auth.RequestMetadataCallback;
4140
import com.google.auth.http.HttpTransportFactory;
4241
import com.google.common.base.MoreObjects;
42+
import com.google.common.base.Preconditions;
4343
import com.google.errorprone.annotations.CanIgnoreReturnValue;
4444
import java.io.IOException;
4545
import java.io.InputStream;
4646
import java.io.ObjectInputStream;
4747
import java.math.BigDecimal;
4848
import java.net.URI;
49-
import java.nio.charset.StandardCharsets;
5049
import java.util.ArrayList;
5150
import java.util.Collection;
5251
import java.util.Collections;
@@ -387,17 +386,22 @@ public static ExternalAccountCredentials fromStream(InputStream credentialsStrea
387386
*/
388387
public static ExternalAccountCredentials fromStream(
389388
InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException {
390-
checkNotNull(credentialsStream);
391-
checkNotNull(transportFactory);
392-
393-
JsonObjectParser parser = new JsonObjectParser(OAuth2Utils.JSON_FACTORY);
394-
GenericJson fileContents =
395-
parser.parseAndClose(credentialsStream, StandardCharsets.UTF_8, GenericJson.class);
396-
try {
397-
return fromJson(fileContents, transportFactory);
398-
} catch (ClassCastException | IllegalArgumentException e) {
399-
throw new CredentialFormatException("An invalid input stream was provided.", e);
389+
Preconditions.checkNotNull(transportFactory);
390+
GenericJson fileContents = parseJsonInputStream(credentialsStream);
391+
String fileType = extractFromJson(fileContents, "type");
392+
if (fileType.equals(GoogleCredentialsInfo.EXTERNAL_ACCOUNT_CREDENTIALS.getFileType())) {
393+
try {
394+
return fromJson(fileContents, transportFactory);
395+
} catch (ClassCastException | IllegalArgumentException e) {
396+
throw new CredentialFormatException("An invalid input stream was provided.", e);
397+
}
400398
}
399+
400+
throw new IOException(
401+
String.format(
402+
"Error reading credentials from stream, 'type' value '%s' not recognized."
403+
+ " Expecting '%s'.",
404+
fileType, GoogleCredentialsInfo.EXTERNAL_ACCOUNT_CREDENTIALS.getFileType()));
401405
}
402406

403407
/**
@@ -417,9 +421,6 @@ public static ExternalAccountCredentials fromStream(
417421
@SuppressWarnings("unchecked")
418422
static ExternalAccountCredentials fromJson(
419423
Map<String, Object> json, HttpTransportFactory transportFactory) {
420-
checkNotNull(json);
421-
checkNotNull(transportFactory);
422-
423424
String audience = (String) json.get("audience");
424425
String subjectTokenType = (String) json.get("subject_token_type");
425426
String tokenUrl = (String) json.get("token_url");

oauth2_http/java/com/google/auth/oauth2/GdchCredentials.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import com.google.api.client.http.HttpTransport;
4040
import com.google.api.client.http.UrlEncodedContent;
4141
import com.google.api.client.http.javanet.NetHttpTransport;
42+
import com.google.api.client.json.GenericJson;
4243
import com.google.api.client.json.JsonFactory;
4344
import com.google.api.client.json.JsonObjectParser;
4445
import com.google.api.client.json.webtoken.JsonWebSignature;
@@ -99,6 +100,58 @@ public class GdchCredentials extends GoogleCredentials {
99100
this.name = GoogleCredentialsInfo.GDCH_CREDENTIALS.getCredentialName();
100101
}
101102

103+
/**
104+
* Returns credentials defined by a GdchCredentials key file in JSON format from the Google
105+
* Developers Console.
106+
*
107+
* <p>Important: If you accept a credential configuration (credential JSON/File/Stream) from an
108+
* external source for authentication to Google Cloud Platform, you must validate it before
109+
* providing it to any Google API or library. Providing an unvalidated credential configuration to
110+
* Google APIs can compromise the security of your systems and data. For more information, refer
111+
* to {@see <a
112+
* href="https://cloud.google.com/docs/authentication/external/externally-sourced-credentials">documentation</a>}.
113+
*
114+
* @param credentialsStream the stream with the credential definition.
115+
* @return the credential defined by the credentialsStream.
116+
* @throws IOException if the credential cannot be created from the stream.
117+
*/
118+
public static GdchCredentials fromStream(InputStream credentialsStream) throws IOException {
119+
return fromStream(credentialsStream, OAuth2Utils.HTTP_TRANSPORT_FACTORY);
120+
}
121+
122+
/**
123+
* Returns credentials defined by a GdchCredentials key file in JSON format from the Google
124+
* Developers Console.
125+
*
126+
* <p>Important: If you accept a credential configuration (credential JSON/File/Stream) from an
127+
* external source for authentication to Google Cloud Platform, you must validate it before
128+
* providing it to any Google API or library. Providing an unvalidated credential configuration to
129+
* Google APIs can compromise the security of your systems and data. For more information, refer
130+
* to {@see <a
131+
* href="https://cloud.google.com/docs/authentication/external/externally-sourced-credentials">documentation</a>}.
132+
*
133+
* @param credentialsStream the stream with the credential definition.
134+
* @param transportFactory HTTP transport factory, creates the transport used to get access
135+
* tokens.
136+
* @return the credential defined by the credentialsStream.
137+
* @throws IOException if the credential cannot be created from the stream.
138+
*/
139+
public static GdchCredentials fromStream(
140+
InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException {
141+
Preconditions.checkNotNull(transportFactory);
142+
GenericJson fileContents = parseJsonInputStream(credentialsStream);
143+
String fileType = extractFromJson(fileContents, "type");
144+
if (fileType.equals(GoogleCredentialsInfo.GDCH_CREDENTIALS.getFileType())) {
145+
return fromJson(fileContents, transportFactory);
146+
}
147+
148+
throw new IOException(
149+
String.format(
150+
"Error reading credentials from stream, 'type' value '%s' not recognized."
151+
+ " Expecting '%s'.",
152+
fileType, GoogleCredentialsInfo.GDCH_CREDENTIALS.getFileType()));
153+
}
154+
102155
/**
103156
* Create GDCH service account credentials defined by JSON.
104157
*

oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
package com.google.auth.oauth2;
3333

3434
import com.google.api.client.json.GenericJson;
35-
import com.google.api.client.json.JsonFactory;
3635
import com.google.api.client.json.JsonObjectParser;
3736
import com.google.api.client.util.Preconditions;
3837
import com.google.api.core.ObsoleteApi;
@@ -235,6 +234,29 @@ public static GoogleCredentials fromStream(InputStream credentialsStream) throws
235234
return fromStream(credentialsStream, OAuth2Utils.HTTP_TRANSPORT_FACTORY);
236235
}
237236

237+
/**
238+
* Parses the Credential InputStream into JSON for each credential subclass to consume. The
239+
* Credential InputStream must be non-null and valid.
240+
*/
241+
static GenericJson parseJsonInputStream(InputStream credentialsStream) throws IOException {
242+
Preconditions.checkNotNull(credentialsStream);
243+
JsonObjectParser parser = new JsonObjectParser(OAuth2Utils.JSON_FACTORY);
244+
return parser.parseAndClose(credentialsStream, StandardCharsets.UTF_8, GenericJson.class);
245+
}
246+
247+
/**
248+
* Internal helper method to try and extract a field from the json stream and throw an exception
249+
* if it doesn't exist.
250+
*/
251+
static String extractFromJson(Map<String, Object> json, String field) throws IOException {
252+
String fileType = (String) json.get(field);
253+
if (fileType == null) {
254+
throw new IOException(
255+
"Error reading credentials from stream, '" + field + "' field not specified.");
256+
}
257+
return fileType;
258+
}
259+
238260
/**
239261
* This method is obsolete because of a potential security risk. Use the credential specific load
240262
* method instead
@@ -276,14 +298,8 @@ public static GoogleCredentials fromStream(InputStream credentialsStream) throws
276298
"This method is obsolete because of a potential security risk. Use the credential specific load method instead")
277299
public static GoogleCredentials fromStream(
278300
InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException {
279-
Preconditions.checkNotNull(credentialsStream);
280301
Preconditions.checkNotNull(transportFactory);
281-
282-
JsonFactory jsonFactory = OAuth2Utils.JSON_FACTORY;
283-
JsonObjectParser parser = new JsonObjectParser(jsonFactory);
284-
GenericJson fileContents =
285-
parser.parseAndClose(credentialsStream, StandardCharsets.UTF_8, GenericJson.class);
286-
302+
GenericJson fileContents = parseJsonInputStream(credentialsStream);
287303
String fileType = (String) fileContents.get("type");
288304
if (fileType == null) {
289305
throw new IOException("Error reading credentials from stream, 'type' field not specified.");

oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import com.google.api.client.http.HttpResponse;
4242
import com.google.api.client.http.HttpTransport;
4343
import com.google.api.client.http.json.JsonHttpContent;
44+
import com.google.api.client.json.GenericJson;
4445
import com.google.api.client.json.JsonObjectParser;
4546
import com.google.api.client.util.GenericData;
4647
import com.google.auth.CredentialTypeForMetrics;
@@ -55,6 +56,7 @@
5556
import com.google.common.collect.ImmutableMap;
5657
import com.google.errorprone.annotations.CanIgnoreReturnValue;
5758
import java.io.IOException;
59+
import java.io.InputStream;
5860
import java.io.ObjectInputStream;
5961
import java.text.DateFormat;
6062
import java.text.ParseException;
@@ -360,6 +362,59 @@ public byte[] sign(byte[] toSign) {
360362
}
361363
}
362364

365+
/**
366+
* Returns credentials defined by a ImpersonatedCredential key file in JSON format from the Google
367+
* Developers Console.
368+
*
369+
* <p>Important: If you accept a credential configuration (credential JSON/File/Stream) from an
370+
* external source for authentication to Google Cloud Platform, you must validate it before
371+
* providing it to any Google API or library. Providing an unvalidated credential configuration to
372+
* Google APIs can compromise the security of your systems and data. For more information, refer
373+
* to {@see <a
374+
* href="https://cloud.google.com/docs/authentication/external/externally-sourced-credentials">documentation</a>}.
375+
*
376+
* @param credentialsStream the stream with the credential definition.
377+
* @return the credential defined by the credentialsStream.
378+
* @throws IOException if the credential cannot be created from the stream.
379+
*/
380+
public static ImpersonatedCredentials fromStream(InputStream credentialsStream)
381+
throws IOException {
382+
return fromStream(credentialsStream, OAuth2Utils.HTTP_TRANSPORT_FACTORY);
383+
}
384+
385+
/**
386+
* Returns credentials defined by a ImpersonatedCredential key file in JSON format from the Google
387+
* Developers Console.
388+
*
389+
* <p>Important: If you accept a credential configuration (credential JSON/File/Stream) from an
390+
* external source for authentication to Google Cloud Platform, you must validate it before
391+
* providing it to any Google API or library. Providing an unvalidated credential configuration to
392+
* Google APIs can compromise the security of your systems and data. For more information, refer
393+
* to {@see <a
394+
* href="https://cloud.google.com/docs/authentication/external/externally-sourced-credentials">documentation</a>}.
395+
*
396+
* @param credentialsStream the stream with the credential definition.
397+
* @param transportFactory HTTP transport factory, creates the transport used to get access
398+
* tokens.
399+
* @return the credential defined by the credentialsStream.
400+
* @throws IOException if the credential cannot be created from the stream.
401+
*/
402+
public static ImpersonatedCredentials fromStream(
403+
InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException {
404+
Preconditions.checkNotNull(transportFactory);
405+
GenericJson fileContents = parseJsonInputStream(credentialsStream);
406+
String fileType = extractFromJson(fileContents, "type");
407+
if (fileType.equals(GoogleCredentialsInfo.IMPERSONATED_CREDENTIALS.getFileType())) {
408+
return fromJson(fileContents, transportFactory);
409+
}
410+
411+
throw new IOException(
412+
String.format(
413+
"Error reading credentials from stream, 'type' value '%s' not recognized."
414+
+ " Expecting '%s'.",
415+
fileType, GoogleCredentialsInfo.IMPERSONATED_CREDENTIALS.getFileType()));
416+
}
417+
363418
/**
364419
* Returns impersonation account credentials defined by JSON using the format generated by gCloud.
365420
* The source credentials in the JSON should be either user account credentials or service account
@@ -380,7 +435,6 @@ public byte[] sign(byte[] toSign) {
380435
@SuppressWarnings("unchecked")
381436
static ImpersonatedCredentials fromJson(
382437
Map<String, Object> json, HttpTransportFactory transportFactory) throws IOException {
383-
384438
checkNotNull(json);
385439
checkNotNull(transportFactory);
386440

oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import com.google.api.client.http.HttpResponse;
4444
import com.google.api.client.http.HttpResponseException;
4545
import com.google.api.client.http.UrlEncodedContent;
46+
import com.google.api.client.json.GenericJson;
4647
import com.google.api.client.json.JsonFactory;
4748
import com.google.api.client.json.JsonObjectParser;
4849
import com.google.api.client.json.webtoken.JsonWebSignature;
@@ -485,14 +486,18 @@ public static ServiceAccountCredentials fromStream(InputStream credentialsStream
485486
*/
486487
public static ServiceAccountCredentials fromStream(
487488
InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException {
488-
ServiceAccountCredentials credential =
489-
(ServiceAccountCredentials)
490-
GoogleCredentials.fromStream(credentialsStream, transportFactory);
491-
if (credential == null) {
492-
throw new IOException(
493-
"Error reading credentials from stream, ServiceAccountCredentials type is not recognized.");
489+
Preconditions.checkNotNull(transportFactory);
490+
GenericJson fileContents = parseJsonInputStream(credentialsStream);
491+
String fileType = extractFromJson(fileContents, "type");
492+
if (fileType.equals(GoogleCredentialsInfo.SERVICE_ACCOUNT_CREDENTIALS.getFileType())) {
493+
return fromJson(fileContents, transportFactory);
494494
}
495-
return credential;
495+
496+
throw new IOException(
497+
String.format(
498+
"Error reading credentials from stream, 'type' value '%s' not recognized."
499+
+ " Expecting '%s'.",
500+
fileType, GoogleCredentialsInfo.SERVICE_ACCOUNT_CREDENTIALS.getFileType()));
496501
}
497502

498503
/** Returns whether the scopes are empty, meaning createScoped must be called before use. */

oauth2_http/java/com/google/auth/oauth2/UserCredentials.java

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
import com.google.api.client.http.HttpResponseException;
4242
import com.google.api.client.http.UrlEncodedContent;
4343
import com.google.api.client.json.GenericJson;
44-
import com.google.api.client.json.JsonFactory;
4544
import com.google.api.client.json.JsonObjectParser;
4645
import com.google.api.client.util.GenericData;
4746
import com.google.api.client.util.Preconditions;
@@ -173,19 +172,10 @@ public static UserCredentials fromStream(InputStream credentialsStream) throws I
173172
*/
174173
public static UserCredentials fromStream(
175174
InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException {
176-
Preconditions.checkNotNull(credentialsStream);
177175
Preconditions.checkNotNull(transportFactory);
178-
179-
JsonFactory jsonFactory = JSON_FACTORY;
180-
JsonObjectParser parser = new JsonObjectParser(jsonFactory);
181-
GenericJson fileContents =
182-
parser.parseAndClose(credentialsStream, StandardCharsets.UTF_8, GenericJson.class);
183-
184-
String fileType = (String) fileContents.get("type");
185-
if (fileType == null) {
186-
throw new IOException("Error reading credentials from stream, 'type' field not specified.");
187-
}
188-
if (GoogleCredentialsInfo.USER_CREDENTIALS.getFileType().equals(fileType)) {
176+
GenericJson fileContents = parseJsonInputStream(credentialsStream);
177+
String fileType = extractFromJson(fileContents, "type");
178+
if (fileType.equals(GoogleCredentialsInfo.USER_CREDENTIALS.getFileType())) {
189179
return fromJson(fileContents, transportFactory);
190180
}
191181
throw new IOException(

0 commit comments

Comments
 (0)