• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 Google LLC
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 LLC 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 com.google.auth.Credentials.GOOGLE_DEFAULT_UNIVERSE;
35 import static com.google.auth.oauth2.ExternalAccountAuthorizedUserCredentials.EXTERNAL_ACCOUNT_AUTHORIZED_USER_FILE_TYPE;
36 import static org.junit.Assert.assertEquals;
37 import static org.junit.Assert.assertFalse;
38 import static org.junit.Assert.assertNotEquals;
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.HttpTransport;
45 import com.google.api.client.json.GenericJson;
46 import com.google.api.client.testing.http.MockLowLevelHttpRequest;
47 import com.google.api.client.util.Clock;
48 import com.google.auth.TestUtils;
49 import com.google.auth.http.AuthHttpConstants;
50 import com.google.auth.http.HttpTransportFactory;
51 import com.google.common.collect.ImmutableList;
52 import com.google.common.collect.ImmutableMap;
53 import com.google.common.io.BaseEncoding;
54 import java.io.IOException;
55 import java.net.URI;
56 import java.nio.charset.StandardCharsets;
57 import java.util.Arrays;
58 import java.util.Collection;
59 import java.util.Collections;
60 import java.util.Date;
61 import java.util.HashMap;
62 import java.util.List;
63 import java.util.Map;
64 import org.junit.Before;
65 import org.junit.Test;
66 import org.junit.runner.RunWith;
67 import org.junit.runners.JUnit4;
68 
69 /** Test case for {@link ExternalAccountAuthorizedUserCredentials}. */
70 @RunWith(JUnit4.class)
71 public class ExternalAccountAuthorizedUserCredentialsTest extends BaseSerializationTest {
72 
73   private static final String AUDIENCE =
74       "//iam.googleapis.com/locations/global/workforcePools/$WORKFORCE_POOL_ID/providers/$PROVIDER_ID";
75   private static final String CLIENT_SECRET = "jakuaL9YyieakhECKL2SwZcu";
76   private static final String CLIENT_ID = "ya29.1.AADtN_UtlxN3PuGAxrN2XQnZTVRvDyVWnYq4I6dws";
77   private static final String REFRESH_TOKEN = "1/Tl6awhpFjkMkSJoj1xsli0H2eL5YsMgU_NKPY2TyGWY";
78   private static final String ACCESS_TOKEN = "1/MkSJoj1xsli0AccessToken_NKPY2";
79   private static final String TOKEN_URL = "https://sts.googleapis.com/v1/oauthtoken";
80   private static final String TOKEN_INFO_URL = "https://sts.googleapis.com/v1/introspect";
81   private static final String REVOKE_URL = "https://sts.googleapis.com/v1/revoke";
82   private static final String QUOTA_PROJECT = "sample-quota-project-id";
83   private static final String UNIVERSE_DOMAIN = "foo.bar";
84 
85   private static final String BASIC_AUTH =
86       String.format(
87           "Basic %s",
88           BaseEncoding.base64()
89               .encode(
90                   String.format("%s:%s", CLIENT_ID, CLIENT_SECRET)
91                       .getBytes(StandardCharsets.UTF_8)));
92 
93   private static final Collection<String> SCOPES = Collections.singletonList("dummy.scope");
94   private static final URI CALL_URI = URI.create("http://googleapis.com/testapi/v1/foo");
95 
96   private MockExternalAccountAuthorizedUserCredentialsTransportFactory transportFactory;
97 
98   static class MockExternalAccountAuthorizedUserCredentialsTransportFactory
99       implements HttpTransportFactory {
100 
101     MockStsTransport transport = new MockStsTransport();
102 
103     @Override
create()104     public HttpTransport create() {
105       return transport;
106     }
107   }
108 
109   @Before
setup()110   public void setup() {
111     transportFactory = new MockExternalAccountAuthorizedUserCredentialsTransportFactory();
112   }
113 
114   @Test
builder_allFields()115   public void builder_allFields() throws IOException {
116     ExternalAccountAuthorizedUserCredentials credentials =
117         ExternalAccountAuthorizedUserCredentials.newBuilder()
118             .setAudience(AUDIENCE)
119             .setClientId(CLIENT_ID)
120             .setClientSecret(CLIENT_SECRET)
121             .setRefreshToken(REFRESH_TOKEN)
122             .setTokenUrl(TOKEN_URL)
123             .setTokenInfoUrl(TOKEN_INFO_URL)
124             .setRevokeUrl(REVOKE_URL)
125             .setAccessToken(new AccessToken(ACCESS_TOKEN, /* expirationTime= */ null))
126             .setQuotaProjectId(QUOTA_PROJECT)
127             .setUniverseDomain(UNIVERSE_DOMAIN)
128             .build();
129 
130     assertEquals(AUDIENCE, credentials.getAudience());
131     assertEquals(CLIENT_ID, credentials.getClientId());
132     assertEquals(CLIENT_SECRET, credentials.getClientSecret());
133     assertEquals(REFRESH_TOKEN, credentials.getRefreshToken());
134     assertEquals(TOKEN_URL, credentials.getTokenUrl());
135     assertEquals(TOKEN_INFO_URL, credentials.getTokenInfoUrl());
136     assertEquals(REVOKE_URL, credentials.getRevokeUrl());
137     assertEquals(ACCESS_TOKEN, credentials.getAccessToken().getTokenValue());
138     assertEquals(QUOTA_PROJECT, credentials.getQuotaProjectId());
139     assertEquals(UNIVERSE_DOMAIN, credentials.getUniverseDomain());
140   }
141 
142   @Test
builder_minimumRequiredFieldsForRefresh()143   public void builder_minimumRequiredFieldsForRefresh() {
144     ExternalAccountAuthorizedUserCredentials credentials =
145         ExternalAccountAuthorizedUserCredentials.newBuilder()
146             .setClientId(CLIENT_ID)
147             .setClientSecret(CLIENT_SECRET)
148             .setRefreshToken(REFRESH_TOKEN)
149             .setTokenUrl(TOKEN_URL)
150             .build();
151 
152     assertEquals(CLIENT_ID, credentials.getClientId());
153     assertEquals(CLIENT_SECRET, credentials.getClientSecret());
154     assertEquals(REFRESH_TOKEN, credentials.getRefreshToken());
155     assertEquals(TOKEN_URL, credentials.getTokenUrl());
156     assertNull(credentials.getAudience());
157     assertNull(credentials.getTokenInfoUrl());
158     assertNull(credentials.getRevokeUrl());
159     assertNull(credentials.getAccessToken());
160     assertNull(credentials.getQuotaProjectId());
161   }
162 
163   @Test
builder_accessTokenOnly()164   public void builder_accessTokenOnly() {
165     ExternalAccountAuthorizedUserCredentials credentials =
166         ExternalAccountAuthorizedUserCredentials.newBuilder()
167             .setAccessToken(AccessToken.newBuilder().setTokenValue(ACCESS_TOKEN).build())
168             .build();
169 
170     assertEquals(ACCESS_TOKEN, credentials.getAccessToken().getTokenValue());
171     assertNull(credentials.getAudience());
172     assertNull(credentials.getTokenUrl());
173     assertNull(credentials.getTokenInfoUrl());
174     assertNull(credentials.getRevokeUrl());
175     assertNull(credentials.getClientId());
176     assertNull(credentials.getClientSecret());
177     assertNull(credentials.getRefreshToken());
178     assertNull(credentials.getQuotaProjectId());
179   }
180 
181   @Test
builder_credentialConstructor()182   public void builder_credentialConstructor() {
183     ExternalAccountAuthorizedUserCredentials credentials =
184         ExternalAccountAuthorizedUserCredentials.newBuilder()
185             .setAudience(AUDIENCE)
186             .setClientId(CLIENT_ID)
187             .setClientSecret(CLIENT_SECRET)
188             .setRefreshToken(REFRESH_TOKEN)
189             .setTokenUrl(TOKEN_URL)
190             .setTokenInfoUrl(TOKEN_INFO_URL)
191             .setRevokeUrl(REVOKE_URL)
192             .setQuotaProjectId(QUOTA_PROJECT)
193             .build();
194 
195     ExternalAccountAuthorizedUserCredentials otherCredentials = credentials.toBuilder().build();
196 
197     assertEquals(AUDIENCE, otherCredentials.getAudience());
198     assertEquals(CLIENT_ID, otherCredentials.getClientId());
199     assertEquals(CLIENT_SECRET, otherCredentials.getClientSecret());
200     assertEquals(REFRESH_TOKEN, otherCredentials.getRefreshToken());
201     assertEquals(TOKEN_URL, otherCredentials.getTokenUrl());
202     assertEquals(TOKEN_INFO_URL, otherCredentials.getTokenInfoUrl());
203     assertEquals(REVOKE_URL, otherCredentials.getRevokeUrl());
204     assertEquals(QUOTA_PROJECT, otherCredentials.getQuotaProjectId());
205   }
206 
207   @Test
builder_accessTokenWithMissingRefreshFields()208   public void builder_accessTokenWithMissingRefreshFields() {
209     ExternalAccountAuthorizedUserCredentials credentials =
210         ExternalAccountAuthorizedUserCredentials.newBuilder()
211             .setAccessToken(AccessToken.newBuilder().setTokenValue(ACCESS_TOKEN).build())
212             .setRefreshToken(REFRESH_TOKEN)
213             .setTokenUrl(TOKEN_URL)
214             .setClientId(CLIENT_ID)
215             .build();
216 
217     assertEquals(ACCESS_TOKEN, credentials.getAccessToken().getTokenValue());
218     assertEquals(REFRESH_TOKEN, credentials.getRefreshToken());
219     assertEquals(TOKEN_URL, credentials.getTokenUrl());
220     assertEquals(CLIENT_ID, credentials.getClientId());
221     assertNull(credentials.getAudience());
222     assertNull(credentials.getTokenInfoUrl());
223     assertNull(credentials.getRevokeUrl());
224     assertNull(credentials.getClientSecret());
225     assertNull(credentials.getQuotaProjectId());
226   }
227 
228   @Test
builder_accessAndRefreshTokenNull_throws()229   public void builder_accessAndRefreshTokenNull_throws() {
230     try {
231       ExternalAccountAuthorizedUserCredentials.newBuilder().build();
232       fail("Should not be able to continue without exception.");
233     } catch (IllegalStateException exception) {
234       assertEquals(
235           "ExternalAccountAuthorizedUserCredentials must be initialized with "
236               + "an access token or fields to enable refresh: "
237               + "('refresh_token', 'token_url', 'client_id', 'client_secret').",
238           exception.getMessage());
239     }
240   }
241 
242   @Test
builder_missingTokenUrl_throws()243   public void builder_missingTokenUrl_throws() {
244     try {
245       ExternalAccountAuthorizedUserCredentials.newBuilder()
246           .setRefreshToken(REFRESH_TOKEN)
247           .setClientId(CLIENT_ID)
248           .setClientSecret(CLIENT_SECRET)
249           .build();
250       fail("Should not be able to continue without exception.");
251     } catch (IllegalStateException exception) {
252       assertEquals(
253           "ExternalAccountAuthorizedUserCredentials must be initialized with "
254               + "an access token or fields to enable refresh: "
255               + "('refresh_token', 'token_url', 'client_id', 'client_secret').",
256           exception.getMessage());
257     }
258   }
259 
260   @Test
builder_missingClientId_throws()261   public void builder_missingClientId_throws() {
262     try {
263       ExternalAccountAuthorizedUserCredentials.newBuilder()
264           .setRefreshToken(REFRESH_TOKEN)
265           .setTokenUrl(TOKEN_URL)
266           .setClientSecret(CLIENT_SECRET)
267           .build();
268       fail("Should not be able to continue without exception.");
269     } catch (IllegalStateException exception) {
270       assertEquals(
271           "ExternalAccountAuthorizedUserCredentials must be initialized with "
272               + "an access token or fields to enable refresh: "
273               + "('refresh_token', 'token_url', 'client_id', 'client_secret').",
274           exception.getMessage());
275     }
276   }
277 
278   @Test
builder_missingClientSecret_throws()279   public void builder_missingClientSecret_throws() {
280     try {
281       ExternalAccountAuthorizedUserCredentials.newBuilder()
282           .setRefreshToken(REFRESH_TOKEN)
283           .setTokenUrl(TOKEN_URL)
284           .setClientId(CLIENT_ID)
285           .build();
286       fail("Should not be able to continue without exception.");
287     } catch (IllegalStateException exception) {
288       assertEquals(
289           "ExternalAccountAuthorizedUserCredentials must be initialized with "
290               + "an access token or fields to enable refresh: "
291               + "('refresh_token', 'token_url', 'client_id', 'client_secret').",
292           exception.getMessage());
293     }
294   }
295 
296   @Test
builder_missingUniverseDomain_defaults()297   public void builder_missingUniverseDomain_defaults() throws IOException {
298     ExternalAccountAuthorizedUserCredentials credentials =
299         ExternalAccountAuthorizedUserCredentials.newBuilder()
300             .setAudience(AUDIENCE)
301             .setClientId(CLIENT_ID)
302             .setClientSecret(CLIENT_SECRET)
303             .setRefreshToken(REFRESH_TOKEN)
304             .setTokenUrl(TOKEN_URL)
305             .setTokenInfoUrl(TOKEN_INFO_URL)
306             .setRevokeUrl(REVOKE_URL)
307             .setAccessToken(new AccessToken(ACCESS_TOKEN, /* expirationTime= */ null))
308             .setQuotaProjectId(QUOTA_PROJECT)
309             .build();
310 
311     assertEquals(AUDIENCE, credentials.getAudience());
312     assertEquals(CLIENT_ID, credentials.getClientId());
313     assertEquals(CLIENT_SECRET, credentials.getClientSecret());
314     assertEquals(REFRESH_TOKEN, credentials.getRefreshToken());
315     assertEquals(TOKEN_URL, credentials.getTokenUrl());
316     assertEquals(TOKEN_INFO_URL, credentials.getTokenInfoUrl());
317     assertEquals(REVOKE_URL, credentials.getRevokeUrl());
318     assertEquals(ACCESS_TOKEN, credentials.getAccessToken().getTokenValue());
319     assertEquals(QUOTA_PROJECT, credentials.getQuotaProjectId());
320     assertEquals(GOOGLE_DEFAULT_UNIVERSE, credentials.getUniverseDomain());
321   }
322 
323   @Test
toBuilder_allFields()324   public void toBuilder_allFields() {
325     ExternalAccountAuthorizedUserCredentials credentials =
326         ExternalAccountAuthorizedUserCredentials.newBuilder()
327             .setAudience(AUDIENCE)
328             .setClientId(CLIENT_ID)
329             .setClientSecret(CLIENT_SECRET)
330             .setRefreshToken(REFRESH_TOKEN)
331             .setTokenUrl(TOKEN_URL)
332             .setTokenInfoUrl(TOKEN_INFO_URL)
333             .setRevokeUrl(REVOKE_URL)
334             .setAccessToken(new AccessToken(ACCESS_TOKEN, new Date()))
335             .setQuotaProjectId(QUOTA_PROJECT)
336             .setUniverseDomain(UNIVERSE_DOMAIN)
337             .build();
338 
339     ExternalAccountAuthorizedUserCredentials secondCredentials = credentials.toBuilder().build();
340 
341     assertEquals(credentials, secondCredentials);
342   }
343 
344   @Test
toBuilder_missingUniverseDomain_defaults()345   public void toBuilder_missingUniverseDomain_defaults() throws IOException {
346     ExternalAccountAuthorizedUserCredentials credentials =
347         ExternalAccountAuthorizedUserCredentials.newBuilder()
348             .setAudience(AUDIENCE)
349             .setClientId(CLIENT_ID)
350             .setClientSecret(CLIENT_SECRET)
351             .setRefreshToken(REFRESH_TOKEN)
352             .setTokenUrl(TOKEN_URL)
353             .setTokenInfoUrl(TOKEN_INFO_URL)
354             .setRevokeUrl(REVOKE_URL)
355             .setAccessToken(new AccessToken(ACCESS_TOKEN, new Date()))
356             .setQuotaProjectId(QUOTA_PROJECT)
357             .build();
358 
359     ExternalAccountAuthorizedUserCredentials secondCredentials = credentials.toBuilder().build();
360 
361     assertEquals(credentials, secondCredentials);
362     assertEquals(GOOGLE_DEFAULT_UNIVERSE, secondCredentials.getUniverseDomain());
363   }
364 
365   @Test
fromJson_allFields()366   public void fromJson_allFields() throws IOException {
367     ExternalAccountAuthorizedUserCredentials credentials =
368         ExternalAccountAuthorizedUserCredentials.fromJson(
369             buildJsonCredentials(), OAuth2Utils.HTTP_TRANSPORT_FACTORY);
370 
371     assertEquals(AUDIENCE, credentials.getAudience());
372     assertEquals(CLIENT_ID, credentials.getClientId());
373     assertEquals(CLIENT_SECRET, credentials.getClientSecret());
374     assertEquals(REFRESH_TOKEN, credentials.getRefreshToken());
375     assertEquals(TOKEN_URL, credentials.getTokenUrl());
376     assertEquals(TOKEN_INFO_URL, credentials.getTokenInfoUrl());
377     assertEquals(REVOKE_URL, credentials.getRevokeUrl());
378     assertEquals(QUOTA_PROJECT, credentials.getQuotaProjectId());
379     assertEquals(UNIVERSE_DOMAIN, credentials.getUniverseDomain());
380   }
381 
382   @Test
fromJson_minimumRequiredFieldsForRefresh()383   public void fromJson_minimumRequiredFieldsForRefresh() throws IOException {
384     GenericJson json = new GenericJson();
385     json.put("client_id", CLIENT_ID);
386     json.put("client_secret", CLIENT_SECRET);
387     json.put("refresh_token", REFRESH_TOKEN);
388     json.put("token_url", TOKEN_URL);
389 
390     ExternalAccountAuthorizedUserCredentials credentials =
391         ExternalAccountAuthorizedUserCredentials.fromJson(json, OAuth2Utils.HTTP_TRANSPORT_FACTORY);
392 
393     assertEquals(CLIENT_ID, credentials.getClientId());
394     assertEquals(CLIENT_SECRET, credentials.getClientSecret());
395     assertEquals(REFRESH_TOKEN, credentials.getRefreshToken());
396     assertEquals(TOKEN_URL, credentials.getTokenUrl());
397     assertNull(credentials.getAudience());
398     assertNull(credentials.getTokenInfoUrl());
399     assertNull(credentials.getRevokeUrl());
400     assertNull(credentials.getAccessToken());
401     assertNull(credentials.getQuotaProjectId());
402   }
403 
404   @Test
fromJson_accessTokenOnly_notSupported()405   public void fromJson_accessTokenOnly_notSupported() throws IOException {
406     GenericJson json = new GenericJson();
407     json.put("access_token", ACCESS_TOKEN);
408 
409     try {
410       ExternalAccountAuthorizedUserCredentials.fromJson(json, OAuth2Utils.HTTP_TRANSPORT_FACTORY);
411       fail("Should not be able to continue without exception.");
412     } catch (IllegalStateException exception) {
413       assertEquals(
414           "ExternalAccountAuthorizedUserCredentials must be initialized with "
415               + "an access token or fields to enable refresh: "
416               + "('refresh_token', 'token_url', 'client_id', 'client_secret').",
417           exception.getMessage());
418     }
419   }
420 
421   @Test
fromJson_missingRefreshToken_throws()422   public void fromJson_missingRefreshToken_throws() throws IOException {
423     try {
424       GenericJson json = buildJsonCredentials();
425       json.remove("refresh_token");
426       ExternalAccountAuthorizedUserCredentials.fromJson(json, OAuth2Utils.HTTP_TRANSPORT_FACTORY);
427       fail("Should not be able to continue without exception.");
428     } catch (IllegalStateException exception) {
429       assertEquals(
430           "ExternalAccountAuthorizedUserCredentials must be initialized with "
431               + "an access token or fields to enable refresh: "
432               + "('refresh_token', 'token_url', 'client_id', 'client_secret').",
433           exception.getMessage());
434     }
435   }
436 
437   @Test
fromJson_missingTokenUrl_throws()438   public void fromJson_missingTokenUrl_throws() throws IOException {
439     try {
440       GenericJson json = buildJsonCredentials();
441       json.remove("token_url");
442       ExternalAccountAuthorizedUserCredentials.fromJson(json, OAuth2Utils.HTTP_TRANSPORT_FACTORY);
443       fail("Should not be able to continue without exception.");
444     } catch (IllegalStateException exception) {
445       assertEquals(
446           "ExternalAccountAuthorizedUserCredentials must be initialized with "
447               + "an access token or fields to enable refresh: "
448               + "('refresh_token', 'token_url', 'client_id', 'client_secret').",
449           exception.getMessage());
450     }
451   }
452 
453   @Test
fromJson_missingClientId_throws()454   public void fromJson_missingClientId_throws() throws IOException {
455     try {
456       GenericJson json = buildJsonCredentials();
457       json.remove("client_id");
458       ExternalAccountAuthorizedUserCredentials.fromJson(json, OAuth2Utils.HTTP_TRANSPORT_FACTORY);
459       fail("Should not be able to continue without exception.");
460     } catch (IllegalStateException exception) {
461       assertEquals(
462           "ExternalAccountAuthorizedUserCredentials must be initialized with "
463               + "an access token or fields to enable refresh: "
464               + "('refresh_token', 'token_url', 'client_id', 'client_secret').",
465           exception.getMessage());
466     }
467   }
468 
469   @Test
fromJson_missingClientSecret_throws()470   public void fromJson_missingClientSecret_throws() throws IOException {
471     try {
472       GenericJson json = buildJsonCredentials();
473       json.remove("client_secret");
474       ExternalAccountAuthorizedUserCredentials.fromJson(json, OAuth2Utils.HTTP_TRANSPORT_FACTORY);
475       fail("Should not be able to continue without exception.");
476     } catch (IllegalStateException exception) {
477       assertEquals(
478           "ExternalAccountAuthorizedUserCredentials must be initialized with "
479               + "an access token or fields to enable refresh: "
480               + "('refresh_token', 'token_url', 'client_id', 'client_secret').",
481           exception.getMessage());
482     }
483   }
484 
485   @Test
fromJson_missingUniverseDomain_defaults()486   public void fromJson_missingUniverseDomain_defaults() throws IOException {
487     GenericJson json = buildJsonCredentials();
488     json.remove("universe_domain");
489 
490     ExternalAccountAuthorizedUserCredentials credentials =
491         ExternalAccountAuthorizedUserCredentials.fromJson(json, OAuth2Utils.HTTP_TRANSPORT_FACTORY);
492 
493     assertEquals(AUDIENCE, credentials.getAudience());
494     assertEquals(CLIENT_ID, credentials.getClientId());
495     assertEquals(CLIENT_SECRET, credentials.getClientSecret());
496     assertEquals(REFRESH_TOKEN, credentials.getRefreshToken());
497     assertEquals(TOKEN_URL, credentials.getTokenUrl());
498     assertEquals(TOKEN_INFO_URL, credentials.getTokenInfoUrl());
499     assertEquals(REVOKE_URL, credentials.getRevokeUrl());
500     assertEquals(QUOTA_PROJECT, credentials.getQuotaProjectId());
501     assertEquals(GOOGLE_DEFAULT_UNIVERSE, credentials.getUniverseDomain());
502   }
503 
504   @Test
fromStream_allFields()505   public void fromStream_allFields() throws IOException {
506     GenericJson json = buildJsonCredentials();
507 
508     ExternalAccountAuthorizedUserCredentials credentials =
509         ExternalAccountAuthorizedUserCredentials.fromStream(TestUtils.jsonToInputStream(json));
510 
511     assertEquals(AUDIENCE, credentials.getAudience());
512     assertEquals(CLIENT_ID, credentials.getClientId());
513     assertEquals(CLIENT_SECRET, credentials.getClientSecret());
514     assertEquals(REFRESH_TOKEN, credentials.getRefreshToken());
515     assertEquals(TOKEN_URL, credentials.getTokenUrl());
516     assertEquals(TOKEN_INFO_URL, credentials.getTokenInfoUrl());
517     assertEquals(REVOKE_URL, credentials.getRevokeUrl());
518     assertEquals(QUOTA_PROJECT, credentials.getQuotaProjectId());
519   }
520 
521   @Test
fromStream_minimumRequiredFieldsForRefresh()522   public void fromStream_minimumRequiredFieldsForRefresh() throws IOException {
523     GenericJson json = new GenericJson();
524     json.put("client_id", CLIENT_ID);
525     json.put("client_secret", CLIENT_SECRET);
526     json.put("refresh_token", REFRESH_TOKEN);
527     json.put("token_url", TOKEN_URL);
528 
529     ExternalAccountAuthorizedUserCredentials credentials =
530         ExternalAccountAuthorizedUserCredentials.fromStream(TestUtils.jsonToInputStream(json));
531 
532     assertEquals(CLIENT_ID, credentials.getClientId());
533     assertEquals(CLIENT_SECRET, credentials.getClientSecret());
534     assertEquals(REFRESH_TOKEN, credentials.getRefreshToken());
535     assertEquals(TOKEN_URL, credentials.getTokenUrl());
536     assertNull(credentials.getAudience());
537     assertNull(credentials.getTokenInfoUrl());
538     assertNull(credentials.getRevokeUrl());
539     assertNull(credentials.getAccessToken());
540     assertNull(credentials.getQuotaProjectId());
541   }
542 
543   @Test
fromStream_accessTokenOnly_notSupported()544   public void fromStream_accessTokenOnly_notSupported() throws IOException {
545     GenericJson json = new GenericJson();
546     json.put("access_token", ACCESS_TOKEN);
547     try {
548       ExternalAccountAuthorizedUserCredentials.fromStream(TestUtils.jsonToInputStream(json));
549       fail("Should not be able to continue without exception.");
550     } catch (IllegalStateException exception) {
551       assertEquals(
552           "ExternalAccountAuthorizedUserCredentials must be initialized with "
553               + "an access token or fields to enable refresh: "
554               + "('refresh_token', 'token_url', 'client_id', 'client_secret').",
555           exception.getMessage());
556     }
557   }
558 
559   @Test
fromStream_missingRefreshToken_throws()560   public void fromStream_missingRefreshToken_throws() throws IOException {
561     try {
562       GenericJson json = buildJsonCredentials();
563       json.remove("refresh_token");
564       ExternalAccountAuthorizedUserCredentials.fromStream(TestUtils.jsonToInputStream(json));
565       fail("Should not be able to continue without exception.");
566     } catch (IllegalStateException exception) {
567       assertEquals(
568           "ExternalAccountAuthorizedUserCredentials must be initialized with "
569               + "an access token or fields to enable refresh: "
570               + "('refresh_token', 'token_url', 'client_id', 'client_secret').",
571           exception.getMessage());
572     }
573   }
574 
575   @Test
fromStream_missingTokenUrl_throws()576   public void fromStream_missingTokenUrl_throws() throws IOException {
577     try {
578       GenericJson json = buildJsonCredentials();
579       json.remove("token_url");
580       ExternalAccountAuthorizedUserCredentials.fromStream(TestUtils.jsonToInputStream(json));
581       fail("Should not be able to continue without exception.");
582     } catch (IllegalStateException exception) {
583       assertEquals(
584           "ExternalAccountAuthorizedUserCredentials must be initialized with "
585               + "an access token or fields to enable refresh: "
586               + "('refresh_token', 'token_url', 'client_id', 'client_secret').",
587           exception.getMessage());
588     }
589   }
590 
591   @Test
fromStream_missingClientId_throws()592   public void fromStream_missingClientId_throws() throws IOException {
593     try {
594       GenericJson json = buildJsonCredentials();
595       json.remove("client_id");
596       ExternalAccountAuthorizedUserCredentials.fromStream(TestUtils.jsonToInputStream(json));
597       fail("Should not be able to continue without exception.");
598     } catch (IllegalStateException exception) {
599       assertEquals(
600           "ExternalAccountAuthorizedUserCredentials must be initialized with "
601               + "an access token or fields to enable refresh: "
602               + "('refresh_token', 'token_url', 'client_id', 'client_secret').",
603           exception.getMessage());
604     }
605   }
606 
607   @Test
fromStream_missingClientSecret_throws()608   public void fromStream_missingClientSecret_throws() throws IOException {
609     try {
610       GenericJson json = buildJsonCredentials();
611       json.remove("client_secret");
612       ExternalAccountAuthorizedUserCredentials.fromStream(TestUtils.jsonToInputStream(json));
613       fail("Should not be able to continue without exception.");
614     } catch (IllegalStateException exception) {
615       assertEquals(
616           "ExternalAccountAuthorizedUserCredentials must be initialized with "
617               + "an access token or fields to enable refresh: "
618               + "('refresh_token', 'token_url', 'client_id', 'client_secret').",
619           exception.getMessage());
620     }
621   }
622 
623   @Test
fromStream_missingUniverseDomain_defaults()624   public void fromStream_missingUniverseDomain_defaults() throws IOException {
625     GenericJson json = buildJsonCredentials();
626     json.remove("universe_domain");
627 
628     ExternalAccountAuthorizedUserCredentials credentials =
629         ExternalAccountAuthorizedUserCredentials.fromStream(TestUtils.jsonToInputStream(json));
630 
631     assertEquals(AUDIENCE, credentials.getAudience());
632     assertEquals(CLIENT_ID, credentials.getClientId());
633     assertEquals(CLIENT_SECRET, credentials.getClientSecret());
634     assertEquals(REFRESH_TOKEN, credentials.getRefreshToken());
635     assertEquals(TOKEN_URL, credentials.getTokenUrl());
636     assertEquals(TOKEN_INFO_URL, credentials.getTokenInfoUrl());
637     assertEquals(REVOKE_URL, credentials.getRevokeUrl());
638     assertEquals(QUOTA_PROJECT, credentials.getQuotaProjectId());
639     assertEquals(GOOGLE_DEFAULT_UNIVERSE, credentials.getUniverseDomain());
640   }
641 
642   @Test
fromStream_invalidInputStream_throws()643   public void fromStream_invalidInputStream_throws() throws IOException {
644     try {
645       GenericJson json = buildJsonCredentials();
646       json.put("audience", new HashMap<>());
647       ExternalAccountAuthorizedUserCredentials.fromStream(TestUtils.jsonToInputStream(json));
648       fail("Should not be able to continue without exception.");
649     } catch (CredentialFormatException e) {
650       assertEquals("Invalid input stream provided.", e.getMessage());
651     }
652   }
653 
654   @Test
createScoped_noChange()655   public void createScoped_noChange() {
656     ExternalAccountAuthorizedUserCredentials externalAccountAuthorizedUserCredentials =
657         ExternalAccountAuthorizedUserCredentials.newBuilder()
658             .setTokenUrl(TOKEN_URL)
659             .setClientId(CLIENT_ID)
660             .setClientSecret(CLIENT_SECRET)
661             .setRefreshToken(REFRESH_TOKEN)
662             .build();
663     assertSame(
664         externalAccountAuthorizedUserCredentials,
665         externalAccountAuthorizedUserCredentials.createScoped(SCOPES));
666   }
667 
668   @Test
createScopedRequired_false()669   public void createScopedRequired_false() {
670     ExternalAccountAuthorizedUserCredentials externalAccountAuthorizedUserCredentials =
671         ExternalAccountAuthorizedUserCredentials.newBuilder()
672             .setTokenUrl(TOKEN_URL)
673             .setClientId(CLIENT_ID)
674             .setClientSecret(CLIENT_SECRET)
675             .setRefreshToken(REFRESH_TOKEN)
676             .build();
677     assertFalse(externalAccountAuthorizedUserCredentials.createScopedRequired());
678   }
679 
680   @Test
getRequestMetadata()681   public void getRequestMetadata() throws IOException {
682     GoogleCredentials credentials =
683         ExternalAccountAuthorizedUserCredentials.fromJson(buildJsonCredentials(), transportFactory);
684 
685     Map<String, List<String>> metadata = credentials.getRequestMetadata(CALL_URI);
686 
687     TestUtils.assertContainsBearerToken(metadata, transportFactory.transport.getAccessToken());
688     validateAuthHeader(transportFactory.transport.getRequest());
689   }
690 
691   @Test
getRequestMetadata_withQuotaProjectId()692   public void getRequestMetadata_withQuotaProjectId() throws IOException {
693     GoogleCredentials credentials =
694         ExternalAccountAuthorizedUserCredentials.fromJson(buildJsonCredentials(), transportFactory);
695 
696     Map<String, List<String>> metadata = credentials.getRequestMetadata(CALL_URI);
697 
698     assertTrue(metadata.containsKey(GoogleCredentials.QUOTA_PROJECT_ID_HEADER_KEY));
699     assertEquals(
700         metadata.get(GoogleCredentials.QUOTA_PROJECT_ID_HEADER_KEY),
701         Collections.singletonList(QUOTA_PROJECT));
702 
703     validateAuthHeader(transportFactory.transport.getRequest());
704   }
705 
706   @Test
getRequestMetadata_withAccessToken()707   public void getRequestMetadata_withAccessToken() throws IOException {
708     ExternalAccountAuthorizedUserCredentials credentials =
709         ExternalAccountAuthorizedUserCredentials.newBuilder()
710             .setHttpTransportFactory(transportFactory)
711             .setAccessToken(new AccessToken(ACCESS_TOKEN, /* expirationTime= */ null))
712             .build();
713 
714     Map<String, List<String>> metadata = credentials.getRequestMetadata(CALL_URI);
715 
716     TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN);
717   }
718 
719   @Test
refreshAccessToken()720   public void refreshAccessToken() throws IOException {
721     ExternalAccountAuthorizedUserCredentials credentials =
722         ExternalAccountAuthorizedUserCredentials.fromJson(buildJsonCredentials(), transportFactory);
723 
724     AccessToken accessToken = credentials.refreshAccessToken();
725 
726     assertEquals(transportFactory.transport.getAccessToken(), accessToken.getTokenValue());
727     validateAuthHeader(transportFactory.transport.getRequest());
728   }
729 
730   @Test
refreshAccessToken_withRefreshTokenRotation()731   public void refreshAccessToken_withRefreshTokenRotation() throws IOException {
732     ExternalAccountAuthorizedUserCredentials credentials =
733         ExternalAccountAuthorizedUserCredentials.fromJson(buildJsonCredentials(), transportFactory);
734 
735     transportFactory.transport.addRefreshTokenSequence("aNewRefreshToken");
736 
737     AccessToken accessToken = credentials.refreshAccessToken();
738 
739     assertEquals(transportFactory.transport.getAccessToken(), accessToken.getTokenValue());
740     // Validate new refresh token was set.
741     assertEquals("aNewRefreshToken", credentials.getRefreshToken());
742     assertNotEquals(REFRESH_TOKEN, credentials.getRefreshToken());
743     validateAuthHeader(transportFactory.transport.getRequest());
744   }
745 
746   @Test
refreshAccessToken_genericAuthError_throws()747   public void refreshAccessToken_genericAuthError_throws() throws IOException {
748     transportFactory.transport.addResponseErrorSequence(
749         TestUtils.buildHttpResponseException(
750             "invalid_request", "Invalid request.", /* errorUri= */ null));
751 
752     ExternalAccountAuthorizedUserCredentials credentials =
753         ExternalAccountAuthorizedUserCredentials.fromJson(buildJsonCredentials(), transportFactory);
754 
755     try {
756       credentials.refreshAccessToken();
757       fail("");
758     } catch (OAuthException e) {
759       assertEquals("invalid_request", e.getErrorCode());
760       assertEquals("Invalid request.", e.getErrorDescription());
761     }
762   }
763 
764   @Test(expected = IOException.class)
refreshAccessToken_genericIOError_throws()765   public void refreshAccessToken_genericIOError_throws() throws IOException {
766     transportFactory.transport.addResponseErrorSequence(new IOException(""));
767 
768     ExternalAccountAuthorizedUserCredentials credentials =
769         ExternalAccountAuthorizedUserCredentials.fromJson(buildJsonCredentials(), transportFactory);
770 
771     credentials.refreshAccessToken();
772   }
773 
774   @Test(expected = IllegalStateException.class)
refreshAccessToken_missingRefreshFields_throws()775   public void refreshAccessToken_missingRefreshFields_throws() throws IOException {
776     ExternalAccountAuthorizedUserCredentials credentials =
777         ExternalAccountAuthorizedUserCredentials.newBuilder()
778             .setClientId(CLIENT_ID)
779             .setClientSecret(CLIENT_SECRET)
780             .setTokenUrl(TOKEN_URL)
781             .setAccessToken(new AccessToken(ACCESS_TOKEN, /* expirationTime= */ null))
782             .setHttpTransportFactory(transportFactory)
783             .build();
784 
785     credentials.refreshAccessToken();
786   }
787 
788   @Test
hashCode_sameCredentials()789   public void hashCode_sameCredentials() {
790     ExternalAccountAuthorizedUserCredentials credentials =
791         ExternalAccountAuthorizedUserCredentials.newBuilder()
792             .setAudience(AUDIENCE)
793             .setClientId(CLIENT_ID)
794             .setClientSecret(CLIENT_SECRET)
795             .setRefreshToken(REFRESH_TOKEN)
796             .setTokenUrl(TOKEN_URL)
797             .setTokenInfoUrl(TOKEN_INFO_URL)
798             .setRevokeUrl(REVOKE_URL)
799             .setAccessToken(new AccessToken(ACCESS_TOKEN, /* expirationTime= */ null))
800             .setQuotaProjectId(QUOTA_PROJECT)
801             .build();
802 
803     ExternalAccountAuthorizedUserCredentials secondCredentials =
804         ExternalAccountAuthorizedUserCredentials.newBuilder()
805             .setAudience(AUDIENCE)
806             .setClientId(CLIENT_ID)
807             .setClientSecret(CLIENT_SECRET)
808             .setRefreshToken(REFRESH_TOKEN)
809             .setTokenUrl(TOKEN_URL)
810             .setTokenInfoUrl(TOKEN_INFO_URL)
811             .setRevokeUrl(REVOKE_URL)
812             .setAccessToken(new AccessToken(ACCESS_TOKEN, /* expirationTime= */ null))
813             .setQuotaProjectId(QUOTA_PROJECT)
814             .build();
815 
816     assertEquals(credentials, secondCredentials);
817     assertEquals(secondCredentials, credentials);
818     assertEquals(credentials.hashCode(), secondCredentials.hashCode());
819   }
820 
821   @Test
hashCode_differentCredentials()822   public void hashCode_differentCredentials() {
823     ExternalAccountAuthorizedUserCredentials credentials =
824         ExternalAccountAuthorizedUserCredentials.newBuilder()
825             .setAudience(AUDIENCE)
826             .setClientId(CLIENT_ID)
827             .setClientSecret(CLIENT_SECRET)
828             .setRefreshToken(REFRESH_TOKEN)
829             .setTokenUrl(TOKEN_URL)
830             .setTokenInfoUrl(TOKEN_INFO_URL)
831             .setRevokeUrl(REVOKE_URL)
832             .build();
833 
834     // Second credentials have an access token set.
835     ExternalAccountAuthorizedUserCredentials secondCredentials =
836         ExternalAccountAuthorizedUserCredentials.newBuilder()
837             .setAudience(AUDIENCE)
838             .setClientId(CLIENT_ID)
839             .setClientSecret(CLIENT_SECRET)
840             .setRefreshToken(REFRESH_TOKEN)
841             .setTokenUrl(TOKEN_URL)
842             .setTokenInfoUrl(TOKEN_INFO_URL)
843             .setRevokeUrl(REVOKE_URL)
844             .setAccessToken(new AccessToken(ACCESS_TOKEN, /* expirationTime= */ null))
845             .setQuotaProjectId(QUOTA_PROJECT)
846             .build();
847 
848     assertNotEquals(secondCredentials, credentials);
849     assertNotEquals(credentials, secondCredentials);
850     assertNotEquals(credentials.hashCode(), secondCredentials.hashCode());
851   }
852 
853   @Test
hashCode_differentCredentialsWithCredentialsFile()854   public void hashCode_differentCredentialsWithCredentialsFile() throws IOException {
855     // Optional fields that can be specified in the credentials file.
856     List<String> fields = Arrays.asList("audience", "revoke_url", "quota_project_id");
857 
858     // Credential initialized with all fields.
859     ExternalAccountAuthorizedUserCredentials credentials =
860         ExternalAccountAuthorizedUserCredentials.fromJson(buildJsonCredentials(), transportFactory);
861 
862     for (String field : fields) {
863       // Build credential with one of these fields missing.
864       GenericJson json = buildJsonCredentials();
865       json.remove(field);
866 
867       ExternalAccountAuthorizedUserCredentials secondCredentials =
868           ExternalAccountAuthorizedUserCredentials.fromJson(json, transportFactory);
869       assertNotEquals(secondCredentials, credentials);
870       assertNotEquals(credentials, secondCredentials);
871       assertNotEquals(credentials.hashCode(), secondCredentials.hashCode());
872     }
873   }
874 
875   @Test
equals_differentCredentials()876   public void equals_differentCredentials() throws IOException {
877     UserCredentials userCredentials =
878         UserCredentials.newBuilder()
879             .setClientId(CLIENT_ID)
880             .setClientSecret(CLIENT_SECRET)
881             .setRefreshToken(REFRESH_TOKEN)
882             .setHttpTransportFactory(transportFactory)
883             .setTokenServerUri(URI.create(TOKEN_URL))
884             .setQuotaProjectId(QUOTA_PROJECT)
885             .build();
886 
887     ExternalAccountAuthorizedUserCredentials credentials =
888         ExternalAccountAuthorizedUserCredentials.fromJson(buildJsonCredentials(), transportFactory);
889 
890     assertNotEquals(userCredentials, credentials);
891     assertNotEquals(credentials, userCredentials);
892   }
893 
894   @Test
equals_differentAudience()895   public void equals_differentAudience() {
896     ExternalAccountAuthorizedUserCredentials credentials =
897         ExternalAccountAuthorizedUserCredentials.newBuilder()
898             .setAudience(AUDIENCE)
899             .setClientId(CLIENT_ID)
900             .setClientSecret(CLIENT_SECRET)
901             .setRefreshToken(REFRESH_TOKEN)
902             .setTokenUrl(TOKEN_URL)
903             .setTokenInfoUrl(TOKEN_INFO_URL)
904             .setRevokeUrl(REVOKE_URL)
905             .setAccessToken(new AccessToken(ACCESS_TOKEN, /* expirationTime= */ null))
906             .setQuotaProjectId(QUOTA_PROJECT)
907             .setHttpTransportFactory(transportFactory)
908             .build();
909 
910     ExternalAccountAuthorizedUserCredentials secondCredentials =
911         credentials.toBuilder().setAudience("different").build();
912 
913     assertNotEquals(secondCredentials, credentials);
914     assertNotEquals(credentials, secondCredentials);
915     assertNotEquals(credentials.hashCode(), secondCredentials.hashCode());
916   }
917 
918   @Test
equals_differentClientId()919   public void equals_differentClientId() {
920     ExternalAccountAuthorizedUserCredentials credentials =
921         ExternalAccountAuthorizedUserCredentials.newBuilder()
922             .setAudience(AUDIENCE)
923             .setClientId(CLIENT_ID)
924             .setClientSecret(CLIENT_SECRET)
925             .setRefreshToken(REFRESH_TOKEN)
926             .setTokenUrl(TOKEN_URL)
927             .setTokenInfoUrl(TOKEN_INFO_URL)
928             .setRevokeUrl(REVOKE_URL)
929             .setAccessToken(new AccessToken(ACCESS_TOKEN, /* expirationTime= */ null))
930             .setQuotaProjectId(QUOTA_PROJECT)
931             .setHttpTransportFactory(transportFactory)
932             .build();
933 
934     ExternalAccountAuthorizedUserCredentials secondCredentials =
935         credentials.toBuilder().setClientId("different").build();
936 
937     assertNotEquals(secondCredentials, credentials);
938     assertNotEquals(credentials, secondCredentials);
939     assertNotEquals(credentials.hashCode(), secondCredentials.hashCode());
940   }
941 
942   @Test
equals_differentClientSecret()943   public void equals_differentClientSecret() {
944     ExternalAccountAuthorizedUserCredentials credentials =
945         ExternalAccountAuthorizedUserCredentials.newBuilder()
946             .setAudience(AUDIENCE)
947             .setClientId(CLIENT_ID)
948             .setClientSecret(CLIENT_SECRET)
949             .setRefreshToken(REFRESH_TOKEN)
950             .setTokenUrl(TOKEN_URL)
951             .setTokenInfoUrl(TOKEN_INFO_URL)
952             .setRevokeUrl(REVOKE_URL)
953             .setAccessToken(new AccessToken(ACCESS_TOKEN, /* expirationTime= */ null))
954             .setQuotaProjectId(QUOTA_PROJECT)
955             .setHttpTransportFactory(transportFactory)
956             .build();
957 
958     ExternalAccountAuthorizedUserCredentials secondCredentials =
959         credentials.toBuilder().setClientSecret("different").build();
960 
961     assertNotEquals(secondCredentials, credentials);
962     assertNotEquals(credentials, secondCredentials);
963     assertNotEquals(credentials.hashCode(), secondCredentials.hashCode());
964   }
965 
966   @Test
equals_differentRefreshToken()967   public void equals_differentRefreshToken() {
968     ExternalAccountAuthorizedUserCredentials credentials =
969         ExternalAccountAuthorizedUserCredentials.newBuilder()
970             .setAudience(AUDIENCE)
971             .setClientId(CLIENT_ID)
972             .setClientSecret(CLIENT_SECRET)
973             .setRefreshToken(REFRESH_TOKEN)
974             .setTokenUrl(TOKEN_URL)
975             .setTokenInfoUrl(TOKEN_INFO_URL)
976             .setRevokeUrl(REVOKE_URL)
977             .setAccessToken(new AccessToken(ACCESS_TOKEN, /* expirationTime= */ null))
978             .setQuotaProjectId(QUOTA_PROJECT)
979             .setHttpTransportFactory(transportFactory)
980             .build();
981 
982     ExternalAccountAuthorizedUserCredentials secondCredentials =
983         credentials.toBuilder().setRefreshToken("different").build();
984 
985     assertNotEquals(secondCredentials, credentials);
986     assertNotEquals(credentials, secondCredentials);
987     assertNotEquals(credentials.hashCode(), secondCredentials.hashCode());
988   }
989 
990   @Test
equals_differentTokenUrl()991   public void equals_differentTokenUrl() {
992     ExternalAccountAuthorizedUserCredentials credentials =
993         ExternalAccountAuthorizedUserCredentials.newBuilder()
994             .setAudience(AUDIENCE)
995             .setClientId(CLIENT_ID)
996             .setClientSecret(CLIENT_SECRET)
997             .setRefreshToken(REFRESH_TOKEN)
998             .setTokenUrl(TOKEN_URL)
999             .setTokenInfoUrl(TOKEN_INFO_URL)
1000             .setRevokeUrl(REVOKE_URL)
1001             .setAccessToken(new AccessToken(ACCESS_TOKEN, /* expirationTime= */ null))
1002             .setQuotaProjectId(QUOTA_PROJECT)
1003             .setHttpTransportFactory(transportFactory)
1004             .build();
1005 
1006     ExternalAccountAuthorizedUserCredentials secondCredentials =
1007         credentials.toBuilder().setTokenUrl("different").build();
1008 
1009     assertNotEquals(secondCredentials, credentials);
1010     assertNotEquals(credentials, secondCredentials);
1011     assertNotEquals(credentials.hashCode(), secondCredentials.hashCode());
1012   }
1013 
1014   @Test
equals_differentTokenInfoUrl()1015   public void equals_differentTokenInfoUrl() {
1016     ExternalAccountAuthorizedUserCredentials credentials =
1017         ExternalAccountAuthorizedUserCredentials.newBuilder()
1018             .setAudience(AUDIENCE)
1019             .setClientId(CLIENT_ID)
1020             .setClientSecret(CLIENT_SECRET)
1021             .setRefreshToken(REFRESH_TOKEN)
1022             .setTokenUrl(TOKEN_URL)
1023             .setTokenInfoUrl(TOKEN_INFO_URL)
1024             .setRevokeUrl(REVOKE_URL)
1025             .setAccessToken(new AccessToken(ACCESS_TOKEN, /* expirationTime= */ null))
1026             .setQuotaProjectId(QUOTA_PROJECT)
1027             .setHttpTransportFactory(transportFactory)
1028             .build();
1029 
1030     ExternalAccountAuthorizedUserCredentials secondCredentials =
1031         credentials.toBuilder().setTokenInfoUrl("different").build();
1032 
1033     assertNotEquals(secondCredentials, credentials);
1034     assertNotEquals(credentials, secondCredentials);
1035     assertNotEquals(credentials.hashCode(), secondCredentials.hashCode());
1036   }
1037 
1038   @Test
equals_differentRevokeUrl()1039   public void equals_differentRevokeUrl() {
1040     ExternalAccountAuthorizedUserCredentials credentials =
1041         ExternalAccountAuthorizedUserCredentials.newBuilder()
1042             .setAudience(AUDIENCE)
1043             .setClientId(CLIENT_ID)
1044             .setClientSecret(CLIENT_SECRET)
1045             .setRefreshToken(REFRESH_TOKEN)
1046             .setTokenUrl(TOKEN_URL)
1047             .setTokenInfoUrl(TOKEN_INFO_URL)
1048             .setRevokeUrl(REVOKE_URL)
1049             .setAccessToken(new AccessToken(ACCESS_TOKEN, /* expirationTime= */ null))
1050             .setQuotaProjectId(QUOTA_PROJECT)
1051             .setHttpTransportFactory(transportFactory)
1052             .build();
1053 
1054     ExternalAccountAuthorizedUserCredentials secondCredentials =
1055         credentials.toBuilder().setRevokeUrl("different").build();
1056 
1057     assertNotEquals(secondCredentials, credentials);
1058     assertNotEquals(credentials, secondCredentials);
1059     assertNotEquals(credentials.hashCode(), secondCredentials.hashCode());
1060   }
1061 
1062   @Test
equals_differentAccessToken()1063   public void equals_differentAccessToken() {
1064     ExternalAccountAuthorizedUserCredentials credentials =
1065         ExternalAccountAuthorizedUserCredentials.newBuilder()
1066             .setAudience(AUDIENCE)
1067             .setClientId(CLIENT_ID)
1068             .setClientSecret(CLIENT_SECRET)
1069             .setRefreshToken(REFRESH_TOKEN)
1070             .setTokenUrl(TOKEN_URL)
1071             .setTokenInfoUrl(TOKEN_INFO_URL)
1072             .setRevokeUrl(REVOKE_URL)
1073             .setAccessToken(new AccessToken(ACCESS_TOKEN, new Date()))
1074             .setQuotaProjectId(QUOTA_PROJECT)
1075             .setHttpTransportFactory(transportFactory)
1076             .build();
1077 
1078     ExternalAccountAuthorizedUserCredentials secondCredentials =
1079         credentials.toBuilder().setAccessToken(new AccessToken("different", new Date())).build();
1080 
1081     assertNotEquals(secondCredentials, credentials);
1082     assertNotEquals(credentials, secondCredentials);
1083     assertNotEquals(credentials.hashCode(), secondCredentials.hashCode());
1084   }
1085 
1086   @Test
equals_differentQuotaProjectId()1087   public void equals_differentQuotaProjectId() {
1088     ExternalAccountAuthorizedUserCredentials credentials =
1089         ExternalAccountAuthorizedUserCredentials.newBuilder()
1090             .setAudience(AUDIENCE)
1091             .setClientId(CLIENT_ID)
1092             .setClientSecret(CLIENT_SECRET)
1093             .setRefreshToken(REFRESH_TOKEN)
1094             .setTokenUrl(TOKEN_URL)
1095             .setTokenInfoUrl(TOKEN_INFO_URL)
1096             .setRevokeUrl(REVOKE_URL)
1097             .setAccessToken(new AccessToken(ACCESS_TOKEN, /* expirationTime= */ null))
1098             .setQuotaProjectId(QUOTA_PROJECT)
1099             .setHttpTransportFactory(transportFactory)
1100             .build();
1101 
1102     ExternalAccountAuthorizedUserCredentials secondCredentials =
1103         credentials.toBuilder().setQuotaProjectId("different").build();
1104 
1105     assertNotEquals(secondCredentials, credentials);
1106     assertNotEquals(credentials, secondCredentials);
1107     assertNotEquals(credentials.hashCode(), secondCredentials.hashCode());
1108   }
1109 
1110   @Test
equals_differentTransportFactory()1111   public void equals_differentTransportFactory() {
1112     ExternalAccountAuthorizedUserCredentials credentials =
1113         ExternalAccountAuthorizedUserCredentials.newBuilder()
1114             .setAudience(AUDIENCE)
1115             .setClientId(CLIENT_ID)
1116             .setClientSecret(CLIENT_SECRET)
1117             .setRefreshToken(REFRESH_TOKEN)
1118             .setTokenUrl(TOKEN_URL)
1119             .setTokenInfoUrl(TOKEN_INFO_URL)
1120             .setRevokeUrl(REVOKE_URL)
1121             .setAccessToken(new AccessToken(ACCESS_TOKEN, /* expirationTime= */ null))
1122             .setQuotaProjectId(QUOTA_PROJECT)
1123             .setHttpTransportFactory(transportFactory)
1124             .build();
1125 
1126     ExternalAccountAuthorizedUserCredentials secondCredentials =
1127         credentials.toBuilder().setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY).build();
1128 
1129     assertNotEquals(secondCredentials, credentials);
1130     assertNotEquals(credentials, secondCredentials);
1131     assertNotEquals(credentials.hashCode(), secondCredentials.hashCode());
1132   }
1133 
1134   @Test
equals_differentUniverseDomain()1135   public void equals_differentUniverseDomain() {
1136     ExternalAccountAuthorizedUserCredentials credentials =
1137         ExternalAccountAuthorizedUserCredentials.newBuilder()
1138             .setAudience(AUDIENCE)
1139             .setClientId(CLIENT_ID)
1140             .setClientSecret(CLIENT_SECRET)
1141             .setRefreshToken(REFRESH_TOKEN)
1142             .setTokenUrl(TOKEN_URL)
1143             .setTokenInfoUrl(TOKEN_INFO_URL)
1144             .setRevokeUrl(REVOKE_URL)
1145             .setAccessToken(new AccessToken(ACCESS_TOKEN, /* expirationTime= */ null))
1146             .setQuotaProjectId(QUOTA_PROJECT)
1147             .setHttpTransportFactory(transportFactory)
1148             .setUniverseDomain(UNIVERSE_DOMAIN)
1149             .build();
1150 
1151     ExternalAccountAuthorizedUserCredentials secondCredentials =
1152         credentials.toBuilder().setUniverseDomain("different").build();
1153 
1154     assertNotEquals(secondCredentials, credentials);
1155     assertNotEquals(credentials, secondCredentials);
1156     assertNotEquals(credentials.hashCode(), secondCredentials.hashCode());
1157   }
1158 
1159   @Test
toString_expectedFormat()1160   public void toString_expectedFormat() {
1161     AccessToken accessToken = new AccessToken(ACCESS_TOKEN, new Date());
1162     ExternalAccountAuthorizedUserCredentials credentials =
1163         ExternalAccountAuthorizedUserCredentials.newBuilder()
1164             .setAudience(AUDIENCE)
1165             .setClientId(CLIENT_ID)
1166             .setClientSecret(CLIENT_SECRET)
1167             .setRefreshToken(REFRESH_TOKEN)
1168             .setTokenUrl(TOKEN_URL)
1169             .setTokenInfoUrl(TOKEN_INFO_URL)
1170             .setRevokeUrl(REVOKE_URL)
1171             .setAccessToken(accessToken)
1172             .setQuotaProjectId(QUOTA_PROJECT)
1173             .setHttpTransportFactory(transportFactory)
1174             .build();
1175 
1176     String expectedToString =
1177         String.format(
1178             "ExternalAccountAuthorizedUserCredentials{requestMetadata=%s, temporaryAccess=%s, "
1179                 + "clientId=%s, clientSecret=%s, refreshToken=%s, tokenUrl=%s, tokenInfoUrl=%s, "
1180                 + "revokeUrl=%s, audience=%s, transportFactoryClassName=%s, quotaProjectId=%s}",
1181             ImmutableMap.of(
1182                 AuthHttpConstants.AUTHORIZATION,
1183                 ImmutableList.of(OAuth2Utils.BEARER_PREFIX + accessToken.getTokenValue())),
1184             accessToken,
1185             CLIENT_ID,
1186             CLIENT_SECRET,
1187             REFRESH_TOKEN,
1188             TOKEN_URL,
1189             TOKEN_INFO_URL,
1190             REVOKE_URL,
1191             AUDIENCE,
1192             transportFactory.getClass().getName(),
1193             QUOTA_PROJECT);
1194 
1195     assertEquals(expectedToString, credentials.toString());
1196   }
1197 
1198   @Test
serialize()1199   public void serialize() throws IOException, ClassNotFoundException {
1200     ExternalAccountAuthorizedUserCredentials credentials =
1201         ExternalAccountAuthorizedUserCredentials.newBuilder()
1202             .setAudience(AUDIENCE)
1203             .setClientId(CLIENT_ID)
1204             .setClientSecret(CLIENT_SECRET)
1205             .setRefreshToken(REFRESH_TOKEN)
1206             .setTokenUrl(TOKEN_URL)
1207             .setTokenInfoUrl(TOKEN_INFO_URL)
1208             .setRevokeUrl(REVOKE_URL)
1209             .setAccessToken(new AccessToken(ACCESS_TOKEN, /* expirationTime= */ null))
1210             .setQuotaProjectId(QUOTA_PROJECT)
1211             .build();
1212 
1213     ExternalAccountAuthorizedUserCredentials deserializedCredentials =
1214         serializeAndDeserialize(credentials);
1215     assertEquals(credentials, deserializedCredentials);
1216     assertEquals(credentials.hashCode(), deserializedCredentials.hashCode());
1217     assertEquals(credentials.toString(), deserializedCredentials.toString());
1218     assertSame(deserializedCredentials.clock, Clock.SYSTEM);
1219   }
1220 
buildJsonCredentials()1221   static GenericJson buildJsonCredentials() {
1222     GenericJson json = new GenericJson();
1223     json.put("type", EXTERNAL_ACCOUNT_AUTHORIZED_USER_FILE_TYPE);
1224     json.put("audience", AUDIENCE);
1225     json.put("refresh_token", REFRESH_TOKEN);
1226     json.put("client_id", CLIENT_ID);
1227     json.put("client_secret", CLIENT_SECRET);
1228     json.put("token_url", TOKEN_URL);
1229     json.put("token_info_url", TOKEN_INFO_URL);
1230     json.put("revoke_url", REVOKE_URL);
1231     json.put("quota_project_id", QUOTA_PROJECT);
1232     json.put("universe_domain", UNIVERSE_DOMAIN);
1233     return json;
1234   }
1235 
validateAuthHeader(MockLowLevelHttpRequest request)1236   private static void validateAuthHeader(MockLowLevelHttpRequest request) {
1237     Map<String, List<String>> headers = request.getHeaders();
1238     List<String> authHeader = headers.get("authorization");
1239 
1240     assertEquals(BASIC_AUTH, authHeader.iterator().next());
1241     assertEquals(1, authHeader.size());
1242   }
1243 }
1244