1 /*
2  * Copyright 2022, Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *    * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *    * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *
15  *    * Neither the name of Google Inc. nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 package com.google.auth.oauth2;
33 
34 import static org.junit.Assert.assertEquals;
35 import static org.junit.Assert.assertFalse;
36 import static org.junit.Assert.assertNotNull;
37 import static org.junit.Assert.assertNull;
38 import static org.junit.Assert.assertSame;
39 import static org.junit.Assert.assertTrue;
40 import static org.junit.Assert.fail;
41 
42 import com.google.api.client.json.GenericJson;
43 import com.google.api.client.json.JsonFactory;
44 import com.google.api.client.json.webtoken.JsonWebSignature;
45 import com.google.api.client.json.webtoken.JsonWebToken;
46 import com.google.api.client.testing.http.FixedClock;
47 import com.google.api.client.util.Clock;
48 import com.google.auth.TestUtils;
49 import java.io.File;
50 import java.io.IOException;
51 import java.io.InputStream;
52 import java.net.URI;
53 import java.nio.file.Files;
54 import java.util.List;
55 import java.util.Map;
56 import org.junit.Test;
57 import org.junit.runner.RunWith;
58 import org.junit.runners.JUnit4;
59 
60 /** Test case for {@link GdchCredentials}. */
61 @RunWith(JUnit4.class)
62 public class GdchCredentialsTest extends BaseSerializationTest {
63   private static final String FORMAT_VERSION = GdchCredentials.SUPPORTED_FORMAT_VERSION;
64   private static final String PRIVATE_KEY_ID = "d84a4fefcf50791d4a90f2d7af17469d6282df9d";
65   static final String PRIVATE_KEY_PKCS8 =
66       "-----BEGIN PRIVATE KEY-----\n"
67           + "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALX0PQoe1igW12i"
68           + "kv1bN/r9lN749y2ijmbc/mFHPyS3hNTyOCjDvBbXYbDhQJzWVUikh4mvGBA07qTj79Xc3yBDfKP2IeyYQIFe0t0"
69           + "zkd7R9Zdn98Y2rIQC47aAbDfubtkU1U72t4zL11kHvoa0/RuFZjncvlr42X7be7lYh4p3NAgMBAAECgYASk5wDw"
70           + "4Az2ZkmeuN6Fk/y9H+Lcb2pskJIXjrL533vrDWGOC48LrsThMQPv8cxBky8HFSEklPpkfTF95tpD43iVwJRB/Gr"
71           + "CtGTw65IfJ4/tI09h6zGc4yqvIo1cHX/LQ+SxKLGyir/dQM925rGt/VojxY5ryJR7GLbCzxPnJm/oQJBANwOCO6"
72           + "D2hy1LQYJhXh7O+RLtA/tSnT1xyMQsGT+uUCMiKS2bSKx2wxo9k7h3OegNJIu1q6nZ6AbxDK8H3+d0dUCQQDTrP"
73           + "SXagBxzp8PecbaCHjzNRSQE2in81qYnrAFNB4o3DpHyMMY6s5ALLeHKscEWnqP8Ur6X4PvzZecCWU9BKAZAkAut"
74           + "LPknAuxSCsUOvUfS1i87ex77Ot+w6POp34pEX+UWb+u5iFn2cQacDTHLV1LtE80L8jVLSbrbrlH43H0DjU5AkEA"
75           + "gidhycxS86dxpEljnOMCw8CKoUBd5I880IUahEiUltk7OLJYS/Ts1wbn3kPOVX3wyJs8WBDtBkFrDHW2ezth2QJ"
76           + "ADj3e1YhMVdjJW5jqwlD/VNddGjgzyunmiZg0uOXsHXbytYmsA545S8KRQFaJKFXYYFo2kOjqOiC1T2cAzMDjCQ"
77           + "==\n-----END PRIVATE KEY-----\n";
78   private static final String PROJECT_ID = "project-id";
79   private static final String SERVICE_IDENTITY_NAME = "service-identity-name";
80   private static final String ACCESS_TOKEN = "1/MkSJoj1xsli0AccessToken_NKPY2";
81   private static final URI TOKEN_SERVER_URI =
82       URI.create("https://service-identity.domain/authenticate");
83   private static final String CA_CERT_FILE_NAME = "cert.pem";
84   private static final String CA_CERT_PATH =
85       GdchCredentialsTest.class.getClassLoader().getResource(CA_CERT_FILE_NAME).getPath();
86   private static final URI API_AUDIENCE = URI.create("https://gdch-api-audience");
87   private static final URI CALL_URI = URI.create("http://googleapis.com/testapi/v1/foo");
88 
89   @Test
fromJSON_getProjectId()90   public void fromJSON_getProjectId() throws IOException {
91     GenericJson json =
92         writeGdchServiceAccountJson(
93             FORMAT_VERSION,
94             PROJECT_ID,
95             PRIVATE_KEY_ID,
96             PRIVATE_KEY_PKCS8,
97             SERVICE_IDENTITY_NAME,
98             CA_CERT_PATH,
99             TOKEN_SERVER_URI);
100     GdchCredentials credentials = GdchCredentials.fromJson(json);
101 
102     assertEquals(PROJECT_ID, credentials.getProjectId());
103   }
104 
105   @Test
fromJSON_getServiceIdentityName()106   public void fromJSON_getServiceIdentityName() throws IOException {
107     GenericJson json =
108         writeGdchServiceAccountJson(
109             FORMAT_VERSION,
110             PROJECT_ID,
111             PRIVATE_KEY_ID,
112             PRIVATE_KEY_PKCS8,
113             SERVICE_IDENTITY_NAME,
114             CA_CERT_PATH,
115             TOKEN_SERVER_URI);
116     GdchCredentials credentials = GdchCredentials.fromJson(json);
117 
118     assertEquals(SERVICE_IDENTITY_NAME, credentials.getServiceIdentityName());
119   }
120 
121   @Test
fromJSON_getCaCertPath()122   public void fromJSON_getCaCertPath() throws IOException {
123     GenericJson json =
124         writeGdchServiceAccountJson(
125             FORMAT_VERSION,
126             PROJECT_ID,
127             PRIVATE_KEY_ID,
128             PRIVATE_KEY_PKCS8,
129             SERVICE_IDENTITY_NAME,
130             CA_CERT_PATH,
131             TOKEN_SERVER_URI);
132     GdchCredentials credentials = GdchCredentials.fromJson(json);
133 
134     assertEquals(CA_CERT_PATH, credentials.getCaCertPath());
135   }
136 
137   @Test
fromJSON_getTokenServerUri()138   public void fromJSON_getTokenServerUri() throws IOException {
139     GenericJson json =
140         writeGdchServiceAccountJson(
141             FORMAT_VERSION,
142             PROJECT_ID,
143             PRIVATE_KEY_ID,
144             PRIVATE_KEY_PKCS8,
145             SERVICE_IDENTITY_NAME,
146             CA_CERT_PATH,
147             TOKEN_SERVER_URI);
148     GdchCredentials credentials = GdchCredentials.fromJson(json);
149 
150     assertEquals(TOKEN_SERVER_URI, credentials.getTokenServerUri());
151   }
152 
153   @Test
fromJSON_nullFormatVersion()154   public void fromJSON_nullFormatVersion() throws IOException {
155     GenericJson json =
156         writeGdchServiceAccountJson(
157             null,
158             PROJECT_ID,
159             PRIVATE_KEY_ID,
160             PRIVATE_KEY_PKCS8,
161             SERVICE_IDENTITY_NAME,
162             CA_CERT_PATH,
163             TOKEN_SERVER_URI);
164 
165     try {
166       GdchCredentials credentials = GdchCredentials.fromJson(json);
167       fail("Should not be able to create GDCH credential without exception.");
168     } catch (IOException ex) {
169       assertTrue(
170           ex.getMessage()
171               .contains(
172                   String.format(
173                       "Error reading GDCH service account credential from JSON, "
174                           + "%s is misconfigured.",
175                       "format_version")));
176     }
177   }
178 
179   @Test
fromJSON_nullProjectId()180   public void fromJSON_nullProjectId() throws IOException {
181     GenericJson json =
182         writeGdchServiceAccountJson(
183             FORMAT_VERSION,
184             null,
185             PRIVATE_KEY_ID,
186             PRIVATE_KEY_PKCS8,
187             SERVICE_IDENTITY_NAME,
188             CA_CERT_PATH,
189             TOKEN_SERVER_URI);
190 
191     try {
192       GdchCredentials credentials = GdchCredentials.fromJson(json);
193       fail("Should not be able to create GDCH credential without exception.");
194     } catch (IOException ex) {
195       assertTrue(
196           ex.getMessage()
197               .contains(
198                   String.format(
199                       "Error reading GDCH service account credential from JSON, "
200                           + "%s is misconfigured.",
201                       "project")));
202     }
203   }
204 
205   @Test
fromJSON_nullPrivateKeyId()206   public void fromJSON_nullPrivateKeyId() throws IOException {
207     GenericJson json =
208         writeGdchServiceAccountJson(
209             FORMAT_VERSION,
210             PROJECT_ID,
211             null,
212             PRIVATE_KEY_PKCS8,
213             SERVICE_IDENTITY_NAME,
214             CA_CERT_PATH,
215             TOKEN_SERVER_URI);
216 
217     try {
218       GdchCredentials credentials = GdchCredentials.fromJson(json);
219       fail("Should not be able to create GDCH credential without exception.");
220     } catch (IOException ex) {
221       assertTrue(
222           ex.getMessage()
223               .contains(
224                   String.format(
225                       "Error reading GDCH service account credential from JSON, "
226                           + "%s is misconfigured.",
227                       "private_key_id")));
228     }
229   }
230 
231   @Test
fromJSON_nullPrivateKey()232   public void fromJSON_nullPrivateKey() throws IOException {
233     GenericJson json =
234         writeGdchServiceAccountJson(
235             FORMAT_VERSION,
236             PROJECT_ID,
237             PRIVATE_KEY_ID,
238             null,
239             SERVICE_IDENTITY_NAME,
240             CA_CERT_PATH,
241             TOKEN_SERVER_URI);
242 
243     try {
244       GdchCredentials credentials = GdchCredentials.fromJson(json);
245       fail("Should not be able to create GDCH credential without exception.");
246     } catch (IOException ex) {
247       assertTrue(
248           ex.getMessage()
249               .contains(
250                   String.format(
251                       "Error reading GDCH service account credential from JSON, "
252                           + "%s is misconfigured.",
253                       "private_key")));
254     }
255   }
256 
257   @Test
fromJSON_nullServiceIdentityName()258   public void fromJSON_nullServiceIdentityName() throws IOException {
259     GenericJson json =
260         writeGdchServiceAccountJson(
261             FORMAT_VERSION,
262             PROJECT_ID,
263             PRIVATE_KEY_ID,
264             PRIVATE_KEY_PKCS8,
265             null,
266             CA_CERT_PATH,
267             TOKEN_SERVER_URI);
268 
269     try {
270       GdchCredentials credentials = GdchCredentials.fromJson(json);
271       fail("Should not be able to create GDCH credential without exception.");
272     } catch (IOException ex) {
273       assertTrue(
274           ex.getMessage()
275               .contains(
276                   String.format(
277                       "Error reading GDCH service account credential from JSON, "
278                           + "%s is misconfigured.",
279                       "name")));
280     }
281   }
282 
283   @Test
fromJSON_nullCaCertPath()284   public void fromJSON_nullCaCertPath() throws IOException {
285     GenericJson json =
286         writeGdchServiceAccountJson(
287             FORMAT_VERSION,
288             PROJECT_ID,
289             PRIVATE_KEY_ID,
290             PRIVATE_KEY_PKCS8,
291             SERVICE_IDENTITY_NAME,
292             null,
293             TOKEN_SERVER_URI);
294     GdchCredentials credentials = GdchCredentials.fromJson(json);
295     assertNull(credentials.getCaCertPath());
296   }
297 
298   @Test
fromJSON_nullTokenServerUri()299   public void fromJSON_nullTokenServerUri() throws IOException {
300     GenericJson json =
301         writeGdchServiceAccountJson(
302             FORMAT_VERSION,
303             PROJECT_ID,
304             PRIVATE_KEY_ID,
305             PRIVATE_KEY_PKCS8,
306             SERVICE_IDENTITY_NAME,
307             CA_CERT_PATH,
308             null);
309 
310     try {
311       GdchCredentials credentials = GdchCredentials.fromJson(json);
312       fail("Should not be able to create GDCH credential without exception.");
313     } catch (IOException ex) {
314       assertTrue(
315           ex.getMessage()
316               .contains(
317                   String.format(
318                       "Error reading GDCH service account credential from JSON, "
319                           + "%s is misconfigured.",
320                       "token_uri")));
321     }
322   }
323 
324   @Test
fromJSON_invalidFormatVersion()325   public void fromJSON_invalidFormatVersion() throws IOException {
326     GenericJson json =
327         writeGdchServiceAccountJson(
328             "100",
329             PROJECT_ID,
330             PRIVATE_KEY_ID,
331             PRIVATE_KEY_PKCS8,
332             SERVICE_IDENTITY_NAME,
333             CA_CERT_PATH,
334             TOKEN_SERVER_URI);
335 
336     try {
337       GdchCredentials credentials = GdchCredentials.fromJson(json);
338       fail("Should not be able to create GDCH credential without exception.");
339     } catch (IOException ex) {
340       assertTrue(
341           ex.getMessage()
342               .contains(String.format("Only format version %s is supported", FORMAT_VERSION)));
343     }
344   }
345 
346   @Test
fromJSON_invalidCaCertPath()347   public void fromJSON_invalidCaCertPath() throws IOException {
348     GenericJson json =
349         writeGdchServiceAccountJson(
350             FORMAT_VERSION,
351             PROJECT_ID,
352             PRIVATE_KEY_ID,
353             PRIVATE_KEY_PKCS8,
354             SERVICE_IDENTITY_NAME,
355             "/path/to/missing/file",
356             TOKEN_SERVER_URI);
357 
358     try {
359       GdchCredentials credentials = GdchCredentials.fromJson(json);
360       fail("Should not be able to create GDCH credential without exception.");
361     } catch (IOException ex) {
362       assertTrue(ex.getMessage().contains("Error reading certificate file from CA cert path"));
363     }
364   }
365 
366   @Test
fromJSON_emptyCaCertPath()367   public void fromJSON_emptyCaCertPath() throws IOException {
368     GenericJson json =
369         writeGdchServiceAccountJson(
370             FORMAT_VERSION,
371             PROJECT_ID,
372             PRIVATE_KEY_ID,
373             PRIVATE_KEY_PKCS8,
374             SERVICE_IDENTITY_NAME,
375             "",
376             TOKEN_SERVER_URI);
377     GdchCredentials credentials = GdchCredentials.fromJson(json);
378     assertEquals("", credentials.getCaCertPath());
379   }
380 
381   @Test
fromJSON_transportFactoryForGdch()382   public void fromJSON_transportFactoryForGdch() throws IOException {
383     GenericJson json =
384         writeGdchServiceAccountJson(
385             FORMAT_VERSION,
386             PROJECT_ID,
387             PRIVATE_KEY_ID,
388             PRIVATE_KEY_PKCS8,
389             SERVICE_IDENTITY_NAME,
390             CA_CERT_PATH,
391             TOKEN_SERVER_URI);
392     GdchCredentials credentials = GdchCredentials.fromJson(json);
393     assertEquals(
394         GdchCredentials.TransportFactoryForGdch.class,
395         credentials.getTransportFactory().getClass());
396   }
397 
398   @Test
fromJSON_hasAccessToken()399   public void fromJSON_hasAccessToken() throws IOException {
400     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
401     GenericJson json =
402         writeGdchServiceAccountJson(
403             FORMAT_VERSION,
404             PROJECT_ID,
405             PRIVATE_KEY_ID,
406             PRIVATE_KEY_PKCS8,
407             SERVICE_IDENTITY_NAME,
408             CA_CERT_PATH,
409             TOKEN_SERVER_URI);
410     GdchCredentials credentials = GdchCredentials.fromJson(json, transportFactory);
411     GdchCredentials gdchWithAudience = credentials.createWithGdchAudience(API_AUDIENCE);
412     transportFactory.transport.addGdchServiceAccount(
413         GdchCredentials.getIssuerSubjectValue(PROJECT_ID, SERVICE_IDENTITY_NAME), ACCESS_TOKEN);
414     transportFactory.transport.setTokenServerUri(TOKEN_SERVER_URI);
415     Map<String, List<String>> metadata = gdchWithAudience.getRequestMetadata(CALL_URI);
416     TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN);
417   }
418 
419   @Test
createWithGdchAudience_correct()420   public void createWithGdchAudience_correct() throws IOException {
421     GenericJson json =
422         writeGdchServiceAccountJson(
423             FORMAT_VERSION,
424             PROJECT_ID,
425             PRIVATE_KEY_ID,
426             PRIVATE_KEY_PKCS8,
427             SERVICE_IDENTITY_NAME,
428             CA_CERT_PATH,
429             TOKEN_SERVER_URI);
430     GdchCredentials credentials = GdchCredentials.fromJson(json);
431 
432     assertEquals(PROJECT_ID, credentials.getProjectId());
433     assertEquals(SERVICE_IDENTITY_NAME, credentials.getServiceIdentityName());
434     assertEquals(TOKEN_SERVER_URI, credentials.getTokenServerUri());
435     assertEquals(CA_CERT_PATH, credentials.getCaCertPath());
436     assertNull(credentials.getApiAudience());
437 
438     GdchCredentials gdchWithAudience = credentials.createWithGdchAudience(API_AUDIENCE);
439 
440     assertEquals(PROJECT_ID, gdchWithAudience.getProjectId());
441     assertEquals(SERVICE_IDENTITY_NAME, gdchWithAudience.getServiceIdentityName());
442     assertEquals(TOKEN_SERVER_URI, gdchWithAudience.getTokenServerUri());
443     assertEquals(CA_CERT_PATH, credentials.getCaCertPath());
444     assertEquals(API_AUDIENCE, gdchWithAudience.getApiAudience());
445   }
446 
447   @Test
createWithGdchAudience_nullApiAudience()448   public void createWithGdchAudience_nullApiAudience() throws IOException {
449     GenericJson json =
450         writeGdchServiceAccountJson(
451             FORMAT_VERSION,
452             PROJECT_ID,
453             PRIVATE_KEY_ID,
454             PRIVATE_KEY_PKCS8,
455             SERVICE_IDENTITY_NAME,
456             CA_CERT_PATH,
457             TOKEN_SERVER_URI);
458     GdchCredentials credentials = GdchCredentials.fromJson(json);
459 
460     try {
461       GdchCredentials gdchWithAudience = credentials.createWithGdchAudience(null);
462       fail("Should not be able to create GDCH credential without exception.");
463     } catch (NullPointerException ex) {
464       assertTrue(ex.getMessage().contains("Audience are not configured for GDCH service account"));
465     }
466   }
467 
468   @Test
createAssertion_correct()469   public void createAssertion_correct() throws IOException {
470     GenericJson json =
471         writeGdchServiceAccountJson(
472             FORMAT_VERSION,
473             PROJECT_ID,
474             PRIVATE_KEY_ID,
475             PRIVATE_KEY_PKCS8,
476             SERVICE_IDENTITY_NAME,
477             CA_CERT_PATH,
478             TOKEN_SERVER_URI);
479     GdchCredentials credentials = GdchCredentials.fromJson(json);
480     JsonFactory jsonFactory = OAuth2Utils.JSON_FACTORY;
481     long currentTimeMillis = Clock.SYSTEM.currentTimeMillis();
482     String assertion = credentials.createAssertion(jsonFactory, currentTimeMillis, API_AUDIENCE);
483 
484     JsonWebSignature signature = JsonWebSignature.parse(jsonFactory, assertion);
485     JsonWebToken.Payload payload = signature.getPayload();
486 
487     String expectedIssSubValue =
488         GdchCredentials.getIssuerSubjectValue(PROJECT_ID, SERVICE_IDENTITY_NAME);
489     assertEquals(expectedIssSubValue, payload.getIssuer());
490     assertEquals(expectedIssSubValue, payload.getSubject());
491     assertEquals(TOKEN_SERVER_URI.toString(), payload.getAudience());
492     assertEquals(currentTimeMillis / 1000, (long) payload.getIssuedAtTimeSeconds());
493     assertEquals(currentTimeMillis / 1000 + 3600, (long) payload.getExpirationTimeSeconds());
494   }
495 
496   @Test
refreshAccessToken_correct()497   public void refreshAccessToken_correct() throws IOException {
498     final String tokenString = "1/MkSJoj1xsli0AccessToken_NKPY2";
499     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
500     GenericJson json =
501         writeGdchServiceAccountJson(
502             FORMAT_VERSION,
503             PROJECT_ID,
504             PRIVATE_KEY_ID,
505             PRIVATE_KEY_PKCS8,
506             SERVICE_IDENTITY_NAME,
507             CA_CERT_PATH,
508             TOKEN_SERVER_URI);
509     GdchCredentials credentials = GdchCredentials.fromJson(json, transportFactory);
510     GdchCredentials gdchWithAudience = credentials.createWithGdchAudience(API_AUDIENCE);
511 
512     GdchCredentialsTestUtil.registerGdchCredentialWithMockTransport(
513         gdchWithAudience,
514         transportFactory.transport,
515         PROJECT_ID,
516         SERVICE_IDENTITY_NAME,
517         tokenString,
518         TOKEN_SERVER_URI);
519 
520     AccessToken accessToken = gdchWithAudience.refreshAccessToken();
521     assertNotNull(accessToken);
522     assertEquals(tokenString, accessToken.getTokenValue());
523     assertEquals(3600 * 1000L, accessToken.getExpirationTimeMillis().longValue());
524 
525     // Test for large expires_in values (should not overflow).
526     transportFactory.transport.setExpiresInSeconds(3600 * 1000);
527     accessToken = gdchWithAudience.refreshAccessToken();
528     assertNotNull(accessToken);
529     assertEquals(tokenString, accessToken.getTokenValue());
530     assertEquals(3600 * 1000 * 1000L, accessToken.getExpirationTimeMillis().longValue());
531   }
532 
533   @Test
refreshAccessToken_nullApiAudience()534   public void refreshAccessToken_nullApiAudience() throws IOException {
535     final String tokenString = "1/MkSJoj1xsli0AccessToken_NKPY2";
536     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
537     GenericJson json =
538         writeGdchServiceAccountJson(
539             FORMAT_VERSION,
540             PROJECT_ID,
541             PRIVATE_KEY_ID,
542             PRIVATE_KEY_PKCS8,
543             SERVICE_IDENTITY_NAME,
544             CA_CERT_PATH,
545             TOKEN_SERVER_URI);
546     GdchCredentials credentials = GdchCredentials.fromJson(json, transportFactory);
547 
548     credentials.clock = new FixedClock(0L);
549 
550     transportFactory.transport.addGdchServiceAccount(
551         GdchCredentials.getIssuerSubjectValue(PROJECT_ID, SERVICE_IDENTITY_NAME), tokenString);
552     transportFactory.transport.setTokenServerUri(TOKEN_SERVER_URI);
553     try {
554       AccessToken accessToken = credentials.refreshAccessToken();
555       fail("Should not be able to refresh access token without exception.");
556     } catch (NullPointerException ex) {
557       assertTrue(
558           ex.getMessage()
559               .contains(
560                   "Audience are not configured for GDCH service account. Specify the "
561                       + "audience by calling createWithGDCHAudience"));
562     }
563   }
564 
565   @Test
getIssuerSubjectValue_correct()566   public void getIssuerSubjectValue_correct() throws IOException {
567     GenericJson json =
568         writeGdchServiceAccountJson(
569             FORMAT_VERSION,
570             PROJECT_ID,
571             PRIVATE_KEY_ID,
572             PRIVATE_KEY_PKCS8,
573             SERVICE_IDENTITY_NAME,
574             CA_CERT_PATH,
575             TOKEN_SERVER_URI);
576     GdchCredentials credentials = GdchCredentials.fromJson(json);
577     Object expectedIssSubValue =
578         String.format("system:serviceaccount:%s:%s", PROJECT_ID, SERVICE_IDENTITY_NAME);
579     assertEquals(
580         expectedIssSubValue,
581         GdchCredentials.getIssuerSubjectValue(PROJECT_ID, SERVICE_IDENTITY_NAME));
582   }
583 
584   @Test
equals_same()585   public void equals_same() throws IOException {
586     GenericJson json =
587         writeGdchServiceAccountJson(
588             FORMAT_VERSION,
589             PROJECT_ID,
590             PRIVATE_KEY_ID,
591             PRIVATE_KEY_PKCS8,
592             SERVICE_IDENTITY_NAME,
593             CA_CERT_PATH,
594             TOKEN_SERVER_URI);
595     OAuth2Credentials credentials = GdchCredentials.fromJson(json);
596     GenericJson otherJson =
597         writeGdchServiceAccountJson(
598             FORMAT_VERSION,
599             PROJECT_ID,
600             PRIVATE_KEY_ID,
601             PRIVATE_KEY_PKCS8,
602             SERVICE_IDENTITY_NAME,
603             CA_CERT_PATH,
604             TOKEN_SERVER_URI);
605     OAuth2Credentials otherCredentials = GdchCredentials.fromJson(otherJson);
606     assertTrue(credentials.equals(otherCredentials));
607     assertTrue(otherCredentials.equals(credentials));
608 
609     credentials = ((GdchCredentials) credentials).createWithGdchAudience(API_AUDIENCE);
610     otherCredentials = ((GdchCredentials) otherCredentials).createWithGdchAudience(API_AUDIENCE);
611     assertTrue(credentials.equals(otherCredentials));
612     assertTrue(otherCredentials.equals(credentials));
613   }
614 
615   @Test
equals_false_projectId()616   public void equals_false_projectId() throws IOException {
617     GenericJson json =
618         writeGdchServiceAccountJson(
619             FORMAT_VERSION,
620             PROJECT_ID,
621             PRIVATE_KEY_ID,
622             PRIVATE_KEY_PKCS8,
623             SERVICE_IDENTITY_NAME,
624             CA_CERT_PATH,
625             TOKEN_SERVER_URI);
626     OAuth2Credentials credentials = GdchCredentials.fromJson(json);
627     GenericJson otherJson =
628         writeGdchServiceAccountJson(
629             FORMAT_VERSION,
630             "otherProjectId",
631             PRIVATE_KEY_ID,
632             PRIVATE_KEY_PKCS8,
633             SERVICE_IDENTITY_NAME,
634             CA_CERT_PATH,
635             TOKEN_SERVER_URI);
636     OAuth2Credentials otherCredentials = GdchCredentials.fromJson(otherJson);
637     assertFalse(credentials.equals(otherCredentials));
638     assertFalse(otherCredentials.equals(credentials));
639 
640     credentials = ((GdchCredentials) credentials).createWithGdchAudience(API_AUDIENCE);
641     otherCredentials = ((GdchCredentials) otherCredentials).createWithGdchAudience(API_AUDIENCE);
642     assertFalse(credentials.equals(otherCredentials));
643     assertFalse(otherCredentials.equals(credentials));
644   }
645 
646   @Test
equals_false_keyId()647   public void equals_false_keyId() throws IOException {
648     GenericJson json =
649         writeGdchServiceAccountJson(
650             FORMAT_VERSION,
651             PROJECT_ID,
652             PRIVATE_KEY_ID,
653             PRIVATE_KEY_PKCS8,
654             SERVICE_IDENTITY_NAME,
655             CA_CERT_PATH,
656             TOKEN_SERVER_URI);
657     OAuth2Credentials credentials = GdchCredentials.fromJson(json);
658     GenericJson otherJson =
659         writeGdchServiceAccountJson(
660             FORMAT_VERSION,
661             PROJECT_ID,
662             "otherId",
663             PRIVATE_KEY_PKCS8,
664             SERVICE_IDENTITY_NAME,
665             CA_CERT_PATH,
666             TOKEN_SERVER_URI);
667     OAuth2Credentials otherCredentials = GdchCredentials.fromJson(otherJson);
668     assertFalse(credentials.equals(otherCredentials));
669     assertFalse(otherCredentials.equals(credentials));
670 
671     credentials = ((GdchCredentials) credentials).createWithGdchAudience(API_AUDIENCE);
672     otherCredentials = ((GdchCredentials) otherCredentials).createWithGdchAudience(API_AUDIENCE);
673     assertFalse(credentials.equals(otherCredentials));
674     assertFalse(otherCredentials.equals(credentials));
675   }
676 
677   @Test
equals_false_serviceIdentityName()678   public void equals_false_serviceIdentityName() throws IOException {
679     GenericJson json =
680         writeGdchServiceAccountJson(
681             FORMAT_VERSION,
682             PROJECT_ID,
683             PRIVATE_KEY_ID,
684             PRIVATE_KEY_PKCS8,
685             SERVICE_IDENTITY_NAME,
686             CA_CERT_PATH,
687             TOKEN_SERVER_URI);
688     OAuth2Credentials credentials = GdchCredentials.fromJson(json);
689     GenericJson otherJson =
690         writeGdchServiceAccountJson(
691             FORMAT_VERSION,
692             PROJECT_ID,
693             PRIVATE_KEY_ID,
694             PRIVATE_KEY_PKCS8,
695             "otherServiceIdentityName",
696             CA_CERT_PATH,
697             TOKEN_SERVER_URI);
698     OAuth2Credentials otherCredentials = GdchCredentials.fromJson(otherJson);
699     assertFalse(credentials.equals(otherCredentials));
700     assertFalse(otherCredentials.equals(credentials));
701 
702     credentials = ((GdchCredentials) credentials).createWithGdchAudience(API_AUDIENCE);
703     otherCredentials = ((GdchCredentials) otherCredentials).createWithGdchAudience(API_AUDIENCE);
704     assertFalse(credentials.equals(otherCredentials));
705     assertFalse(otherCredentials.equals(credentials));
706   }
707 
708   @Test
equals_false_caCertPath()709   public void equals_false_caCertPath() throws IOException {
710     File tmpDirectory = Files.createTempDirectory("tmpDirectory").toFile();
711     File testCaCertFile = File.createTempFile("testCert", ".pem", tmpDirectory);
712     GenericJson json =
713         writeGdchServiceAccountJson(
714             FORMAT_VERSION,
715             PROJECT_ID,
716             PRIVATE_KEY_ID,
717             PRIVATE_KEY_PKCS8,
718             SERVICE_IDENTITY_NAME,
719             CA_CERT_PATH,
720             TOKEN_SERVER_URI);
721     OAuth2Credentials credentials = GdchCredentials.fromJson(json);
722     GenericJson otherJson =
723         writeGdchServiceAccountJson(
724             FORMAT_VERSION,
725             PROJECT_ID,
726             PRIVATE_KEY_ID,
727             PRIVATE_KEY_PKCS8,
728             SERVICE_IDENTITY_NAME,
729             testCaCertFile.getPath(),
730             TOKEN_SERVER_URI);
731     OAuth2Credentials otherCredentials = GdchCredentials.fromJson(otherJson);
732     assertFalse(credentials.equals(otherCredentials));
733     assertFalse(otherCredentials.equals(credentials));
734 
735     credentials = ((GdchCredentials) credentials).createWithGdchAudience(API_AUDIENCE);
736     otherCredentials = ((GdchCredentials) otherCredentials).createWithGdchAudience(API_AUDIENCE);
737     assertFalse(credentials.equals(otherCredentials));
738     assertFalse(otherCredentials.equals(credentials));
739 
740     testCaCertFile.delete();
741   }
742 
743   @Test
equals_false_tokenServer()744   public void equals_false_tokenServer() throws IOException {
745     GenericJson json =
746         writeGdchServiceAccountJson(
747             FORMAT_VERSION,
748             PROJECT_ID,
749             PRIVATE_KEY_ID,
750             PRIVATE_KEY_PKCS8,
751             SERVICE_IDENTITY_NAME,
752             CA_CERT_PATH,
753             TOKEN_SERVER_URI);
754     OAuth2Credentials credentials = GdchCredentials.fromJson(json);
755     GenericJson otherJson =
756         writeGdchServiceAccountJson(
757             FORMAT_VERSION,
758             PROJECT_ID,
759             PRIVATE_KEY_ID,
760             PRIVATE_KEY_PKCS8,
761             SERVICE_IDENTITY_NAME,
762             CA_CERT_PATH,
763             URI.create("https://foo1.com/bar"));
764     OAuth2Credentials otherCredentials = GdchCredentials.fromJson(otherJson);
765     assertFalse(credentials.equals(otherCredentials));
766     assertFalse(otherCredentials.equals(credentials));
767 
768     credentials = ((GdchCredentials) credentials).createWithGdchAudience(API_AUDIENCE);
769     otherCredentials = ((GdchCredentials) otherCredentials).createWithGdchAudience(API_AUDIENCE);
770     assertFalse(credentials.equals(otherCredentials));
771     assertFalse(otherCredentials.equals(credentials));
772   }
773 
774   @Test
equals_false_apiAudience()775   public void equals_false_apiAudience() throws IOException {
776     URI otherApiAudience = URI.create("https://foo1.com/bar");
777 
778     GenericJson json =
779         writeGdchServiceAccountJson(
780             FORMAT_VERSION,
781             PROJECT_ID,
782             PRIVATE_KEY_ID,
783             PRIVATE_KEY_PKCS8,
784             SERVICE_IDENTITY_NAME,
785             CA_CERT_PATH,
786             TOKEN_SERVER_URI);
787     OAuth2Credentials credentials = GdchCredentials.fromJson(json);
788     GenericJson otherJson =
789         writeGdchServiceAccountJson(
790             FORMAT_VERSION,
791             PROJECT_ID,
792             PRIVATE_KEY_ID,
793             PRIVATE_KEY_PKCS8,
794             SERVICE_IDENTITY_NAME,
795             CA_CERT_PATH,
796             TOKEN_SERVER_URI);
797     OAuth2Credentials otherCredentials = GdchCredentials.fromJson(otherJson);
798 
799     credentials = ((GdchCredentials) credentials).createWithGdchAudience(API_AUDIENCE);
800     otherCredentials =
801         ((GdchCredentials) otherCredentials).createWithGdchAudience(otherApiAudience);
802     assertFalse(credentials.equals(otherCredentials));
803     assertFalse(otherCredentials.equals(credentials));
804   }
805 
806   @Test
toString_containsFields()807   public void toString_containsFields() throws IOException {
808     GenericJson json =
809         writeGdchServiceAccountJson(
810             FORMAT_VERSION,
811             PROJECT_ID,
812             PRIVATE_KEY_ID,
813             PRIVATE_KEY_PKCS8,
814             SERVICE_IDENTITY_NAME,
815             CA_CERT_PATH,
816             TOKEN_SERVER_URI);
817     OAuth2Credentials credentials = GdchCredentials.fromJson(json);
818     credentials = ((GdchCredentials) credentials).createWithGdchAudience(API_AUDIENCE);
819     String expectedToString =
820         String.format(
821             "GdchCredentials{projectId=%s, privateKeyId=%s, serviceIdentityName=%s, "
822                 + "tokenServerUri=%s, transportFactoryClassName=%s, caCertPath=%s, apiAudience=%s, lifetime=3600}",
823             PROJECT_ID,
824             PRIVATE_KEY_ID,
825             SERVICE_IDENTITY_NAME,
826             TOKEN_SERVER_URI,
827             GdchCredentials.TransportFactoryForGdch.class.getName(),
828             CA_CERT_PATH,
829             API_AUDIENCE);
830     assertEquals(expectedToString, credentials.toString());
831   }
832 
833   @Test
hashCode_equals()834   public void hashCode_equals() throws IOException {
835     GenericJson json =
836         writeGdchServiceAccountJson(
837             FORMAT_VERSION,
838             PROJECT_ID,
839             PRIVATE_KEY_ID,
840             PRIVATE_KEY_PKCS8,
841             SERVICE_IDENTITY_NAME,
842             CA_CERT_PATH,
843             TOKEN_SERVER_URI);
844     OAuth2Credentials credentials = GdchCredentials.fromJson(json);
845     GenericJson otherJson =
846         writeGdchServiceAccountJson(
847             FORMAT_VERSION,
848             PROJECT_ID,
849             PRIVATE_KEY_ID,
850             PRIVATE_KEY_PKCS8,
851             SERVICE_IDENTITY_NAME,
852             CA_CERT_PATH,
853             TOKEN_SERVER_URI);
854     OAuth2Credentials otherCredentials = GdchCredentials.fromJson(otherJson);
855     assertEquals(credentials.hashCode(), otherCredentials.hashCode());
856 
857     credentials = ((GdchCredentials) credentials).createWithGdchAudience(API_AUDIENCE);
858     otherCredentials = ((GdchCredentials) otherCredentials).createWithGdchAudience(API_AUDIENCE);
859     assertEquals(credentials.hashCode(), otherCredentials.hashCode());
860   }
861 
862   @Test
serialize_correct()863   public void serialize_correct() throws IOException, ClassNotFoundException {
864     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
865     GenericJson json =
866         writeGdchServiceAccountJson(
867             FORMAT_VERSION,
868             PROJECT_ID,
869             PRIVATE_KEY_ID,
870             PRIVATE_KEY_PKCS8,
871             SERVICE_IDENTITY_NAME,
872             CA_CERT_PATH,
873             TOKEN_SERVER_URI);
874     GdchCredentials credentials = GdchCredentials.fromJson(json, transportFactory);
875     credentials = credentials.createWithGdchAudience(API_AUDIENCE);
876 
877     GdchCredentials deserializedCredentials = serializeAndDeserialize(credentials);
878     assertEquals(credentials, deserializedCredentials);
879     assertEquals(credentials.hashCode(), deserializedCredentials.hashCode());
880     assertEquals(credentials.toString(), deserializedCredentials.toString());
881     assertSame(deserializedCredentials.clock, Clock.SYSTEM);
882     assertEquals(
883         MockTokenServerTransportFactory.class,
884         deserializedCredentials.toBuilder().getHttpTransportFactory().getClass());
885   }
886 
writeGdchServiceAccountJson( String formatVersion, String project, String privateKeyId, String privateKeyPkcs8, String serviceIdentityName, String caCertPath, URI tokenServerUri)887   static GenericJson writeGdchServiceAccountJson(
888       String formatVersion,
889       String project,
890       String privateKeyId,
891       String privateKeyPkcs8,
892       String serviceIdentityName,
893       String caCertPath,
894       URI tokenServerUri) {
895     GenericJson json = new GenericJson();
896 
897     if (formatVersion != null) {
898       json.put("format_version", formatVersion);
899     }
900     if (project != null) {
901       json.put("project", project);
902     }
903     if (privateKeyId != null) {
904       json.put("private_key_id", privateKeyId);
905     }
906     if (privateKeyPkcs8 != null) {
907       json.put("private_key", privateKeyPkcs8);
908     }
909     if (serviceIdentityName != null) {
910       json.put("name", serviceIdentityName);
911     }
912     if (caCertPath != null) {
913       json.put("ca_cert_path", caCertPath);
914     }
915     if (tokenServerUri != null) {
916       json.put("token_uri", tokenServerUri.toString());
917     }
918     json.put("type", GoogleCredentials.GDCH_SERVICE_ACCOUNT_FILE_TYPE);
919     return json;
920   }
921 
writeGdchServiceAccountStream( String formatVersion, String project, String privateKeyId, String privateKeyPkcs8, String serviceIdentityName, String caCertPath, URI tokenServerUri)922   static InputStream writeGdchServiceAccountStream(
923       String formatVersion,
924       String project,
925       String privateKeyId,
926       String privateKeyPkcs8,
927       String serviceIdentityName,
928       String caCertPath,
929       URI tokenServerUri)
930       throws IOException {
931     GenericJson json =
932         writeGdchServiceAccountJson(
933             formatVersion,
934             project,
935             privateKeyId,
936             privateKeyPkcs8,
937             serviceIdentityName,
938             caCertPath,
939             tokenServerUri);
940     return TestUtils.jsonToInputStream(json);
941   }
942 }
943