Skip to content

Commit 82442dc

Browse files
committed
Add unit tests for IdentityPoolCredentials.
1 parent faa0a9e commit 82442dc

File tree

5 files changed

+324
-139
lines changed

5 files changed

+324
-139
lines changed

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

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
import java.io.IOException;
4242
import java.io.InputStream;
4343
import java.nio.file.Files;
44-
import java.nio.file.InvalidPathException;
4544
import java.nio.file.Paths;
4645
import java.security.cert.CertificateEncodingException;
4746
import java.security.cert.CertificateException;
@@ -74,13 +73,7 @@ public class CertificateIdentityPoolSubjectTokenSupplier
7473
private static X509Certificate loadLeafCertificate(String path)
7574
throws IOException, CertificateException {
7675
byte[] leafCertBytes;
77-
try {
78-
// IdentityPoolCredentials should have already validated the path exists via X509Provider.
79-
leafCertBytes = Files.readAllBytes(Paths.get(path));
80-
} catch (InvalidPathException e) {
81-
throw new IOException("Invalid certificate file path provided: " + path, e);
82-
}
83-
// Files.readAllBytes throws IOException for other read errors.
76+
leafCertBytes = Files.readAllBytes(Paths.get(path));
8477
return parseCertificate(leafCertBytes);
8578
}
8679

@@ -95,6 +88,8 @@ static X509Certificate parseCertificate(byte[] certData) throws CertificateExcep
9588
InputStream certificateStream = new ByteArrayInputStream(certData);
9689
return (X509Certificate) certificateFactory.generateCertificate(certificateStream);
9790
} catch (CertificateException e) {
91+
// Catch the original exception to add context about the operation being performed.
92+
// This helps pinpoint the failure point during debugging.
9893
throw new CertificateException("Failed to parse X.509 certificate data.", e);
9994
}
10095
}
@@ -117,6 +112,9 @@ public String getSubjectToken(ExternalAccountSupplierContext context) throws IOE
117112

118113
return GSON.toJson(certChain);
119114
} catch (CertificateException e) {
115+
// Catch CertificateException to provide a more specific error message including
116+
// the path of the file that failed to parse, and re-throw as IOException
117+
// as expected by the getSubjectToken method signature for I/O related issues.
120118
throw new IOException(
121119
"Failed to parse certificate from: " + credentialSource.credentialLocation, e);
122120
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,11 +174,11 @@ public static class CertificateConfig implements java.io.Serializable {
174174

175175
checkArgument(
176176
(useDefault || locationIsPresent),
177-
"credentials: \"certificate\" object must either specify a certificate_config_location or use_default_certificate_config should be true");
177+
"Invalid 'certificate' configuration in credential source: Must specify either 'certificate_config_location' or set 'use_default_certificate_config' to true.");
178178

179179
checkArgument(
180180
!(useDefault && locationIsPresent),
181-
"credentials: \"certificate\" object cannot specify both a certificate_config_location and use_default_certificate_config=true");
181+
"Invalid 'certificate' configuration in credential source: Cannot specify both 'certificate_config_location' and set 'use_default_certificate_config' to true.");
182182

183183
this.useDefaultCertificateConfig = useDefault;
184184
this.certificateConfigLocation = certificateConfigLocation;
@@ -245,7 +245,7 @@ public IdentityPoolCredentialSource(Map<String, Object> credentialSourceMap) {
245245
this.certificateConfig = getCertificateConfig(credentialSourceMap);
246246
} else {
247247
throw new IllegalArgumentException(
248-
"Missing credential source file location or URL. At least one must be specified.");
248+
"Missing credential source file location, URL, or certificate. At least one must be specified.");
249249
}
250250

251251
Map<String, String> headersMap = (Map<String, String>) credentialSourceMap.get("headers");

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

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -100,19 +100,19 @@ public class IdentityPoolCredentials extends ExternalAccountCredentials {
100100
final IdentityPoolCredentialSource.CertificateConfig certConfig =
101101
credentialSource.certificateConfig;
102102

103-
// Determine the certificate path based on the configuration.
104-
String explicitCertConfigPath = null;
105-
if (!certConfig.useDefaultCertificateConfig()) {
106-
explicitCertConfigPath = certConfig.getCertificateConfigLocation();
107-
if (explicitCertConfigPath == null || explicitCertConfigPath.isEmpty()) {
108-
throw new IllegalArgumentException(
109-
"certificateConfigLocation must be provided when useDefaultCertificateConfig is false.");
110-
}
103+
// Use the provided X509Provider if available.
104+
X509Provider x509Provider = builder.x509Provider;
105+
if (x509Provider == null) {
106+
// Determine the certificate path based on the configuration.
107+
String explicitCertConfigPath =
108+
certConfig.useDefaultCertificateConfig()
109+
? null
110+
: certConfig.getCertificateConfigLocation();
111+
112+
// Initialize X509Provider with the explicit path (if provided).
113+
x509Provider = new X509Provider(explicitCertConfigPath);
111114
}
112115

113-
// Initialize X509Provider with the explicit path (if provided).
114-
X509Provider x509Provider = new X509Provider(explicitCertConfigPath);
115-
116116
// Update the transport factory to use a mTLS transport with the provided certificate.
117117
KeyStore mtlsKeyStore = x509Provider.getKeyStore();
118118
final NetHttpTransport mtlsTransport =
@@ -129,7 +129,7 @@ public class IdentityPoolCredentials extends ExternalAccountCredentials {
129129
} catch (GeneralSecurityException | IOException e) {
130130
// Catch exceptions from X509Provider or transport creation
131131
throw new RuntimeException(
132-
"Failed to initialize mTLS transport for IdentityPoolCredentials using X509Provider",
132+
"Failed to initialize mTLS transport for IdentityPoolCredentials using X509Provider.",
133133
e);
134134
}
135135
} else {
@@ -184,6 +184,7 @@ public static Builder newBuilder(IdentityPoolCredentials identityPoolCredentials
184184
public static class Builder extends ExternalAccountCredentials.Builder {
185185

186186
private IdentityPoolSubjectTokenSupplier subjectTokenSupplier;
187+
private X509Provider x509Provider;
187188

188189
Builder() {}
189190

@@ -194,6 +195,21 @@ public static class Builder extends ExternalAccountCredentials.Builder {
194195
}
195196
}
196197

198+
/**
199+
* Sets a custom {@link X509Provider} to manage the client certificate and private key for mTLS.
200+
* If set, this provider will be used instead of the default behavior which initializes an
201+
* {@code X509Provider} based on the {@code certificateConfigLocation} or default paths found in
202+
* the {@code credentialSource}. This is primarily used for testing.
203+
*
204+
* @param x509Provider the custom X509 provider to use.
205+
* @return this {@code Builder} object
206+
*/
207+
@CanIgnoreReturnValue
208+
public Builder setX509Provider(X509Provider x509Provider) {
209+
this.x509Provider = x509Provider;
210+
return this;
211+
}
212+
197213
/**
198214
* Sets the subject token supplier. The supplier should return a valid subject token string.
199215
*

oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsSourceTest.java

Lines changed: 45 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -31,126 +31,122 @@
3131

3232
package com.google.auth.oauth2;
3333

34-
import static com.google.auth.Credentials.GOOGLE_DEFAULT_UNIVERSE;
35-
import static com.google.auth.oauth2.MockExternalAccountCredentialsTransport.SERVICE_ACCOUNT_IMPERSONATION_URL;
36-
import static com.google.auth.oauth2.OAuth2Utils.JSON_FACTORY;
3734
import static org.junit.Assert.*;
3835

39-
import com.google.api.client.http.HttpTransport;
40-
import com.google.api.client.json.GenericJson;
41-
import com.google.api.client.util.Clock;
42-
import com.google.auth.TestUtils;
43-
import com.google.auth.http.HttpTransportFactory;
44-
import java.io.ByteArrayInputStream;
45-
import java.io.File;
46-
import java.io.IOException;
47-
import java.io.InputStream;
48-
import java.nio.charset.StandardCharsets;
49-
import java.util.Arrays;
36+
import com.google.auth.oauth2.IdentityPoolCredentialSource.IdentityPoolCredentialSourceType;
5037
import java.util.HashMap;
51-
import java.util.List;
5238
import java.util.Map;
53-
import javax.annotation.Nullable;
5439
import org.junit.Test;
5540
import org.junit.runner.RunWith;
5641
import org.junit.runners.JUnit4;
5742

58-
import com.google.auth.oauth2.IdentityPoolCredentialSource.IdentityPoolCredentialSourceType;
59-
6043
/** Tests for {@link IdentityPoolCredentialSource}. */
6144
@RunWith(JUnit4.class)
6245
public class IdentityPoolCredentialsSourceTest {
6346

6447
@Test
65-
public void constructor_certificateConfig(){
48+
public void constructor_certificateConfig() {
6649
Map<String, Object> certificateMap = new HashMap<>();
6750
certificateMap.put("certificate_config_location", "/path/to/certificate");
6851

6952
Map<String, Object> credentialSourceMap = new HashMap<>();
7053
credentialSourceMap.put("certificate", certificateMap);
7154

72-
IdentityPoolCredentialSource credentialSource = new IdentityPoolCredentialSource(credentialSourceMap);
73-
assertEquals(IdentityPoolCredentialSourceType.CERTIFICATE, credentialSource.credentialSourceType);
55+
IdentityPoolCredentialSource credentialSource =
56+
new IdentityPoolCredentialSource(credentialSourceMap);
57+
assertEquals(
58+
IdentityPoolCredentialSourceType.CERTIFICATE, credentialSource.credentialSourceType);
7459
assertNotNull(credentialSource.certificateConfig);
7560
assertFalse(credentialSource.certificateConfig.useDefaultCertificateConfig());
76-
assertEquals("/path/to/certificate", credentialSource.certificateConfig.getCertificateConfigLocation());
61+
assertEquals(
62+
"/path/to/certificate", credentialSource.certificateConfig.getCertificateConfigLocation());
7763
}
7864

7965
@Test
80-
public void constructor_certificateConfig_useDefault(){
66+
public void constructor_certificateConfig_useDefault() {
8167
Map<String, Object> certificateMap = new HashMap<>();
8268
certificateMap.put("use_default_certificate_config", true);
8369

8470
Map<String, Object> credentialSourceMap = new HashMap<>();
8571
credentialSourceMap.put("certificate", certificateMap);
8672

87-
IdentityPoolCredentialSource credentialSource = new IdentityPoolCredentialSource(credentialSourceMap);
88-
assertEquals(IdentityPoolCredentialSourceType.CERTIFICATE, credentialSource.credentialSourceType);
73+
IdentityPoolCredentialSource credentialSource =
74+
new IdentityPoolCredentialSource(credentialSourceMap);
75+
assertEquals(
76+
IdentityPoolCredentialSourceType.CERTIFICATE, credentialSource.credentialSourceType);
8977
assertNotNull(credentialSource.certificateConfig);
9078
assertTrue(credentialSource.certificateConfig.useDefaultCertificateConfig());
9179
}
9280

9381
@Test
94-
public void constructor_certificateConfig_missingRequiredFields_throws(){
82+
public void constructor_certificateConfig_missingRequiredFields_throws() {
9583
Map<String, Object> certificateMap = new HashMap<>();
96-
//Missing both use_default_certificate_config and certificate_config_location
84+
// Missing both use_default_certificate_config and certificate_config_location.
9785
certificateMap.put("trust_chain_path", "path/to/trust/chain");
9886

9987
Map<String, Object> credentialSourceMap = new HashMap<>();
10088
credentialSourceMap.put("certificate", certificateMap);
10189

102-
IllegalArgumentException exception = assertThrows(
103-
IllegalArgumentException.class,
104-
() -> new IdentityPoolCredentialSource(credentialSourceMap)
105-
);
106-
assertTrue(exception.getMessage().contains("must either specify a certificate_config_location or use_default_certificate_config should be true"));
90+
IllegalArgumentException exception =
91+
assertThrows(
92+
IllegalArgumentException.class,
93+
() -> new IdentityPoolCredentialSource(credentialSourceMap));
94+
assertEquals(
95+
"Invalid 'certificate' configuration in credential source: Must specify either 'certificate_config_location' or set 'use_default_certificate_config' to true.",
96+
exception.getMessage());
10797
}
10898

10999
@Test
110-
public void constructor_certificateConfig_bothFieldsSet_throws(){
100+
public void constructor_certificateConfig_bothFieldsSet_throws() {
111101
Map<String, Object> certificateMap = new HashMap<>();
112102
certificateMap.put("use_default_certificate_config", true);
113103
certificateMap.put("certificate_config_location", "/path/to/certificate");
114104

115105
Map<String, Object> credentialSourceMap = new HashMap<>();
116106
credentialSourceMap.put("certificate", certificateMap);
117107

118-
IllegalArgumentException exception = assertThrows(
119-
IllegalArgumentException.class,
120-
() -> new IdentityPoolCredentialSource(credentialSourceMap)
121-
);
122-
assertTrue(exception.getMessage().contains("cannot specify both a certificate_config_location and use_default_certificate_config=true"));
108+
IllegalArgumentException exception =
109+
assertThrows(
110+
IllegalArgumentException.class,
111+
() -> new IdentityPoolCredentialSource(credentialSourceMap));
112+
113+
assertEquals(
114+
"Invalid 'certificate' configuration in credential source: Cannot specify both 'certificate_config_location' and set 'use_default_certificate_config' to true.",
115+
exception.getMessage());
123116
}
124117

125118
@Test
126-
public void constructor_certificateConfig_trustChainPath(){
119+
public void constructor_certificateConfig_trustChainPath() {
127120
Map<String, Object> certificateMap = new HashMap<>();
128121
certificateMap.put("use_default_certificate_config", true);
129122
certificateMap.put("trust_chain_path", "path/to/trust/chain");
130123

131124
Map<String, Object> credentialSourceMap = new HashMap<>();
132125
credentialSourceMap.put("certificate", certificateMap);
133126

134-
IdentityPoolCredentialSource credentialSource = new IdentityPoolCredentialSource(credentialSourceMap);
135-
assertEquals(IdentityPoolCredentialSourceType.CERTIFICATE, credentialSource.credentialSourceType);
127+
IdentityPoolCredentialSource credentialSource =
128+
new IdentityPoolCredentialSource(credentialSourceMap);
129+
assertEquals(
130+
IdentityPoolCredentialSourceType.CERTIFICATE, credentialSource.credentialSourceType);
136131
assertNotNull(credentialSource.certificateConfig);
137132
assertEquals("path/to/trust/chain", credentialSource.certificateConfig.getTrustChainPath());
138133
}
139134

140-
141135
@Test
142-
public void constructor_certificateConfig_invalidType_throws(){
136+
public void constructor_certificateConfig_invalidType_throws() {
143137
Map<String, Object> certificateMap = new HashMap<>();
144138
certificateMap.put("use_default_certificate_config", "invalid-type");
145139

146140
Map<String, Object> credentialSourceMap = new HashMap<>();
147141
credentialSourceMap.put("certificate", certificateMap);
148142

149-
IllegalArgumentException exception = assertThrows(
150-
IllegalArgumentException.class,
151-
() -> new IdentityPoolCredentialSource(credentialSourceMap)
152-
);
153-
assertTrue(exception.getMessage().contains("Invalid type for 'use_default_certificate_config' in certificate configuration: expected Boolean"));
154-
}
143+
IllegalArgumentException exception =
144+
assertThrows(
145+
IllegalArgumentException.class,
146+
() -> new IdentityPoolCredentialSource(credentialSourceMap));
155147

148+
assertEquals(
149+
"Invalid type for 'use_default_certificate_config' in certificate configuration: expected Boolean, got String.",
150+
exception.getMessage());
151+
}
156152
}

0 commit comments

Comments
 (0)