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.assertEquals;
35 import static org.junit.Assert.assertFalse;
36 import static org.junit.Assert.assertNotEquals;
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.json.GenericJson;
44 import com.google.api.client.testing.http.MockLowLevelHttpResponse;
45 import com.google.api.client.util.Clock;
46 import com.google.auth.RequestMetadataCallback;
47 import com.google.auth.TestUtils;
48 import com.google.auth.http.AuthHttpConstants;
49 import com.google.common.collect.ImmutableList;
50 import com.google.common.collect.ImmutableMap;
51 import java.io.ByteArrayInputStream;
52 import java.io.File;
53 import java.io.FileInputStream;
54 import java.io.IOException;
55 import java.io.InputStream;
56 import java.net.URI;
57 import java.time.Duration;
58 import java.time.temporal.ChronoUnit;
59 import java.util.Collection;
60 import java.util.Collections;
61 import java.util.Date;
62 import java.util.List;
63 import java.util.Map;
64 import java.util.concurrent.atomic.AtomicBoolean;
65 import org.junit.Test;
66 import org.junit.runner.RunWith;
67 import org.junit.runners.JUnit4;
68 
69 /** Test case for {@link UserCredentials}. */
70 @RunWith(JUnit4.class)
71 public class UserCredentialsTest extends BaseSerializationTest {
72 
73   private static final String CLIENT_SECRET = "jakuaL9YyieakhECKL2SwZcu";
74   private static final String CLIENT_ID = "ya29.1.AADtN_UtlxN3PuGAxrN2XQnZTVRvDyVWnYq4I6dws";
75   private static final String REFRESH_TOKEN = "1/Tl6awhpFjkMkSJoj1xsli0H2eL5YsMgU_NKPY2TyGWY";
76   private static final String ACCESS_TOKEN = "1/MkSJoj1xsli0AccessToken_NKPY2";
77   private static final String QUOTA_PROJECT = "sample-quota-project-id";
78   private static final Collection<String> SCOPES = Collections.singletonList("dummy.scope");
79   private static final URI CALL_URI = URI.create("http://googleapis.com/testapi/v1/foo");
80   public static final String DEFAULT_ID_TOKEN =
81       "eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmMzc1ODkwOGI3OTIyO"
82           + "TNhZDk3N2EwYjk5MWQ5OGE3N2Y0ZWVlY2QiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL2Zvby5iYXIiL"
83           + "CJhenAiOiIxMDIxMDE1NTA4MzQyMDA3MDg1NjgiLCJleHAiOjE1NjQ0NzUwNTEsImlhdCI6MTU2NDQ3MTQ1MSwi"
84           + "aXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTAyMTAxNTUwODM0MjAwNzA4NTY4In0"
85           + ".redacted";
86 
87   @Test(expected = IllegalStateException.class)
constructor_accessAndRefreshTokenNull_throws()88   public void constructor_accessAndRefreshTokenNull_throws() {
89     UserCredentials.newBuilder().setClientId(CLIENT_ID).setClientSecret(CLIENT_SECRET).build();
90   }
91 
92   @Test
constructor()93   public void constructor() {
94     UserCredentials credentials =
95         UserCredentials.newBuilder()
96             .setClientId(CLIENT_ID)
97             .setClientSecret(CLIENT_SECRET)
98             .setRefreshToken(REFRESH_TOKEN)
99             .setQuotaProjectId(QUOTA_PROJECT)
100             .build();
101     assertEquals(CLIENT_ID, credentials.getClientId());
102     assertEquals(CLIENT_SECRET, credentials.getClientSecret());
103     assertEquals(REFRESH_TOKEN, credentials.getRefreshToken());
104     assertEquals(QUOTA_PROJECT, credentials.getQuotaProjectId());
105   }
106 
107   @Test
createScoped_same()108   public void createScoped_same() {
109     UserCredentials userCredentials =
110         UserCredentials.newBuilder()
111             .setClientId(CLIENT_ID)
112             .setClientSecret(CLIENT_SECRET)
113             .setRefreshToken(REFRESH_TOKEN)
114             .build();
115     assertSame(userCredentials, userCredentials.createScoped(SCOPES));
116   }
117 
118   @Test
createScopedRequired_false()119   public void createScopedRequired_false() {
120     UserCredentials userCredentials =
121         UserCredentials.newBuilder()
122             .setClientId(CLIENT_ID)
123             .setClientSecret(CLIENT_SECRET)
124             .setRefreshToken(REFRESH_TOKEN)
125             .build();
126     assertFalse(userCredentials.createScopedRequired());
127   }
128 
129   @Test
fromJson_hasAccessToken()130   public void fromJson_hasAccessToken() throws IOException {
131     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
132     transportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET);
133     transportFactory.transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN);
134     GenericJson json = writeUserJson(CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, null);
135 
136     GoogleCredentials credentials = UserCredentials.fromJson(json, transportFactory);
137 
138     Map<String, List<String>> metadata = credentials.getRequestMetadata(CALL_URI);
139     TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN);
140   }
141 
142   @Test
fromJson_hasQuotaProjectId()143   public void fromJson_hasQuotaProjectId() throws IOException {
144     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
145     transportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET);
146     transportFactory.transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN);
147     GenericJson json = writeUserJson(CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, QUOTA_PROJECT);
148 
149     GoogleCredentials credentials = UserCredentials.fromJson(json, transportFactory);
150 
151     Map<String, List<String>> metadata = credentials.getRequestMetadata(CALL_URI);
152     assertTrue(metadata.containsKey(GoogleCredentials.QUOTA_PROJECT_ID_HEADER_KEY));
153     assertEquals(
154         metadata.get(GoogleCredentials.QUOTA_PROJECT_ID_HEADER_KEY),
155         Collections.singletonList(QUOTA_PROJECT));
156   }
157 
158   @Test
getRequestMetadata_initialToken_hasAccessToken()159   public void getRequestMetadata_initialToken_hasAccessToken() throws IOException {
160     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
161     transportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET);
162     AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null);
163     UserCredentials userCredentials =
164         UserCredentials.newBuilder()
165             .setClientId(CLIENT_ID)
166             .setClientSecret(CLIENT_SECRET)
167             .setAccessToken(accessToken)
168             .setHttpTransportFactory(transportFactory)
169             .build();
170 
171     Map<String, List<String>> metadata = userCredentials.getRequestMetadata(CALL_URI);
172 
173     TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN);
174   }
175 
176   @Test
getRequestMetadata_initialTokenRefreshed_throws()177   public void getRequestMetadata_initialTokenRefreshed_throws() throws IOException {
178     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
179     transportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET);
180     AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null);
181     UserCredentials userCredentials =
182         UserCredentials.newBuilder()
183             .setClientId(CLIENT_ID)
184             .setClientSecret(CLIENT_SECRET)
185             .setAccessToken(accessToken)
186             .setHttpTransportFactory(transportFactory)
187             .build();
188 
189     try {
190       userCredentials.refresh();
191       fail("Should not be able to refresh without refresh token.");
192     } catch (IllegalStateException expected) {
193       // Expected
194     }
195   }
196 
197   @Test
getRequestMetadata_fromRefreshToken_hasAccessToken()198   public void getRequestMetadata_fromRefreshToken_hasAccessToken() throws IOException {
199     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
200     transportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET);
201     transportFactory.transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN);
202     UserCredentials userCredentials =
203         UserCredentials.newBuilder()
204             .setClientId(CLIENT_ID)
205             .setClientSecret(CLIENT_SECRET)
206             .setRefreshToken(REFRESH_TOKEN)
207             .setHttpTransportFactory(transportFactory)
208             .build();
209 
210     Map<String, List<String>> metadata = userCredentials.getRequestMetadata(CALL_URI);
211 
212     TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN);
213   }
214 
215   @Test
getRequestMetadata_customTokenServer_hasAccessToken()216   public void getRequestMetadata_customTokenServer_hasAccessToken() throws IOException {
217     final URI TOKEN_SERVER = URI.create("https://foo.com/bar");
218     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
219     transportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET);
220     transportFactory.transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN);
221     transportFactory.transport.setTokenServerUri(TOKEN_SERVER);
222     UserCredentials userCredentials =
223         UserCredentials.newBuilder()
224             .setClientId(CLIENT_ID)
225             .setClientSecret(CLIENT_SECRET)
226             .setRefreshToken(REFRESH_TOKEN)
227             .setHttpTransportFactory(transportFactory)
228             .setTokenServerUri(TOKEN_SERVER)
229             .build();
230 
231     Map<String, List<String>> metadata = userCredentials.getRequestMetadata(CALL_URI);
232 
233     TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN);
234   }
235 
236   @Test
equals_true()237   public void equals_true() throws IOException {
238     final URI tokenServer = URI.create("https://foo.com/bar");
239     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
240     AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null);
241     UserCredentials credentials =
242         UserCredentials.newBuilder()
243             .setClientId(CLIENT_ID)
244             .setClientSecret(CLIENT_SECRET)
245             .setRefreshToken(REFRESH_TOKEN)
246             .setAccessToken(accessToken)
247             .setHttpTransportFactory(transportFactory)
248             .setTokenServerUri(tokenServer)
249             .setQuotaProjectId(QUOTA_PROJECT)
250             .build();
251     UserCredentials otherCredentials =
252         UserCredentials.newBuilder()
253             .setClientId(CLIENT_ID)
254             .setClientSecret(CLIENT_SECRET)
255             .setRefreshToken(REFRESH_TOKEN)
256             .setAccessToken(accessToken)
257             .setHttpTransportFactory(transportFactory)
258             .setTokenServerUri(tokenServer)
259             .setQuotaProjectId(QUOTA_PROJECT)
260             .build();
261     assertTrue(credentials.equals(otherCredentials));
262     assertTrue(otherCredentials.equals(credentials));
263   }
264 
265   @Test
equals_false_clientId()266   public void equals_false_clientId() throws IOException {
267     final URI tokenServer1 = URI.create("https://foo1.com/bar");
268     AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null);
269     MockHttpTransportFactory httpTransportFactory = new MockHttpTransportFactory();
270     UserCredentials credentials =
271         UserCredentials.newBuilder()
272             .setClientId(CLIENT_ID)
273             .setClientSecret(CLIENT_SECRET)
274             .setRefreshToken(REFRESH_TOKEN)
275             .setAccessToken(accessToken)
276             .setHttpTransportFactory(httpTransportFactory)
277             .setTokenServerUri(tokenServer1)
278             .build();
279     UserCredentials otherCredentials =
280         UserCredentials.newBuilder()
281             .setClientId("other client id")
282             .setClientSecret(CLIENT_SECRET)
283             .setRefreshToken(REFRESH_TOKEN)
284             .setAccessToken(accessToken)
285             .setHttpTransportFactory(httpTransportFactory)
286             .setTokenServerUri(tokenServer1)
287             .build();
288     assertFalse(credentials.equals(otherCredentials));
289     assertFalse(otherCredentials.equals(credentials));
290   }
291 
292   @Test
equals_false_clientSecret()293   public void equals_false_clientSecret() throws IOException {
294     final URI tokenServer1 = URI.create("https://foo1.com/bar");
295     AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null);
296     MockHttpTransportFactory httpTransportFactory = new MockHttpTransportFactory();
297     UserCredentials credentials =
298         UserCredentials.newBuilder()
299             .setClientId(CLIENT_ID)
300             .setClientSecret(CLIENT_SECRET)
301             .setRefreshToken(REFRESH_TOKEN)
302             .setAccessToken(accessToken)
303             .setHttpTransportFactory(httpTransportFactory)
304             .setTokenServerUri(tokenServer1)
305             .build();
306     UserCredentials otherCredentials =
307         UserCredentials.newBuilder()
308             .setClientId(CLIENT_ID)
309             .setClientSecret("other client secret")
310             .setRefreshToken(REFRESH_TOKEN)
311             .setAccessToken(accessToken)
312             .setHttpTransportFactory(httpTransportFactory)
313             .setTokenServerUri(tokenServer1)
314             .build();
315     assertFalse(credentials.equals(otherCredentials));
316     assertFalse(otherCredentials.equals(credentials));
317   }
318 
319   @Test
equals_false_refreshToken()320   public void equals_false_refreshToken() throws IOException {
321     final URI tokenServer1 = URI.create("https://foo1.com/bar");
322     AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null);
323     MockHttpTransportFactory httpTransportFactory = new MockHttpTransportFactory();
324     OAuth2Credentials credentials =
325         UserCredentials.newBuilder()
326             .setClientId(CLIENT_ID)
327             .setClientSecret(CLIENT_SECRET)
328             .setRefreshToken(REFRESH_TOKEN)
329             .setAccessToken(accessToken)
330             .setHttpTransportFactory(httpTransportFactory)
331             .setTokenServerUri(tokenServer1)
332             .build();
333     OAuth2Credentials otherCredentials =
334         UserCredentials.newBuilder()
335             .setClientId(CLIENT_ID)
336             .setClientSecret(CLIENT_SECRET)
337             .setRefreshToken("otherRefreshToken")
338             .setAccessToken(accessToken)
339             .setHttpTransportFactory(httpTransportFactory)
340             .setTokenServerUri(tokenServer1)
341             .build();
342     assertFalse(credentials.equals(otherCredentials));
343     assertFalse(otherCredentials.equals(credentials));
344   }
345 
346   @Test
equals_false_accessToken()347   public void equals_false_accessToken() throws IOException {
348     final URI tokenServer1 = URI.create("https://foo1.com/bar");
349     AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null);
350     AccessToken otherAccessToken = new AccessToken("otherAccessToken", null);
351     MockHttpTransportFactory httpTransportFactory = new MockHttpTransportFactory();
352     UserCredentials credentials =
353         UserCredentials.newBuilder()
354             .setClientId(CLIENT_ID)
355             .setClientSecret(CLIENT_SECRET)
356             .setRefreshToken(REFRESH_TOKEN)
357             .setAccessToken(accessToken)
358             .setHttpTransportFactory(httpTransportFactory)
359             .setTokenServerUri(tokenServer1)
360             .build();
361     UserCredentials otherCredentials =
362         UserCredentials.newBuilder()
363             .setClientId(CLIENT_ID)
364             .setClientSecret(CLIENT_SECRET)
365             .setRefreshToken(REFRESH_TOKEN)
366             .setAccessToken(otherAccessToken)
367             .setHttpTransportFactory(httpTransportFactory)
368             .setTokenServerUri(tokenServer1)
369             .build();
370     assertFalse(credentials.equals(otherCredentials));
371     assertFalse(otherCredentials.equals(credentials));
372     assertNotEquals(credentials.hashCode(), otherAccessToken.hashCode());
373   }
374 
375   @Test
equals_false_transportFactory()376   public void equals_false_transportFactory() throws IOException {
377     final URI tokenServer1 = URI.create("https://foo1.com/bar");
378     AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null);
379     MockHttpTransportFactory httpTransportFactory = new MockHttpTransportFactory();
380     MockTokenServerTransportFactory serverTransportFactory = new MockTokenServerTransportFactory();
381     UserCredentials credentials =
382         UserCredentials.newBuilder()
383             .setClientId(CLIENT_ID)
384             .setClientSecret(CLIENT_SECRET)
385             .setRefreshToken(REFRESH_TOKEN)
386             .setAccessToken(accessToken)
387             .setHttpTransportFactory(httpTransportFactory)
388             .setTokenServerUri(tokenServer1)
389             .build();
390     UserCredentials otherCredentials =
391         UserCredentials.newBuilder()
392             .setClientId(CLIENT_ID)
393             .setClientSecret(CLIENT_SECRET)
394             .setRefreshToken(REFRESH_TOKEN)
395             .setAccessToken(accessToken)
396             .setHttpTransportFactory(serverTransportFactory)
397             .setTokenServerUri(tokenServer1)
398             .build();
399     assertFalse(credentials.equals(otherCredentials));
400     assertFalse(otherCredentials.equals(credentials));
401   }
402 
403   @Test
equals_false_tokenServer()404   public void equals_false_tokenServer() throws IOException {
405     final URI tokenServer1 = URI.create("https://foo1.com/bar");
406     final URI tokenServer2 = URI.create("https://foo2.com/bar");
407     AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null);
408     MockHttpTransportFactory httpTransportFactory = new MockHttpTransportFactory();
409     UserCredentials credentials =
410         UserCredentials.newBuilder()
411             .setClientId(CLIENT_ID)
412             .setClientSecret(CLIENT_SECRET)
413             .setRefreshToken(REFRESH_TOKEN)
414             .setAccessToken(accessToken)
415             .setHttpTransportFactory(httpTransportFactory)
416             .setTokenServerUri(tokenServer1)
417             .build();
418     UserCredentials otherCredentials =
419         UserCredentials.newBuilder()
420             .setClientId(CLIENT_ID)
421             .setClientSecret(CLIENT_SECRET)
422             .setRefreshToken(REFRESH_TOKEN)
423             .setAccessToken(accessToken)
424             .setHttpTransportFactory(httpTransportFactory)
425             .setTokenServerUri(tokenServer2)
426             .build();
427     assertFalse(credentials.equals(otherCredentials));
428     assertFalse(otherCredentials.equals(credentials));
429   }
430 
431   @Test
equals_false_quotaProjectId()432   public void equals_false_quotaProjectId() throws IOException {
433     final String quotaProject1 = "sample-id-1";
434     final String quotaProject2 = "sample-id-2";
435     AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null);
436     MockHttpTransportFactory httpTransportFactory = new MockHttpTransportFactory();
437     UserCredentials credentials =
438         UserCredentials.newBuilder()
439             .setClientId(CLIENT_ID)
440             .setClientSecret(CLIENT_SECRET)
441             .setRefreshToken(REFRESH_TOKEN)
442             .setAccessToken(accessToken)
443             .setHttpTransportFactory(httpTransportFactory)
444             .setQuotaProjectId(quotaProject1)
445             .build();
446     UserCredentials otherCredentials =
447         UserCredentials.newBuilder()
448             .setClientId(CLIENT_ID)
449             .setClientSecret(CLIENT_SECRET)
450             .setRefreshToken(REFRESH_TOKEN)
451             .setAccessToken(accessToken)
452             .setHttpTransportFactory(httpTransportFactory)
453             .setQuotaProjectId(quotaProject2)
454             .build();
455     assertFalse(credentials.equals(otherCredentials));
456     assertFalse(otherCredentials.equals(credentials));
457   }
458 
459   @Test
toString_containsFields()460   public void toString_containsFields() throws IOException {
461     AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null);
462     final URI tokenServer = URI.create("https://foo.com/bar");
463     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
464     UserCredentials credentials =
465         UserCredentials.newBuilder()
466             .setClientId(CLIENT_ID)
467             .setClientSecret(CLIENT_SECRET)
468             .setRefreshToken(REFRESH_TOKEN)
469             .setAccessToken(accessToken)
470             .setHttpTransportFactory(transportFactory)
471             .setTokenServerUri(tokenServer)
472             .setQuotaProjectId(QUOTA_PROJECT)
473             .build();
474 
475     String expectedToString =
476         String.format(
477             "UserCredentials{requestMetadata=%s, temporaryAccess=%s, clientId=%s, refreshToken=%s, "
478                 + "tokenServerUri=%s, transportFactoryClassName=%s, quotaProjectId=%s}",
479             ImmutableMap.of(
480                 AuthHttpConstants.AUTHORIZATION,
481                 ImmutableList.of(OAuth2Utils.BEARER_PREFIX + accessToken.getTokenValue())),
482             accessToken.toString(),
483             CLIENT_ID,
484             REFRESH_TOKEN,
485             tokenServer,
486             MockTokenServerTransportFactory.class.getName(),
487             QUOTA_PROJECT);
488     assertEquals(expectedToString, credentials.toString());
489   }
490 
491   @Test
hashCode_equals()492   public void hashCode_equals() throws IOException {
493     final URI tokenServer = URI.create("https://foo.com/bar");
494     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
495     AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null);
496     UserCredentials credentials =
497         UserCredentials.newBuilder()
498             .setClientId(CLIENT_ID)
499             .setClientSecret(CLIENT_SECRET)
500             .setRefreshToken(REFRESH_TOKEN)
501             .setAccessToken(accessToken)
502             .setHttpTransportFactory(transportFactory)
503             .setTokenServerUri(tokenServer)
504             .setQuotaProjectId(QUOTA_PROJECT)
505             .build();
506     UserCredentials otherCredentials =
507         UserCredentials.newBuilder()
508             .setClientId(CLIENT_ID)
509             .setClientSecret(CLIENT_SECRET)
510             .setRefreshToken(REFRESH_TOKEN)
511             .setAccessToken(accessToken)
512             .setHttpTransportFactory(transportFactory)
513             .setTokenServerUri(tokenServer)
514             .setQuotaProjectId(QUOTA_PROJECT)
515             .build();
516     assertEquals(credentials.hashCode(), otherCredentials.hashCode());
517   }
518 
519   @Test
serialize()520   public void serialize() throws IOException, ClassNotFoundException {
521     final URI tokenServer = URI.create("https://foo.com/bar");
522     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
523     AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null);
524     UserCredentials credentials =
525         UserCredentials.newBuilder()
526             .setClientId(CLIENT_ID)
527             .setClientSecret(CLIENT_SECRET)
528             .setRefreshToken(REFRESH_TOKEN)
529             .setAccessToken(accessToken)
530             .setHttpTransportFactory(transportFactory)
531             .setTokenServerUri(tokenServer)
532             .build();
533     UserCredentials deserializedCredentials = serializeAndDeserialize(credentials);
534     assertEquals(credentials, deserializedCredentials);
535     assertEquals(credentials.hashCode(), deserializedCredentials.hashCode());
536     assertEquals(credentials.toString(), deserializedCredentials.toString());
537     assertSame(deserializedCredentials.clock, Clock.SYSTEM);
538   }
539 
540   @Test
fromStream_nullTransport_throws()541   public void fromStream_nullTransport_throws() throws IOException {
542     InputStream stream = new ByteArrayInputStream("foo".getBytes());
543     try {
544       UserCredentials.fromStream(stream, null);
545       fail("Should throw if HttpTransportFactory is null");
546     } catch (NullPointerException expected) {
547       // Expected
548     }
549   }
550 
551   @Test
fromStream_nullStream_throws()552   public void fromStream_nullStream_throws() throws IOException {
553     MockHttpTransportFactory transportFactory = new MockHttpTransportFactory();
554     try {
555       UserCredentials.fromStream(null, transportFactory);
556       fail("Should throw if InputStream is null");
557     } catch (NullPointerException expected) {
558       // Expected
559     }
560   }
561 
562   @Test
fromStream_user_providesToken()563   public void fromStream_user_providesToken() throws IOException {
564     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
565     transportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET);
566     transportFactory.transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN);
567     InputStream userStream =
568         writeUserStream(CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, QUOTA_PROJECT);
569 
570     UserCredentials credentials = UserCredentials.fromStream(userStream, transportFactory);
571 
572     assertNotNull(credentials);
573     Map<String, List<String>> metadata = credentials.getRequestMetadata(CALL_URI);
574     TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN);
575   }
576 
577   @Test
fromStream_userNoClientId_throws()578   public void fromStream_userNoClientId_throws() throws IOException {
579     InputStream userStream = writeUserStream(null, CLIENT_SECRET, REFRESH_TOKEN, QUOTA_PROJECT);
580 
581     testFromStreamException(userStream, "client_id");
582   }
583 
584   @Test
fromStream_userNoClientSecret_throws()585   public void fromStream_userNoClientSecret_throws() throws IOException {
586     InputStream userStream = writeUserStream(CLIENT_ID, null, REFRESH_TOKEN, QUOTA_PROJECT);
587 
588     testFromStreamException(userStream, "client_secret");
589   }
590 
591   @Test
fromStream_userNoRefreshToken_throws()592   public void fromStream_userNoRefreshToken_throws() throws IOException {
593     InputStream userStream = writeUserStream(CLIENT_ID, CLIENT_SECRET, null, QUOTA_PROJECT);
594 
595     testFromStreamException(userStream, "refresh_token");
596   }
597 
598   @Test
saveUserCredentials_saved_throws()599   public void saveUserCredentials_saved_throws() throws IOException {
600     UserCredentials userCredentials =
601         UserCredentials.newBuilder()
602             .setClientId(CLIENT_ID)
603             .setClientSecret(CLIENT_SECRET)
604             .setRefreshToken(REFRESH_TOKEN)
605             .build();
606     File file = File.createTempFile("GOOGLE_APPLICATION_CREDENTIALS", null, null);
607     file.deleteOnExit();
608 
609     String filePath = file.getAbsolutePath();
610     userCredentials.save(filePath);
611   }
612 
613   @Test
saveAndRestoreUserCredential_saveAndRestored_throws()614   public void saveAndRestoreUserCredential_saveAndRestored_throws() throws IOException {
615     UserCredentials userCredentials =
616         UserCredentials.newBuilder()
617             .setClientId(CLIENT_ID)
618             .setClientSecret(CLIENT_SECRET)
619             .setRefreshToken(REFRESH_TOKEN)
620             .build();
621 
622     File file = File.createTempFile("GOOGLE_APPLICATION_CREDENTIALS", null, null);
623     file.deleteOnExit();
624 
625     String filePath = file.getAbsolutePath();
626 
627     userCredentials.save(filePath);
628 
629     FileInputStream inputStream = new FileInputStream(new File(filePath));
630 
631     UserCredentials restoredCredentials = UserCredentials.fromStream(inputStream);
632 
633     assertEquals(userCredentials.getClientId(), restoredCredentials.getClientId());
634     assertEquals(userCredentials.getClientSecret(), restoredCredentials.getClientSecret());
635     assertEquals(userCredentials.getRefreshToken(), restoredCredentials.getRefreshToken());
636   }
637 
638   @Test
getRequestMetadataSetsQuotaProjectId()639   public void getRequestMetadataSetsQuotaProjectId() throws IOException {
640     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
641     transportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET);
642     transportFactory.transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN);
643 
644     UserCredentials userCredentials =
645         UserCredentials.newBuilder()
646             .setClientId(CLIENT_ID)
647             .setClientSecret(CLIENT_SECRET)
648             .setRefreshToken(REFRESH_TOKEN)
649             .setQuotaProjectId("my-quota-project-id")
650             .setHttpTransportFactory(transportFactory)
651             .build();
652 
653     Map<String, List<String>> metadata = userCredentials.getRequestMetadata(CALL_URI);
654     assertTrue(metadata.containsKey("x-goog-user-project"));
655     List<String> headerValues = metadata.get("x-goog-user-project");
656     assertEquals(1, headerValues.size());
657     assertEquals("my-quota-project-id", headerValues.get(0));
658   }
659 
660   @Test
getRequestMetadataNoQuotaProjectId()661   public void getRequestMetadataNoQuotaProjectId() throws IOException {
662     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
663     transportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET);
664     transportFactory.transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN);
665 
666     UserCredentials userCredentials =
667         UserCredentials.newBuilder()
668             .setClientId(CLIENT_ID)
669             .setClientSecret(CLIENT_SECRET)
670             .setRefreshToken(REFRESH_TOKEN)
671             .setHttpTransportFactory(transportFactory)
672             .build();
673 
674     Map<String, List<String>> metadata = userCredentials.getRequestMetadata(CALL_URI);
675     assertFalse(metadata.containsKey("x-goog-user-project"));
676   }
677 
678   @Test
getRequestMetadataWithCallback()679   public void getRequestMetadataWithCallback() throws IOException {
680     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
681     transportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET);
682     transportFactory.transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN);
683 
684     UserCredentials userCredentials =
685         UserCredentials.newBuilder()
686             .setClientId(CLIENT_ID)
687             .setClientSecret(CLIENT_SECRET)
688             .setRefreshToken(REFRESH_TOKEN)
689             .setQuotaProjectId("my-quota-project-id")
690             .setHttpTransportFactory(transportFactory)
691             .build();
692     final Map<String, List<String>> plainMetadata = userCredentials.getRequestMetadata(CALL_URI);
693     final AtomicBoolean success = new AtomicBoolean(false);
694     userCredentials.getRequestMetadata(
695         null,
696         null,
697         new RequestMetadataCallback() {
698           @Override
699           public void onSuccess(Map<String, List<String>> metadata) {
700             assertEquals(plainMetadata, metadata);
701             success.set(true);
702           }
703 
704           @Override
705           public void onFailure(Throwable exception) {
706             fail("Should not throw a failure.");
707           }
708         });
709 
710     assertTrue("Should have run onSuccess() callback", success.get());
711   }
712 
713   @Test
IdTokenCredentials_WithUserEmailScope_success()714   public void IdTokenCredentials_WithUserEmailScope_success() throws IOException {
715     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
716     String refreshToken = MockTokenServerTransport.REFRESH_TOKEN_WITH_USER_SCOPE;
717 
718     transportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET);
719     transportFactory.transport.addRefreshToken(refreshToken, ACCESS_TOKEN);
720     InputStream userStream = writeUserStream(CLIENT_ID, CLIENT_SECRET, refreshToken, QUOTA_PROJECT);
721 
722     UserCredentials credentials = UserCredentials.fromStream(userStream, transportFactory);
723     credentials.refresh();
724 
725     assertEquals(ACCESS_TOKEN, credentials.getAccessToken().getTokenValue());
726 
727     IdTokenCredentials tokenCredential =
728         IdTokenCredentials.newBuilder().setIdTokenProvider(credentials).build();
729 
730     assertNull(tokenCredential.getAccessToken());
731     assertNull(tokenCredential.getIdToken());
732 
733     // trigger the refresh like it would happen during a request build
734     tokenCredential.getRequestMetadata(CALL_URI);
735 
736     assertEquals(DEFAULT_ID_TOKEN, tokenCredential.getAccessToken().getTokenValue());
737     assertEquals(DEFAULT_ID_TOKEN, tokenCredential.getIdToken().getTokenValue());
738   }
739 
740   @Test
IdTokenCredentials_NoRetry_RetryableStatus_throws()741   public void IdTokenCredentials_NoRetry_RetryableStatus_throws() throws IOException {
742     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
743     String refreshToken = MockTokenServerTransport.REFRESH_TOKEN_WITH_USER_SCOPE;
744 
745     transportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET);
746     transportFactory.transport.addRefreshToken(refreshToken, ACCESS_TOKEN);
747     InputStream userStream = writeUserStream(CLIENT_ID, CLIENT_SECRET, refreshToken, QUOTA_PROJECT);
748 
749     MockLowLevelHttpResponse response408 = new MockLowLevelHttpResponse().setStatusCode(408);
750     MockLowLevelHttpResponse response429 = new MockLowLevelHttpResponse().setStatusCode(429);
751 
752     UserCredentials credentials = UserCredentials.fromStream(userStream, transportFactory);
753 
754     try {
755       transportFactory.transport.addResponseSequence(response408, response429);
756       credentials.refresh();
757       fail("Should not be able to use credential without exception.");
758     } catch (GoogleAuthException ex) {
759       assertTrue(ex.getMessage().contains("com.google.api.client.http.HttpResponseException: 408"));
760       assertTrue(ex.isRetryable());
761       assertEquals(0, ex.getRetryCount());
762     }
763 
764     IdTokenCredentials tokenCredential =
765         IdTokenCredentials.newBuilder().setIdTokenProvider(credentials).build();
766 
767     assertNull(tokenCredential.getAccessToken());
768     assertNull(tokenCredential.getIdToken());
769 
770     // trigger the refresh like it would happen during a request build
771     try {
772       tokenCredential.getRequestMetadata(CALL_URI);
773       fail("Should not be able to use credential without exception.");
774     } catch (GoogleAuthException ex) {
775       assertTrue(ex.getMessage().contains("com.google.api.client.http.HttpResponseException: 429"));
776       assertTrue(ex.isRetryable());
777       assertEquals(0, ex.getRetryCount());
778     }
779   }
780 
781   @Test
refreshAccessToken_4xx_5xx_NonRetryableFails()782   public void refreshAccessToken_4xx_5xx_NonRetryableFails() throws IOException {
783     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
784     String refreshToken = MockTokenServerTransport.REFRESH_TOKEN_WITH_USER_SCOPE;
785 
786     transportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET);
787     transportFactory.transport.addRefreshToken(refreshToken, ACCESS_TOKEN);
788     InputStream userStream = writeUserStream(CLIENT_ID, CLIENT_SECRET, refreshToken, QUOTA_PROJECT);
789 
790     UserCredentials credentials = UserCredentials.fromStream(userStream, transportFactory);
791 
792     for (int status = 400; status < 600; status++) {
793       if (OAuth2Utils.TOKEN_ENDPOINT_RETRYABLE_STATUS_CODES.contains(status)) {
794         continue;
795       }
796 
797       MockLowLevelHttpResponse mockResponse = new MockLowLevelHttpResponse().setStatusCode(status);
798       try {
799         transportFactory.transport.addResponseSequence(mockResponse);
800         credentials.refresh();
801         fail("Should not be able to use credential without exception.");
802       } catch (GoogleAuthException ex) {
803         assertFalse(ex.isRetryable());
804         assertEquals(0, ex.getRetryCount());
805       }
806     }
807   }
808 
809   @Test
IdTokenCredentials_NoUserEmailScope_throws()810   public void IdTokenCredentials_NoUserEmailScope_throws() throws IOException {
811     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
812     transportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET);
813     transportFactory.transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN);
814     InputStream userStream =
815         writeUserStream(CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, QUOTA_PROJECT);
816 
817     UserCredentials credentials = UserCredentials.fromStream(userStream, transportFactory);
818 
819     IdTokenCredentials tokenCredential =
820         IdTokenCredentials.newBuilder().setIdTokenProvider(credentials).build();
821 
822     String expectedMessageContent =
823         "UserCredentials can obtain an id token only when authenticated through"
824             + " gcloud running 'gcloud auth login --update-adc' or 'gcloud auth application-default"
825             + " login'. The latter form would not work for Cloud Run, but would still generate an"
826             + " id token.";
827 
828     try {
829       tokenCredential.refresh();
830       fail("Should not be able to use credential without exception.");
831     } catch (IOException expected) {
832       assertTrue(expected.getMessage().equals(expectedMessageContent));
833     }
834   }
835 
836   @Test
userCredentials_toBuilder_copyEveryAttribute()837   public void userCredentials_toBuilder_copyEveryAttribute() {
838     MockHttpTransportFactory httpTransportFactory = new MockHttpTransportFactory();
839     UserCredentials credentials =
840         UserCredentials.newBuilder()
841             .setClientId(CLIENT_ID)
842             .setClientSecret(CLIENT_SECRET)
843             .setRefreshToken(REFRESH_TOKEN)
844             .setAccessToken(new AccessToken(ACCESS_TOKEN, new Date()))
845             .setHttpTransportFactory(httpTransportFactory)
846             .setTokenServerUri(URI.create("https://foo1.com/bar"))
847             .setQuotaProjectId(QUOTA_PROJECT)
848             .setExpirationMargin(Duration.of(10, ChronoUnit.SECONDS))
849             .setRefreshMargin(Duration.of(12, ChronoUnit.MINUTES))
850             .build();
851 
852     UserCredentials otherCredentials = credentials.toBuilder().build();
853     assertEquals(credentials, otherCredentials);
854   }
855 
writeUserJson( String clientId, String clientSecret, String refreshToken, String quotaProjectId)856   static GenericJson writeUserJson(
857       String clientId, String clientSecret, String refreshToken, String quotaProjectId) {
858     GenericJson json = new GenericJson();
859     if (clientId != null) {
860       json.put("client_id", clientId);
861     }
862     if (clientSecret != null) {
863       json.put("client_secret", clientSecret);
864     }
865     if (refreshToken != null) {
866       json.put("refresh_token", refreshToken);
867     }
868     if (quotaProjectId != null) {
869       json.put("quota_project_id", quotaProjectId);
870     }
871     json.put("type", GoogleCredentials.USER_FILE_TYPE);
872     return json;
873   }
874 
writeUserStream( String clientId, String clientSecret, String refreshToken, String quotaProjectId)875   static InputStream writeUserStream(
876       String clientId, String clientSecret, String refreshToken, String quotaProjectId)
877       throws IOException {
878     GenericJson json = writeUserJson(clientId, clientSecret, refreshToken, quotaProjectId);
879     return TestUtils.jsonToInputStream(json);
880   }
881 
testFromStreamException(InputStream stream, String expectedMessageContent)882   private static void testFromStreamException(InputStream stream, String expectedMessageContent) {
883     try {
884       UserCredentials.fromStream(stream);
885       fail(
886           String.format(
887               "Should throw exception with message containing '%s'", expectedMessageContent));
888     } catch (IOException expected) {
889       assertTrue(expected.getMessage().contains(expectedMessageContent));
890     }
891   }
892 }
893