• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.assertNotNull;
38 import static org.junit.Assert.assertNull;
39 import static org.junit.Assert.assertSame;
40 import static org.junit.Assert.assertTrue;
41 import static org.junit.Assert.fail;
42 
43 import com.google.api.client.http.HttpStatusCodes;
44 import com.google.api.client.http.HttpTransport;
45 import com.google.api.client.json.GenericJson;
46 import com.google.api.client.json.JsonFactory;
47 import com.google.api.client.json.JsonGenerator;
48 import com.google.api.client.json.gson.GsonFactory;
49 import com.google.api.client.json.webtoken.JsonWebToken.Payload;
50 import com.google.api.client.testing.http.MockLowLevelHttpRequest;
51 import com.google.api.client.util.Clock;
52 import com.google.auth.ServiceAccountSigner.SigningException;
53 import com.google.auth.TestUtils;
54 import com.google.auth.http.HttpTransportFactory;
55 import com.google.common.collect.ImmutableList;
56 import com.google.common.collect.ImmutableSet;
57 import java.io.ByteArrayOutputStream;
58 import java.io.IOException;
59 import java.io.InputStream;
60 import java.nio.charset.Charset;
61 import java.security.PrivateKey;
62 import java.text.DateFormat;
63 import java.text.SimpleDateFormat;
64 import java.time.temporal.ChronoUnit;
65 import java.util.ArrayList;
66 import java.util.Arrays;
67 import java.util.Calendar;
68 import java.util.Date;
69 import java.util.List;
70 import java.util.Map;
71 import java.util.TimeZone;
72 import org.junit.Before;
73 import org.junit.Test;
74 import org.junit.runner.RunWith;
75 import org.junit.runners.JUnit4;
76 
77 /** Test case for {@link ImpersonatedCredentials}. */
78 @RunWith(JUnit4.class)
79 public class ImpersonatedCredentialsTest extends BaseSerializationTest {
80 
81   public static final String SA_CLIENT_EMAIL =
82       "36680232662-vrd7ji19qe3nelgchd0ah2csanun6bnr@developer.gserviceaccount.com";
83   private static final String SA_PRIVATE_KEY_ID = "d84a4fefcf50791d4a90f2d7af17469d6282df9d";
84   static final String SA_PRIVATE_KEY_PKCS8 =
85       "-----BEGIN PRIVATE KEY-----\n"
86           + "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALX0PQoe1igW12i"
87           + "kv1bN/r9lN749y2ijmbc/mFHPyS3hNTyOCjDvBbXYbDhQJzWVUikh4mvGBA07qTj79Xc3yBDfKP2IeyYQIFe0t0"
88           + "zkd7R9Zdn98Y2rIQC47aAbDfubtkU1U72t4zL11kHvoa0/RuFZjncvlr42X7be7lYh4p3NAgMBAAECgYASk5wDw"
89           + "4Az2ZkmeuN6Fk/y9H+Lcb2pskJIXjrL533vrDWGOC48LrsThMQPv8cxBky8HFSEklPpkfTF95tpD43iVwJRB/Gr"
90           + "CtGTw65IfJ4/tI09h6zGc4yqvIo1cHX/LQ+SxKLGyir/dQM925rGt/VojxY5ryJR7GLbCzxPnJm/oQJBANwOCO6"
91           + "D2hy1LQYJhXh7O+RLtA/tSnT1xyMQsGT+uUCMiKS2bSKx2wxo9k7h3OegNJIu1q6nZ6AbxDK8H3+d0dUCQQDTrP"
92           + "SXagBxzp8PecbaCHjzNRSQE2in81qYnrAFNB4o3DpHyMMY6s5ALLeHKscEWnqP8Ur6X4PvzZecCWU9BKAZAkAut"
93           + "LPknAuxSCsUOvUfS1i87ex77Ot+w6POp34pEX+UWb+u5iFn2cQacDTHLV1LtE80L8jVLSbrbrlH43H0DjU5AkEA"
94           + "gidhycxS86dxpEljnOMCw8CKoUBd5I880IUahEiUltk7OLJYS/Ts1wbn3kPOVX3wyJs8WBDtBkFrDHW2ezth2QJ"
95           + "ADj3e1YhMVdjJW5jqwlD/VNddGjgzyunmiZg0uOXsHXbytYmsA545S8KRQFaJKFXYYFo2kOjqOiC1T2cAzMDjCQ"
96           + "==\n-----END PRIVATE KEY-----\n";
97 
98   // Id Token provided by the default IAM API that does not include the "email" claim
99   public static final String STANDARD_ID_TOKEN =
100       "eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmMzc1ODkwOGI3OTIy"
101           + "OTNhZDk3N2EwYjk5MWQ5OGE3N2Y0ZWVlY2QiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL2Zvby5iYXIi"
102           + "LCJhenAiOiIxMDIxMDE1NTA4MzQyMDA3MDg1NjgiLCJleHAiOjE1NjQ1MzI5NzIsImlhdCI6MTU2NDUyOTM3Miw"
103           + "iaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTAyMTAxNTUwODM0MjAwNzA4NTY4In"
104           + "0.redacted";
105 
106   // Id Token provided by the default IAM API that includes the "email" claim
107   public static final String TOKEN_WITH_EMAIL =
108       "eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmMzc1ODkwOGI3OTIy"
109           + "OTNhZDk3N2EwYjk5MWQ5OGE3N2Y0ZWVlY2QiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL2Zvby5iYXIi"
110           + "LCJhenAiOiIxMDIxMDE1NTA4MzQyMDA3MDg1NjgiLCJlbWFpbCI6ImltcGVyc29uYXRlZC1hY2NvdW50QGZhYmx"
111           + "lZC1yYXktMTA0MTE3LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cC"
112           + "I6MTU2NDUzMzA0MiwiaWF0IjoxNTY0NTI5NDQyLCJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iL"
113           + "CJzdWIiOiIxMDIxMDE1NTA4MzQyMDA3MDg1NjgifQ.redacted";
114   public static final String ACCESS_TOKEN = "1/MkSJoj1xsli0AccessToken_NKPY2";
115 
116   private static final ImmutableSet<String> IMMUTABLE_SCOPES_SET =
117       ImmutableSet.of("scope1", "scope2");
118   private static final String PROJECT_ID = "project-id";
119   public static final String IMPERSONATED_CLIENT_EMAIL =
120       "impersonated-account@iam.gserviceaccount.com";
121   private static final List<String> IMMUTABLE_SCOPES_LIST = ImmutableList.of("scope1", "scope2");
122   private static final int VALID_LIFETIME = 300;
123   private static final int INVALID_LIFETIME = 43210;
124   private static JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();
125 
126   private static final String RFC3339 = "yyyy-MM-dd'T'HH:mm:ssX";
127   public static final String DEFAULT_IMPERSONATION_URL =
128       "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/"
129           + IMPERSONATED_CLIENT_EMAIL
130           + ":generateAccessToken";
131   public static final String IMPERSONATION_URL =
132       "https://us-east1-iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/"
133           + IMPERSONATED_CLIENT_EMAIL
134           + ":generateAccessToken";
135   private static final String USER_ACCOUNT_CLIENT_ID =
136       "76408650-6qr441hur.apps.googleusercontent.com";
137   private static final String USER_ACCOUNT_CLIENT_SECRET = "d-F499q74hFpdHD0T5";
138   public static final String QUOTA_PROJECT_ID = "quota-project-id";
139   private static final String REFRESH_TOKEN = "dasdfasdffa4ffdfadgyjirasdfadsft";
140   public static final List<String> DELEGATES =
141       Arrays.asList("sa1@developer.gserviceaccount.com", "sa2@developer.gserviceaccount.com");
142 
143   static class MockIAMCredentialsServiceTransportFactory implements HttpTransportFactory {
144 
145     MockIAMCredentialsServiceTransport transport = new MockIAMCredentialsServiceTransport();
146 
147     @Override
create()148     public HttpTransport create() {
149       return transport;
150     }
151   }
152 
153   private GoogleCredentials sourceCredentials;
154   private MockIAMCredentialsServiceTransportFactory mockTransportFactory;
155 
156   @Before
setup()157   public void setup() throws IOException {
158     sourceCredentials = getSourceCredentials();
159     mockTransportFactory = new MockIAMCredentialsServiceTransportFactory();
160   }
161 
getSourceCredentials()162   private GoogleCredentials getSourceCredentials() throws IOException {
163     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
164     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
165     ServiceAccountCredentials sourceCredentials =
166         ServiceAccountCredentials.newBuilder()
167             .setClientEmail(SA_CLIENT_EMAIL)
168             .setPrivateKey(privateKey)
169             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
170             .setScopes(IMMUTABLE_SCOPES_LIST)
171             .setProjectId(PROJECT_ID)
172             .setHttpTransportFactory(transportFactory)
173             .build();
174     transportFactory.transport.addServiceAccount(SA_CLIENT_EMAIL, ACCESS_TOKEN);
175 
176     return sourceCredentials;
177   }
178 
179   @Test()
fromJson_userAsSource_WithQuotaProjectId()180   public void fromJson_userAsSource_WithQuotaProjectId() throws IOException {
181     GenericJson json =
182         buildImpersonationCredentialsJson(
183             IMPERSONATION_URL,
184             DELEGATES,
185             QUOTA_PROJECT_ID,
186             USER_ACCOUNT_CLIENT_ID,
187             USER_ACCOUNT_CLIENT_SECRET,
188             REFRESH_TOKEN);
189     ImpersonatedCredentials credentials =
190         ImpersonatedCredentials.fromJson(json, mockTransportFactory);
191     assertEquals(IMPERSONATED_CLIENT_EMAIL, credentials.getAccount());
192     assertEquals(IMPERSONATION_URL, credentials.getIamEndpointOverride());
193     assertEquals(QUOTA_PROJECT_ID, credentials.getQuotaProjectId());
194     assertEquals(DELEGATES, credentials.getDelegates());
195     assertEquals(new ArrayList<String>(), credentials.getScopes());
196     assertEquals(3600, credentials.getLifetime());
197     GoogleCredentials sourceCredentials = credentials.getSourceCredentials();
198     assertTrue(sourceCredentials instanceof UserCredentials);
199   }
200 
201   @Test()
fromJson_userAsSource_WithoutQuotaProjectId()202   public void fromJson_userAsSource_WithoutQuotaProjectId() throws IOException {
203     GenericJson json =
204         buildImpersonationCredentialsJson(
205             IMPERSONATION_URL,
206             DELEGATES,
207             null,
208             USER_ACCOUNT_CLIENT_ID,
209             USER_ACCOUNT_CLIENT_SECRET,
210             REFRESH_TOKEN);
211     ImpersonatedCredentials credentials =
212         ImpersonatedCredentials.fromJson(json, mockTransportFactory);
213     assertEquals(IMPERSONATED_CLIENT_EMAIL, credentials.getAccount());
214     assertEquals(IMPERSONATION_URL, credentials.getIamEndpointOverride());
215     assertNull(credentials.getQuotaProjectId());
216     assertEquals(DELEGATES, credentials.getDelegates());
217     assertEquals(new ArrayList<String>(), credentials.getScopes());
218     assertEquals(3600, credentials.getLifetime());
219     GoogleCredentials sourceCredentials = credentials.getSourceCredentials();
220     assertTrue(sourceCredentials instanceof UserCredentials);
221   }
222 
223   @Test()
fromJson_userAsSource_MissingDelegatesField()224   public void fromJson_userAsSource_MissingDelegatesField() throws IOException {
225     GenericJson json =
226         buildImpersonationCredentialsJson(
227             IMPERSONATION_URL,
228             DELEGATES,
229             null,
230             USER_ACCOUNT_CLIENT_ID,
231             USER_ACCOUNT_CLIENT_SECRET,
232             REFRESH_TOKEN);
233     json.remove("delegates");
234     ImpersonatedCredentials credentials =
235         ImpersonatedCredentials.fromJson(json, mockTransportFactory);
236     assertEquals(IMPERSONATED_CLIENT_EMAIL, credentials.getAccount());
237     assertEquals(IMPERSONATION_URL, credentials.getIamEndpointOverride());
238     assertNull(credentials.getQuotaProjectId());
239     assertEquals(new ArrayList<String>(), credentials.getDelegates());
240     assertEquals(new ArrayList<String>(), credentials.getScopes());
241     assertEquals(3600, credentials.getLifetime());
242     GoogleCredentials sourceCredentials = credentials.getSourceCredentials();
243     assertTrue(sourceCredentials instanceof UserCredentials);
244   }
245 
246   @Test()
fromJson_ServiceAccountAsSource()247   public void fromJson_ServiceAccountAsSource() throws IOException {
248     GenericJson json =
249         buildImpersonationCredentialsJson(IMPERSONATION_URL, DELEGATES, QUOTA_PROJECT_ID);
250     ImpersonatedCredentials credentials =
251         ImpersonatedCredentials.fromJson(json, mockTransportFactory);
252     assertEquals(IMPERSONATED_CLIENT_EMAIL, credentials.getAccount());
253     assertEquals(IMPERSONATION_URL, credentials.getIamEndpointOverride());
254     assertEquals(QUOTA_PROJECT_ID, credentials.getQuotaProjectId());
255     assertEquals(DELEGATES, credentials.getDelegates());
256     assertEquals(new ArrayList<String>(), credentials.getScopes());
257     assertEquals(3600, credentials.getLifetime());
258     GoogleCredentials sourceCredentials = credentials.getSourceCredentials();
259     assertTrue(sourceCredentials instanceof ServiceAccountCredentials);
260   }
261 
262   @Test()
fromJson_InvalidFormat()263   public void fromJson_InvalidFormat() throws IOException {
264     GenericJson json = buildInvalidCredentialsJson();
265     try {
266       ImpersonatedCredentials.fromJson(json, mockTransportFactory);
267       fail("An exception should be thrown.");
268     } catch (CredentialFormatException e) {
269       assertEquals("An invalid input stream was provided.", e.getMessage());
270     }
271   }
272 
273   @Test()
createScopedRequired_True()274   public void createScopedRequired_True() {
275     ImpersonatedCredentials targetCredentials =
276         ImpersonatedCredentials.create(
277             sourceCredentials,
278             IMPERSONATED_CLIENT_EMAIL,
279             null,
280             new ArrayList<String>(),
281             VALID_LIFETIME,
282             mockTransportFactory);
283     assertTrue(targetCredentials.createScopedRequired());
284   }
285 
286   @Test()
createScopedRequired_False()287   public void createScopedRequired_False() {
288     ImpersonatedCredentials targetCredentials =
289         ImpersonatedCredentials.create(
290             sourceCredentials,
291             IMPERSONATED_CLIENT_EMAIL,
292             null,
293             IMMUTABLE_SCOPES_LIST,
294             VALID_LIFETIME,
295             mockTransportFactory);
296     assertFalse(targetCredentials.createScopedRequired());
297   }
298 
299   @Test
createScoped()300   public void createScoped() {
301     ImpersonatedCredentials targetCredentials =
302         ImpersonatedCredentials.create(
303             sourceCredentials,
304             IMPERSONATED_CLIENT_EMAIL,
305             DELEGATES,
306             IMMUTABLE_SCOPES_LIST,
307             VALID_LIFETIME,
308             mockTransportFactory,
309             QUOTA_PROJECT_ID);
310 
311     ImpersonatedCredentials scoped_credentials =
312         (ImpersonatedCredentials) targetCredentials.createScoped(IMMUTABLE_SCOPES_LIST);
313     assertEquals(targetCredentials.getAccount(), scoped_credentials.getAccount());
314     assertEquals(targetCredentials.getDelegates(), scoped_credentials.getDelegates());
315     assertEquals(targetCredentials.getLifetime(), scoped_credentials.getLifetime());
316     assertEquals(
317         targetCredentials.getSourceCredentials(), scoped_credentials.getSourceCredentials());
318     assertEquals(targetCredentials.getQuotaProjectId(), scoped_credentials.getQuotaProjectId());
319     assertEquals(Arrays.asList("scope1", "scope2"), scoped_credentials.getScopes());
320   }
321 
322   @Test
createScopedWithImmutableScopes()323   public void createScopedWithImmutableScopes() {
324     ImpersonatedCredentials targetCredentials =
325         ImpersonatedCredentials.create(
326             sourceCredentials,
327             IMPERSONATED_CLIENT_EMAIL,
328             DELEGATES,
329             IMMUTABLE_SCOPES_LIST,
330             VALID_LIFETIME,
331             mockTransportFactory,
332             QUOTA_PROJECT_ID);
333 
334     ImpersonatedCredentials scoped_credentials =
335         (ImpersonatedCredentials) targetCredentials.createScoped(IMMUTABLE_SCOPES_SET);
336     assertEquals(targetCredentials.getAccount(), scoped_credentials.getAccount());
337     assertEquals(targetCredentials.getDelegates(), scoped_credentials.getDelegates());
338     assertEquals(targetCredentials.getLifetime(), scoped_credentials.getLifetime());
339     assertEquals(
340         targetCredentials.getSourceCredentials(), scoped_credentials.getSourceCredentials());
341     assertEquals(targetCredentials.getQuotaProjectId(), scoped_credentials.getQuotaProjectId());
342     assertEquals(Arrays.asList("scope1", "scope2"), scoped_credentials.getScopes());
343   }
344 
345   @Test
createScopedWithIamEndpointOverride()346   public void createScopedWithIamEndpointOverride() {
347     ImpersonatedCredentials targetCredentials =
348         ImpersonatedCredentials.create(
349             sourceCredentials,
350             IMPERSONATED_CLIENT_EMAIL,
351             DELEGATES,
352             IMMUTABLE_SCOPES_LIST,
353             VALID_LIFETIME,
354             mockTransportFactory,
355             QUOTA_PROJECT_ID,
356             IMPERSONATION_URL);
357 
358     ImpersonatedCredentials scoped_credentials =
359         (ImpersonatedCredentials) targetCredentials.createScoped(IMMUTABLE_SCOPES_SET);
360     assertEquals(
361         targetCredentials.getIamEndpointOverride(), scoped_credentials.getIamEndpointOverride());
362   }
363 
364   @Test
refreshAccessToken_unauthorized()365   public void refreshAccessToken_unauthorized() throws IOException {
366 
367     String expectedMessage = "The caller does not have permission";
368     mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
369     mockTransportFactory.transport.setTokenResponseErrorCode(
370         HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
371     mockTransportFactory.transport.setTokenResponseErrorContent(
372         generateErrorJson(
373             HttpStatusCodes.STATUS_CODE_UNAUTHORIZED, expectedMessage, "global", "forbidden"));
374     ImpersonatedCredentials targetCredentials =
375         ImpersonatedCredentials.create(
376             sourceCredentials,
377             IMPERSONATED_CLIENT_EMAIL,
378             null,
379             IMMUTABLE_SCOPES_LIST,
380             VALID_LIFETIME,
381             mockTransportFactory);
382 
383     try {
384       targetCredentials.refreshAccessToken().getTokenValue();
385       fail(String.format("Should throw exception with message containing '%s'", expectedMessage));
386     } catch (IOException expected) {
387       assertEquals("Error requesting access token", expected.getMessage());
388       assertTrue(expected.getCause().getMessage().contains(expectedMessage));
389     }
390   }
391 
392   @Test()
refreshAccessToken_malformedTarget()393   public void refreshAccessToken_malformedTarget() throws IOException {
394 
395     String invalidTargetEmail = "foo";
396     String expectedMessage = "Request contains an invalid argument";
397     mockTransportFactory.transport.setTargetPrincipal(invalidTargetEmail);
398     mockTransportFactory.transport.setTokenResponseErrorCode(
399         HttpStatusCodes.STATUS_CODE_BAD_REQUEST);
400     mockTransportFactory.transport.setTokenResponseErrorContent(
401         generateErrorJson(
402             HttpStatusCodes.STATUS_CODE_BAD_REQUEST, expectedMessage, "global", "badRequest"));
403     ImpersonatedCredentials targetCredentials =
404         ImpersonatedCredentials.create(
405             sourceCredentials,
406             invalidTargetEmail,
407             null,
408             IMMUTABLE_SCOPES_LIST,
409             VALID_LIFETIME,
410             mockTransportFactory);
411 
412     try {
413       targetCredentials.refreshAccessToken().getTokenValue();
414       fail(String.format("Should throw exception with message containing '%s'", expectedMessage));
415     } catch (IOException expected) {
416       assertEquals("Error requesting access token", expected.getMessage());
417       assertTrue(expected.getCause().getMessage().contains(expectedMessage));
418     }
419   }
420 
421   @Test()
credential_with_zero_lifetime()422   public void credential_with_zero_lifetime() throws IllegalStateException {
423     ImpersonatedCredentials targetCredentials =
424         ImpersonatedCredentials.create(
425             sourceCredentials, IMPERSONATED_CLIENT_EMAIL, null, IMMUTABLE_SCOPES_LIST, 0);
426     assertEquals(3600, targetCredentials.getLifetime());
427   }
428 
429   @Test()
credential_with_invalid_lifetime()430   public void credential_with_invalid_lifetime() throws IOException, IllegalStateException {
431 
432     try {
433       ImpersonatedCredentials targetCredentials =
434           ImpersonatedCredentials.create(
435               sourceCredentials,
436               IMPERSONATED_CLIENT_EMAIL,
437               null,
438               IMMUTABLE_SCOPES_LIST,
439               INVALID_LIFETIME);
440       targetCredentials.refreshAccessToken().getTokenValue();
441       fail(
442           String.format(
443               "Should throw exception with message containing '%s'",
444               "lifetime must be less than or equal to 43200"));
445     } catch (IllegalStateException expected) {
446       assertTrue(expected.getMessage().contains("lifetime must be less than or equal to 43200"));
447     }
448   }
449 
450   @Test()
credential_with_invalid_scope()451   public void credential_with_invalid_scope() throws IOException, IllegalStateException {
452 
453     try {
454       ImpersonatedCredentials targetCredentials =
455           ImpersonatedCredentials.create(
456               sourceCredentials, IMPERSONATED_CLIENT_EMAIL, null, null, VALID_LIFETIME);
457       targetCredentials.refreshAccessToken().getTokenValue();
458       fail(
459           String.format(
460               "Should throw exception with message containing '%s'", "Scopes cannot be null"));
461     } catch (IllegalStateException expected) {
462       assertTrue(expected.getMessage().contains("Scopes cannot be null"));
463     }
464   }
465 
466   @Test()
refreshAccessToken_success()467   public void refreshAccessToken_success() throws IOException, IllegalStateException {
468 
469     mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
470     mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN);
471     mockTransportFactory.transport.setExpireTime(getDefaultExpireTime());
472     ImpersonatedCredentials targetCredentials =
473         ImpersonatedCredentials.create(
474             sourceCredentials,
475             IMPERSONATED_CLIENT_EMAIL,
476             null,
477             IMMUTABLE_SCOPES_LIST,
478             VALID_LIFETIME,
479             mockTransportFactory);
480 
481     assertEquals(ACCESS_TOKEN, targetCredentials.refreshAccessToken().getTokenValue());
482     assertEquals(DEFAULT_IMPERSONATION_URL, mockTransportFactory.transport.getRequest().getUrl());
483   }
484 
485   @Test
refreshAccessToken_endpointOverride()486   public void refreshAccessToken_endpointOverride() throws IOException, IllegalStateException {
487     mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
488     mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN);
489     mockTransportFactory.transport.setExpireTime(getDefaultExpireTime());
490     mockTransportFactory.transport.setAccessTokenEndpoint(IMPERSONATION_URL);
491 
492     ImpersonatedCredentials targetCredentials =
493         ImpersonatedCredentials.create(
494             sourceCredentials,
495             IMPERSONATED_CLIENT_EMAIL,
496             null,
497             IMMUTABLE_SCOPES_LIST,
498             VALID_LIFETIME,
499             mockTransportFactory,
500             QUOTA_PROJECT_ID,
501             IMPERSONATION_URL);
502 
503     assertEquals(ACCESS_TOKEN, targetCredentials.refreshAccessToken().getTokenValue());
504     assertEquals(IMPERSONATION_URL, mockTransportFactory.transport.getRequest().getUrl());
505   }
506 
507   @Test()
getRequestMetadata_withQuotaProjectId()508   public void getRequestMetadata_withQuotaProjectId() throws IOException, IllegalStateException {
509 
510     mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
511     mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN);
512     mockTransportFactory.transport.setExpireTime(getDefaultExpireTime());
513     ImpersonatedCredentials targetCredentials =
514         ImpersonatedCredentials.create(
515             sourceCredentials,
516             IMPERSONATED_CLIENT_EMAIL,
517             null,
518             IMMUTABLE_SCOPES_LIST,
519             VALID_LIFETIME,
520             mockTransportFactory,
521             QUOTA_PROJECT_ID);
522 
523     Map<String, List<String>> metadata = targetCredentials.getRequestMetadata();
524     assertTrue(metadata.containsKey("x-goog-user-project"));
525     List<String> headerValues = metadata.get("x-goog-user-project");
526     assertEquals(1, headerValues.size());
527     assertEquals(QUOTA_PROJECT_ID, headerValues.get(0));
528   }
529 
530   @Test()
getRequestMetadata_withoutQuotaProjectId()531   public void getRequestMetadata_withoutQuotaProjectId() throws IOException, IllegalStateException {
532 
533     mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
534     mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN);
535     mockTransportFactory.transport.setExpireTime(getDefaultExpireTime());
536     ImpersonatedCredentials targetCredentials =
537         ImpersonatedCredentials.create(
538             sourceCredentials,
539             IMPERSONATED_CLIENT_EMAIL,
540             null,
541             IMMUTABLE_SCOPES_LIST,
542             VALID_LIFETIME,
543             mockTransportFactory);
544 
545     Map<String, List<String>> metadata = targetCredentials.getRequestMetadata();
546     assertFalse(metadata.containsKey("x-goog-user-project"));
547   }
548 
549   @Test()
refreshAccessToken_delegates_success()550   public void refreshAccessToken_delegates_success() throws IOException, IllegalStateException {
551 
552     mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
553     mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN);
554     mockTransportFactory.transport.setExpireTime(getDefaultExpireTime());
555     List<String> delegates = Arrays.asList("delegate-account@iam.gserviceaccount.com");
556     ImpersonatedCredentials targetCredentials =
557         ImpersonatedCredentials.create(
558             sourceCredentials,
559             IMPERSONATED_CLIENT_EMAIL,
560             delegates,
561             IMMUTABLE_SCOPES_LIST,
562             VALID_LIFETIME,
563             mockTransportFactory);
564 
565     assertEquals(ACCESS_TOKEN, targetCredentials.refreshAccessToken().getTokenValue());
566   }
567 
568   @Test
refreshAccessToken_GMT_dateParsedCorrectly()569   public void refreshAccessToken_GMT_dateParsedCorrectly()
570       throws IOException, IllegalStateException {
571     Calendar c = Calendar.getInstance();
572     c.add(Calendar.SECOND, VALID_LIFETIME);
573 
574     mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
575     mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN);
576     mockTransportFactory.transport.setExpireTime(getFormattedTime(c.getTime()));
577     ImpersonatedCredentials targetCredentials =
578         ImpersonatedCredentials.create(
579                 sourceCredentials,
580                 IMPERSONATED_CLIENT_EMAIL,
581                 null,
582                 IMMUTABLE_SCOPES_LIST,
583                 VALID_LIFETIME,
584                 mockTransportFactory)
585             .createWithCustomCalendar(
586                 // Set system timezone to GMT
587                 Calendar.getInstance(TimeZone.getTimeZone("GMT")));
588 
589     assertTrue(
590         c.getTime().toInstant().truncatedTo(ChronoUnit.SECONDS).toEpochMilli()
591             == targetCredentials.refreshAccessToken().getExpirationTimeMillis());
592   }
593 
594   @Test
refreshAccessToken_nonGMT_dateParsedCorrectly()595   public void refreshAccessToken_nonGMT_dateParsedCorrectly()
596       throws IOException, IllegalStateException {
597     Calendar c = Calendar.getInstance();
598     c.add(Calendar.SECOND, VALID_LIFETIME);
599 
600     mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
601     mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN);
602     mockTransportFactory.transport.setExpireTime(getFormattedTime(c.getTime()));
603     ImpersonatedCredentials targetCredentials =
604         ImpersonatedCredentials.create(
605                 sourceCredentials,
606                 IMPERSONATED_CLIENT_EMAIL,
607                 null,
608                 IMMUTABLE_SCOPES_LIST,
609                 VALID_LIFETIME,
610                 mockTransportFactory)
611             .createWithCustomCalendar(
612                 // Set system timezone to one different than GMT
613                 Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles")));
614 
615     assertTrue(
616         c.getTime().toInstant().truncatedTo(ChronoUnit.SECONDS).toEpochMilli()
617             == targetCredentials.refreshAccessToken().getExpirationTimeMillis());
618   }
619 
620   @Test
refreshAccessToken_invalidDate()621   public void refreshAccessToken_invalidDate() throws IllegalStateException {
622 
623     String expectedMessage = "Unparseable date";
624     mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
625     mockTransportFactory.transport.setAccessToken("foo");
626     mockTransportFactory.transport.setExpireTime("1973-09-29T15:01:23");
627     ImpersonatedCredentials targetCredentials =
628         ImpersonatedCredentials.create(
629             sourceCredentials,
630             IMPERSONATED_CLIENT_EMAIL,
631             null,
632             IMMUTABLE_SCOPES_LIST,
633             VALID_LIFETIME,
634             mockTransportFactory);
635 
636     try {
637       targetCredentials.refreshAccessToken().getTokenValue();
638       fail(String.format("Should throw exception with message containing '%s'", expectedMessage));
639     } catch (IOException expected) {
640       assertTrue(expected.getMessage().contains(expectedMessage));
641     }
642   }
643 
644   @Test
getAccount_sameAs()645   public void getAccount_sameAs() {
646     mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
647     mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN);
648     mockTransportFactory.transport.setExpireTime(getDefaultExpireTime());
649     ImpersonatedCredentials targetCredentials =
650         ImpersonatedCredentials.create(
651             sourceCredentials,
652             IMPERSONATED_CLIENT_EMAIL,
653             null,
654             IMMUTABLE_SCOPES_LIST,
655             VALID_LIFETIME,
656             mockTransportFactory);
657 
658     assertEquals(IMPERSONATED_CLIENT_EMAIL, targetCredentials.getAccount());
659   }
660 
661   @Test
sign_sameAs()662   public void sign_sameAs() {
663     mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
664     mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN);
665     mockTransportFactory.transport.setExpireTime(getDefaultExpireTime());
666     ImpersonatedCredentials targetCredentials =
667         ImpersonatedCredentials.create(
668             sourceCredentials,
669             IMPERSONATED_CLIENT_EMAIL,
670             null,
671             IMMUTABLE_SCOPES_LIST,
672             VALID_LIFETIME,
673             mockTransportFactory);
674 
675     byte[] expectedSignature = {0xD, 0xE, 0xA, 0xD};
676 
677     mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
678     mockTransportFactory.transport.setSignedBlob(expectedSignature);
679 
680     assertArrayEquals(expectedSignature, targetCredentials.sign(expectedSignature));
681   }
682 
683   @Test
sign_requestIncludesDelegates()684   public void sign_requestIncludesDelegates() throws IOException {
685     mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
686     mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN);
687     mockTransportFactory.transport.setExpireTime(getDefaultExpireTime());
688     ImpersonatedCredentials targetCredentials =
689         ImpersonatedCredentials.create(
690             sourceCredentials,
691             IMPERSONATED_CLIENT_EMAIL,
692             ImmutableList.of("delegate@example.com"),
693             IMMUTABLE_SCOPES_LIST,
694             VALID_LIFETIME,
695             mockTransportFactory);
696 
697     byte[] expectedSignature = {0xD, 0xE, 0xA, 0xD};
698 
699     mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
700     mockTransportFactory.transport.setSignedBlob(expectedSignature);
701 
702     assertArrayEquals(expectedSignature, targetCredentials.sign(expectedSignature));
703 
704     MockLowLevelHttpRequest request = mockTransportFactory.transport.getRequest();
705     GenericJson body =
706         JSON_FACTORY
707             .createJsonParser(request.getContentAsString())
708             .parseAndClose(GenericJson.class);
709     List<String> delegates = new ArrayList<>();
710     delegates.add("delegate@example.com");
711     assertEquals(delegates, body.get("delegates"));
712   }
713 
714   @Test
sign_usesSourceCredentials()715   public void sign_usesSourceCredentials() {
716     Calendar c = Calendar.getInstance();
717     c.add(Calendar.DATE, 1);
718     Date expiry = c.getTime();
719     GoogleCredentials sourceCredentials =
720         new GoogleCredentials.Builder()
721             .setAccessToken(new AccessToken("source-token", expiry))
722             .build();
723 
724     mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
725     mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN);
726     mockTransportFactory.transport.setExpireTime(getDefaultExpireTime());
727     ImpersonatedCredentials targetCredentials =
728         ImpersonatedCredentials.create(
729             sourceCredentials,
730             IMPERSONATED_CLIENT_EMAIL,
731             ImmutableList.of("delegate@example.com"),
732             IMMUTABLE_SCOPES_LIST,
733             VALID_LIFETIME,
734             mockTransportFactory);
735 
736     byte[] expectedSignature = {0xD, 0xE, 0xA, 0xD};
737 
738     mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
739     mockTransportFactory.transport.setSignedBlob(expectedSignature);
740 
741     assertArrayEquals(expectedSignature, targetCredentials.sign(expectedSignature));
742 
743     MockLowLevelHttpRequest request = mockTransportFactory.transport.getRequest();
744     assertEquals("Bearer source-token", request.getFirstHeaderValue("Authorization"));
745   }
746 
747   @Test
sign_accessDenied_throws()748   public void sign_accessDenied_throws() {
749     mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
750     mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN);
751     mockTransportFactory.transport.setExpireTime(getDefaultExpireTime());
752     ImpersonatedCredentials targetCredentials =
753         ImpersonatedCredentials.create(
754             sourceCredentials,
755             IMPERSONATED_CLIENT_EMAIL,
756             null,
757             IMMUTABLE_SCOPES_LIST,
758             VALID_LIFETIME,
759             mockTransportFactory);
760 
761     byte[] expectedSignature = {0xD, 0xE, 0xA, 0xD};
762 
763     mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
764     mockTransportFactory.transport.setSignedBlob(expectedSignature);
765     mockTransportFactory.transport.setErrorResponseCodeAndMessage(
766         HttpStatusCodes.STATUS_CODE_FORBIDDEN, "Sign Error");
767 
768     try {
769       byte[] bytes = {0xD, 0xE, 0xA, 0xD};
770       targetCredentials.sign(bytes);
771       fail("Signing should have failed");
772     } catch (SigningException e) {
773       assertEquals("Failed to sign the provided bytes", e.getMessage());
774       assertNotNull(e.getCause());
775       assertTrue(e.getCause().getMessage().contains("403"));
776     }
777   }
778 
779   @Test
sign_serverError_throws()780   public void sign_serverError_throws() {
781     mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
782     mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN);
783     mockTransportFactory.transport.setExpireTime(getDefaultExpireTime());
784     ImpersonatedCredentials targetCredentials =
785         ImpersonatedCredentials.create(
786             sourceCredentials,
787             IMPERSONATED_CLIENT_EMAIL,
788             null,
789             IMMUTABLE_SCOPES_LIST,
790             VALID_LIFETIME,
791             mockTransportFactory);
792 
793     byte[] expectedSignature = {0xD, 0xE, 0xA, 0xD};
794 
795     mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
796     mockTransportFactory.transport.setSignedBlob(expectedSignature);
797     mockTransportFactory.transport.setErrorResponseCodeAndMessage(
798         HttpStatusCodes.STATUS_CODE_SERVER_ERROR, "Sign Error");
799 
800     try {
801       byte[] bytes = {0xD, 0xE, 0xA, 0xD};
802       targetCredentials.sign(bytes);
803       fail("Signing should have failed");
804     } catch (SigningException e) {
805       assertEquals("Failed to sign the provided bytes", e.getMessage());
806       assertNotNull(e.getCause());
807       assertTrue(e.getCause().getMessage().contains("500"));
808     }
809   }
810 
811   @Test
idTokenWithAudience_sameAs()812   public void idTokenWithAudience_sameAs() throws IOException {
813     mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
814     mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN);
815     mockTransportFactory.transport.setExpireTime(getDefaultExpireTime());
816 
817     ImpersonatedCredentials targetCredentials =
818         ImpersonatedCredentials.create(
819             sourceCredentials,
820             IMPERSONATED_CLIENT_EMAIL,
821             null,
822             IMMUTABLE_SCOPES_LIST,
823             VALID_LIFETIME,
824             mockTransportFactory);
825 
826     mockTransportFactory.transport.setIdToken(STANDARD_ID_TOKEN);
827 
828     String targetAudience = "https://foo.bar";
829     IdTokenCredentials tokenCredential =
830         IdTokenCredentials.newBuilder()
831             .setIdTokenProvider(targetCredentials)
832             .setTargetAudience(targetAudience)
833             .build();
834     tokenCredential.refresh();
835     assertEquals(STANDARD_ID_TOKEN, tokenCredential.getAccessToken().getTokenValue());
836     assertEquals(STANDARD_ID_TOKEN, tokenCredential.getIdToken().getTokenValue());
837     assertEquals(
838         targetAudience,
839         (String) tokenCredential.getIdToken().getJsonWebSignature().getPayload().getAudience());
840   }
841 
842   @Test
idTokenWithAudience_withEmail()843   public void idTokenWithAudience_withEmail() throws IOException {
844     mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
845     mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN);
846     mockTransportFactory.transport.setExpireTime(getDefaultExpireTime());
847 
848     ImpersonatedCredentials targetCredentials =
849         ImpersonatedCredentials.create(
850             sourceCredentials,
851             IMPERSONATED_CLIENT_EMAIL,
852             null,
853             IMMUTABLE_SCOPES_LIST,
854             VALID_LIFETIME,
855             mockTransportFactory);
856 
857     mockTransportFactory.transport.setIdToken(TOKEN_WITH_EMAIL);
858 
859     String targetAudience = "https://foo.bar";
860     IdTokenCredentials tokenCredential =
861         IdTokenCredentials.newBuilder()
862             .setIdTokenProvider(targetCredentials)
863             .setTargetAudience(targetAudience)
864             .setOptions(Arrays.asList(IdTokenProvider.Option.INCLUDE_EMAIL))
865             .build();
866     tokenCredential.refresh();
867     assertEquals(TOKEN_WITH_EMAIL, tokenCredential.getAccessToken().getTokenValue());
868     Payload p = tokenCredential.getIdToken().getJsonWebSignature().getPayload();
869     assertTrue(p.containsKey("email"));
870   }
871 
872   @Test
idToken_withServerError()873   public void idToken_withServerError() {
874     mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
875     mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN);
876     mockTransportFactory.transport.setExpireTime(getDefaultExpireTime());
877 
878     ImpersonatedCredentials targetCredentials =
879         ImpersonatedCredentials.create(
880             sourceCredentials,
881             IMPERSONATED_CLIENT_EMAIL,
882             null,
883             IMMUTABLE_SCOPES_LIST,
884             VALID_LIFETIME,
885             mockTransportFactory);
886 
887     mockTransportFactory.transport.setIdToken(STANDARD_ID_TOKEN);
888     mockTransportFactory.transport.setErrorResponseCodeAndMessage(
889         HttpStatusCodes.STATUS_CODE_SERVER_ERROR, "Internal Server Error");
890 
891     String targetAudience = "https://foo.bar";
892     IdTokenCredentials tokenCredential =
893         IdTokenCredentials.newBuilder()
894             .setIdTokenProvider(targetCredentials)
895             .setTargetAudience(targetAudience)
896             .build();
897     try {
898       tokenCredential.refresh();
899       fail("Should not be able to use credential without exception.");
900     } catch (IOException e) {
901       assertTrue(e.getMessage().contains("Error code 500 trying to getIDToken"));
902     }
903   }
904 
905   @Test
idToken_withOtherError()906   public void idToken_withOtherError() {
907     mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
908     mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN);
909     mockTransportFactory.transport.setExpireTime(getDefaultExpireTime());
910 
911     ImpersonatedCredentials targetCredentials =
912         ImpersonatedCredentials.create(
913             sourceCredentials,
914             IMPERSONATED_CLIENT_EMAIL,
915             null,
916             IMMUTABLE_SCOPES_LIST,
917             VALID_LIFETIME,
918             mockTransportFactory);
919 
920     mockTransportFactory.transport.setIdToken(STANDARD_ID_TOKEN);
921     mockTransportFactory.transport.setErrorResponseCodeAndMessage(
922         HttpStatusCodes.STATUS_CODE_MOVED_PERMANENTLY, "Redirect");
923 
924     String targetAudience = "https://foo.bar";
925     IdTokenCredentials tokenCredential =
926         IdTokenCredentials.newBuilder()
927             .setIdTokenProvider(targetCredentials)
928             .setTargetAudience(targetAudience)
929             .build();
930     try {
931       tokenCredential.refresh();
932       fail("Should not be able to use credential without exception.");
933     } catch (IOException e) {
934       assertTrue(e.getMessage().contains("Unexpected Error code 301 trying to getIDToken"));
935     }
936   }
937 
938   @Test
hashCode_equals()939   public void hashCode_equals() throws IOException {
940     mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
941     mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN);
942     mockTransportFactory.transport.setExpireTime(getDefaultExpireTime());
943 
944     ImpersonatedCredentials credentials =
945         ImpersonatedCredentials.create(
946             sourceCredentials,
947             IMPERSONATED_CLIENT_EMAIL,
948             null,
949             IMMUTABLE_SCOPES_LIST,
950             VALID_LIFETIME,
951             mockTransportFactory);
952 
953     ImpersonatedCredentials otherCredentials =
954         ImpersonatedCredentials.create(
955             sourceCredentials,
956             IMPERSONATED_CLIENT_EMAIL,
957             null,
958             IMMUTABLE_SCOPES_LIST,
959             VALID_LIFETIME,
960             mockTransportFactory);
961 
962     assertEquals(credentials.hashCode(), otherCredentials.hashCode());
963   }
964 
965   @Test
serialize()966   public void serialize() throws IOException, ClassNotFoundException {
967 
968     mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
969     mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN);
970     mockTransportFactory.transport.setExpireTime(getDefaultExpireTime());
971 
972     ImpersonatedCredentials targetCredentials =
973         ImpersonatedCredentials.create(
974             sourceCredentials,
975             IMPERSONATED_CLIENT_EMAIL,
976             null,
977             IMMUTABLE_SCOPES_LIST,
978             VALID_LIFETIME,
979             mockTransportFactory);
980     GoogleCredentials deserializedCredentials = serializeAndDeserialize(targetCredentials);
981     assertEquals(targetCredentials, deserializedCredentials);
982     assertEquals(targetCredentials.hashCode(), deserializedCredentials.hashCode());
983     assertEquals(targetCredentials.toString(), deserializedCredentials.toString());
984     assertSame(deserializedCredentials.clock, Clock.SYSTEM);
985   }
986 
getDefaultExpireTime()987   public static String getDefaultExpireTime() {
988     Calendar c = Calendar.getInstance();
989     c.add(Calendar.SECOND, VALID_LIFETIME);
990     return getFormattedTime(c.getTime());
991   }
992 
993   /**
994    * Given a {@link Date}, it will return a string of the date formatted like
995    * <b>yyyy-MM-dd'T'HH:mm:ss'Z'</b>
996    */
getFormattedTime(final Date date)997   private static String getFormattedTime(final Date date) {
998     // Set timezone to GMT since that's the TZ used in the response from the service impersonation
999     // token exchange
1000     final DateFormat formatter = new SimpleDateFormat(RFC3339);
1001     formatter.setTimeZone(TimeZone.getTimeZone("GMT"));
1002     return formatter.format(date);
1003   }
1004 
generateErrorJson( int errorCode, String errorMessage, String errorDomain, String errorReason)1005   private String generateErrorJson(
1006       int errorCode, String errorMessage, String errorDomain, String errorReason)
1007       throws IOException {
1008 
1009     JsonFactory factory = new GsonFactory();
1010     ByteArrayOutputStream bout = new ByteArrayOutputStream();
1011     JsonGenerator generator = factory.createJsonGenerator(bout, Charset.defaultCharset());
1012     generator.enablePrettyPrint();
1013 
1014     generator.writeStartObject();
1015     generator.writeFieldName("error");
1016 
1017     generator.writeStartObject();
1018     generator.writeFieldName("code");
1019     generator.writeNumber(errorCode);
1020     generator.writeFieldName("message");
1021     generator.writeString(errorMessage);
1022 
1023     generator.writeFieldName("errors");
1024     generator.writeStartArray();
1025     generator.writeStartObject();
1026     generator.writeFieldName("message");
1027     generator.writeString(errorMessage);
1028     generator.writeFieldName("domain");
1029     generator.writeString(errorDomain);
1030     generator.writeFieldName("reason");
1031     generator.writeString(errorReason);
1032     generator.writeEndObject();
1033     generator.writeEndArray();
1034 
1035     generator.writeFieldName("status");
1036     generator.writeString("PERMISSION_DENIED");
1037 
1038     generator.writeEndObject();
1039     generator.writeEndObject();
1040     generator.close();
1041     return bout.toString();
1042   }
1043 
buildImpersonationCredentialsJson( String impersonationUrl, List<String> delegates, String quotaProjectId, String sourceClientId, String sourceClientSecret, String sourceRefreshToken)1044   static GenericJson buildImpersonationCredentialsJson(
1045       String impersonationUrl,
1046       List<String> delegates,
1047       String quotaProjectId,
1048       String sourceClientId,
1049       String sourceClientSecret,
1050       String sourceRefreshToken) {
1051     GenericJson sourceJson = new GenericJson();
1052 
1053     sourceJson.put("client_id", sourceClientId);
1054     sourceJson.put("client_secret", sourceClientSecret);
1055     sourceJson.put("refresh_token", sourceRefreshToken);
1056     sourceJson.put("type", "authorized_user");
1057     GenericJson json = new GenericJson();
1058 
1059     json.put("service_account_impersonation_url", impersonationUrl);
1060     json.put("delegates", delegates);
1061     if (quotaProjectId != null) {
1062       json.put("quota_project_id", quotaProjectId);
1063     }
1064     json.put("source_credentials", sourceJson);
1065     json.put("type", "impersonated_service_account");
1066     return json;
1067   }
1068 
buildImpersonationCredentialsJson( String impersonationUrl, List<String> delegates, String quotaProjectId)1069   static GenericJson buildImpersonationCredentialsJson(
1070       String impersonationUrl, List<String> delegates, String quotaProjectId) {
1071     GenericJson sourceJson = new GenericJson();
1072     sourceJson.put("type", "service_account");
1073     sourceJson.put("project_id", PROJECT_ID);
1074     sourceJson.put("private_key_id", SA_PRIVATE_KEY_ID);
1075     sourceJson.put("private_key", SA_PRIVATE_KEY_PKCS8);
1076     sourceJson.put("client_email", SA_CLIENT_EMAIL);
1077     sourceJson.put("client_id", "10848832332323213");
1078     sourceJson.put("auth_uri", "https://oauth2.googleapis.com/o/oauth2/auth");
1079     sourceJson.put("token_uri", "https://oauth2.googleapis.com/token");
1080     sourceJson.put("auth_provider_x509_cert_url", "https://www.googleapis.com/oauth2/v1/certs");
1081     sourceJson.put(
1082         "client_x509_cert_url",
1083         "https://www.googleapis.com/robot/v1/metadata/x509/chaoren-test-sc%40cloudsdktest.iam.gserviceaccount.com");
1084 
1085     GenericJson json = new GenericJson();
1086     json.put("source_credentials", sourceJson);
1087     json.put("service_account_impersonation_url", impersonationUrl);
1088     json.put("delegates", delegates);
1089     if (quotaProjectId != null) {
1090       json.put("quota_project_id", quotaProjectId);
1091     }
1092     json.put("type", "impersonated_service_account");
1093     return json;
1094   }
1095 
buildInvalidCredentialsJson()1096   static GenericJson buildInvalidCredentialsJson() {
1097     GenericJson json = new GenericJson();
1098     json.put("service_account_impersonation_url", "mock_url");
1099     return json;
1100   }
1101 
writeImpersonationCredentialsStream( String impersonationUrl, List<String> delegates, String quotaProjectId)1102   static InputStream writeImpersonationCredentialsStream(
1103       String impersonationUrl, List<String> delegates, String quotaProjectId) throws IOException {
1104     GenericJson json =
1105         buildImpersonationCredentialsJson(impersonationUrl, delegates, quotaProjectId);
1106     return TestUtils.jsonToInputStream(json);
1107   }
1108 }
1109