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.assertNotNull;
37 import static org.junit.Assert.assertNull;
38 import static org.junit.Assert.assertSame;
39 import static org.junit.Assert.assertTrue;
40 import static org.junit.Assert.fail;
41 
42 import com.google.api.client.http.HttpTransport;
43 import com.google.api.client.http.LowLevelHttpRequest;
44 import com.google.api.client.http.LowLevelHttpResponse;
45 import com.google.api.client.testing.http.MockHttpTransport;
46 import com.google.api.client.testing.http.MockLowLevelHttpRequest;
47 import com.google.auth.TestUtils;
48 import com.google.auth.http.HttpTransportFactory;
49 import com.google.auth.oauth2.ComputeEngineCredentialsTest.MockMetadataServerTransportFactory;
50 import java.io.BufferedReader;
51 import java.io.ByteArrayInputStream;
52 import java.io.File;
53 import java.io.FileNotFoundException;
54 import java.io.IOException;
55 import java.io.InputStream;
56 import java.io.StringReader;
57 import java.net.URI;
58 import java.nio.file.Paths;
59 import java.security.AccessControlException;
60 import java.util.Collection;
61 import java.util.Collections;
62 import java.util.HashMap;
63 import java.util.List;
64 import java.util.Map;
65 import java.util.logging.Handler;
66 import java.util.logging.Level;
67 import java.util.logging.LogRecord;
68 import java.util.logging.Logger;
69 import org.junit.Test;
70 import org.junit.runner.RunWith;
71 import org.junit.runners.JUnit4;
72 
73 /** Test case for {@link DefaultCredentialsProvider}. */
74 @RunWith(JUnit4.class)
75 public class DefaultCredentialsProviderTest {
76 
77   private static final String USER_CLIENT_SECRET = "jakuaL9YyieakhECKL2SwZcu";
78   private static final String USER_CLIENT_ID = "ya29.1.AADtN_UtlxN3PuGAxrN2XQnZTVRvDyVWnYq4I6dws";
79   private static final String GCLOUDSDK_CLIENT_ID =
80       "764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com";
81   private static final String REFRESH_TOKEN = "1/Tl6awhpFjkMkSJoj1xsli0H2eL5YsMgU_NKPY2TyGWY";
82   private static final String ACCESS_TOKEN = "1/MkSJoj1xsli0AccessToken_NKPY2";
83   private static final String SA_CLIENT_EMAIL =
84       "36680232662-vrd7ji19qe3nelgchd0ah2csanun6bnr@developer.gserviceaccount.com";
85   private static final String SA_CLIENT_ID =
86       "36680232662-vrd7ji19qe3nelgchd0ah2csanun6bnr.apps.googleusercontent.com";
87   private static final String SA_PRIVATE_KEY_ID = "d84a4fefcf50791d4a90f2d7af17469d6282df9d";
88   private static final String SA_PRIVATE_KEY_PKCS8 =
89       ServiceAccountCredentialsTest.PRIVATE_KEY_PKCS8;
90   private static final String GDCH_SA_FORMAT_VERSION = GdchCredentials.SUPPORTED_FORMAT_VERSION;
91   private static final String GDCH_SA_PROJECT_ID = "gdch-service-account-project-id";
92   private static final String GDCH_SA_PRIVATE_KEY_ID = "d84a4fefcf50791d4a90f2d7af17469d6282df9d";
93   private static final String GDCH_SA_PRIVATE_KEY_PKC8 = GdchCredentialsTest.PRIVATE_KEY_PKCS8;
94   private static final String GDCH_SA_SERVICE_IDENTITY_NAME =
95       "gdch-service-account-service-identity-name";
96   private static final URI GDCH_SA_TOKEN_SERVER_URI =
97       URI.create("https://service-identity.domain/authenticate");
98   private static final String GDCH_SA_CA_CERT_FILE_NAME = "cert.pem";
99   private static final String GDCH_SA_CA_CERT_PATH =
100       GdchCredentialsTest.class.getClassLoader().getResource(GDCH_SA_CA_CERT_FILE_NAME).getPath();
101   private static final URI GDCH_SA_API_AUDIENCE = URI.create("https://gdch-api-audience");
102   private static final Collection<String> SCOPES = Collections.singletonList("dummy.scope");
103   private static final URI CALL_URI = URI.create("http://googleapis.com/testapi/v1/foo");
104   private static final String QUOTA_PROJECT = "sample-quota-project-id";
105   private static final String QUOTA_PROJECT_FROM_ENVIRONMENT = "environment-quota-project-id";
106   private static final String QUOTA_PROJECT_EXPLICIT = "explicit-quota-project-id";
107   private static final String SMBIOS_PATH_LINUX = "/sys/class/dmi/id/product_name";
108 
109   @Test
getDefaultCredentials_noCredentials_throws()110   public void getDefaultCredentials_noCredentials_throws() {
111     MockHttpTransportFactory transportFactory = new MockHttpTransportFactory();
112     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
113 
114     try {
115       testProvider.getDefaultCredentials(transportFactory);
116       fail("No credential expected.");
117     } catch (IOException e) {
118       String message = e.getMessage();
119       assertTrue(message.equals(DefaultCredentialsProvider.CLOUDSDK_MISSING_CREDENTIALS));
120     }
121   }
122 
123   @Test
getDefaultCredentials_noCredentialsSandbox_throwsNonSecurity()124   public void getDefaultCredentials_noCredentialsSandbox_throwsNonSecurity() {
125     MockHttpTransportFactory transportFactory = new MockHttpTransportFactory();
126     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
127     testProvider.setFileSandbox(true);
128 
129     try {
130       testProvider.getDefaultCredentials(transportFactory);
131       fail("No credential expected.");
132     } catch (IOException e) {
133       String message = e.getMessage();
134       assertTrue(message.equals(DefaultCredentialsProvider.CLOUDSDK_MISSING_CREDENTIALS));
135     }
136   }
137 
138   @Test
getDefaultCredentials_envValidSandbox_throwsNonSecurity()139   public void getDefaultCredentials_envValidSandbox_throwsNonSecurity() throws Exception {
140     MockHttpTransportFactory transportFactory = new MockHttpTransportFactory();
141     InputStream userStream =
142         UserCredentialsTest.writeUserStream(
143             USER_CLIENT_ID, USER_CLIENT_SECRET, REFRESH_TOKEN, QUOTA_PROJECT);
144     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
145     testProvider.setFileSandbox(true);
146     String userPath = tempFilePath("user.json");
147     testProvider.addFile(userPath, userStream);
148     testProvider.setEnv(DefaultCredentialsProvider.CREDENTIAL_ENV_VAR, userPath);
149 
150     try {
151       testProvider.getDefaultCredentials(transportFactory);
152       fail("No credential expected.");
153     } catch (IOException e) {
154       String message = e.getMessage();
155       assertTrue(message.equals(DefaultCredentialsProvider.CLOUDSDK_MISSING_CREDENTIALS));
156     }
157   }
158 
159   @Test
getDefaultCredentials_noCredentials_singleGceTestRequest()160   public void getDefaultCredentials_noCredentials_singleGceTestRequest() {
161     MockRequestCountingTransportFactory transportFactory =
162         new MockRequestCountingTransportFactory();
163     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
164 
165     try {
166       testProvider.getDefaultCredentials(transportFactory);
167       fail("No credential expected.");
168     } catch (IOException expected) {
169       String message = expected.getMessage();
170       assertTrue(message.equals(DefaultCredentialsProvider.CLOUDSDK_MISSING_CREDENTIALS));
171     }
172     assertEquals(
173         transportFactory.transport.getRequestCount(),
174         ComputeEngineCredentials.MAX_COMPUTE_PING_TRIES);
175     try {
176       testProvider.getDefaultCredentials(transportFactory);
177       fail("No credential expected.");
178     } catch (IOException expected) {
179       // Expected
180     }
181     assertEquals(
182         transportFactory.transport.getRequestCount(),
183         ComputeEngineCredentials.MAX_COMPUTE_PING_TRIES);
184   }
185 
186   @Test
getDefaultCredentials_noCredentials_linuxNotGce()187   public void getDefaultCredentials_noCredentials_linuxNotGce() throws IOException {
188     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
189     testProvider.setProperty("os.name", "Linux");
190     String productFilePath = SMBIOS_PATH_LINUX;
191     InputStream productStream = new ByteArrayInputStream("test".getBytes());
192     testProvider.addFile(productFilePath, productStream);
193 
194     assertFalse(ComputeEngineCredentials.checkStaticGceDetection(testProvider));
195   }
196 
197   @Test
getDefaultCredentials_static_linux()198   public void getDefaultCredentials_static_linux() throws IOException {
199     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
200     testProvider.setProperty("os.name", "Linux");
201     String productFilePath = SMBIOS_PATH_LINUX;
202     File productFile = new File(productFilePath);
203     InputStream productStream = new ByteArrayInputStream("Googlekdjsfhg".getBytes());
204     testProvider.addFile(productFile.getAbsolutePath(), productStream);
205 
206     assertTrue(ComputeEngineCredentials.checkStaticGceDetection(testProvider));
207   }
208 
209   @Test
getDefaultCredentials_static_windows_configuredAsLinux_notGce()210   public void getDefaultCredentials_static_windows_configuredAsLinux_notGce() throws IOException {
211     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
212     testProvider.setProperty("os.name", "windows");
213     String productFilePath = SMBIOS_PATH_LINUX;
214     InputStream productStream = new ByteArrayInputStream("Googlekdjsfhg".getBytes());
215     testProvider.addFile(productFilePath, productStream);
216 
217     assertFalse(ComputeEngineCredentials.checkStaticGceDetection(testProvider));
218   }
219 
220   @Test
getDefaultCredentials_static_unsupportedPlatform_notGce()221   public void getDefaultCredentials_static_unsupportedPlatform_notGce() throws IOException {
222     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
223     testProvider.setProperty("os.name", "macos");
224     String productFilePath = SMBIOS_PATH_LINUX;
225     InputStream productStream = new ByteArrayInputStream("Googlekdjsfhg".getBytes());
226     testProvider.addFile(productFilePath, productStream);
227 
228     assertFalse(ComputeEngineCredentials.checkStaticGceDetection(testProvider));
229   }
230 
231   @Test
checkGcpLinuxPlatformData()232   public void checkGcpLinuxPlatformData() throws Exception {
233     BufferedReader reader;
234     reader = new BufferedReader(new StringReader("HP Z440 Workstation"));
235     assertFalse(ComputeEngineCredentials.checkProductNameOnLinux(reader));
236     reader = new BufferedReader(new StringReader("Google"));
237     assertTrue(ComputeEngineCredentials.checkProductNameOnLinux(reader));
238     reader = new BufferedReader(new StringReader("Google Compute Engine"));
239     assertTrue(ComputeEngineCredentials.checkProductNameOnLinux(reader));
240     reader = new BufferedReader(new StringReader("Google Compute Engine    "));
241     assertTrue(ComputeEngineCredentials.checkProductNameOnLinux(reader));
242   }
243 
244   @Test
getDefaultCredentials_caches()245   public void getDefaultCredentials_caches() throws IOException {
246     MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
247     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
248 
249     GoogleCredentials firstCall = testProvider.getDefaultCredentials(transportFactory);
250     GoogleCredentials secondCall = testProvider.getDefaultCredentials(transportFactory);
251 
252     assertNotNull(firstCall);
253     assertSame(firstCall, secondCall);
254   }
255 
256   @Test
getDefaultCredentials_appEngineClassWithoutRuntime_NotFoundError()257   public void getDefaultCredentials_appEngineClassWithoutRuntime_NotFoundError() {
258     MockHttpTransportFactory transportFactory = new MockHttpTransportFactory();
259     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
260     testProvider.addType(
261         DefaultCredentialsProvider.APP_ENGINE_SIGNAL_CLASS, MockOffAppEngineSystemProperty.class);
262     testProvider.setProperty("isOnGAEStandard7", "true");
263 
264     try {
265       testProvider.getDefaultCredentials(transportFactory);
266       fail("No credential expected when not on App Engine.");
267     } catch (IOException e) {
268       String message = e.getMessage();
269       assertTrue(message.equals(DefaultCredentialsProvider.CLOUDSDK_MISSING_CREDENTIALS));
270     }
271   }
272 
273   @Test
getDefaultCredentials_appEngineRuntimeWithoutClass_throwsHelpfulLoadError()274   public void getDefaultCredentials_appEngineRuntimeWithoutClass_throwsHelpfulLoadError() {
275     MockHttpTransportFactory transportFactory = new MockHttpTransportFactory();
276     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
277     testProvider.addType(
278         DefaultCredentialsProvider.APP_ENGINE_SIGNAL_CLASS, MockAppEngineSystemProperty.class);
279     testProvider.setProperty("isOnGAEStandard7", "true");
280 
281     try {
282       testProvider.getDefaultCredentials(transportFactory);
283       fail("Credential expected to fail to load if credential class not present.");
284     } catch (IOException e) {
285       String message = e.getMessage();
286       assertFalse(message.equals(DefaultCredentialsProvider.CLOUDSDK_MISSING_CREDENTIALS));
287       assertTrue(message.contains("Check that the App Engine SDK is deployed."));
288     }
289   }
290 
291   @Test
getDefaultCredentials_appEngineSkipWorks_retrievesCloudShellCredential()292   public void getDefaultCredentials_appEngineSkipWorks_retrievesCloudShellCredential()
293       throws IOException {
294     MockHttpTransportFactory transportFactory = new MockHttpTransportFactory();
295     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
296     testProvider.addType(
297         DefaultCredentialsProvider.APP_ENGINE_SIGNAL_CLASS, MockOffAppEngineSystemProperty.class);
298     testProvider.setEnv(DefaultCredentialsProvider.CLOUD_SHELL_ENV_VAR, "9090");
299     testProvider.setEnv(DefaultCredentialsProvider.SKIP_APP_ENGINE_ENV_VAR, "true");
300     testProvider.setProperty("isOnGAEStanadard7", "true");
301     GoogleCredentials credentials = testProvider.getDefaultCredentials(transportFactory);
302     assertNotNull(credentials);
303     assertTrue(credentials instanceof CloudShellCredentials);
304   }
305 
306   @Test
getDefaultCredentials_compute_providesToken()307   public void getDefaultCredentials_compute_providesToken() throws IOException {
308     MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
309     transportFactory.transport.setAccessToken(ACCESS_TOKEN);
310     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
311 
312     GoogleCredentials defaultCredentials = testProvider.getDefaultCredentials(transportFactory);
313 
314     assertNotNull(defaultCredentials);
315     Map<String, List<String>> metadata = defaultCredentials.getRequestMetadata(CALL_URI);
316     TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN);
317   }
318 
319   @Test
getDefaultCredentials_cloudshell()320   public void getDefaultCredentials_cloudshell() throws IOException {
321     MockHttpTransportFactory transportFactory = new MockHttpTransportFactory();
322     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
323     testProvider.setEnv(DefaultCredentialsProvider.CLOUD_SHELL_ENV_VAR, "4");
324 
325     GoogleCredentials defaultCredentials = testProvider.getDefaultCredentials(transportFactory);
326 
327     assertTrue(defaultCredentials instanceof CloudShellCredentials);
328     assertEquals(((CloudShellCredentials) defaultCredentials).getAuthPort(), 4);
329   }
330 
331   @Test
getDefaultCredentials_cloudshell_withComputCredentialsPresent()332   public void getDefaultCredentials_cloudshell_withComputCredentialsPresent() throws IOException {
333     MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
334     transportFactory.transport.setAccessToken(ACCESS_TOKEN);
335     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
336     testProvider.setEnv(DefaultCredentialsProvider.CLOUD_SHELL_ENV_VAR, "4");
337 
338     GoogleCredentials defaultCredentials = testProvider.getDefaultCredentials(transportFactory);
339 
340     assertTrue(defaultCredentials instanceof CloudShellCredentials);
341     assertEquals(((CloudShellCredentials) defaultCredentials).getAuthPort(), 4);
342   }
343 
344   @Test
getDefaultCredentials_envMissingFile_throws()345   public void getDefaultCredentials_envMissingFile_throws() {
346     final String invalidPath = "/invalid/path";
347     MockHttpTransportFactory transportFactory = new MockHttpTransportFactory();
348     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
349     testProvider.setEnv(DefaultCredentialsProvider.CREDENTIAL_ENV_VAR, invalidPath);
350 
351     try {
352       testProvider.getDefaultCredentials(transportFactory);
353       fail("Non existent credential should throw exception");
354     } catch (IOException e) {
355       String message = e.getMessage();
356       assertTrue(message.contains(DefaultCredentialsProvider.CREDENTIAL_ENV_VAR));
357       assertTrue(message.contains(invalidPath));
358     }
359   }
360 
361   @Test
getDefaultCredentials_envServiceAccount_providesToken()362   public void getDefaultCredentials_envServiceAccount_providesToken() throws IOException {
363     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
364     transportFactory.transport.addServiceAccount(SA_CLIENT_EMAIL, ACCESS_TOKEN);
365     InputStream serviceAccountStream =
366         ServiceAccountCredentialsTest.writeServiceAccountStream(
367             SA_CLIENT_ID, SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID);
368     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
369     String serviceAccountPath = tempFilePath("service_account.json");
370     testProvider.addFile(serviceAccountPath, serviceAccountStream);
371     testProvider.setEnv(DefaultCredentialsProvider.CREDENTIAL_ENV_VAR, serviceAccountPath);
372 
373     GoogleCredentials defaultCredentials = testProvider.getDefaultCredentials(transportFactory);
374 
375     assertNotNull(defaultCredentials);
376     defaultCredentials = defaultCredentials.createScoped(SCOPES);
377     Map<String, List<String>> metadata = defaultCredentials.getRequestMetadata(CALL_URI);
378     TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN);
379   }
380 
381   @Test
getDefaultCredentials_envUser_providesToken()382   public void getDefaultCredentials_envUser_providesToken() throws IOException {
383     InputStream userStream =
384         UserCredentialsTest.writeUserStream(
385             USER_CLIENT_ID, USER_CLIENT_SECRET, REFRESH_TOKEN, QUOTA_PROJECT);
386     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
387     String userPath = tempFilePath("user.json");
388     testProvider.addFile(userPath, userStream);
389     testProvider.setEnv(DefaultCredentialsProvider.CREDENTIAL_ENV_VAR, userPath);
390 
391     testUserProvidesToken(testProvider, USER_CLIENT_ID, USER_CLIENT_SECRET, REFRESH_TOKEN);
392   }
393 
394   @Test
getDefaultCredentials_GdchServiceAccount()395   public void getDefaultCredentials_GdchServiceAccount() throws IOException {
396     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
397     InputStream gdchServiceAccountStream =
398         GdchCredentialsTest.writeGdchServiceAccountStream(
399             GDCH_SA_FORMAT_VERSION,
400             GDCH_SA_PROJECT_ID,
401             GDCH_SA_PRIVATE_KEY_ID,
402             GDCH_SA_PRIVATE_KEY_PKC8,
403             GDCH_SA_SERVICE_IDENTITY_NAME,
404             GDCH_SA_CA_CERT_PATH,
405             GDCH_SA_TOKEN_SERVER_URI);
406     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
407     String gdchServiceAccountPath = tempFilePath("gdch_service_account.json");
408     testProvider.addFile(gdchServiceAccountPath, gdchServiceAccountStream);
409     testProvider.setEnv(DefaultCredentialsProvider.CREDENTIAL_ENV_VAR, gdchServiceAccountPath);
410 
411     GoogleCredentials defaultCredentials = testProvider.getDefaultCredentials(transportFactory);
412 
413     assertNotNull(defaultCredentials);
414     assertTrue(defaultCredentials instanceof GdchCredentials);
415     assertEquals(GDCH_SA_PROJECT_ID, ((GdchCredentials) defaultCredentials).getProjectId());
416     assertEquals(
417         GDCH_SA_SERVICE_IDENTITY_NAME,
418         ((GdchCredentials) defaultCredentials).getServiceIdentityName());
419     assertEquals(
420         GDCH_SA_TOKEN_SERVER_URI, ((GdchCredentials) defaultCredentials).getTokenServerUri());
421     assertEquals(GDCH_SA_CA_CERT_PATH, ((GdchCredentials) defaultCredentials).getCaCertPath());
422     assertNull(((GdchCredentials) defaultCredentials).getApiAudience());
423 
424     defaultCredentials =
425         ((GdchCredentials) defaultCredentials).createWithGdchAudience(GDCH_SA_API_AUDIENCE);
426     assertNotNull(defaultCredentials);
427     assertTrue(defaultCredentials instanceof GdchCredentials);
428     assertEquals(GDCH_SA_PROJECT_ID, ((GdchCredentials) defaultCredentials).getProjectId());
429     assertEquals(
430         GDCH_SA_SERVICE_IDENTITY_NAME,
431         ((GdchCredentials) defaultCredentials).getServiceIdentityName());
432     assertEquals(
433         GDCH_SA_TOKEN_SERVER_URI, ((GdchCredentials) defaultCredentials).getTokenServerUri());
434     assertEquals(GDCH_SA_CA_CERT_PATH, ((GdchCredentials) defaultCredentials).getCaCertPath());
435     assertNotNull(((GdchCredentials) defaultCredentials).getApiAudience());
436   }
437 
getDefaultCredentials_quota_project()438   public void getDefaultCredentials_quota_project() throws IOException {
439     InputStream userStream =
440         UserCredentialsTest.writeUserStream(
441             USER_CLIENT_ID, USER_CLIENT_SECRET, REFRESH_TOKEN, QUOTA_PROJECT);
442     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
443     String userPath = tempFilePath("user.json");
444     testProvider.addFile(userPath, userStream);
445     testProvider.setEnv(DefaultCredentialsProvider.CREDENTIAL_ENV_VAR, userPath);
446     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
447     transportFactory.transport.addClient(USER_CLIENT_ID, USER_CLIENT_SECRET);
448     transportFactory.transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN);
449 
450     // Validate that quota from env overrides the value from json
451     testProvider.setEnv(
452         DefaultCredentialsProvider.QUOTA_PROJECT_ENV_VAR, QUOTA_PROJECT_FROM_ENVIRONMENT);
453     GoogleCredentials credentials = testProvider.getDefaultCredentials(transportFactory);
454     assertEquals(QUOTA_PROJECT_FROM_ENVIRONMENT, credentials.getQuotaProjectId());
455 
456     // Validate that if user sets quota, env and json value not used
457     credentials = credentials.toBuilder().setQuotaProjectId(QUOTA_PROJECT_EXPLICIT).build();
458     assertEquals(QUOTA_PROJECT_EXPLICIT, credentials.getQuotaProjectId());
459 
460     testUserProvidesToken(testProvider, USER_CLIENT_ID, USER_CLIENT_SECRET, REFRESH_TOKEN);
461   }
462 
463   @Test
getDefaultCredentials_compute_quotaProject()464   public void getDefaultCredentials_compute_quotaProject() throws IOException {
465     MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
466     transportFactory.transport.setAccessToken(ACCESS_TOKEN);
467     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
468     testProvider.setEnv(
469         DefaultCredentialsProvider.QUOTA_PROJECT_ENV_VAR, QUOTA_PROJECT_FROM_ENVIRONMENT);
470 
471     GoogleCredentials defaultCredentials = testProvider.getDefaultCredentials(transportFactory);
472 
473     assertTrue(defaultCredentials instanceof ComputeEngineCredentials);
474     assertEquals(QUOTA_PROJECT_FROM_ENVIRONMENT, defaultCredentials.getQuotaProjectId());
475   }
476 
477   @Test
getDefaultCredentials_cloudshell_quotaProject()478   public void getDefaultCredentials_cloudshell_quotaProject() throws IOException {
479     MockHttpTransportFactory transportFactory = new MockHttpTransportFactory();
480     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
481     testProvider.setEnv(DefaultCredentialsProvider.CLOUD_SHELL_ENV_VAR, "4");
482     testProvider.setEnv(
483         DefaultCredentialsProvider.QUOTA_PROJECT_ENV_VAR, QUOTA_PROJECT_FROM_ENVIRONMENT);
484 
485     GoogleCredentials defaultCredentials = testProvider.getDefaultCredentials(transportFactory);
486 
487     assertTrue(defaultCredentials instanceof CloudShellCredentials);
488     assertEquals(QUOTA_PROJECT_FROM_ENVIRONMENT, defaultCredentials.getQuotaProjectId());
489   }
490 
491   @Test
getDefaultCredentials_envNoGceCheck_noGceRequest()492   public void getDefaultCredentials_envNoGceCheck_noGceRequest() throws IOException {
493     MockRequestCountingTransportFactory transportFactory =
494         new MockRequestCountingTransportFactory();
495     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
496     testProvider.setEnv(DefaultCredentialsProvider.NO_GCE_CHECK_ENV_VAR, "true");
497 
498     try {
499       testProvider.getDefaultCredentials(transportFactory);
500       fail("No credential expected.");
501     } catch (IOException expected) {
502       // Expected
503     }
504     assertEquals(transportFactory.transport.getRequestCount(), 0);
505   }
506 
507   @Test
getDefaultCredentials_linuxSetup_envNoGceCheck_noGce()508   public void getDefaultCredentials_linuxSetup_envNoGceCheck_noGce() throws IOException {
509     MockRequestCountingTransportFactory transportFactory =
510         new MockRequestCountingTransportFactory();
511     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
512     testProvider.setEnv(DefaultCredentialsProvider.NO_GCE_CHECK_ENV_VAR, "true");
513     testProvider.setProperty("os.name", "Linux");
514     String productFilePath = SMBIOS_PATH_LINUX;
515     File productFile = new File(productFilePath);
516     InputStream productStream = new ByteArrayInputStream("Googlekdjsfhg".getBytes());
517     testProvider.addFile(productFile.getAbsolutePath(), productStream);
518     try {
519       testProvider.getDefaultCredentials(transportFactory);
520       fail("No credential expected.");
521     } catch (IOException expected) {
522       // Expected
523     }
524     assertEquals(transportFactory.transport.getRequestCount(), 0);
525   }
526 
527   @Test
getDefaultCredentials_envGceMetadataHost_setsMetadataServerUrl()528   public void getDefaultCredentials_envGceMetadataHost_setsMetadataServerUrl() {
529     String testUrl = "192.0.2.0";
530     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
531     testProvider.setEnv(DefaultCredentialsProvider.GCE_METADATA_HOST_ENV_VAR, testUrl);
532     assertEquals(ComputeEngineCredentials.getMetadataServerUrl(testProvider), "http://" + testUrl);
533   }
534 
535   @Test
getDefaultCredentials_envGceMetadataHost_setsTokenServerUrl()536   public void getDefaultCredentials_envGceMetadataHost_setsTokenServerUrl() {
537     String testUrl = "192.0.2.0";
538     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
539     testProvider.setEnv(DefaultCredentialsProvider.GCE_METADATA_HOST_ENV_VAR, testUrl);
540     assertEquals(
541         ComputeEngineCredentials.getTokenServerEncodedUrl(testProvider),
542         "http://" + testUrl + "/computeMetadata/v1/instance/service-accounts/default/token");
543   }
544 
545   @Test
getDefaultCredentials_wellKnownFileEnv_providesToken()546   public void getDefaultCredentials_wellKnownFileEnv_providesToken() throws IOException {
547     File cloudConfigDir = getTempDirectory();
548     InputStream userStream =
549         UserCredentialsTest.writeUserStream(
550             USER_CLIENT_ID, USER_CLIENT_SECRET, REFRESH_TOKEN, QUOTA_PROJECT);
551     File wellKnownFile =
552         new File(cloudConfigDir, DefaultCredentialsProvider.WELL_KNOWN_CREDENTIALS_FILE);
553     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
554     testProvider.setEnv("CLOUDSDK_CONFIG", cloudConfigDir.getAbsolutePath());
555     testProvider.addFile(wellKnownFile.getAbsolutePath(), userStream);
556 
557     testUserProvidesToken(testProvider, USER_CLIENT_ID, USER_CLIENT_SECRET, REFRESH_TOKEN);
558   }
559 
560   @Test
getDefaultCredentials_wellKnownFileNonWindows_providesToken()561   public void getDefaultCredentials_wellKnownFileNonWindows_providesToken() throws IOException {
562     File homeDir = getTempDirectory();
563     File configDir = new File(homeDir, ".config");
564     File cloudConfigDir = new File(configDir, DefaultCredentialsProvider.CLOUDSDK_CONFIG_DIRECTORY);
565     InputStream userStream =
566         UserCredentialsTest.writeUserStream(
567             USER_CLIENT_ID, USER_CLIENT_SECRET, REFRESH_TOKEN, QUOTA_PROJECT);
568     File wellKnownFile =
569         new File(cloudConfigDir, DefaultCredentialsProvider.WELL_KNOWN_CREDENTIALS_FILE);
570     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
571     testProvider.setProperty("os.name", "linux");
572     testProvider.setProperty("user.home", homeDir.getAbsolutePath());
573     testProvider.addFile(wellKnownFile.getAbsolutePath(), userStream);
574 
575     testUserProvidesToken(testProvider, USER_CLIENT_ID, USER_CLIENT_SECRET, REFRESH_TOKEN);
576   }
577 
578   @Test
getDefaultCredentials_wellKnownFileWindows_providesToken()579   public void getDefaultCredentials_wellKnownFileWindows_providesToken() throws IOException {
580     File homeDir = getTempDirectory();
581     File cloudConfigDir = new File(homeDir, DefaultCredentialsProvider.CLOUDSDK_CONFIG_DIRECTORY);
582     InputStream userStream =
583         UserCredentialsTest.writeUserStream(
584             USER_CLIENT_ID, USER_CLIENT_SECRET, REFRESH_TOKEN, QUOTA_PROJECT);
585     File wellKnownFile =
586         new File(cloudConfigDir, DefaultCredentialsProvider.WELL_KNOWN_CREDENTIALS_FILE);
587     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
588     testProvider.setProperty("os.name", "windows");
589     testProvider.setEnv("APPDATA", homeDir.getAbsolutePath());
590     testProvider.addFile(wellKnownFile.getAbsolutePath(), userStream);
591 
592     testUserProvidesToken(testProvider, USER_CLIENT_ID, USER_CLIENT_SECRET, REFRESH_TOKEN);
593   }
594 
595   @Test
getDefaultCredentials_envAndWellKnownFile_envPrecedence()596   public void getDefaultCredentials_envAndWellKnownFile_envPrecedence() throws IOException {
597     final String refreshTokenEnv = "2/Tl6awhpFjkMkSJoj1xsli0H2eL5YsMgU_NKPY2TyGWY";
598     final String accessTokenEnv = "2/MkSJoj1xsli0AccessToken_NKPY2";
599     final String refreshTokenWkf = "3/Tl6awhpFjkMkSJoj1xsli0H2eL5YsMgU_NKPY2TyGWY";
600     final String accessTokenWkf = "3/MkSJoj1xsli0AccessToken_NKPY2";
601     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
602 
603     InputStream envStream =
604         UserCredentialsTest.writeUserStream(
605             USER_CLIENT_ID, USER_CLIENT_SECRET, refreshTokenEnv, QUOTA_PROJECT);
606     String envPath = tempFilePath("env.json");
607     testProvider.setEnv(DefaultCredentialsProvider.CREDENTIAL_ENV_VAR, envPath);
608     testProvider.addFile(envPath, envStream);
609 
610     File homeDir = getTempDirectory();
611     File configDir = new File(homeDir, ".config");
612     File cloudConfigDir = new File(configDir, DefaultCredentialsProvider.CLOUDSDK_CONFIG_DIRECTORY);
613     InputStream wkfStream =
614         UserCredentialsTest.writeUserStream(
615             USER_CLIENT_ID, USER_CLIENT_SECRET, refreshTokenWkf, QUOTA_PROJECT);
616     File wellKnownFile =
617         new File(cloudConfigDir, DefaultCredentialsProvider.WELL_KNOWN_CREDENTIALS_FILE);
618     testProvider.setProperty("os.name", "linux");
619     testProvider.setProperty("user.home", homeDir.getAbsolutePath());
620     testProvider.addFile(wellKnownFile.getAbsolutePath(), wkfStream);
621 
622     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
623     transportFactory.transport.addClient(USER_CLIENT_ID, USER_CLIENT_SECRET);
624     transportFactory.transport.addRefreshToken(refreshTokenWkf, accessTokenWkf);
625     transportFactory.transport.addRefreshToken(refreshTokenEnv, accessTokenEnv);
626 
627     testUserProvidesToken(testProvider, transportFactory, accessTokenEnv);
628   }
629 
tempFilePath(String filename)630   private String tempFilePath(String filename) {
631     return Paths.get(System.getProperty("java.io.tmpdir"), filename).toString();
632   }
633 
634   private class LogHandler extends Handler {
635     LogRecord lastRecord;
636 
637     @Override
publish(LogRecord record)638     public void publish(LogRecord record) {
639       lastRecord = record;
640     }
641 
getRecord()642     public LogRecord getRecord() {
643       return lastRecord;
644     }
645 
646     @Override
close()647     public void close() {}
648 
649     @Override
flush()650     public void flush() {}
651   }
652 
653   @Test
getDefaultCredentials_wellKnownFile_logsGcloudWarning()654   public void getDefaultCredentials_wellKnownFile_logsGcloudWarning() throws IOException {
655     LogRecord message = getCredentialsAndReturnLogMessage(false, true);
656     assertNotNull(message);
657     assertEquals(Level.WARNING, message.getLevel());
658     assertTrue(
659         message.getMessage().equals(DefaultCredentialsProvider.CLOUDSDK_CREDENTIALS_WARNING));
660   }
661 
662   @Test
getDefaultCredentials_wellKnownFile_noGcloudWarning()663   public void getDefaultCredentials_wellKnownFile_noGcloudWarning() throws IOException {
664     LogRecord message = getCredentialsAndReturnLogMessage(false, false);
665     assertNull(message);
666   }
667 
668   @Test
getDefaultCredentials_wellKnownFile_suppressGcloudWarning()669   public void getDefaultCredentials_wellKnownFile_suppressGcloudWarning() throws IOException {
670     LogRecord message = getCredentialsAndReturnLogMessage(true, true);
671     assertNull(message);
672   }
673 
getCredentialsAndReturnLogMessage(boolean suppressWarning, boolean isGce)674   private LogRecord getCredentialsAndReturnLogMessage(boolean suppressWarning, boolean isGce)
675       throws IOException {
676     Logger logger = Logger.getLogger(DefaultCredentialsProvider.class.getName());
677     LogHandler handler = new LogHandler();
678     logger.addHandler(handler);
679 
680     File homeDir = getTempDirectory();
681     File configDir = new File(homeDir, ".config");
682     File cloudConfigDir = new File(configDir, DefaultCredentialsProvider.CLOUDSDK_CONFIG_DIRECTORY);
683     InputStream userStream =
684         UserCredentialsTest.writeUserStream(
685             GCLOUDSDK_CLIENT_ID, USER_CLIENT_SECRET, REFRESH_TOKEN, QUOTA_PROJECT);
686     File wellKnownFile =
687         new File(cloudConfigDir, DefaultCredentialsProvider.WELL_KNOWN_CREDENTIALS_FILE);
688     TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider();
689     testProvider.setEnv(
690         DefaultCredentialsProvider.SUPPRESS_GCLOUD_CREDS_WARNING_ENV_VAR,
691         Boolean.toString(suppressWarning));
692     testProvider.setProperty("os.name", "linux");
693     testProvider.setProperty("user.home", homeDir.getAbsolutePath());
694     if (isGce) {
695       String productFilePath = SMBIOS_PATH_LINUX;
696       File productFile = new File(productFilePath);
697       InputStream productStream = new ByteArrayInputStream("Googlekdjsfhg".getBytes());
698       testProvider.addFile(productFile.getAbsolutePath(), productStream);
699     }
700     testProvider.addFile(wellKnownFile.getAbsolutePath(), userStream);
701     testUserProvidesToken(testProvider, GCLOUDSDK_CLIENT_ID, USER_CLIENT_SECRET, REFRESH_TOKEN);
702     return handler.getRecord();
703   }
704 
getTempDirectory()705   private static File getTempDirectory() {
706     return new File(System.getProperty("java.io.tmpdir"));
707   }
708 
testUserProvidesToken( TestDefaultCredentialsProvider testProvider, String clientId, String clientSecret, String refreshToken)709   private void testUserProvidesToken(
710       TestDefaultCredentialsProvider testProvider,
711       String clientId,
712       String clientSecret,
713       String refreshToken)
714       throws IOException {
715     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
716     transportFactory.transport.addClient(clientId, clientSecret);
717     transportFactory.transport.addRefreshToken(refreshToken, ACCESS_TOKEN);
718     testUserProvidesToken(testProvider, transportFactory, ACCESS_TOKEN);
719   }
720 
testUserProvidesToken( TestDefaultCredentialsProvider testProvider, HttpTransportFactory transportFactory, String accessToken)721   private void testUserProvidesToken(
722       TestDefaultCredentialsProvider testProvider,
723       HttpTransportFactory transportFactory,
724       String accessToken)
725       throws IOException {
726     GoogleCredentials defaultCredentials = testProvider.getDefaultCredentials(transportFactory);
727 
728     assertNotNull(defaultCredentials);
729     Map<String, List<String>> metadata = defaultCredentials.getRequestMetadata(CALL_URI);
730     TestUtils.assertContainsBearerToken(metadata, accessToken);
731   }
732 
733   public static class MockAppEngineCredentials extends GoogleCredentials {
734     private static final long serialVersionUID = 2695173591854484322L;
735 
MockAppEngineCredentials(Collection<String> scopes)736     public MockAppEngineCredentials(Collection<String> scopes) {}
737 
738     @Override
refreshAccessToken()739     public AccessToken refreshAccessToken() throws IOException {
740       return null;
741     }
742   }
743 
744   /*
745    * App Engine is detected by calling SystemProperty.environment.value() via Reflection.
746    * The following mock types simulate the shape and behavior of that call sequence.
747    */
748 
749   private static class MockAppEngineSystemProperty {
750 
751     @SuppressWarnings("unused")
752     public static final MockEnvironment environment =
753         new MockEnvironment(MockEnvironmentEnum.Production);
754   }
755 
756   private static class MockOffAppEngineSystemProperty {
757 
758     @SuppressWarnings("unused")
759     public static final MockEnvironment environment = new MockEnvironment(null);
760   }
761 
762   private enum MockEnvironmentEnum {
763     Production,
764     Development;
765   }
766 
767   public static class MockEnvironment {
768 
769     private MockEnvironmentEnum innerValue;
770 
MockEnvironment(MockEnvironmentEnum value)771     MockEnvironment(MockEnvironmentEnum value) {
772       this.innerValue = value;
773     }
774 
value()775     public MockEnvironmentEnum value() {
776       return innerValue;
777     }
778   }
779 
780   /*
781    * End of types simulating SystemProperty.environment.value() to detect App Engine.
782    */
783 
784   static class MockRequestCountingTransport extends MockHttpTransport {
785     int requestCount = 0;
786 
MockRequestCountingTransport()787     MockRequestCountingTransport() {}
788 
getRequestCount()789     int getRequestCount() {
790       return requestCount;
791     }
792 
793     @Override
buildRequest(String method, String url)794     public LowLevelHttpRequest buildRequest(String method, String url) {
795       return new MockLowLevelHttpRequest(url) {
796         @Override
797         public LowLevelHttpResponse execute() throws IOException {
798           requestCount++;
799           throw new IOException("MockRequestCountingTransport request failed.");
800         }
801       };
802     }
803   }
804 
805   static class TestDefaultCredentialsProvider extends DefaultCredentialsProvider {
806 
807     private final Map<String, Class<?>> types = new HashMap<>();
808     private final Map<String, String> variables = new HashMap<>();
809     private final Map<String, String> properties = new HashMap<>();
810     private final Map<String, InputStream> files = new HashMap<>();
811     private boolean fileSandbox = false;
812 
813     TestDefaultCredentialsProvider() {}
814 
815     void addFile(String file, InputStream stream) {
816       files.put(file, stream);
817     }
818 
819     void addType(String className, Class<?> type) {
820       types.put(className, type);
821     }
822 
823     @Override
824     String getEnv(String name) {
825       return variables.get(name);
826     }
827 
828     void setEnv(String name, String value) {
829       variables.put(name, value);
830     }
831 
832     @Override
833     String getProperty(String property, String def) {
834       String value = properties.get(property);
835       return value == null ? def : value;
836     }
837 
838     void setProperty(String property, String value) {
839       properties.put(property, value);
840     }
841 
842     @Override
843     Class<?> forName(String className) throws ClassNotFoundException {
844       Class<?> lookup = types.get(className);
845       if (lookup != null) {
846         return lookup;
847       }
848       throw new ClassNotFoundException("TestDefaultCredentialProvider: Class not found.");
849     }
850 
851     @Override
852     protected boolean isOnGAEStandard7() {
853       return getProperty("isOnGAEStandard7", "false").equals("true");
854     }
855 
856     @Override
857     boolean isFile(File file) {
858       if (fileSandbox) {
859         throw new AccessControlException("No file permission.");
860       }
861       return files.containsKey(file.getAbsolutePath());
862     }
863 
864     @Override
865     InputStream readStream(File file) throws FileNotFoundException {
866       if (fileSandbox) {
867         throw new AccessControlException("No file permission.");
868       }
869       InputStream stream = files.get(file.getAbsolutePath());
870       if (stream == null) {
871         throw new FileNotFoundException(file.getAbsolutePath());
872       }
873       return stream;
874     }
875 
876     void setFileSandbox(boolean fileSandbox) {
877       this.fileSandbox = fileSandbox;
878     }
879   }
880 
881   static class MockRequestCountingTransportFactory implements HttpTransportFactory {
882 
883     MockRequestCountingTransport transport = new MockRequestCountingTransport();
884 
885     @Override
886     public HttpTransport create() {
887       return transport;
888     }
889   }
890 }
891