1 /*
2  * Copyright 2015, 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.assertArrayEquals;
35 import static org.junit.Assert.assertEquals;
36 import static org.junit.Assert.assertFalse;
37 import static org.junit.Assert.assertNotEquals;
38 import static org.junit.Assert.assertNotNull;
39 import static org.junit.Assert.assertNull;
40 import static org.junit.Assert.assertSame;
41 import static org.junit.Assert.assertTrue;
42 import static org.junit.Assert.fail;
43 
44 import com.google.api.client.http.HttpResponseException;
45 import com.google.api.client.json.GenericJson;
46 import com.google.api.client.json.JsonFactory;
47 import com.google.api.client.json.gson.GsonFactory;
48 import com.google.api.client.json.webtoken.JsonWebSignature;
49 import com.google.api.client.json.webtoken.JsonWebToken;
50 import com.google.api.client.testing.http.FixedClock;
51 import com.google.api.client.testing.http.MockLowLevelHttpResponse;
52 import com.google.api.client.util.Clock;
53 import com.google.api.client.util.Joiner;
54 import com.google.auth.Credentials;
55 import com.google.auth.RequestMetadataCallback;
56 import com.google.auth.TestUtils;
57 import com.google.auth.http.AuthHttpConstants;
58 import com.google.auth.http.HttpTransportFactory;
59 import com.google.common.collect.ImmutableSet;
60 import java.io.ByteArrayInputStream;
61 import java.io.ByteArrayOutputStream;
62 import java.io.IOException;
63 import java.io.InputStream;
64 import java.io.ObjectOutputStream;
65 import java.net.URI;
66 import java.security.InvalidKeyException;
67 import java.security.NoSuchAlgorithmException;
68 import java.security.PrivateKey;
69 import java.security.Signature;
70 import java.security.SignatureException;
71 import java.time.Duration;
72 import java.time.Instant;
73 import java.util.Arrays;
74 import java.util.Collection;
75 import java.util.Collections;
76 import java.util.List;
77 import java.util.Map;
78 import java.util.concurrent.atomic.AtomicBoolean;
79 import org.junit.Test;
80 import org.junit.runner.RunWith;
81 import org.junit.runners.JUnit4;
82 
83 /** Test case for {@link ServiceAccountCredentials}. */
84 @RunWith(JUnit4.class)
85 public class ServiceAccountCredentialsTest extends BaseSerializationTest {
86 
87   private static final String CLIENT_EMAIL =
88       "36680232662-vrd7ji19qe3nelgchd0ah2csanun6bnr@developer.gserviceaccount.com";
89   private static final String CLIENT_ID =
90       "36680232662-vrd7ji19qe3nelgchd0ah2csanun6bnr.apps.googleusercontent.com";
91   private static final String PRIVATE_KEY_ID = "d84a4fefcf50791d4a90f2d7af17469d6282df9d";
92   static final String PRIVATE_KEY_PKCS8 =
93       "-----BEGIN PRIVATE KEY-----\n"
94           + "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALX0PQoe1igW12i"
95           + "kv1bN/r9lN749y2ijmbc/mFHPyS3hNTyOCjDvBbXYbDhQJzWVUikh4mvGBA07qTj79Xc3yBDfKP2IeyYQIFe0t0"
96           + "zkd7R9Zdn98Y2rIQC47aAbDfubtkU1U72t4zL11kHvoa0/RuFZjncvlr42X7be7lYh4p3NAgMBAAECgYASk5wDw"
97           + "4Az2ZkmeuN6Fk/y9H+Lcb2pskJIXjrL533vrDWGOC48LrsThMQPv8cxBky8HFSEklPpkfTF95tpD43iVwJRB/Gr"
98           + "CtGTw65IfJ4/tI09h6zGc4yqvIo1cHX/LQ+SxKLGyir/dQM925rGt/VojxY5ryJR7GLbCzxPnJm/oQJBANwOCO6"
99           + "D2hy1LQYJhXh7O+RLtA/tSnT1xyMQsGT+uUCMiKS2bSKx2wxo9k7h3OegNJIu1q6nZ6AbxDK8H3+d0dUCQQDTrP"
100           + "SXagBxzp8PecbaCHjzNRSQE2in81qYnrAFNB4o3DpHyMMY6s5ALLeHKscEWnqP8Ur6X4PvzZecCWU9BKAZAkAut"
101           + "LPknAuxSCsUOvUfS1i87ex77Ot+w6POp34pEX+UWb+u5iFn2cQacDTHLV1LtE80L8jVLSbrbrlH43H0DjU5AkEA"
102           + "gidhycxS86dxpEljnOMCw8CKoUBd5I880IUahEiUltk7OLJYS/Ts1wbn3kPOVX3wyJs8WBDtBkFrDHW2ezth2QJ"
103           + "ADj3e1YhMVdjJW5jqwlD/VNddGjgzyunmiZg0uOXsHXbytYmsA545S8KRQFaJKFXYYFo2kOjqOiC1T2cAzMDjCQ"
104           + "==\n-----END PRIVATE KEY-----\n";
105   private static final String ACCESS_TOKEN = "1/MkSJoj1xsli0AccessToken_NKPY2";
106   private static final Collection<String> SCOPES = Collections.singletonList("dummy.scope");
107   private static final Collection<String> DEFAULT_SCOPES =
108       Collections.singletonList("dummy.default.scope");
109   private static final String USER = "[email protected]";
110   private static final String PROJECT_ID = "project-id";
111   private static final Collection<String> EMPTY_SCOPES = Collections.emptyList();
112   private static final URI CALL_URI = URI.create("http://googleapis.com/testapi/v1/foo");
113   private static final String JWT_AUDIENCE = "http://googleapis.com/";
114   private static final HttpTransportFactory DUMMY_TRANSPORT_FACTORY =
115       new MockTokenServerTransportFactory();
116   public static final String DEFAULT_ID_TOKEN =
117       "eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmMzc1ODkwOGI3OTIyO"
118           + "TNhZDk3N2EwYjk5MWQ5OGE3N2Y0ZWVlY2QiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL2Zvby5iYXIiL"
119           + "CJhenAiOiIxMDIxMDE1NTA4MzQyMDA3MDg1NjgiLCJleHAiOjE1NjQ0NzUwNTEsImlhdCI6MTU2NDQ3MTQ1MSwi"
120           + "aXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTAyMTAxNTUwODM0MjAwNzA4NTY4In0"
121           + ".redacted";
122   private static final String QUOTA_PROJECT = "sample-quota-project-id";
123   private static final int DEFAULT_LIFETIME_IN_SECONDS = 3600;
124   private static final int INVALID_LIFETIME = 43210;
125   private static final String JWT_ACCESS_PREFIX = "Bearer ";
126   private static final String GOOGLE_DEFAULT_UNIVERSE = "googleapis.com";
127 
createDefaultBuilderWithToken(String accessToken)128   private ServiceAccountCredentials.Builder createDefaultBuilderWithToken(String accessToken)
129       throws IOException {
130     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
131     transportFactory.transport.addServiceAccount(CLIENT_EMAIL, accessToken);
132     return createDefaultBuilder().setHttpTransportFactory(transportFactory);
133   }
134 
createDefaultBuilderWithScopes( Collection<String> scopes)135   private ServiceAccountCredentials.Builder createDefaultBuilderWithScopes(
136       Collection<String> scopes) throws IOException {
137     return createDefaultBuilder().setScopes(scopes);
138   }
139 
createDefaultBuilderWithKey(PrivateKey privateKey)140   private ServiceAccountCredentials.Builder createDefaultBuilderWithKey(PrivateKey privateKey) {
141     ServiceAccountCredentials.Builder builder =
142         ServiceAccountCredentials.newBuilder()
143             .setClientId(CLIENT_ID)
144             .setClientEmail(CLIENT_EMAIL)
145             .setPrivateKey(privateKey)
146             .setPrivateKeyId(PRIVATE_KEY_ID)
147             .setProjectId(PROJECT_ID)
148             .setQuotaProjectId(QUOTA_PROJECT)
149             .setHttpTransportFactory(new MockHttpTransportFactory());
150 
151     return builder;
152   }
153 
createDefaultBuilder()154   private ServiceAccountCredentials.Builder createDefaultBuilder() throws IOException {
155     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8);
156     return createDefaultBuilderWithKey(privateKey);
157   }
158 
159   @Test
setLifetime()160   public void setLifetime() throws IOException {
161     ServiceAccountCredentials.Builder builder = createDefaultBuilder();
162     assertEquals(DEFAULT_LIFETIME_IN_SECONDS, builder.getLifetime());
163     assertEquals(DEFAULT_LIFETIME_IN_SECONDS, builder.build().getLifetime());
164 
165     builder.setLifetime(4000);
166     assertEquals(4000, builder.getLifetime());
167     assertEquals(4000, builder.build().getLifetime());
168 
169     builder.setLifetime(0);
170     assertEquals(DEFAULT_LIFETIME_IN_SECONDS, builder.build().getLifetime());
171   }
172 
173   @Test
setLifetime_invalid_lifetime()174   public void setLifetime_invalid_lifetime() throws IOException, IllegalStateException {
175     try {
176       createDefaultBuilder().setLifetime(INVALID_LIFETIME).build();
177       fail(
178           String.format(
179               "Should throw exception with message containing '%s'",
180               "lifetime must be less than or equal to 43200"));
181     } catch (IllegalStateException expected) {
182       assertTrue(expected.getMessage().contains("lifetime must be less than or equal to 43200"));
183     }
184   }
185 
186   @Test
createWithCustomLifetime()187   public void createWithCustomLifetime() throws IOException {
188     ServiceAccountCredentials credentials = createDefaultBuilder().build();
189     credentials = credentials.createWithCustomLifetime(4000);
190     assertEquals(4000, credentials.getLifetime());
191   }
192 
193   @Test
createdScoped_clones()194   public void createdScoped_clones() throws IOException {
195     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8);
196     ServiceAccountCredentials credentials =
197         createDefaultBuilderWithKey(privateKey)
198             .setServiceAccountUser(USER)
199             .setScopes(SCOPES)
200             .build();
201     List<String> newScopes = Arrays.asList("scope1", "scope2");
202 
203     ServiceAccountCredentials newCredentials =
204         (ServiceAccountCredentials) credentials.createScoped(newScopes);
205 
206     assertEquals(CLIENT_ID, newCredentials.getClientId());
207     assertEquals(CLIENT_EMAIL, newCredentials.getClientEmail());
208     assertEquals(privateKey, newCredentials.getPrivateKey());
209     assertEquals(PRIVATE_KEY_ID, newCredentials.getPrivateKeyId());
210     assertArrayEquals(newScopes.toArray(), newCredentials.getScopes().toArray());
211     assertEquals(USER, newCredentials.getServiceAccountUser());
212     assertEquals(PROJECT_ID, newCredentials.getProjectId());
213     assertEquals(GOOGLE_DEFAULT_UNIVERSE, newCredentials.getUniverseDomain());
214 
215     assertArrayEquals(
216         SCOPES.toArray(), ((ServiceAccountCredentials) credentials).getScopes().toArray());
217   }
218 
219   @Test
createdDelegated_clones()220   public void createdDelegated_clones() throws IOException {
221     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8);
222     ServiceAccountCredentials credentials =
223         createDefaultBuilderWithKey(privateKey)
224             .setScopes(SCOPES)
225             .setServiceAccountUser(USER)
226             .build();
227     String newServiceAccountUser = "[email protected]";
228 
229     ServiceAccountCredentials newCredentials =
230         (ServiceAccountCredentials) credentials.createDelegated(newServiceAccountUser);
231 
232     assertEquals(CLIENT_ID, newCredentials.getClientId());
233     assertEquals(CLIENT_EMAIL, newCredentials.getClientEmail());
234     assertEquals(privateKey, newCredentials.getPrivateKey());
235     assertEquals(PRIVATE_KEY_ID, newCredentials.getPrivateKeyId());
236     assertArrayEquals(SCOPES.toArray(), newCredentials.getScopes().toArray());
237     assertEquals(newServiceAccountUser, newCredentials.getServiceAccountUser());
238     assertEquals(PROJECT_ID, newCredentials.getProjectId());
239     assertEquals(QUOTA_PROJECT, newCredentials.getQuotaProjectId());
240 
241     assertEquals(USER, ((ServiceAccountCredentials) credentials).getServiceAccountUser());
242   }
243 
244   @Test
createAssertion_correct()245   public void createAssertion_correct() throws IOException {
246     List<String> scopes = Arrays.asList("scope1", "scope2");
247     ServiceAccountCredentials.Builder builder = createDefaultBuilderWithScopes(scopes);
248     ServiceAccountCredentials credentials = builder.setServiceAccountUser(USER).build();
249 
250     JsonFactory jsonFactory = OAuth2Utils.JSON_FACTORY;
251     long currentTimeMillis = Clock.SYSTEM.currentTimeMillis();
252     String assertion = credentials.createAssertion(jsonFactory, currentTimeMillis);
253 
254     JsonWebSignature signature = JsonWebSignature.parse(jsonFactory, assertion);
255     JsonWebToken.Payload payload = signature.getPayload();
256     assertEquals(CLIENT_EMAIL, payload.getIssuer());
257     assertEquals(OAuth2Utils.TOKEN_SERVER_URI.toString(), payload.getAudience());
258     assertEquals(currentTimeMillis / 1000, (long) payload.getIssuedAtTimeSeconds());
259     assertEquals(currentTimeMillis / 1000 + 3600, (long) payload.getExpirationTimeSeconds());
260     assertEquals(USER, payload.getSubject());
261     assertEquals(Joiner.on(' ').join(scopes), payload.get("scope"));
262   }
263 
264   @Test
createAssertion_defaultScopes_correct()265   public void createAssertion_defaultScopes_correct() throws IOException {
266     List<String> defaultScopes = Arrays.asList("scope1", "scope2");
267     ServiceAccountCredentials.Builder builder = createDefaultBuilder();
268     builder.setScopes(null, defaultScopes).setServiceAccountUser(USER);
269 
270     assertEquals(2, builder.getDefaultScopes().size());
271     ServiceAccountCredentials credentials = builder.build();
272 
273     JsonFactory jsonFactory = OAuth2Utils.JSON_FACTORY;
274     long currentTimeMillis = Clock.SYSTEM.currentTimeMillis();
275     String assertion = credentials.createAssertion(jsonFactory, currentTimeMillis);
276 
277     JsonWebSignature signature = JsonWebSignature.parse(jsonFactory, assertion);
278     JsonWebToken.Payload payload = signature.getPayload();
279     assertEquals(CLIENT_EMAIL, payload.getIssuer());
280     assertEquals(OAuth2Utils.TOKEN_SERVER_URI.toString(), payload.getAudience());
281     assertEquals(currentTimeMillis / 1000, (long) payload.getIssuedAtTimeSeconds());
282     assertEquals(currentTimeMillis / 1000 + 3600, (long) payload.getExpirationTimeSeconds());
283     assertEquals(USER, payload.getSubject());
284     assertEquals(Joiner.on(' ').join(defaultScopes), payload.get("scope"));
285   }
286 
287   @Test
createAssertion_custom_lifetime()288   public void createAssertion_custom_lifetime() throws IOException {
289     ServiceAccountCredentials credentials = createDefaultBuilder().setLifetime(4000).build();
290 
291     JsonFactory jsonFactory = OAuth2Utils.JSON_FACTORY;
292     long currentTimeMillis = Clock.SYSTEM.currentTimeMillis();
293     String assertion = credentials.createAssertion(jsonFactory, currentTimeMillis);
294 
295     JsonWebSignature signature = JsonWebSignature.parse(jsonFactory, assertion);
296     JsonWebToken.Payload payload = signature.getPayload();
297     assertEquals(currentTimeMillis / 1000 + 4000, (long) payload.getExpirationTimeSeconds());
298   }
299 
300   @Test
createAssertionForIdToken_correct()301   public void createAssertionForIdToken_correct() throws IOException {
302     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8);
303     ServiceAccountCredentials credentials =
304         createDefaultBuilder()
305             .setPrivateKeyId(PRIVATE_KEY_ID)
306             .setServiceAccountUser(USER)
307             .setProjectId(PROJECT_ID)
308             .build();
309 
310     JsonFactory jsonFactory = OAuth2Utils.JSON_FACTORY;
311     long currentTimeMillis = Clock.SYSTEM.currentTimeMillis();
312     String assertion =
313         credentials.createAssertionForIdToken(
314             jsonFactory, currentTimeMillis, null, "https://foo.com/bar");
315 
316     JsonWebSignature signature = JsonWebSignature.parse(jsonFactory, assertion);
317     JsonWebToken.Payload payload = signature.getPayload();
318     assertEquals(CLIENT_EMAIL, payload.getIssuer());
319     assertEquals("https://foo.com/bar", (String) (payload.getUnknownKeys().get("target_audience")));
320     assertEquals(currentTimeMillis / 1000, (long) payload.getIssuedAtTimeSeconds());
321     assertEquals(currentTimeMillis / 1000 + 3600, (long) payload.getExpirationTimeSeconds());
322     assertEquals(USER, payload.getSubject());
323   }
324 
325   @Test
createAssertionForIdToken_custom_lifetime()326   public void createAssertionForIdToken_custom_lifetime() throws IOException {
327     ServiceAccountCredentials credentials = createDefaultBuilder().setLifetime(4000).build();
328 
329     JsonFactory jsonFactory = OAuth2Utils.JSON_FACTORY;
330     long currentTimeMillis = Clock.SYSTEM.currentTimeMillis();
331     String assertion =
332         credentials.createAssertionForIdToken(
333             jsonFactory, currentTimeMillis, null, "https://foo.com/bar");
334 
335     JsonWebSignature signature = JsonWebSignature.parse(jsonFactory, assertion);
336     JsonWebToken.Payload payload = signature.getPayload();
337     assertEquals(currentTimeMillis / 1000 + 4000, (long) payload.getExpirationTimeSeconds());
338   }
339 
340   @Test
createAssertionForIdToken_incorrect()341   public void createAssertionForIdToken_incorrect() throws IOException {
342     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8);
343     ServiceAccountCredentials credentials =
344         ServiceAccountCredentials.newBuilder()
345             .setClientId(CLIENT_ID)
346             .setClientEmail(CLIENT_EMAIL)
347             .setPrivateKey(privateKey)
348             .setPrivateKeyId(PRIVATE_KEY_ID)
349             .setServiceAccountUser(USER)
350             .setProjectId(PROJECT_ID)
351             .build();
352 
353     JsonFactory jsonFactory = OAuth2Utils.JSON_FACTORY;
354     long currentTimeMillis = Clock.SYSTEM.currentTimeMillis();
355     String assertion =
356         credentials.createAssertionForIdToken(
357             jsonFactory, currentTimeMillis, null, "https://foo.com/bar");
358 
359     JsonWebSignature signature = JsonWebSignature.parse(jsonFactory, assertion);
360     JsonWebToken.Payload payload = signature.getPayload();
361     assertEquals(CLIENT_EMAIL, payload.getIssuer());
362     assertNotEquals(
363         "https://bar.com/foo", (String) (payload.getUnknownKeys().get("target_audience")));
364     assertEquals(currentTimeMillis / 1000, (long) payload.getIssuedAtTimeSeconds());
365     assertEquals(currentTimeMillis / 1000 + 3600, (long) payload.getExpirationTimeSeconds());
366     assertEquals(USER, payload.getSubject());
367   }
368 
369   @Test
createdScoped_withAud_noUniverse_jwtWithScopesDisabled_accessToken()370   public void createdScoped_withAud_noUniverse_jwtWithScopesDisabled_accessToken()
371       throws IOException {
372     GoogleCredentials credentials = createDefaultBuilderWithToken(ACCESS_TOKEN).build();
373 
374     // No aud, no scopes gives an exception.
375     try {
376       credentials.getRequestMetadata(null);
377       fail("Should not be able to get token without scopes");
378     } catch (IOException e) {
379       assertTrue(
380           "expected to fail with exception",
381           e.getMessage().contains("Scopes and uri are not configured for service account"));
382     }
383 
384     GoogleCredentials scopedCredentials = credentials.createScoped(SCOPES);
385     assertEquals(false, credentials.isExplicitUniverseDomain());
386     assertEquals(GOOGLE_DEFAULT_UNIVERSE, credentials.getUniverseDomain());
387     Map<String, List<String>> metadata = scopedCredentials.getRequestMetadata(CALL_URI);
388     TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN);
389   }
390 
391   @Test
createdScoped_withUniverse_selfSignedJwt()392   public void createdScoped_withUniverse_selfSignedJwt() throws IOException {
393     ServiceAccountCredentials credentials =
394         createDefaultBuilder().setUniverseDomain("foo.bar").build();
395 
396     try {
397       credentials.getRequestMetadata(null);
398       fail("Should not be able to get token without scopes");
399     } catch (IOException e) {
400       assertTrue(
401           "expected to fail with exception",
402           e.getMessage().contains("Scopes and uri are not configured for service account"));
403     }
404 
405     GoogleCredentials scopedCredentials = credentials.createScoped("dummy.scope");
406     Map<String, List<String>> metadata = scopedCredentials.getRequestMetadata(null);
407     verifyJwtAccess(metadata, "dummy.scope");
408 
409     // Recreate to avoid jwt caching.
410     scopedCredentials = credentials.createScoped("dummy.scope2");
411     assertEquals(true, scopedCredentials.isExplicitUniverseDomain());
412     assertEquals("foo.bar", scopedCredentials.getUniverseDomain());
413     metadata = scopedCredentials.getRequestMetadata(CALL_URI);
414     verifyJwtAccess(metadata, "dummy.scope2");
415 
416     // Recreate to avoid jwt caching.
417     scopedCredentials =
418         credentials.createScoped(
419             Collections.<String>emptyList(), Arrays.asList("dummy.default.scope"));
420     metadata = scopedCredentials.getRequestMetadata(null);
421     verifyJwtAccess(metadata, "dummy.default.scope");
422 
423     // Recreate to avoid jwt caching.
424     scopedCredentials =
425         credentials.createScoped(
426             Collections.<String>emptyList(), Arrays.asList("dummy.default.scope2"));
427     metadata = scopedCredentials.getRequestMetadata(CALL_URI);
428     verifyJwtAccess(metadata, "dummy.default.scope2");
429   }
430 
431   @Test
noScopes_withUniverse_selfSignedJwt()432   public void noScopes_withUniverse_selfSignedJwt() throws IOException {
433     GoogleCredentials credentials = createDefaultBuilder().setUniverseDomain("foo.bar").build();
434 
435     try {
436       credentials.getRequestMetadata(null);
437       fail("Should not be able to get token without scopes");
438     } catch (IOException e) {
439       assertTrue(
440           "expected to fail with exception",
441           e.getMessage().contains("Scopes and uri are not configured for service account"));
442     }
443 
444     Map<String, List<String>> metadata = credentials.getRequestMetadata(CALL_URI);
445     assertNull(((ServiceAccountCredentials) credentials).getSelfSignedJwtCredentialsWithScope());
446     verifyJwtAccess(metadata, null);
447   }
448 
449   @Test
createdScoped_defaultScopes()450   public void createdScoped_defaultScopes() throws IOException {
451     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
452 
453     ServiceAccountCredentials credentials =
454         ServiceAccountCredentials.fromPkcs8(
455             CLIENT_ID, CLIENT_EMAIL, PRIVATE_KEY_PKCS8, PRIVATE_KEY_ID, SCOPES, DEFAULT_SCOPES);
456     assertEquals(1, credentials.getDefaultScopes().size());
457     assertEquals("dummy.default.scope", credentials.getDefaultScopes().toArray()[0]);
458 
459     credentials =
460         ServiceAccountCredentials.fromPkcs8(
461             CLIENT_ID,
462             CLIENT_EMAIL,
463             PRIVATE_KEY_PKCS8,
464             PRIVATE_KEY_ID,
465             SCOPES,
466             DEFAULT_SCOPES,
467             transportFactory,
468             null);
469     assertEquals(1, credentials.getDefaultScopes().size());
470     assertEquals("dummy.default.scope", credentials.getDefaultScopes().toArray()[0]);
471 
472     credentials =
473         ServiceAccountCredentials.fromPkcs8(
474             CLIENT_ID,
475             CLIENT_EMAIL,
476             PRIVATE_KEY_PKCS8,
477             PRIVATE_KEY_ID,
478             SCOPES,
479             DEFAULT_SCOPES,
480             transportFactory,
481             null,
482             "service_account_user");
483     assertEquals(1, credentials.getDefaultScopes().size());
484     assertEquals("dummy.default.scope", credentials.getDefaultScopes().toArray()[0]);
485   }
486 
487   @Test
createScopedRequired_emptyScopes()488   public void createScopedRequired_emptyScopes() throws IOException {
489     GoogleCredentials credentials =
490         ServiceAccountCredentials.fromPkcs8(
491             CLIENT_ID, CLIENT_EMAIL, PRIVATE_KEY_PKCS8, PRIVATE_KEY_ID, EMPTY_SCOPES);
492 
493     assertTrue(credentials.createScopedRequired());
494   }
495 
496   @Test
createScopedRequired_nonEmptyScopes()497   public void createScopedRequired_nonEmptyScopes() throws IOException {
498     GoogleCredentials credentials =
499         ServiceAccountCredentials.fromPkcs8(
500             CLIENT_ID, CLIENT_EMAIL, PRIVATE_KEY_PKCS8, PRIVATE_KEY_ID, SCOPES);
501 
502     assertFalse(credentials.createScopedRequired());
503   }
504 
505   @Test
createScopedRequired_nonEmptyDefaultScopes()506   public void createScopedRequired_nonEmptyDefaultScopes() throws IOException {
507     GoogleCredentials credentials =
508         ServiceAccountCredentials.fromPkcs8(
509             CLIENT_ID, CLIENT_EMAIL, PRIVATE_KEY_PKCS8, PRIVATE_KEY_ID, null, SCOPES);
510 
511     assertFalse(credentials.createScopedRequired());
512   }
513 
514   @Test
fromJSON_getProjectId()515   public void fromJSON_getProjectId() throws IOException {
516     GenericJson json = writeServiceAccountJson(PROJECT_ID, null, null);
517 
518     ServiceAccountCredentials credentials =
519         ServiceAccountCredentials.fromJson(json, new MockTokenServerTransportFactory());
520     assertEquals(PROJECT_ID, credentials.getProjectId());
521     assertEquals(GOOGLE_DEFAULT_UNIVERSE, credentials.getUniverseDomain());
522   }
523 
524   @Test
fromJSON_Universe_getUniverseDomain()525   public void fromJSON_Universe_getUniverseDomain() throws IOException {
526     GenericJson json = writeServiceAccountJson(PROJECT_ID, null, "foo.bar");
527 
528     ServiceAccountCredentials credentials =
529         ServiceAccountCredentials.fromJson(json, new MockTokenServerTransportFactory());
530     assertEquals("foo.bar", credentials.getUniverseDomain());
531   }
532 
533   @Test
fromJSON_getProjectIdNull()534   public void fromJSON_getProjectIdNull() throws IOException {
535     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
536     transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN);
537     GenericJson json = writeServiceAccountJson(null, null, null);
538 
539     ServiceAccountCredentials credentials =
540         ServiceAccountCredentials.fromJson(json, transportFactory);
541     assertNull(credentials.getProjectId());
542   }
543 
544   @Test
fromJSON_hasAccessToken()545   public void fromJSON_hasAccessToken() throws IOException {
546     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
547     transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN);
548     GenericJson json = writeServiceAccountJson(PROJECT_ID, null, null);
549 
550     GoogleCredentials credentials = ServiceAccountCredentials.fromJson(json, transportFactory);
551 
552     credentials = credentials.createScoped(SCOPES);
553     Map<String, List<String>> metadata = credentials.getRequestMetadata(CALL_URI);
554     TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN);
555   }
556 
557   @Test
fromJSON_withUniverse_selfSignedJwt()558   public void fromJSON_withUniverse_selfSignedJwt() throws IOException {
559     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
560     transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN);
561     GenericJson json = writeServiceAccountJson(PROJECT_ID, null, "foo.bar");
562 
563     GoogleCredentials credentials = ServiceAccountCredentials.fromJson(json, transportFactory);
564 
565     credentials = credentials.createScoped(SCOPES);
566     Map<String, List<String>> metadata = credentials.getRequestMetadata(null);
567     verifyJwtAccess(metadata, "dummy.scope");
568   }
569 
570   @Test
fromJSON_tokenServerUri()571   public void fromJSON_tokenServerUri() throws IOException {
572     final String tokenServerUri = "https://foo.com/bar";
573     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
574     transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN);
575     GenericJson json = writeServiceAccountJson(PROJECT_ID, null, null);
576     json.put("token_uri", tokenServerUri);
577     ServiceAccountCredentials credentials =
578         ServiceAccountCredentials.fromJson(json, transportFactory);
579     assertEquals(URI.create(tokenServerUri), credentials.getTokenServerUri());
580   }
581 
582   @Test
fromJson_hasQuotaProjectId()583   public void fromJson_hasQuotaProjectId() throws IOException {
584     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
585     transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN);
586     GenericJson json = writeServiceAccountJson(PROJECT_ID, QUOTA_PROJECT, null);
587     GoogleCredentials credentials = ServiceAccountCredentials.fromJson(json, transportFactory);
588     credentials = credentials.createScoped(SCOPES);
589 
590     Map<String, List<String>> metadata = credentials.getRequestMetadata(CALL_URI);
591 
592     assertTrue(metadata.containsKey(GoogleCredentials.QUOTA_PROJECT_ID_HEADER_KEY));
593     assertEquals(
594         metadata.get(GoogleCredentials.QUOTA_PROJECT_ID_HEADER_KEY),
595         Collections.singletonList(QUOTA_PROJECT));
596   }
597 
598   @Test
getRequestMetadata_hasAccessToken()599   public void getRequestMetadata_hasAccessToken() throws IOException {
600     GoogleCredentials credentials =
601         createDefaultBuilderWithToken(ACCESS_TOKEN).setScopes(SCOPES).build();
602     Map<String, List<String>> metadata = credentials.getRequestMetadata(CALL_URI);
603     TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN);
604   }
605 
606   @Test
getRequestMetadata_customTokenServer_hasAccessToken()607   public void getRequestMetadata_customTokenServer_hasAccessToken() throws IOException {
608     final URI TOKEN_SERVER = URI.create("https://foo.com/bar");
609     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
610     transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN);
611     transportFactory.transport.setTokenServerUri(TOKEN_SERVER);
612     OAuth2Credentials credentials =
613         createDefaultBuilder()
614             .setScopes(SCOPES)
615             .setHttpTransportFactory(transportFactory)
616             .setTokenServerUri(TOKEN_SERVER)
617             .build();
618     Map<String, List<String>> metadata = credentials.getRequestMetadata(CALL_URI);
619 
620     TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN);
621   }
622 
623   @Test
getUniverseDomain_defaultUniverse()624   public void getUniverseDomain_defaultUniverse() throws IOException {
625     ServiceAccountCredentials credentials = createDefaultBuilder().build();
626     assertEquals(GOOGLE_DEFAULT_UNIVERSE, credentials.getUniverseDomain());
627   }
628 
629   @Test
refreshAccessToken_refreshesToken()630   public void refreshAccessToken_refreshesToken() throws IOException {
631     final String accessToken1 = "1/MkSJoj1xsli0AccessToken_NKPY2";
632     final String accessToken2 = "2/MkSJoj1xsli0AccessToken_NKPY2";
633     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
634     MockTokenServerTransport transport = transportFactory.transport;
635     ServiceAccountCredentials credentials =
636         createDefaultBuilder().setScopes(SCOPES).setHttpTransportFactory(transportFactory).build();
637     transport.addServiceAccount(CLIENT_EMAIL, accessToken1);
638     TestUtils.assertContainsBearerToken(credentials.getRequestMetadata(CALL_URI), accessToken1);
639 
640     transport.addServiceAccount(CLIENT_EMAIL, accessToken2);
641     credentials.refresh();
642     TestUtils.assertContainsBearerToken(credentials.getRequestMetadata(CALL_URI), accessToken2);
643   }
644 
645   @Test
refreshAccessToken_tokenExpiry()646   public void refreshAccessToken_tokenExpiry() throws IOException {
647     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
648     MockTokenServerTransport transport = transportFactory.transport;
649     transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN);
650     ServiceAccountCredentials credentials =
651         createDefaultBuilder().setScopes(SCOPES).setHttpTransportFactory(transportFactory).build();
652     credentials.clock = new FixedClock(0L);
653 
654     AccessToken accessToken = credentials.refreshAccessToken();
655     assertEquals(ACCESS_TOKEN, accessToken.getTokenValue());
656     assertEquals(3600 * 1000L, accessToken.getExpirationTimeMillis().longValue());
657 
658     // Test for large expires_in values (should not overflow).
659     transport.setExpiresInSeconds(3600 * 1000);
660     accessToken = credentials.refreshAccessToken();
661     assertEquals(ACCESS_TOKEN, accessToken.getTokenValue());
662     assertEquals(3600 * 1000 * 1000L, accessToken.getExpirationTimeMillis().longValue());
663   }
664 
665   @Test
refreshAccessToken_IOException_Retry()666   public void refreshAccessToken_IOException_Retry() throws IOException {
667     final String accessToken1 = "1/MkSJoj1xsli0AccessToken_NKPY2";
668     final String accessToken2 = "2/MkSJoj1xsli0AccessToken_NKPY2";
669     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
670     MockTokenServerTransport transport = transportFactory.transport;
671     ServiceAccountCredentials credentials =
672         createDefaultBuilder().setScopes(SCOPES).setHttpTransportFactory(transportFactory).build();
673     ;
674 
675     transport.addServiceAccount(CLIENT_EMAIL, accessToken1);
676     TestUtils.assertContainsBearerToken(credentials.getRequestMetadata(CALL_URI), accessToken1);
677 
678     transport.addResponseErrorSequence(new IOException());
679     transport.addServiceAccount(CLIENT_EMAIL, accessToken2);
680     credentials.refresh();
681     TestUtils.assertContainsBearerToken(credentials.getRequestMetadata(CALL_URI), accessToken2);
682   }
683 
684   @Test
refreshAccessToken_retriesServerErrors()685   public void refreshAccessToken_retriesServerErrors() throws IOException {
686     final String accessToken1 = "1/MkSJoj1xsli0AccessToken_NKPY2";
687     final String accessToken2 = "2/MkSJoj1xsli0AccessToken_NKPY2";
688     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
689     MockTokenServerTransport transport = transportFactory.transport;
690     ServiceAccountCredentials credentials =
691         createDefaultBuilder().setScopes(SCOPES).setHttpTransportFactory(transportFactory).build();
692 
693     transport.addServiceAccount(CLIENT_EMAIL, accessToken1);
694     TestUtils.assertContainsBearerToken(credentials.getRequestMetadata(CALL_URI), accessToken1);
695 
696     MockLowLevelHttpResponse response500 = new MockLowLevelHttpResponse().setStatusCode(500);
697     MockLowLevelHttpResponse response503 = new MockLowLevelHttpResponse().setStatusCode(503);
698     transport.addResponseSequence(response500, response503);
699     transport.addServiceAccount(CLIENT_EMAIL, accessToken2);
700     credentials.refresh();
701     TestUtils.assertContainsBearerToken(credentials.getRequestMetadata(CALL_URI), accessToken2);
702   }
703 
704   @Test
refreshAccessToken_retriesTimeoutAndThrottled()705   public void refreshAccessToken_retriesTimeoutAndThrottled() throws IOException {
706     final String accessToken1 = "1/MkSJoj1xsli0AccessToken_NKPY2";
707     final String accessToken2 = "2/MkSJoj1xsli0AccessToken_NKPY2";
708     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
709     MockTokenServerTransport transport = transportFactory.transport;
710     ServiceAccountCredentials credentials =
711         createDefaultBuilder().setScopes(SCOPES).setHttpTransportFactory(transportFactory).build();
712 
713     transport.addServiceAccount(CLIENT_EMAIL, accessToken1);
714     TestUtils.assertContainsBearerToken(credentials.getRequestMetadata(CALL_URI), accessToken1);
715 
716     MockLowLevelHttpResponse response408 = new MockLowLevelHttpResponse().setStatusCode(408);
717     MockLowLevelHttpResponse response429 = new MockLowLevelHttpResponse().setStatusCode(429);
718     transport.addResponseSequence(response408, response429);
719     transport.addServiceAccount(CLIENT_EMAIL, accessToken2);
720     credentials.refresh();
721     TestUtils.assertContainsBearerToken(credentials.getRequestMetadata(CALL_URI), accessToken2);
722   }
723 
724   @Test
refreshAccessToken_defaultRetriesDisabled()725   public void refreshAccessToken_defaultRetriesDisabled() throws IOException {
726     final String accessToken1 = "1/MkSJoj1xsli0AccessToken_NKPY2";
727     final String accessToken2 = "2/MkSJoj1xsli0AccessToken_NKPY2";
728     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
729     MockTokenServerTransport transport = transportFactory.transport;
730     ServiceAccountCredentials credentials =
731         createDefaultBuilder()
732             .setScopes(SCOPES)
733             .setHttpTransportFactory(transportFactory)
734             .build()
735             .createWithCustomRetryStrategy(false);
736 
737     transport.addServiceAccount(CLIENT_EMAIL, accessToken1);
738     TestUtils.assertContainsBearerToken(credentials.getRequestMetadata(CALL_URI), accessToken1);
739 
740     MockLowLevelHttpResponse response408 = new MockLowLevelHttpResponse().setStatusCode(408);
741     MockLowLevelHttpResponse response429 = new MockLowLevelHttpResponse().setStatusCode(429);
742     transport.addServiceAccount(CLIENT_EMAIL, accessToken2);
743 
744     try {
745       transport.addResponseSequence(response408, response429);
746       credentials.refresh();
747       fail("Should not be able to use credential without exception.");
748     } catch (GoogleAuthException ex) {
749       assertTrue(ex.getMessage().contains("Error getting access token for service account: 408"));
750       assertTrue(ex.isRetryable());
751       assertEquals(0, ex.getRetryCount());
752     }
753   }
754 
755   @Test
refreshAccessToken_maxRetries_maxDelay()756   public void refreshAccessToken_maxRetries_maxDelay() throws IOException {
757     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
758     MockTokenServerTransport transport = transportFactory.transport;
759     ServiceAccountCredentials credentials =
760         createDefaultBuilder().setScopes(SCOPES).setHttpTransportFactory(transportFactory).build();
761 
762     transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN);
763     TestUtils.assertContainsBearerToken(credentials.getRequestMetadata(CALL_URI), ACCESS_TOKEN);
764 
765     MockLowLevelHttpResponse response408 = new MockLowLevelHttpResponse().setStatusCode(408);
766     MockLowLevelHttpResponse response429 = new MockLowLevelHttpResponse().setStatusCode(429);
767     MockLowLevelHttpResponse response500 = new MockLowLevelHttpResponse().setStatusCode(500);
768     MockLowLevelHttpResponse response503 = new MockLowLevelHttpResponse().setStatusCode(503);
769 
770     Instant start = Instant.now();
771     try {
772       transport.addResponseSequence(response408, response429, response500, response503);
773       credentials.refresh();
774       fail("Should not be able to use credential without exception.");
775     } catch (GoogleAuthException ex) {
776       Instant finish = Instant.now();
777       long timeElapsed = Duration.between(start, finish).toMillis();
778 
779       // we expect max retry time of 7 sec +/- jitter
780       assertTrue(timeElapsed > 5500 && timeElapsed < 10000);
781       assertTrue(ex.getMessage().contains("Error getting access token for service account: 503"));
782       assertTrue(ex.isRetryable());
783       assertEquals(3, ex.getRetryCount());
784       assertTrue(ex.getCause() instanceof HttpResponseException);
785     }
786   }
787 
788   @Test
refreshAccessToken_RequestFailure_retried()789   public void refreshAccessToken_RequestFailure_retried() throws IOException {
790     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
791     MockTokenServerTransport transport = transportFactory.transport;
792     ServiceAccountCredentials credentials =
793         createDefaultBuilder().setScopes(SCOPES).setHttpTransportFactory(transportFactory).build();
794 
795     transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN);
796     TestUtils.assertContainsBearerToken(credentials.getRequestMetadata(CALL_URI), ACCESS_TOKEN);
797 
798     IOException error = new IOException("Invalid grant: Account not found");
799     MockLowLevelHttpResponse response503 = new MockLowLevelHttpResponse().setStatusCode(503);
800 
801     Instant start = Instant.now();
802     try {
803       transport.addResponseSequence(response503);
804       transport.addResponseErrorSequence(error, error, error);
805       credentials.refresh();
806       fail("Should not be able to use credential without exception.");
807     } catch (GoogleAuthException ex) {
808       Instant finish = Instant.now();
809       long timeElapsed = Duration.between(start, finish).toMillis();
810 
811       // we expect max retry time of 7 sec +/- jitter
812       assertTrue(timeElapsed > 5500 && timeElapsed < 10000);
813       assertTrue(
814           ex.getMessage()
815               .contains("Error getting access token for service account: Invalid grant"));
816       assertTrue(ex.isRetryable());
817       assertEquals(3, ex.getRetryCount());
818       assertTrue(ex.getCause() instanceof IOException);
819     }
820   }
821 
822   @Test
refreshAccessToken_4xx_5xx_NonRetryableFails()823   public void refreshAccessToken_4xx_5xx_NonRetryableFails() throws IOException {
824     final String accessToken1 = "1/MkSJoj1xsli0AccessToken_NKPY2";
825     final String accessToken2 = "2/MkSJoj1xsli0AccessToken_NKPY2";
826     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
827     MockTokenServerTransport transport = transportFactory.transport;
828     ServiceAccountCredentials credentials =
829         createDefaultBuilder().setScopes(SCOPES).setHttpTransportFactory(transportFactory).build();
830 
831     transport.addServiceAccount(CLIENT_EMAIL, accessToken1);
832     TestUtils.assertContainsBearerToken(credentials.getRequestMetadata(CALL_URI), accessToken1);
833 
834     for (int status = 400; status < 600; status++) {
835       if (OAuth2Utils.TOKEN_ENDPOINT_RETRYABLE_STATUS_CODES.contains(status)) {
836         continue;
837       }
838 
839       MockLowLevelHttpResponse mockResponse = new MockLowLevelHttpResponse().setStatusCode(status);
840       try {
841         transport.addResponseSequence(mockResponse);
842         transport.addServiceAccount(CLIENT_EMAIL, accessToken2);
843         credentials.refresh();
844         fail("Should not be able to use credential without exception.");
845       } catch (GoogleAuthException ex) {
846         assertFalse(ex.isRetryable());
847         assertEquals(0, ex.getRetryCount());
848       }
849     }
850   }
851 
852   @Test
idTokenWithAudience_correct()853   public void idTokenWithAudience_correct() throws IOException {
854     String accessToken1 = "1/MkSJoj1xsli0AccessToken_NKPY2";
855     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
856     MockTokenServerTransport transport = transportFactory.transport;
857     ServiceAccountCredentials credentials =
858         createDefaultBuilder().setScopes(SCOPES).setHttpTransportFactory(transportFactory).build();
859 
860     transport.addServiceAccount(CLIENT_EMAIL, accessToken1);
861     TestUtils.assertContainsBearerToken(credentials.getRequestMetadata(CALL_URI), accessToken1);
862 
863     String targetAudience = "https://foo.bar";
864     IdTokenCredentials tokenCredential =
865         IdTokenCredentials.newBuilder()
866             .setIdTokenProvider(credentials)
867             .setTargetAudience(targetAudience)
868             .build();
869     tokenCredential.refresh();
870     assertEquals(DEFAULT_ID_TOKEN, tokenCredential.getAccessToken().getTokenValue());
871     assertEquals(DEFAULT_ID_TOKEN, tokenCredential.getIdToken().getTokenValue());
872     assertEquals(
873         targetAudience,
874         (String) tokenCredential.getIdToken().getJsonWebSignature().getPayload().getAudience());
875   }
876 
877   @Test
idTokenWithAudience_incorrect()878   public void idTokenWithAudience_incorrect() throws IOException {
879     String accessToken1 = "1/MkSJoj1xsli0AccessToken_NKPY2";
880     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
881     MockTokenServerTransport transport = transportFactory.transport;
882     ServiceAccountCredentials credentials =
883         createDefaultBuilder().setScopes(SCOPES).setHttpTransportFactory(transportFactory).build();
884 
885     transport.addServiceAccount(CLIENT_EMAIL, accessToken1);
886     TestUtils.assertContainsBearerToken(credentials.getRequestMetadata(CALL_URI), accessToken1);
887 
888     String targetAudience = "https://bar";
889     IdTokenCredentials tokenCredential =
890         IdTokenCredentials.newBuilder()
891             .setIdTokenProvider(credentials)
892             .setTargetAudience(targetAudience)
893             .build();
894     tokenCredential.refresh();
895     assertNotEquals(
896         targetAudience,
897         (String) tokenCredential.getIdToken().getJsonWebSignature().getPayload().getAudience());
898   }
899 
900   @Test
getScopes_nullReturnsEmpty()901   public void getScopes_nullReturnsEmpty() throws IOException {
902     ServiceAccountCredentials credentials = createDefaultBuilder().build();
903     Collection<String> scopes = credentials.getScopes();
904 
905     assertNotNull(scopes);
906     assertTrue(scopes.isEmpty());
907   }
908 
909   @Test
getAccount_sameAs()910   public void getAccount_sameAs() throws IOException {
911     ServiceAccountCredentials credentials = createDefaultBuilder().build();
912     assertEquals(CLIENT_EMAIL, credentials.getAccount());
913   }
914 
915   @Test
sign_sameAs()916   public void sign_sameAs()
917       throws IOException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
918     byte[] toSign = {0xD, 0xE, 0xA, 0xD};
919     ServiceAccountCredentials credentials = createDefaultBuilder().build();
920 
921     byte[] signedBytes = credentials.sign(toSign);
922     Signature signature = Signature.getInstance(OAuth2Utils.SIGNATURE_ALGORITHM);
923     signature.initSign(credentials.getPrivateKey());
924     signature.update(toSign);
925 
926     assertArrayEquals(signature.sign(), signedBytes);
927   }
928 
929   @Test
equals_true()930   public void equals_true() throws IOException {
931     final URI tokenServer = URI.create("https://foo.com/bar");
932     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
933     OAuth2Credentials credentials =
934         ServiceAccountCredentials.fromPkcs8(
935             CLIENT_ID,
936             CLIENT_EMAIL,
937             PRIVATE_KEY_PKCS8,
938             PRIVATE_KEY_ID,
939             SCOPES,
940             transportFactory,
941             tokenServer);
942     OAuth2Credentials otherCredentials =
943         ServiceAccountCredentials.fromPkcs8(
944             CLIENT_ID,
945             CLIENT_EMAIL,
946             PRIVATE_KEY_PKCS8,
947             PRIVATE_KEY_ID,
948             SCOPES,
949             transportFactory,
950             tokenServer);
951     assertTrue(credentials.equals(otherCredentials));
952     assertTrue(otherCredentials.equals(credentials));
953   }
954 
955   @Test
equals_false_clientId()956   public void equals_false_clientId() throws IOException {
957     final URI tokenServer1 = URI.create("https://foo1.com/bar");
958     MockTokenServerTransportFactory serverTransportFactory = new MockTokenServerTransportFactory();
959     OAuth2Credentials credentials =
960         ServiceAccountCredentials.fromPkcs8(
961             CLIENT_ID,
962             CLIENT_EMAIL,
963             PRIVATE_KEY_PKCS8,
964             PRIVATE_KEY_ID,
965             SCOPES,
966             serverTransportFactory,
967             tokenServer1);
968     OAuth2Credentials otherCredentials =
969         ServiceAccountCredentials.fromPkcs8(
970             "otherClientId",
971             CLIENT_EMAIL,
972             PRIVATE_KEY_PKCS8,
973             PRIVATE_KEY_ID,
974             SCOPES,
975             serverTransportFactory,
976             tokenServer1);
977     assertFalse(credentials.equals(otherCredentials));
978     assertFalse(otherCredentials.equals(credentials));
979   }
980 
981   @Test
equals_false_email()982   public void equals_false_email() throws IOException {
983     final URI tokenServer1 = URI.create("https://foo1.com/bar");
984     MockTokenServerTransportFactory serverTransportFactory = new MockTokenServerTransportFactory();
985     OAuth2Credentials credentials =
986         ServiceAccountCredentials.fromPkcs8(
987             CLIENT_ID,
988             CLIENT_EMAIL,
989             PRIVATE_KEY_PKCS8,
990             PRIVATE_KEY_ID,
991             SCOPES,
992             serverTransportFactory,
993             tokenServer1);
994     OAuth2Credentials otherCredentials =
995         ServiceAccountCredentials.fromPkcs8(
996             CLIENT_ID,
997             "otherEmail",
998             PRIVATE_KEY_PKCS8,
999             PRIVATE_KEY_ID,
1000             SCOPES,
1001             serverTransportFactory,
1002             tokenServer1);
1003     assertFalse(credentials.equals(otherCredentials));
1004     assertFalse(otherCredentials.equals(credentials));
1005   }
1006 
1007   @Test
equals_false_super()1008   public void equals_false_super() throws IOException {
1009     final URI tokenServer1 = URI.create("https://foo1.com/bar");
1010     MockTokenServerTransportFactory serverTransportFactory = new MockTokenServerTransportFactory();
1011     OAuth2Credentials credentials =
1012         ServiceAccountCredentials.fromPkcs8(
1013             CLIENT_ID,
1014             CLIENT_EMAIL,
1015             PRIVATE_KEY_PKCS8,
1016             PRIVATE_KEY_ID,
1017             SCOPES,
1018             serverTransportFactory,
1019             tokenServer1);
1020     OAuth2Credentials otherCredentials =
1021         ServiceAccountCredentials.fromPkcs8(
1022                 CLIENT_ID,
1023                 CLIENT_EMAIL,
1024                 PRIVATE_KEY_PKCS8,
1025                 PRIVATE_KEY_ID,
1026                 SCOPES,
1027                 serverTransportFactory,
1028                 tokenServer1)
1029             .toBuilder()
1030             .setUniverseDomain("universe.com")
1031             .build();
1032     assertFalse(credentials.equals(otherCredentials));
1033     assertFalse(otherCredentials.equals(credentials));
1034   }
1035 
1036   @Test
equals_false_keyId()1037   public void equals_false_keyId() throws IOException {
1038     final URI tokenServer1 = URI.create("https://foo1.com/bar");
1039     MockTokenServerTransportFactory serverTransportFactory = new MockTokenServerTransportFactory();
1040     OAuth2Credentials credentials =
1041         ServiceAccountCredentials.fromPkcs8(
1042             CLIENT_ID,
1043             CLIENT_EMAIL,
1044             PRIVATE_KEY_PKCS8,
1045             PRIVATE_KEY_ID,
1046             SCOPES,
1047             serverTransportFactory,
1048             tokenServer1);
1049     OAuth2Credentials otherCredentials =
1050         ServiceAccountCredentials.fromPkcs8(
1051             CLIENT_ID,
1052             CLIENT_EMAIL,
1053             PRIVATE_KEY_PKCS8,
1054             "otherId",
1055             SCOPES,
1056             serverTransportFactory,
1057             tokenServer1);
1058     assertFalse(credentials.equals(otherCredentials));
1059     assertFalse(otherCredentials.equals(credentials));
1060   }
1061 
1062   @Test
equals_false_scopes()1063   public void equals_false_scopes() throws IOException {
1064     final URI tokenServer1 = URI.create("https://foo1.com/bar");
1065     MockTokenServerTransportFactory serverTransportFactory = new MockTokenServerTransportFactory();
1066     OAuth2Credentials credentials =
1067         ServiceAccountCredentials.fromPkcs8(
1068             CLIENT_ID,
1069             CLIENT_EMAIL,
1070             PRIVATE_KEY_PKCS8,
1071             PRIVATE_KEY_ID,
1072             SCOPES,
1073             serverTransportFactory,
1074             tokenServer1);
1075     OAuth2Credentials otherCredentials =
1076         ServiceAccountCredentials.fromPkcs8(
1077             CLIENT_ID,
1078             CLIENT_EMAIL,
1079             PRIVATE_KEY_PKCS8,
1080             PRIVATE_KEY_ID,
1081             ImmutableSet.<String>of(),
1082             serverTransportFactory,
1083             tokenServer1);
1084     assertFalse(credentials.equals(otherCredentials));
1085     assertFalse(otherCredentials.equals(credentials));
1086   }
1087 
1088   @Test
equals_false_transportFactory()1089   public void equals_false_transportFactory() throws IOException {
1090     final URI tokenServer1 = URI.create("https://foo1.com/bar");
1091     MockHttpTransportFactory httpTransportFactory = new MockHttpTransportFactory();
1092     MockTokenServerTransportFactory serverTransportFactory = new MockTokenServerTransportFactory();
1093     OAuth2Credentials credentials =
1094         ServiceAccountCredentials.fromPkcs8(
1095             CLIENT_ID,
1096             CLIENT_EMAIL,
1097             PRIVATE_KEY_PKCS8,
1098             PRIVATE_KEY_ID,
1099             SCOPES,
1100             serverTransportFactory,
1101             tokenServer1);
1102     OAuth2Credentials otherCredentials =
1103         ServiceAccountCredentials.fromPkcs8(
1104             CLIENT_ID,
1105             CLIENT_EMAIL,
1106             PRIVATE_KEY_PKCS8,
1107             PRIVATE_KEY_ID,
1108             SCOPES,
1109             httpTransportFactory,
1110             tokenServer1);
1111     assertFalse(credentials.equals(otherCredentials));
1112     assertFalse(otherCredentials.equals(credentials));
1113   }
1114 
1115   @Test
equals_false_tokenServer()1116   public void equals_false_tokenServer() throws IOException {
1117     final URI tokenServer1 = URI.create("https://foo1.com/bar");
1118     final URI tokenServer2 = URI.create("https://foo2.com/bar");
1119     MockTokenServerTransportFactory serverTransportFactory = new MockTokenServerTransportFactory();
1120     OAuth2Credentials credentials =
1121         ServiceAccountCredentials.fromPkcs8(
1122             CLIENT_ID,
1123             CLIENT_EMAIL,
1124             PRIVATE_KEY_PKCS8,
1125             PRIVATE_KEY_ID,
1126             SCOPES,
1127             serverTransportFactory,
1128             tokenServer1);
1129     OAuth2Credentials otherCredentials =
1130         ServiceAccountCredentials.fromPkcs8(
1131             CLIENT_ID,
1132             CLIENT_EMAIL,
1133             PRIVATE_KEY_PKCS8,
1134             PRIVATE_KEY_ID,
1135             SCOPES,
1136             serverTransportFactory,
1137             tokenServer2);
1138     assertFalse(credentials.equals(otherCredentials));
1139     assertFalse(otherCredentials.equals(credentials));
1140   }
1141 
1142   @Test
toString_containsFields()1143   public void toString_containsFields() throws IOException {
1144     final URI tokenServer = URI.create("https://foo.com/bar");
1145     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
1146 
1147     ServiceAccountCredentials.Builder builder =
1148         ServiceAccountCredentials.newBuilder()
1149             .setClientId(CLIENT_ID)
1150             .setClientEmail(CLIENT_EMAIL)
1151             .setPrivateKeyId(PRIVATE_KEY_ID)
1152             .setScopes(SCOPES, DEFAULT_SCOPES)
1153             .setHttpTransportFactory(transportFactory)
1154             .setTokenServerUri(tokenServer)
1155             .setServiceAccountUser(USER)
1156             .setQuotaProjectId(QUOTA_PROJECT);
1157 
1158     OAuth2Credentials credentials = ServiceAccountCredentials.fromPkcs8(PRIVATE_KEY_PKCS8, builder);
1159     String expectedToString =
1160         String.format(
1161             "ServiceAccountCredentials{quotaProjectId=%s, universeDomain=%s, isExplicitUniverseDomain=false, clientId=%s, clientEmail=%s, "
1162                 + "privateKeyId=%s, transportFactoryClassName=%s, tokenServerUri=%s, scopes=%s, defaultScopes=%s, "
1163                 + "serviceAccountUser=%s, lifetime=3600, useJwtAccessWithScope=false, defaultRetriesEnabled=true}",
1164             QUOTA_PROJECT,
1165             Credentials.GOOGLE_DEFAULT_UNIVERSE,
1166             CLIENT_ID,
1167             CLIENT_EMAIL,
1168             PRIVATE_KEY_ID,
1169             MockTokenServerTransportFactory.class.getName(),
1170             tokenServer,
1171             SCOPES,
1172             DEFAULT_SCOPES,
1173             USER);
1174     assertEquals(expectedToString, credentials.toString());
1175   }
1176 
1177   @Test
hashCode_equals()1178   public void hashCode_equals() throws IOException {
1179     final URI tokenServer = URI.create("https://foo.com/bar");
1180     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
1181     OAuth2Credentials credentials =
1182         ServiceAccountCredentials.fromPkcs8(
1183                 CLIENT_ID,
1184                 CLIENT_EMAIL,
1185                 PRIVATE_KEY_PKCS8,
1186                 PRIVATE_KEY_ID,
1187                 SCOPES,
1188                 transportFactory,
1189                 tokenServer)
1190             .createWithQuotaProject(QUOTA_PROJECT)
1191             .toBuilder()
1192             .setUniverseDomain("universe.com")
1193             .build();
1194     OAuth2Credentials otherCredentials =
1195         ServiceAccountCredentials.fromPkcs8(
1196                 CLIENT_ID,
1197                 CLIENT_EMAIL,
1198                 PRIVATE_KEY_PKCS8,
1199                 PRIVATE_KEY_ID,
1200                 SCOPES,
1201                 transportFactory,
1202                 tokenServer)
1203             .createWithQuotaProject(QUOTA_PROJECT)
1204             .toBuilder()
1205             .setUniverseDomain("universe.com")
1206             .build();
1207     assertEquals(credentials.hashCode(), otherCredentials.hashCode());
1208   }
1209 
1210   @Test
hashCode_not_equals_quota()1211   public void hashCode_not_equals_quota() throws IOException {
1212     final URI tokenServer = URI.create("https://foo.com/bar");
1213     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
1214     OAuth2Credentials credentials =
1215         ServiceAccountCredentials.fromPkcs8(
1216             CLIENT_ID,
1217             CLIENT_EMAIL,
1218             PRIVATE_KEY_PKCS8,
1219             PRIVATE_KEY_ID,
1220             SCOPES,
1221             transportFactory,
1222             tokenServer);
1223     OAuth2Credentials otherCredentials =
1224         ServiceAccountCredentials.fromPkcs8(
1225                 CLIENT_ID,
1226                 CLIENT_EMAIL,
1227                 PRIVATE_KEY_PKCS8,
1228                 PRIVATE_KEY_ID,
1229                 SCOPES,
1230                 transportFactory,
1231                 tokenServer)
1232             .createWithQuotaProject("some_quota");
1233     assertNotEquals(credentials.hashCode(), otherCredentials.hashCode());
1234   }
1235 
1236   @Test
serialize()1237   public void serialize() throws IOException, ClassNotFoundException {
1238     final URI tokenServer = URI.create("https://foo.com/bar");
1239     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
1240     ServiceAccountCredentials credentials =
1241         ServiceAccountCredentials.fromPkcs8(
1242             CLIENT_ID,
1243             CLIENT_EMAIL,
1244             PRIVATE_KEY_PKCS8,
1245             PRIVATE_KEY_ID,
1246             SCOPES,
1247             transportFactory,
1248             tokenServer);
1249 
1250     ByteArrayOutputStream bytes = new ByteArrayOutputStream();
1251     try (ObjectOutputStream output = new ObjectOutputStream(bytes)) {
1252       output.writeObject(credentials);
1253       String s = output.toString();
1254     }
1255     ServiceAccountCredentials deserializedCredentials = serializeAndDeserialize(credentials);
1256     assertEquals(credentials, deserializedCredentials);
1257     assertEquals(credentials.hashCode(), deserializedCredentials.hashCode());
1258     assertEquals(credentials.toString(), deserializedCredentials.toString());
1259     assertSame(deserializedCredentials.clock, Clock.SYSTEM);
1260     assertEquals(
1261         MockTokenServerTransportFactory.class,
1262         deserializedCredentials.toBuilder().getHttpTransportFactory().getClass());
1263   }
1264 
1265   @Test
fromStream_nullTransport_throws()1266   public void fromStream_nullTransport_throws() throws IOException {
1267     InputStream stream = new ByteArrayInputStream("foo".getBytes());
1268     try {
1269       ServiceAccountCredentials.fromStream(stream, null);
1270       fail("Should throw if HttpTransportFactory is null");
1271     } catch (NullPointerException expected) {
1272       // Expected
1273     }
1274   }
1275 
1276   @Test
fromStream_nullStream_throws()1277   public void fromStream_nullStream_throws() throws IOException {
1278     MockHttpTransportFactory transportFactory = new MockHttpTransportFactory();
1279     try {
1280       ServiceAccountCredentials.fromStream(null, transportFactory);
1281       fail("Should throw if InputStream is null");
1282     } catch (NullPointerException expected) {
1283       // Expected
1284     }
1285   }
1286 
1287   @Test
fromStream_providesToken()1288   public void fromStream_providesToken() throws IOException {
1289     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
1290     transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN);
1291     InputStream serviceAccountStream =
1292         writeServiceAccountStream(CLIENT_ID, CLIENT_EMAIL, PRIVATE_KEY_PKCS8, PRIVATE_KEY_ID);
1293 
1294     GoogleCredentials credentials =
1295         ServiceAccountCredentials.fromStream(serviceAccountStream, transportFactory);
1296 
1297     assertNotNull(credentials);
1298     credentials = credentials.createScoped(SCOPES);
1299     Map<String, List<String>> metadata = credentials.getRequestMetadata(CALL_URI);
1300     TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN);
1301   }
1302 
1303   @Test
fromStream_noClientId_throws()1304   public void fromStream_noClientId_throws() throws IOException {
1305     InputStream serviceAccountStream =
1306         writeServiceAccountStream(null, CLIENT_EMAIL, PRIVATE_KEY_PKCS8, PRIVATE_KEY_ID);
1307 
1308     testFromStreamException(serviceAccountStream, "client_id");
1309   }
1310 
1311   @Test
fromStream_noClientEmail_throws()1312   public void fromStream_noClientEmail_throws() throws IOException {
1313     InputStream serviceAccountStream =
1314         writeServiceAccountStream(CLIENT_ID, null, PRIVATE_KEY_PKCS8, PRIVATE_KEY_ID);
1315 
1316     testFromStreamException(serviceAccountStream, "client_email");
1317   }
1318 
1319   @Test
getIdTokenWithAudience_badEmailError_issClaimTraced()1320   public void getIdTokenWithAudience_badEmailError_issClaimTraced() throws IOException {
1321     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
1322     MockTokenServerTransport transport = transportFactory.transport;
1323     transport.setError(new IOException("Invalid grant: Account not found"));
1324     ServiceAccountCredentials credentials =
1325         createDefaultBuilder().setScopes(SCOPES).setHttpTransportFactory(transportFactory).build();
1326 
1327     String targetAudience = "https://bar";
1328     IdTokenCredentials tokenCredential =
1329         IdTokenCredentials.newBuilder()
1330             .setIdTokenProvider(credentials)
1331             .setTargetAudience(targetAudience)
1332             .build();
1333 
1334     String expectedErrorMessage = String.format("iss: %s", CLIENT_EMAIL);
1335 
1336     try {
1337       tokenCredential.refresh();
1338       fail("Should not be able to use credential without exception.");
1339     } catch (IOException expected) {
1340       assertTrue(expected.getMessage().contains(expectedErrorMessage));
1341     }
1342   }
1343 
1344   @Test
fromStream_noPrivateKey_throws()1345   public void fromStream_noPrivateKey_throws() throws IOException {
1346     InputStream serviceAccountStream =
1347         writeServiceAccountStream(CLIENT_ID, CLIENT_EMAIL, null, PRIVATE_KEY_ID);
1348 
1349     testFromStreamException(serviceAccountStream, "private_key");
1350   }
1351 
1352   @Test
fromStream_noPrivateKeyId_throws()1353   public void fromStream_noPrivateKeyId_throws() throws IOException {
1354     InputStream serviceAccountStream =
1355         writeServiceAccountStream(CLIENT_ID, CLIENT_EMAIL, PRIVATE_KEY_PKCS8, null);
1356 
1357     testFromStreamException(serviceAccountStream, "private_key_id");
1358   }
1359 
1360   @Test
getUriForSelfSignedJWT()1361   public void getUriForSelfSignedJWT() {
1362     assertNull(ServiceAccountCredentials.getUriForSelfSignedJWT(null));
1363 
1364     URI uri = URI.create("https://compute.googleapis.com/compute/v1/projects/");
1365     URI expected = URI.create("https://compute.googleapis.com/");
1366     assertEquals(expected, ServiceAccountCredentials.getUriForSelfSignedJWT(uri));
1367   }
1368 
1369   @Test
getUriForSelfSignedJWT_noHost()1370   public void getUriForSelfSignedJWT_noHost() {
1371     URI uri = URI.create("file:foo");
1372     URI expected = URI.create("file:foo");
1373     assertEquals(expected, ServiceAccountCredentials.getUriForSelfSignedJWT(uri));
1374   }
1375 
1376   @Test
getUriForSelfSignedJWT_forStaticAudience_returnsURI()1377   public void getUriForSelfSignedJWT_forStaticAudience_returnsURI() {
1378     URI uri = URI.create("compute.googleapis.com");
1379     URI expected = URI.create("compute.googleapis.com");
1380     assertEquals(expected, ServiceAccountCredentials.getUriForSelfSignedJWT(uri));
1381   }
1382 
1383   @Test
getRequestMetadata_setsQuotaProjectId()1384   public void getRequestMetadata_setsQuotaProjectId() throws IOException {
1385     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
1386     transportFactory.transport.addClient(CLIENT_ID, "unused-client-secret");
1387     transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN);
1388 
1389     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8);
1390     GoogleCredentials credentials =
1391         ServiceAccountCredentials.newBuilder()
1392             .setClientId(CLIENT_ID)
1393             .setClientEmail(CLIENT_EMAIL)
1394             .setPrivateKey(privateKey)
1395             .setPrivateKeyId(PRIVATE_KEY_ID)
1396             .setScopes(SCOPES)
1397             .setServiceAccountUser(USER)
1398             .setProjectId(PROJECT_ID)
1399             .setQuotaProjectId("my-quota-project-id")
1400             .setHttpTransportFactory(transportFactory)
1401             .build();
1402 
1403     Map<String, List<String>> metadata = credentials.getRequestMetadata(CALL_URI);
1404     assertTrue(metadata.containsKey("x-goog-user-project"));
1405     List<String> headerValues = metadata.get("x-goog-user-project");
1406     assertEquals(1, headerValues.size());
1407     assertEquals("my-quota-project-id", headerValues.get(0));
1408   }
1409 
1410   @Test
getRequestMetadata_noQuotaProjectId()1411   public void getRequestMetadata_noQuotaProjectId() throws IOException {
1412     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
1413     transportFactory.transport.addClient(CLIENT_ID, "unused-client-secret");
1414     transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN);
1415 
1416     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8);
1417     GoogleCredentials credentials =
1418         ServiceAccountCredentials.newBuilder()
1419             .setClientId(CLIENT_ID)
1420             .setClientEmail(CLIENT_EMAIL)
1421             .setPrivateKey(privateKey)
1422             .setPrivateKeyId(PRIVATE_KEY_ID)
1423             .setScopes(SCOPES)
1424             .setServiceAccountUser(USER)
1425             .setProjectId(PROJECT_ID)
1426             .setHttpTransportFactory(transportFactory)
1427             .build();
1428 
1429     Map<String, List<String>> metadata = credentials.getRequestMetadata(CALL_URI);
1430     assertFalse(metadata.containsKey("x-goog-user-project"));
1431   }
1432 
1433   @Test
getRequestMetadata_withCallback()1434   public void getRequestMetadata_withCallback() throws IOException {
1435     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
1436     transportFactory.transport.addClient(CLIENT_ID, "unused-client-secret");
1437     transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN);
1438 
1439     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8);
1440     GoogleCredentials credentials =
1441         ServiceAccountCredentials.newBuilder()
1442             .setClientId(CLIENT_ID)
1443             .setClientEmail(CLIENT_EMAIL)
1444             .setPrivateKey(privateKey)
1445             .setPrivateKeyId(PRIVATE_KEY_ID)
1446             .setScopes(SCOPES)
1447             .setProjectId(PROJECT_ID)
1448             .setQuotaProjectId("my-quota-project-id")
1449             .setHttpTransportFactory(transportFactory)
1450             .build();
1451 
1452     final Map<String, List<String>> plainMetadata = credentials.getRequestMetadata();
1453     final AtomicBoolean success = new AtomicBoolean(false);
1454     credentials.getRequestMetadata(
1455         null,
1456         null,
1457         new RequestMetadataCallback() {
1458           @Override
1459           public void onSuccess(Map<String, List<String>> metadata) {
1460             assertEquals(plainMetadata, metadata);
1461             success.set(true);
1462           }
1463 
1464           @Override
1465           public void onFailure(Throwable exception) {
1466             fail("Should not throw a failure.");
1467           }
1468         });
1469 
1470     assertTrue("Should have run onSuccess() callback", success.get());
1471   }
1472 
1473   @Test
getRequestMetadata_withScopes_withUniverseDomain_SelfSignedJwt()1474   public void getRequestMetadata_withScopes_withUniverseDomain_SelfSignedJwt() throws IOException {
1475     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
1476     transportFactory.transport.addClient(CLIENT_ID, "unused-client-secret");
1477     transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN);
1478 
1479     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8);
1480     GoogleCredentials credentials =
1481         ServiceAccountCredentials.newBuilder()
1482             .setClientId(CLIENT_ID)
1483             .setClientEmail(CLIENT_EMAIL)
1484             .setPrivateKey(privateKey)
1485             .setPrivateKeyId(PRIVATE_KEY_ID)
1486             .setScopes(SCOPES)
1487             .setProjectId(PROJECT_ID)
1488             .setHttpTransportFactory(transportFactory)
1489             .setUniverseDomain("foo.bar")
1490             .build();
1491 
1492     final Map<String, List<String>> plainMetadata = credentials.getRequestMetadata();
1493     final AtomicBoolean success = new AtomicBoolean(false);
1494     credentials.getRequestMetadata(
1495         null,
1496         null,
1497         new RequestMetadataCallback() {
1498           @Override
1499           public void onSuccess(Map<String, List<String>> metadata) {
1500             assertEquals(plainMetadata, metadata);
1501             success.set(true);
1502           }
1503 
1504           @Override
1505           public void onFailure(Throwable exception) {
1506             fail("Should not throw a failure.");
1507           }
1508         });
1509 
1510     assertTrue("Should have run onSuccess() callback", success.get());
1511   }
1512 
1513   @Test
getRequestMetadata_withScopes_selfSignedJWT()1514   public void getRequestMetadata_withScopes_selfSignedJWT() throws IOException {
1515     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8);
1516     GoogleCredentials credentials =
1517         ServiceAccountCredentials.newBuilder()
1518             .setClientId(CLIENT_ID)
1519             .setClientEmail(CLIENT_EMAIL)
1520             .setPrivateKey(privateKey)
1521             .setPrivateKeyId(PRIVATE_KEY_ID)
1522             .setScopes(SCOPES)
1523             .setProjectId(PROJECT_ID)
1524             .setHttpTransportFactory(new MockTokenServerTransportFactory())
1525             .setUseJwtAccessWithScope(true)
1526             .build();
1527 
1528     Map<String, List<String>> metadata = credentials.getRequestMetadata(CALL_URI);
1529     assertNotNull(((ServiceAccountCredentials) credentials).getSelfSignedJwtCredentialsWithScope());
1530     verifyJwtAccess(metadata, "dummy.scope");
1531   }
1532 
1533   @Test
refreshAccessToken_withDomainDelegation_selfSignedJWT_disabled()1534   public void refreshAccessToken_withDomainDelegation_selfSignedJWT_disabled() throws IOException {
1535     final String accessToken1 = "1/MkSJoj1xsli0AccessToken_NKPY2";
1536     final String accessToken2 = "2/MkSJoj1xsli0AccessToken_NKPY2";
1537     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
1538     MockTokenServerTransport transport = transportFactory.transport;
1539     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8);
1540     GoogleCredentials credentials =
1541         ServiceAccountCredentials.newBuilder()
1542             .setClientId(CLIENT_ID)
1543             .setClientEmail(CLIENT_EMAIL)
1544             .setPrivateKey(privateKey)
1545             .setPrivateKeyId(PRIVATE_KEY_ID)
1546             .setScopes(SCOPES)
1547             .setServiceAccountUser(USER)
1548             .setProjectId(PROJECT_ID)
1549             .setHttpTransportFactory(transportFactory)
1550             .setUseJwtAccessWithScope(true)
1551             .build();
1552 
1553     transport.addServiceAccount(CLIENT_EMAIL, accessToken1);
1554     Map<String, List<String>> metadata = credentials.getRequestMetadata(CALL_URI);
1555     TestUtils.assertContainsBearerToken(metadata, accessToken1);
1556 
1557     try {
1558       verifyJwtAccess(metadata, "dummy.scope");
1559       fail("jwt access should fail with ServiceAccountUser");
1560     } catch (Exception ex) {
1561       // expected
1562     }
1563 
1564     transport.addServiceAccount(CLIENT_EMAIL, accessToken2);
1565     credentials.refresh();
1566     TestUtils.assertContainsBearerToken(credentials.getRequestMetadata(CALL_URI), accessToken2);
1567   }
1568 
1569   @Test
getRequestMetadata_withAudience_selfSignedJWT()1570   public void getRequestMetadata_withAudience_selfSignedJWT() throws IOException {
1571     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8);
1572     GoogleCredentials credentials =
1573         ServiceAccountCredentials.newBuilder()
1574             .setClientId(CLIENT_ID)
1575             .setClientEmail(CLIENT_EMAIL)
1576             .setPrivateKey(privateKey)
1577             .setPrivateKeyId(PRIVATE_KEY_ID)
1578             .setProjectId(PROJECT_ID)
1579             .setHttpTransportFactory(new MockTokenServerTransportFactory())
1580             .build();
1581 
1582     Map<String, List<String>> metadata = credentials.getRequestMetadata(CALL_URI);
1583     assertNull(((ServiceAccountCredentials) credentials).getSelfSignedJwtCredentialsWithScope());
1584     verifyJwtAccess(metadata, null);
1585   }
1586 
1587   @Test
getRequestMetadata_withDefaultScopes_selfSignedJWT()1588   public void getRequestMetadata_withDefaultScopes_selfSignedJWT() throws IOException {
1589     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8);
1590     GoogleCredentials credentials =
1591         ServiceAccountCredentials.newBuilder()
1592             .setClientId(CLIENT_ID)
1593             .setClientEmail(CLIENT_EMAIL)
1594             .setPrivateKey(privateKey)
1595             .setPrivateKeyId(PRIVATE_KEY_ID)
1596             .setScopes(null, SCOPES)
1597             .setProjectId(PROJECT_ID)
1598             .setHttpTransportFactory(new MockTokenServerTransportFactory())
1599             .setUseJwtAccessWithScope(true)
1600             .build();
1601 
1602     Map<String, List<String>> metadata = credentials.getRequestMetadata(null);
1603     verifyJwtAccess(metadata, "dummy.scope");
1604   }
1605 
1606   @Test
getRequestMetadataWithCallback_selfSignedJWT()1607   public void getRequestMetadataWithCallback_selfSignedJWT() throws IOException {
1608     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8);
1609     GoogleCredentials credentials =
1610         ServiceAccountCredentials.newBuilder()
1611             .setClientId(CLIENT_ID)
1612             .setClientEmail(CLIENT_EMAIL)
1613             .setPrivateKey(privateKey)
1614             .setPrivateKeyId(PRIVATE_KEY_ID)
1615             .setProjectId(PROJECT_ID)
1616             .setQuotaProjectId("my-quota-project-id")
1617             .setHttpTransportFactory(new MockTokenServerTransportFactory())
1618             .setUseJwtAccessWithScope(true)
1619             .setScopes(SCOPES)
1620             .build();
1621 
1622     final AtomicBoolean success = new AtomicBoolean(false);
1623     credentials.getRequestMetadata(
1624         CALL_URI,
1625         null,
1626         new RequestMetadataCallback() {
1627           @Override
1628           public void onSuccess(Map<String, List<String>> metadata) {
1629             try {
1630               verifyJwtAccess(metadata, "dummy.scope");
1631             } catch (IOException e) {
1632               fail("Should not throw a failure");
1633             }
1634             success.set(true);
1635           }
1636 
1637           @Override
1638           public void onFailure(Throwable exception) {
1639             fail("Should not throw a failure.");
1640           }
1641         });
1642 
1643     assertTrue("Should have run onSuccess() callback", success.get());
1644   }
1645 
verifyJwtAccess(Map<String, List<String>> metadata, String expectedScopeClaim)1646   private void verifyJwtAccess(Map<String, List<String>> metadata, String expectedScopeClaim)
1647       throws IOException {
1648     assertNotNull(metadata);
1649     List<String> authorizations = metadata.get(AuthHttpConstants.AUTHORIZATION);
1650     assertNotNull("Authorization headers not found", authorizations);
1651     String assertion = null;
1652     for (String authorization : authorizations) {
1653       if (authorization.startsWith(JWT_ACCESS_PREFIX)) {
1654         assertNull("Multiple bearer assertions found", assertion);
1655         assertion = authorization.substring(JWT_ACCESS_PREFIX.length());
1656       }
1657     }
1658     assertNotNull("Bearer assertion not found", assertion);
1659     JsonWebSignature signature =
1660         JsonWebSignature.parse(GsonFactory.getDefaultInstance(), assertion);
1661     assertEquals(CLIENT_EMAIL, signature.getPayload().getIssuer());
1662     assertEquals(CLIENT_EMAIL, signature.getPayload().getSubject());
1663     if (expectedScopeClaim != null) {
1664       assertEquals(expectedScopeClaim, signature.getPayload().get("scope"));
1665       assertFalse(signature.getPayload().containsKey("aud"));
1666     } else {
1667       assertEquals(JWT_AUDIENCE, signature.getPayload().getAudience());
1668       assertFalse(signature.getPayload().containsKey("scope"));
1669     }
1670     assertEquals(PRIVATE_KEY_ID, signature.getHeader().getKeyId());
1671   }
1672 
writeServiceAccountJson( String projectId, String quotaProjectId, String universeDomain)1673   static GenericJson writeServiceAccountJson(
1674       String projectId, String quotaProjectId, String universeDomain) {
1675     return writeServiceAccountJson(
1676         CLIENT_ID,
1677         CLIENT_EMAIL,
1678         PRIVATE_KEY_PKCS8,
1679         PRIVATE_KEY_ID,
1680         projectId,
1681         quotaProjectId,
1682         universeDomain);
1683   }
1684 
writeServiceAccountJson( String clientId, String clientEmail, String privateKeyPkcs8, String privateKeyId, String projectId, String quotaProjectId, String universeDomain)1685   static GenericJson writeServiceAccountJson(
1686       String clientId,
1687       String clientEmail,
1688       String privateKeyPkcs8,
1689       String privateKeyId,
1690       String projectId,
1691       String quotaProjectId,
1692       String universeDomain) {
1693     GenericJson json = new GenericJson();
1694     if (clientId != null) {
1695       json.put("client_id", clientId);
1696     }
1697     if (clientEmail != null) {
1698       json.put("client_email", clientEmail);
1699     }
1700     if (privateKeyPkcs8 != null) {
1701       json.put("private_key", privateKeyPkcs8);
1702     }
1703     if (privateKeyId != null) {
1704       json.put("private_key_id", privateKeyId);
1705     }
1706     if (projectId != null) {
1707       json.put("project_id", projectId);
1708     }
1709     if (quotaProjectId != null) {
1710       json.put("quota_project_id", quotaProjectId);
1711     }
1712     if (universeDomain != null) {
1713       json.put("universe_domain", universeDomain);
1714     }
1715     json.put("type", GoogleCredentials.SERVICE_ACCOUNT_FILE_TYPE);
1716     return json;
1717   }
1718 
writeServiceAccountStream( String clientId, String clientEmail, String privateKeyPkcs8, String privateKeyId)1719   static InputStream writeServiceAccountStream(
1720       String clientId, String clientEmail, String privateKeyPkcs8, String privateKeyId)
1721       throws IOException {
1722     return writeServiceAccountStream(clientId, clientEmail, privateKeyPkcs8, privateKeyId, null);
1723   }
1724 
writeServiceAccountStream( String clientId, String clientEmail, String privateKeyPkcs8, String privateKeyId, String universeDomain)1725   static InputStream writeServiceAccountStream(
1726       String clientId,
1727       String clientEmail,
1728       String privateKeyPkcs8,
1729       String privateKeyId,
1730       String universeDomain)
1731       throws IOException {
1732     GenericJson json =
1733         writeServiceAccountJson(
1734             clientId, clientEmail, privateKeyPkcs8, privateKeyId, null, null, universeDomain);
1735     return TestUtils.jsonToInputStream(json);
1736   }
1737 
testFromStreamException(InputStream stream, String expectedMessageContent)1738   private static void testFromStreamException(InputStream stream, String expectedMessageContent) {
1739     try {
1740       ServiceAccountCredentials.fromStream(stream, DUMMY_TRANSPORT_FACTORY);
1741       fail(
1742           String.format(
1743               "Should throw exception with message containing '%s'", expectedMessageContent));
1744     } catch (IOException expected) {
1745       assertTrue(expected.getMessage().contains(expectedMessageContent));
1746     }
1747   }
1748 }
1749