1 /*
2  * Copyright 2015, Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *    * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *    * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *
15  *    * Neither the name of Google Inc. nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 package com.google.auth.oauth2;
33 
34 import static org.junit.Assert.assertArrayEquals;
35 import static org.junit.Assert.assertEquals;
36 import static org.junit.Assert.assertFalse;
37 import static org.junit.Assert.assertNotNull;
38 import static org.junit.Assert.assertNull;
39 import static org.junit.Assert.assertSame;
40 import static org.junit.Assert.assertTrue;
41 import static org.junit.Assert.fail;
42 
43 import com.google.api.client.http.HttpStatusCodes;
44 import com.google.api.client.http.HttpTransport;
45 import com.google.api.client.http.LowLevelHttpRequest;
46 import com.google.api.client.http.LowLevelHttpResponse;
47 import com.google.api.client.json.webtoken.JsonWebToken.Payload;
48 import com.google.api.client.testing.http.MockLowLevelHttpRequest;
49 import com.google.api.client.testing.http.MockLowLevelHttpResponse;
50 import com.google.api.client.util.ArrayMap;
51 import com.google.api.client.util.Clock;
52 import com.google.auth.Credentials;
53 import com.google.auth.ServiceAccountSigner.SigningException;
54 import com.google.auth.TestUtils;
55 import com.google.auth.http.HttpTransportFactory;
56 import com.google.auth.oauth2.DefaultCredentialsProviderTest.MockRequestCountingTransportFactory;
57 import java.io.IOException;
58 import java.net.URI;
59 import java.util.ArrayDeque;
60 import java.util.Arrays;
61 import java.util.Collection;
62 import java.util.Collections;
63 import java.util.List;
64 import java.util.Map;
65 import java.util.Queue;
66 import java.util.stream.IntStream;
67 import org.junit.Test;
68 import org.junit.runner.RunWith;
69 import org.junit.runners.JUnit4;
70 
71 /** Test case for {@link ComputeEngineCredentials}. */
72 @RunWith(JUnit4.class)
73 public class ComputeEngineCredentialsTest extends BaseSerializationTest {
74 
75   private static final URI CALL_URI = URI.create("http://googleapis.com/testapi/v1/foo");
76 
77   private static final String TOKEN_URL =
78       "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token";
79 
80   // Id Token which includes basic default claims
81   public static final String STANDARD_ID_TOKEN =
82       "eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmMzc1ODkwOGI3OTIyO"
83           + "TNhZDk3N2EwYjk5MWQ5OGE3N2Y0ZWVlY2QiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL2Zvby5iYXIiL"
84           + "CJhenAiOiIxMDIxMDE1NTA4MzQyMDA3MDg1NjgiLCJleHAiOjE1NjQ0NzUwNTEsImlhdCI6MTU2NDQ3MTQ1MSwi"
85           + "aXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTAyMTAxNTUwODM0MjAwNzA4NTY4In0"
86           + ".redacted";
87 
88   // Id Token which includes GCE extended claims
89   public static final String FULL_ID_TOKEN =
90       "eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmMzc1ODkwOGI3OTIyOTNh"
91           + "ZDk3N2EwYjk5MWQ5OGE3N2Y0ZWVlY2QiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL2Zvby5iYXIiLCJhe"
92           + "nAiOiIxMTIxNzkwNjI3MjAzOTEzMDU4ODUiLCJlbWFpbCI6IjEwNzEyODQxODQ0MzYtY29tcHV0ZUBkZXZlbG9wZ"
93           + "XIuZ3NlcnZpY2VhY2NvdW50LmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJleHAiOjE1NjQ1MTk0OTYsImdvb"
94           + "2dsZSI6eyJjb21wdXRlX2VuZ2luZSI6eyJpbnN0YW5jZV9jcmVhdGlvbl90aW1lc3RhbXAiOjE1NjMyMzA5MDcsI"
95           + "mluc3RhbmNlX2lkIjoiMzQ5Nzk3NDM5MzQ0MTE3OTI0MyIsImluc3RhbmNlX25hbWUiOiJpYW0iLCJwcm9qZWN0X"
96           + "2lkIjoibWluZXJhbC1taW51dGlhLTgyMCIsInByb2plY3RfbnVtYmVyIjoxMDcxMjg0MTg0NDM2LCJ6b25lIjoid"
97           + "XMtY2VudHJhbDEtYSJ9fSwiaWF0IjoxNTY0NTE1ODk2LCJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb"
98           + "20iLCJzdWIiOiIxMTIxNzkwNjI3MjAzOTEzMDU4ODUifQ.redacted";
99 
100   // Id Token which includes GCE extended claims and any VM License data (if applicable)
101   public static final String FULL_ID_TOKEN_WITH_LICENSE =
102       "eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmMzc1ODkwOG"
103           + "I3OTIyOTNhZDk3N2EwYjk5MWQ5OGE3N2Y0ZWVlY2QiLCJ0eXAiOiJKV1QifQ.ew0KICAiYXVkIjogImh0dHBzOi8"
104           + "vZm9vLmJhciIsDQogICJhenAiOiAiMTEyMTc5MDYyNzIwMzkxMzA1ODg1IiwNCiAgImVtYWlsIjogIjEyMzQ1Ni1"
105           + "jb21wdXRlQGRldmVsb3Blci5nc2VydmljZWFjY291bnQuY29tIiwNCiAgImVtYWlsX3ZlcmlmaWVkIjogdHJ1ZSw"
106           + "NCiAgImV4cCI6IDE1NjQ1MTk0OTYsDQogICJnb29nbGUiOiB7DQogICAgImNvbXB1dGVfZW5naW5lIjogew0KICA"
107           + "gICAgImluc3RhbmNlX2NyZWF0aW9uX3RpbWVzdGFtcCI6IDE1NjMyMzA5MDcsDQogICAgICAiaW5zdGFuY2VfaWQ"
108           + "iOiAiMzQ5Nzk3NDM5MzQ0MTE3OTI0MyIsDQogICAgICAiaW5zdGFuY2VfbmFtZSI6ICJpYW0iLA0KICAgICAgInB"
109           + "yb2plY3RfaWQiOiAiZm9vLWJhci04MjAiLA0KICAgICAgInByb2plY3RfbnVtYmVyIjogMTA3MTI4NDE4NDQzNiw"
110           + "NCiAgICAgICJ6b25lIjogInVzLWNlbnRyYWwxLWEiDQogICAgfSwNCiAgICAibGljZW5zZSI6IFsNCiAgICAgICA"
111           + "iTElDRU5TRV8xIiwNCiAgICAgICAiTElDRU5TRV8yIg0KICAgIF0NCiAgfSwNCiAgImlhdCI6IDE1NjQ1MTU4OTY"
112           + "sDQogICJpc3MiOiAiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tIiwNCiAgInN1YiI6ICIxMTIxNzkwNjI3MjA"
113           + "zOTEzMDU4ODUiDQp9.redacted";
114 
115   @Test
buildTokenUrlWithScopes_null_scopes()116   public void buildTokenUrlWithScopes_null_scopes() {
117     ComputeEngineCredentials credentials =
118         ComputeEngineCredentials.newBuilder().setScopes(null).build();
119     Collection<String> scopes = credentials.getScopes();
120     String tokenUrlWithScopes = credentials.createTokenUrlWithScopes();
121 
122     assertEquals(TOKEN_URL, tokenUrlWithScopes);
123     assertTrue(scopes.isEmpty());
124   }
125 
126   @Test
buildTokenUrlWithScopes_empty_scopes()127   public void buildTokenUrlWithScopes_empty_scopes() {
128     ComputeEngineCredentials.Builder builder =
129         ComputeEngineCredentials.newBuilder().setScopes(Collections.<String>emptyList());
130     ComputeEngineCredentials credentials = builder.build();
131     Collection<String> scopes = credentials.getScopes();
132     String tokenUrlWithScopes = credentials.createTokenUrlWithScopes();
133 
134     assertEquals(TOKEN_URL, tokenUrlWithScopes);
135     assertTrue(scopes.isEmpty());
136     assertTrue(builder.getScopes().isEmpty());
137   }
138 
139   @Test
buildTokenUrlWithScopes_single_scope()140   public void buildTokenUrlWithScopes_single_scope() {
141     ComputeEngineCredentials credentials =
142         ComputeEngineCredentials.newBuilder().setScopes(Arrays.asList("foo")).build();
143     String tokenUrlWithScopes = credentials.createTokenUrlWithScopes();
144     Collection<String> scopes = credentials.getScopes();
145 
146     assertEquals(TOKEN_URL + "?scopes=foo", tokenUrlWithScopes);
147     assertEquals(1, scopes.size());
148     assertEquals("foo", scopes.toArray()[0]);
149   }
150 
151   @Test
buildTokenUrlWithScopes_multiple_scopes()152   public void buildTokenUrlWithScopes_multiple_scopes() {
153     ComputeEngineCredentials credentials =
154         ComputeEngineCredentials.newBuilder()
155             .setScopes(Arrays.asList(null, "foo", "", "bar"))
156             .build();
157     Collection<String> scopes = credentials.getScopes();
158     String tokenUrlWithScopes = credentials.createTokenUrlWithScopes();
159 
160     assertEquals(TOKEN_URL + "?scopes=foo,bar", tokenUrlWithScopes);
161     assertEquals(2, scopes.size());
162     assertEquals("foo", scopes.toArray()[0]);
163     assertEquals("bar", scopes.toArray()[1]);
164   }
165 
166   @Test
buildTokenUrlWithScopes_defaultScopes()167   public void buildTokenUrlWithScopes_defaultScopes() {
168     ComputeEngineCredentials credentials = ComputeEngineCredentials.newBuilder().build();
169     credentials =
170         (ComputeEngineCredentials)
171             credentials.createScoped(null, Arrays.asList(null, "foo", "", "bar"));
172     Collection<String> scopes = credentials.getScopes();
173     String tokenUrlWithScopes = credentials.createTokenUrlWithScopes();
174 
175     assertEquals(TOKEN_URL + "?scopes=foo,bar", tokenUrlWithScopes);
176     assertEquals(2, scopes.size());
177     assertEquals("foo", scopes.toArray()[0]);
178     assertEquals("bar", scopes.toArray()[1]);
179   }
180 
181   @Test
buildScoped_scopesPresent()182   public void buildScoped_scopesPresent() throws IOException {
183     ComputeEngineCredentials credentials =
184         ComputeEngineCredentials.newBuilder().setScopes(null).build();
185     ComputeEngineCredentials scopedCredentials =
186         (ComputeEngineCredentials) credentials.createScoped(Arrays.asList("foo"));
187     Collection<String> scopes = scopedCredentials.getScopes();
188 
189     assertEquals(1, scopes.size());
190     assertEquals("foo", scopes.toArray()[0]);
191   }
192 
193   @Test
buildScoped_correctMargins()194   public void buildScoped_correctMargins() throws IOException {
195     ComputeEngineCredentials credentials =
196         ComputeEngineCredentials.newBuilder().setScopes(null).build();
197     ComputeEngineCredentials scopedCredentials =
198         (ComputeEngineCredentials) credentials.createScoped(Arrays.asList("foo"));
199 
200     assertEquals(
201         ComputeEngineCredentials.COMPUTE_EXPIRATION_MARGIN,
202         scopedCredentials.getExpirationMargin());
203     assertEquals(
204         ComputeEngineCredentials.COMPUTE_REFRESH_MARGIN, scopedCredentials.getRefreshMargin());
205   }
206 
207   @Test
buildScoped_explicitUniverse()208   public void buildScoped_explicitUniverse() throws IOException {
209     ComputeEngineCredentials credentials =
210         ComputeEngineCredentials.newBuilder()
211             .setScopes(null)
212             .setUniverseDomain("some-universe")
213             .build();
214     ComputeEngineCredentials scopedCredentials =
215         (ComputeEngineCredentials) credentials.createScoped(Arrays.asList("foo"));
216 
217     assertEquals("some-universe", scopedCredentials.getUniverseDomain());
218     assertEquals(true, scopedCredentials.isExplicitUniverseDomain());
219   }
220 
221   @Test
createScoped_defaultScopes()222   public void createScoped_defaultScopes() {
223     GoogleCredentials credentials =
224         ComputeEngineCredentials.create().createScoped(null, Arrays.asList("foo"));
225     Collection<String> scopes = ((ComputeEngineCredentials) credentials).getScopes();
226 
227     assertEquals(1, scopes.size());
228     assertEquals("foo", scopes.toArray()[0]);
229   }
230 
231   @Test
create_scoped_correctMargins()232   public void create_scoped_correctMargins() {
233     GoogleCredentials credentials =
234         ComputeEngineCredentials.create().createScoped(null, Arrays.asList("foo"));
235 
236     assertEquals(
237         ComputeEngineCredentials.COMPUTE_EXPIRATION_MARGIN, credentials.getExpirationMargin());
238     assertEquals(ComputeEngineCredentials.COMPUTE_REFRESH_MARGIN, credentials.getRefreshMargin());
239   }
240 
241   @Test
getRequestMetadata_hasAccessToken()242   public void getRequestMetadata_hasAccessToken() throws IOException {
243     String accessToken = "1/MkSJoj1xsli0AccessToken_NKPY2";
244     MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
245     transportFactory.transport.setAccessToken(accessToken);
246     ComputeEngineCredentials credentials =
247         ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
248     Map<String, List<String>> metadata = credentials.getRequestMetadata(CALL_URI);
249 
250     TestUtils.assertContainsBearerToken(metadata, accessToken);
251   }
252 
253   @Test
getRequestMetadata_missingServiceAccount_throws()254   public void getRequestMetadata_missingServiceAccount_throws() {
255     String accessToken = "1/MkSJoj1xsli0AccessToken_NKPY2";
256     MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
257     transportFactory.transport.setAccessToken(accessToken);
258     transportFactory.transport.setRequestStatusCode(HttpStatusCodes.STATUS_CODE_NOT_FOUND);
259     ComputeEngineCredentials credentials =
260         ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
261     try {
262       credentials.getRequestMetadata(CALL_URI);
263       fail("Expected error refreshing token.");
264     } catch (IOException expected) {
265       String message = expected.getMessage();
266       assertTrue(message.contains(Integer.toString(HttpStatusCodes.STATUS_CODE_NOT_FOUND)));
267       // Message should mention scopes are missing on the VM.
268       assertTrue(message.contains("scope"));
269     }
270   }
271 
272   @Test
getRequestMetadata_serverError_throws()273   public void getRequestMetadata_serverError_throws() {
274     String accessToken = "1/MkSJoj1xsli0AccessToken_NKPY2";
275     MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
276     transportFactory.transport.setAccessToken(accessToken);
277     transportFactory.transport.setRequestStatusCode(HttpStatusCodes.STATUS_CODE_SERVER_ERROR);
278     ComputeEngineCredentials credentials =
279         ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
280     try {
281       credentials.getRequestMetadata(CALL_URI);
282       fail("Expected error refreshing token.");
283     } catch (IOException expected) {
284       String message = expected.getMessage();
285       assertTrue(message.contains(Integer.toString(HttpStatusCodes.STATUS_CODE_SERVER_ERROR)));
286       assertTrue(message.contains("Unexpected"));
287     }
288   }
289 
290   @Test
equals_true()291   public void equals_true() throws IOException {
292     MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
293     ComputeEngineCredentials explicitUniverseCredentials =
294         ComputeEngineCredentials.newBuilder()
295             .setUniverseDomain(Credentials.GOOGLE_DEFAULT_UNIVERSE)
296             .setHttpTransportFactory(transportFactory)
297             .build();
298     ComputeEngineCredentials otherCredentials =
299         ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
300     assertEquals(Credentials.GOOGLE_DEFAULT_UNIVERSE, otherCredentials.getUniverseDomain());
301     assertFalse(explicitUniverseCredentials.equals(otherCredentials));
302     assertFalse(otherCredentials.equals(explicitUniverseCredentials));
303     ComputeEngineCredentials otherExplicitUniverseCredentials =
304         ComputeEngineCredentials.newBuilder()
305             .setUniverseDomain(Credentials.GOOGLE_DEFAULT_UNIVERSE)
306             .setHttpTransportFactory(transportFactory)
307             .build();
308     assertTrue(explicitUniverseCredentials.equals(otherExplicitUniverseCredentials));
309     assertTrue(otherExplicitUniverseCredentials.equals(explicitUniverseCredentials));
310   }
311 
312   @Test
equals_false_transportFactory()313   public void equals_false_transportFactory() throws IOException {
314     MockHttpTransportFactory httpTransportFactory = new MockHttpTransportFactory();
315     MockMetadataServerTransportFactory serverTransportFactory =
316         new MockMetadataServerTransportFactory();
317     ComputeEngineCredentials credentials =
318         ComputeEngineCredentials.newBuilder()
319             .setHttpTransportFactory(serverTransportFactory)
320             .build();
321     ComputeEngineCredentials otherCredentials =
322         ComputeEngineCredentials.newBuilder().setHttpTransportFactory(httpTransportFactory).build();
323     assertFalse(credentials.equals(otherCredentials));
324     assertFalse(otherCredentials.equals(credentials));
325   }
326 
327   @Test
toString_explicit_containsFields()328   public void toString_explicit_containsFields() throws IOException {
329     MockMetadataServerTransportFactory serverTransportFactory =
330         new MockMetadataServerTransportFactory();
331     String expectedToString =
332         String.format(
333             "ComputeEngineCredentials{quotaProjectId=%s, universeDomain=%s, isExplicitUniverseDomain=%s, transportFactoryClassName=%s, scopes=%s}",
334             "some-project",
335             "some-domain",
336             true,
337             MockMetadataServerTransportFactory.class.getName(),
338             "[some scope]");
339     GoogleCredentials credentials =
340         ComputeEngineCredentials.newBuilder()
341             .setHttpTransportFactory(serverTransportFactory)
342             .setQuotaProjectId("some-project")
343             .setUniverseDomain("some-domain")
344             .build();
345     credentials = credentials.createScoped("some scope");
346     assertEquals(expectedToString, credentials.toString());
347   }
348 
349   @Test
hashCode_equals()350   public void hashCode_equals() throws IOException {
351     MockMetadataServerTransportFactory serverTransportFactory =
352         new MockMetadataServerTransportFactory();
353     ComputeEngineCredentials credentials =
354         ComputeEngineCredentials.newBuilder()
355             .setHttpTransportFactory(serverTransportFactory)
356             .build();
357     ComputeEngineCredentials otherCredentials =
358         ComputeEngineCredentials.newBuilder()
359             .setHttpTransportFactory(serverTransportFactory)
360             .build();
361     assertEquals(credentials.hashCode(), otherCredentials.hashCode());
362   }
363 
364   @Test
toBuilder()365   public void toBuilder() {
366     ComputeEngineCredentials credentials =
367         ComputeEngineCredentials.newBuilder()
368             .setHttpTransportFactory(new MockMetadataServerTransportFactory())
369             .setQuotaProjectId("quota-project")
370             .build();
371 
372     ComputeEngineCredentials secondCredentials = credentials.toBuilder().build();
373 
374     assertEquals(credentials, secondCredentials);
375   }
376 
377   @Test
serialize()378   public void serialize() throws IOException, ClassNotFoundException {
379     MockMetadataServerTransportFactory serverTransportFactory =
380         new MockMetadataServerTransportFactory();
381     ComputeEngineCredentials credentials =
382         ComputeEngineCredentials.newBuilder()
383             .setHttpTransportFactory(serverTransportFactory)
384             .build();
385     GoogleCredentials deserializedCredentials = serializeAndDeserialize(credentials);
386     assertEquals(credentials, deserializedCredentials);
387     assertEquals(credentials.hashCode(), deserializedCredentials.hashCode());
388     assertEquals(credentials.toString(), deserializedCredentials.toString());
389     assertSame(deserializedCredentials.clock, Clock.SYSTEM);
390     credentials = ComputeEngineCredentials.newBuilder().build();
391     deserializedCredentials = serializeAndDeserialize(credentials);
392     assertEquals(credentials, deserializedCredentials);
393     assertEquals(credentials.hashCode(), deserializedCredentials.hashCode());
394     assertEquals(credentials.toString(), deserializedCredentials.toString());
395     assertSame(deserializedCredentials.clock, Clock.SYSTEM);
396   }
397 
398   @Test
getAccount_sameAs()399   public void getAccount_sameAs() throws IOException {
400     MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
401     String defaultAccountEmail = "[email protected]";
402 
403     transportFactory.transport.setServiceAccountEmail(defaultAccountEmail);
404     ComputeEngineCredentials credentials =
405         ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
406 
407     assertEquals(defaultAccountEmail, credentials.getAccount());
408   }
409 
410   @Test
getAccount_missing_throws()411   public void getAccount_missing_throws() {
412     MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
413     String defaultAccountEmail = "[email protected]";
414 
415     transportFactory.transport =
416         new MockMetadataServerTransport() {
417           @Override
418           public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
419             if (isGetServiceAccountsUrl(url)) {
420               return new MockLowLevelHttpRequest(url) {
421                 @Override
422                 public LowLevelHttpResponse execute() throws IOException {
423                   return new MockLowLevelHttpResponse()
424                       .setStatusCode(HttpStatusCodes.STATUS_CODE_NOT_FOUND)
425                       .setContent("");
426                 }
427               };
428             }
429             return super.buildRequest(method, url);
430           }
431         };
432     transportFactory.transport.setServiceAccountEmail(defaultAccountEmail);
433     ComputeEngineCredentials credentials =
434         ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
435 
436     try {
437       credentials.getAccount();
438       fail("Fetching default service account should have failed");
439     } catch (RuntimeException e) {
440       assertEquals("Failed to get service account", e.getMessage());
441       assertNotNull(e.getCause());
442       assertTrue(e.getCause().getMessage().contains("404"));
443     }
444   }
445 
446   @Test
getAccount_emptyContent_throws()447   public void getAccount_emptyContent_throws() {
448     MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
449     String defaultAccountEmail = "[email protected]";
450 
451     transportFactory.transport =
452         new MockMetadataServerTransport() {
453           @Override
454           public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
455             if (isGetServiceAccountsUrl(url)) {
456               return new MockLowLevelHttpRequest(url) {
457                 @Override
458                 public LowLevelHttpResponse execute() throws IOException {
459                   return new MockLowLevelHttpResponse()
460                       .setStatusCode(HttpStatusCodes.STATUS_CODE_OK);
461                 }
462               };
463             }
464             return super.buildRequest(method, url);
465           }
466         };
467     transportFactory.transport.setServiceAccountEmail(defaultAccountEmail);
468     ComputeEngineCredentials credentials =
469         ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
470 
471     try {
472       credentials.getAccount();
473       fail("Fetching default service account should have failed");
474     } catch (RuntimeException e) {
475       assertEquals("Failed to get service account", e.getMessage());
476       assertNotNull(e.getCause());
477       assertTrue(e.getCause().getMessage().contains("Empty content"));
478     }
479   }
480 
481   @Test
sign_sameAs()482   public void sign_sameAs() throws IOException {
483     MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
484     final String accessToken = "1/MkSJoj1xsli0AccessToken_NKPY2";
485     String defaultAccountEmail = "[email protected]";
486     byte[] expectedSignature = {0xD, 0xE, 0xA, 0xD};
487 
488     transportFactory.transport.setAccessToken(accessToken);
489     transportFactory.transport.setServiceAccountEmail(defaultAccountEmail);
490     transportFactory.transport.setSignature(expectedSignature);
491     ComputeEngineCredentials credentials =
492         ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
493 
494     assertArrayEquals(expectedSignature, credentials.sign(expectedSignature));
495   }
496 
497   @Test
sign_getAccountFails()498   public void sign_getAccountFails() throws IOException {
499     MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
500     final String accessToken = "1/MkSJoj1xsli0AccessToken_NKPY2";
501     byte[] expectedSignature = {0xD, 0xE, 0xA, 0xD};
502 
503     transportFactory.transport.setAccessToken(accessToken);
504     transportFactory.transport.setSignature(expectedSignature);
505     ComputeEngineCredentials credentials =
506         ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
507 
508     try {
509       credentials.sign(expectedSignature);
510       fail("Should not be able to use credential without exception.");
511     } catch (SigningException ex) {
512       assertNotNull(ex.getMessage());
513       assertNotNull(ex.getCause());
514     }
515   }
516 
517   @Test
sign_accessDenied_throws()518   public void sign_accessDenied_throws() {
519     MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
520     final String accessToken = "1/MkSJoj1xsli0AccessToken_NKPY2";
521     String defaultAccountEmail = "[email protected]";
522 
523     transportFactory.transport =
524         new MockMetadataServerTransport() {
525           @Override
526           public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
527             if (isSignRequestUrl(url)) {
528               return new MockLowLevelHttpRequest(url) {
529                 @Override
530                 public LowLevelHttpResponse execute() throws IOException {
531                   return new MockLowLevelHttpResponse()
532                       .setStatusCode(HttpStatusCodes.STATUS_CODE_FORBIDDEN)
533                       .setContent(TestUtils.errorJson("Sign Error"));
534                 }
535               };
536             }
537             return super.buildRequest(method, url);
538           }
539         };
540 
541     transportFactory.transport.setAccessToken(accessToken);
542     transportFactory.transport.setServiceAccountEmail(defaultAccountEmail);
543 
544     ComputeEngineCredentials credentials =
545         ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
546 
547     try {
548       byte[] bytes = {0xD, 0xE, 0xA, 0xD};
549       credentials.sign(bytes);
550       fail("Signing should have failed");
551     } catch (SigningException e) {
552       assertEquals("Failed to sign the provided bytes", e.getMessage());
553       assertNotNull(e.getCause());
554       assertTrue(e.getCause().getMessage().contains("403"));
555     }
556   }
557 
558   @Test
sign_serverError_throws()559   public void sign_serverError_throws() {
560     MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
561     final String accessToken = "1/MkSJoj1xsli0AccessToken_NKPY2";
562     String defaultAccountEmail = "[email protected]";
563 
564     transportFactory.transport =
565         new MockMetadataServerTransport() {
566           @Override
567           public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
568             if (isSignRequestUrl(url)) {
569               return new MockLowLevelHttpRequest(url) {
570                 @Override
571                 public LowLevelHttpResponse execute() throws IOException {
572                   return new MockLowLevelHttpResponse()
573                       .setStatusCode(HttpStatusCodes.STATUS_CODE_SERVER_ERROR)
574                       .setContent(TestUtils.errorJson("Sign Error"));
575                 }
576               };
577             }
578             return super.buildRequest(method, url);
579           }
580         };
581 
582     transportFactory.transport.setAccessToken(accessToken);
583     transportFactory.transport.setServiceAccountEmail(defaultAccountEmail);
584 
585     ComputeEngineCredentials credentials =
586         ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
587 
588     try {
589       byte[] bytes = {0xD, 0xE, 0xA, 0xD};
590       credentials.sign(bytes);
591       fail("Signing should have failed");
592     } catch (SigningException e) {
593       assertEquals("Failed to sign the provided bytes", e.getMessage());
594       assertNotNull(e.getCause());
595       assertTrue(e.getCause().getMessage().contains("500"));
596     }
597   }
598 
599   @Test
refresh_503_retryable_throws()600   public void refresh_503_retryable_throws() {
601     MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
602 
603     transportFactory.transport =
604         new MockMetadataServerTransport() {
605           @Override
606           public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
607             return new MockLowLevelHttpRequest(url) {
608               @Override
609               public LowLevelHttpResponse execute() throws IOException {
610                 return new MockLowLevelHttpResponse()
611                     .setStatusCode(HttpStatusCodes.STATUS_CODE_SERVICE_UNAVAILABLE)
612                     .setContent(TestUtils.errorJson("Some error"));
613               }
614             };
615           }
616         };
617 
618     ComputeEngineCredentials credentials =
619         ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
620 
621     try {
622       credentials.refreshAccessToken();
623       fail("Should have failed");
624     } catch (IOException e) {
625       assertTrue(e.getCause().getMessage().contains("503"));
626       assertTrue(e instanceof GoogleAuthException);
627       assertTrue(((GoogleAuthException) e).isRetryable());
628     }
629   }
630 
631   @Test
refresh_non503_ioexception_throws()632   public void refresh_non503_ioexception_throws() {
633     MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
634     final Queue<Integer> responseSequence = new ArrayDeque<>();
635     IntStream.rangeClosed(400, 600).forEach(i -> responseSequence.add(i));
636 
637     while (!responseSequence.isEmpty()) {
638       if (responseSequence.peek() == 503) {
639         responseSequence.poll();
640         continue;
641       }
642 
643       transportFactory.transport =
644           new MockMetadataServerTransport() {
645             @Override
646             public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
647               return new MockLowLevelHttpRequest(url) {
648                 @Override
649                 public LowLevelHttpResponse execute() throws IOException {
650                   return new MockLowLevelHttpResponse()
651                       .setStatusCode(responseSequence.poll())
652                       .setContent(TestUtils.errorJson("Some error"));
653                 }
654               };
655             }
656           };
657 
658       ComputeEngineCredentials credentials =
659           ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
660 
661       try {
662         credentials.refreshAccessToken();
663         fail("Should have failed");
664       } catch (IOException e) {
665         assertFalse(e instanceof GoogleAuthException);
666       }
667     }
668   }
669 
670   @Test
getUniverseDomain_fromMetadata()671   public void getUniverseDomain_fromMetadata() throws IOException {
672     MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
673 
674     transportFactory.transport =
675         new MockMetadataServerTransport() {
676           @Override
677           public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
678             return new MockLowLevelHttpRequest(url) {
679               @Override
680               public LowLevelHttpResponse execute() throws IOException {
681                 return new MockLowLevelHttpResponse()
682                     .setStatusCode(HttpStatusCodes.STATUS_CODE_OK)
683                     .setContent("some-universe.xyz");
684               }
685             };
686           }
687         };
688 
689     ComputeEngineCredentials credentials =
690         ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
691 
692     String universeDomain = credentials.getUniverseDomain();
693     assertEquals("some-universe.xyz", universeDomain);
694     assertEquals(false, credentials.isExplicitUniverseDomain());
695   }
696 
697   @Test
getUniverseDomain_fromMetadata_emptyBecomesDefault()698   public void getUniverseDomain_fromMetadata_emptyBecomesDefault() throws IOException {
699     MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
700 
701     transportFactory.transport =
702         new MockMetadataServerTransport() {
703           @Override
704           public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
705             return new MockLowLevelHttpRequest(url) {
706               @Override
707               public LowLevelHttpResponse execute() throws IOException {
708                 return new MockLowLevelHttpResponse()
709                     .setStatusCode(HttpStatusCodes.STATUS_CODE_OK)
710                     .setContent("");
711               }
712             };
713           }
714         };
715 
716     ComputeEngineCredentials credentials =
717         ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
718 
719     String universeDomain = credentials.getUniverseDomain();
720     assertEquals(Credentials.GOOGLE_DEFAULT_UNIVERSE, universeDomain);
721     assertEquals(false, credentials.isExplicitUniverseDomain());
722   }
723 
724   @Test
getUniverseDomain_fromMetadata_404_default()725   public void getUniverseDomain_fromMetadata_404_default() throws IOException {
726     MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
727 
728     transportFactory.transport =
729         new MockMetadataServerTransport() {
730           @Override
731           public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
732             return new MockLowLevelHttpRequest(url) {
733               @Override
734               public LowLevelHttpResponse execute() throws IOException {
735                 return new MockLowLevelHttpResponse()
736                     .setStatusCode(HttpStatusCodes.STATUS_CODE_NOT_FOUND)
737                     .setContent("some content");
738               }
739             };
740           }
741         };
742 
743     ComputeEngineCredentials credentials =
744         ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
745 
746     String universeDomain = credentials.getUniverseDomain();
747     assertEquals(Credentials.GOOGLE_DEFAULT_UNIVERSE, universeDomain);
748     assertEquals(false, credentials.isExplicitUniverseDomain());
749   }
750 
751   @Test
getUniverseDomain_explicitSet_NoMdsCall()752   public void getUniverseDomain_explicitSet_NoMdsCall() throws IOException {
753     MockRequestCountingTransportFactory transportFactory =
754         new MockRequestCountingTransportFactory();
755 
756     ComputeEngineCredentials credentials =
757         ComputeEngineCredentials.newBuilder()
758             .setHttpTransportFactory(transportFactory)
759             .setUniverseDomain("explicit.universe")
760             .build();
761 
762     String universeDomain = credentials.getUniverseDomain();
763     assertEquals("explicit.universe", universeDomain);
764     assertEquals(true, credentials.isExplicitUniverseDomain());
765     assertEquals(0, transportFactory.transport.getRequestCount());
766   }
767 
768   @Test
getUniverseDomain_explicitGduSet_NoMdsCall()769   public void getUniverseDomain_explicitGduSet_NoMdsCall() throws IOException {
770     MockRequestCountingTransportFactory transportFactory =
771         new MockRequestCountingTransportFactory();
772 
773     ComputeEngineCredentials credentials =
774         ComputeEngineCredentials.newBuilder()
775             .setHttpTransportFactory(transportFactory)
776             .setUniverseDomain(Credentials.GOOGLE_DEFAULT_UNIVERSE)
777             .build();
778 
779     String universeDomain = credentials.getUniverseDomain();
780     assertEquals(Credentials.GOOGLE_DEFAULT_UNIVERSE, universeDomain);
781     assertEquals(true, credentials.isExplicitUniverseDomain());
782     assertEquals(0, transportFactory.transport.getRequestCount());
783   }
784 
785   @Test
getUniverseDomain_fromMetadata_non404error_throws()786   public void getUniverseDomain_fromMetadata_non404error_throws() throws IOException {
787     MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
788     MockMetadataServerTransport transport = transportFactory.transport;
789 
790     ComputeEngineCredentials credentials =
791         ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
792 
793     for (int status = 400; status < 600; status++) {
794       // 404 should not throw and tested separately
795       if (status == 404) {
796         continue;
797       }
798       try {
799         transportFactory.transport.setRequestStatusCode(status);
800         credentials.getUniverseDomain();
801         fail("Should not be able to use credential without exception.");
802       } catch (GoogleAuthException ex) {
803         assertTrue(ex.isRetryable());
804       }
805     }
806   }
807 
808   @Test
sign_emptyContent_throws()809   public void sign_emptyContent_throws() {
810     MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
811     String accessToken = "1/MkSJoj1xsli0AccessToken_NKPY2";
812     String defaultAccountEmail = "[email protected]";
813 
814     transportFactory.transport =
815         new MockMetadataServerTransport() {
816           @Override
817           public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
818             if (isSignRequestUrl(url)) {
819               return new MockLowLevelHttpRequest(url) {
820                 @Override
821                 public LowLevelHttpResponse execute() throws IOException {
822                   return new MockLowLevelHttpResponse()
823                       .setStatusCode(HttpStatusCodes.STATUS_CODE_OK);
824                 }
825               };
826             }
827             return super.buildRequest(method, url);
828           }
829         };
830 
831     transportFactory.transport.setAccessToken(accessToken);
832     transportFactory.transport.setServiceAccountEmail(defaultAccountEmail);
833 
834     ComputeEngineCredentials credentials =
835         ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
836 
837     try {
838       byte[] bytes = {0xD, 0xE, 0xA, 0xD};
839       credentials.sign(bytes);
840       fail("Signing should have failed");
841     } catch (SigningException e) {
842       assertEquals("Failed to sign the provided bytes", e.getMessage());
843       assertNotNull(e.getCause());
844       assertTrue(e.getCause().getMessage().contains("Empty content"));
845     }
846   }
847 
848   @Test
idTokenWithAudience_sameAs()849   public void idTokenWithAudience_sameAs() throws IOException {
850     MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
851     transportFactory.transport.setIdToken(STANDARD_ID_TOKEN);
852     ComputeEngineCredentials credentials =
853         ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
854 
855     String targetAudience = "https://foo.bar";
856     IdTokenCredentials tokenCredential =
857         IdTokenCredentials.newBuilder()
858             .setIdTokenProvider(credentials)
859             .setTargetAudience(targetAudience)
860             .build();
861     tokenCredential.refresh();
862     assertEquals(STANDARD_ID_TOKEN, tokenCredential.getAccessToken().getTokenValue());
863     assertEquals(STANDARD_ID_TOKEN, tokenCredential.getIdToken().getTokenValue());
864     assertEquals(
865         targetAudience,
866         (String) tokenCredential.getIdToken().getJsonWebSignature().getPayload().getAudience());
867   }
868 
869   @Test
idTokenWithAudience_standard()870   public void idTokenWithAudience_standard() throws IOException {
871     MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
872     ComputeEngineCredentials credentials =
873         ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
874 
875     String targetAudience = "https://foo.bar";
876     IdTokenCredentials tokenCredential =
877         IdTokenCredentials.newBuilder()
878             .setIdTokenProvider(credentials)
879             .setTargetAudience(targetAudience)
880             .build();
881     tokenCredential.refresh();
882     assertEquals(STANDARD_ID_TOKEN, tokenCredential.getAccessToken().getTokenValue());
883     assertEquals(STANDARD_ID_TOKEN, tokenCredential.getIdToken().getTokenValue());
884     assertNull(tokenCredential.getIdToken().getJsonWebSignature().getPayload().get("google"));
885   }
886 
887   @Test
888   @SuppressWarnings("unchecked")
idTokenWithAudience_full()889   public void idTokenWithAudience_full() throws IOException {
890     MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
891     ComputeEngineCredentials credentials =
892         ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
893 
894     String targetAudience = "https://foo.bar";
895     IdTokenCredentials tokenCredential =
896         IdTokenCredentials.newBuilder()
897             .setIdTokenProvider(credentials)
898             .setTargetAudience(targetAudience)
899             .setOptions(Arrays.asList(IdTokenProvider.Option.FORMAT_FULL))
900             .build();
901     tokenCredential.refresh();
902     Payload p = tokenCredential.getIdToken().getJsonWebSignature().getPayload();
903     assertTrue("Full ID Token format not provided", p.containsKey("google"));
904     ArrayMap<String, ArrayMap> googleClaim = (ArrayMap<String, ArrayMap>) p.get("google");
905     assertTrue(googleClaim.containsKey("compute_engine"));
906   }
907 
908   @Test
909   @SuppressWarnings("unchecked")
idTokenWithAudience_license()910   public void idTokenWithAudience_license() throws IOException {
911     MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
912     ComputeEngineCredentials credentials =
913         ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
914 
915     String targetAudience = "https://foo.bar";
916     IdTokenCredentials tokenCredential =
917         IdTokenCredentials.newBuilder()
918             .setIdTokenProvider(credentials)
919             .setTargetAudience(targetAudience)
920             .setOptions(
921                 Arrays.asList(
922                     IdTokenProvider.Option.FORMAT_FULL, IdTokenProvider.Option.LICENSES_TRUE))
923             .build();
924     tokenCredential.refresh();
925     Payload p = tokenCredential.getIdToken().getJsonWebSignature().getPayload();
926     assertTrue("Full ID Token format not provided", p.containsKey("google"));
927     ArrayMap<String, ArrayMap> googleClaim = (ArrayMap<String, ArrayMap>) p.get("google");
928     assertTrue(googleClaim.containsKey("license"));
929   }
930 
931   static class MockMetadataServerTransportFactory implements HttpTransportFactory {
932 
933     MockMetadataServerTransport transport = new MockMetadataServerTransport();
934 
935     @Override
create()936     public HttpTransport create() {
937       return transport;
938     }
939   }
940 }
941