• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2015, Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *    * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *    * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *
15  *    * Neither the name of Google Inc. nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 package com.google.auth.oauth2;
33 
34 import static org.junit.Assert.assertArrayEquals;
35 import static org.junit.Assert.assertEquals;
36 import static org.junit.Assert.assertFalse;
37 import static org.junit.Assert.assertNotEquals;
38 import static org.junit.Assert.assertNotNull;
39 import static org.junit.Assert.assertNull;
40 import static org.junit.Assert.assertSame;
41 import static org.junit.Assert.assertTrue;
42 import static org.junit.Assert.fail;
43 
44 import com.google.api.client.json.JsonFactory;
45 import com.google.api.client.json.gson.GsonFactory;
46 import com.google.api.client.json.webtoken.JsonWebSignature;
47 import com.google.api.client.util.Clock;
48 import com.google.auth.Credentials;
49 import com.google.auth.RequestMetadataCallback;
50 import com.google.auth.TestClock;
51 import com.google.auth.http.AuthHttpConstants;
52 import java.io.IOException;
53 import java.io.InputStream;
54 import java.net.URI;
55 import java.security.InvalidKeyException;
56 import java.security.NoSuchAlgorithmException;
57 import java.security.PrivateKey;
58 import java.security.Signature;
59 import java.security.SignatureException;
60 import java.util.Collections;
61 import java.util.List;
62 import java.util.Map;
63 import java.util.concurrent.TimeUnit;
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 ServiceAccountCredentials}. */
70 @RunWith(JUnit4.class)
71 public class ServiceAccountJwtAccessCredentialsTest extends BaseSerializationTest {
72 
73   private static final String SA_CLIENT_EMAIL =
74       "36680232662-vrd7ji19qe3nelgchd0ah2csanun6bnr@developer.gserviceaccount.com";
75   private static final String SA_CLIENT_ID =
76       "36680232662-vrd7ji19qe3nelgchd0ah2csanun6bnr.apps.googleusercontent.com";
77   private static final String SA_PRIVATE_KEY_ID = "d84a4fefcf50791d4a90f2d7af17469d6282df9d";
78   private static final String SA_PRIVATE_KEY_PKCS8 =
79       "-----BEGIN PRIVATE KEY-----\n"
80           + "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALX0PQoe1igW12i"
81           + "kv1bN/r9lN749y2ijmbc/mFHPyS3hNTyOCjDvBbXYbDhQJzWVUikh4mvGBA07qTj79Xc3yBDfKP2IeyYQIFe0t0"
82           + "zkd7R9Zdn98Y2rIQC47aAbDfubtkU1U72t4zL11kHvoa0/RuFZjncvlr42X7be7lYh4p3NAgMBAAECgYASk5wDw"
83           + "4Az2ZkmeuN6Fk/y9H+Lcb2pskJIXjrL533vrDWGOC48LrsThMQPv8cxBky8HFSEklPpkfTF95tpD43iVwJRB/Gr"
84           + "CtGTw65IfJ4/tI09h6zGc4yqvIo1cHX/LQ+SxKLGyir/dQM925rGt/VojxY5ryJR7GLbCzxPnJm/oQJBANwOCO6"
85           + "D2hy1LQYJhXh7O+RLtA/tSnT1xyMQsGT+uUCMiKS2bSKx2wxo9k7h3OegNJIu1q6nZ6AbxDK8H3+d0dUCQQDTrP"
86           + "SXagBxzp8PecbaCHjzNRSQE2in81qYnrAFNB4o3DpHyMMY6s5ALLeHKscEWnqP8Ur6X4PvzZecCWU9BKAZAkAut"
87           + "LPknAuxSCsUOvUfS1i87ex77Ot+w6POp34pEX+UWb+u5iFn2cQacDTHLV1LtE80L8jVLSbrbrlH43H0DjU5AkEA"
88           + "gidhycxS86dxpEljnOMCw8CKoUBd5I880IUahEiUltk7OLJYS/Ts1wbn3kPOVX3wyJs8WBDtBkFrDHW2ezth2QJ"
89           + "ADj3e1YhMVdjJW5jqwlD/VNddGjgzyunmiZg0uOXsHXbytYmsA545S8KRQFaJKFXYYFo2kOjqOiC1T2cAzMDjCQ"
90           + "==\n-----END PRIVATE KEY-----\n";
91   private static final String JWT_ACCESS_PREFIX =
92       ServiceAccountJwtAccessCredentials.JWT_ACCESS_PREFIX;
93   private static final URI CALL_URI = URI.create("http://googleapis.com/testapi/v1/foo");
94   private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();
95   private static final String QUOTA_PROJECT = "sample-quota-project-id";
96 
97   @Test
constructor_allParameters_constructs()98   public void constructor_allParameters_constructs() throws IOException {
99     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
100     ServiceAccountJwtAccessCredentials credentials =
101         ServiceAccountJwtAccessCredentials.newBuilder()
102             .setClientId(SA_CLIENT_ID)
103             .setClientEmail(SA_CLIENT_EMAIL)
104             .setPrivateKey(privateKey)
105             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
106             .setQuotaProjectId(QUOTA_PROJECT)
107             .build();
108 
109     assertEquals(SA_CLIENT_ID, credentials.getClientId());
110     assertEquals(SA_CLIENT_EMAIL, credentials.getClientEmail());
111     assertEquals(privateKey, credentials.getPrivateKey());
112     assertEquals(SA_PRIVATE_KEY_ID, credentials.getPrivateKeyId());
113     assertEquals(QUOTA_PROJECT, credentials.getQuotaProjectId());
114   }
115 
116   @Test
constructor_noClientId_constructs()117   public void constructor_noClientId_constructs() throws IOException {
118     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
119     ServiceAccountJwtAccessCredentials.newBuilder()
120         .setClientEmail(SA_CLIENT_EMAIL)
121         .setPrivateKey(privateKey)
122         .setPrivateKeyId(SA_PRIVATE_KEY_ID)
123         .build();
124   }
125 
126   @Test
constructor_noPrivateKeyId_constructs()127   public void constructor_noPrivateKeyId_constructs() throws IOException {
128     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
129     ServiceAccountJwtAccessCredentials.newBuilder()
130         .setClientId(SA_CLIENT_ID)
131         .setClientEmail(SA_CLIENT_EMAIL)
132         .setPrivateKey(privateKey)
133         .build();
134   }
135 
136   @Test
constructor_noEmail_throws()137   public void constructor_noEmail_throws() throws IOException {
138     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
139     try {
140       ServiceAccountJwtAccessCredentials.newBuilder()
141           .setClientId(SA_CLIENT_ID)
142           .setPrivateKey(privateKey)
143           .setPrivateKeyId(SA_PRIVATE_KEY_ID)
144           .build();
145       fail("exception expected");
146     } catch (NullPointerException e) {
147       // Expected
148     }
149   }
150 
151   @Test
constructor_noPrivateKey_throws()152   public void constructor_noPrivateKey_throws() {
153     try {
154       ServiceAccountJwtAccessCredentials.newBuilder()
155           .setClientId(SA_CLIENT_ID)
156           .setClientEmail(SA_CLIENT_EMAIL)
157           .setPrivateKeyId(SA_PRIVATE_KEY_ID)
158           .build();
159       fail("exception expected");
160     } catch (NullPointerException e) {
161       // Expected
162     }
163   }
164 
165   @Test
getAuthenticationType_returnsJwtAccess()166   public void getAuthenticationType_returnsJwtAccess() throws IOException {
167     Credentials credentials =
168         ServiceAccountJwtAccessCredentials.fromPkcs8(
169             SA_CLIENT_ID, SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID);
170     assertEquals(credentials.getAuthenticationType(), "JWTAccess");
171   }
172 
173   @Test
hasRequestMetadata_returnsTrue()174   public void hasRequestMetadata_returnsTrue() throws IOException {
175     Credentials credentials =
176         ServiceAccountJwtAccessCredentials.fromPkcs8(
177             SA_CLIENT_ID, SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID);
178     assertTrue(credentials.hasRequestMetadata());
179   }
180 
181   @Test
hasRequestMetadataOnly_returnsTrue()182   public void hasRequestMetadataOnly_returnsTrue() throws IOException {
183     Credentials credentials =
184         ServiceAccountJwtAccessCredentials.fromPkcs8(
185             SA_CLIENT_ID, SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID);
186     assertTrue(credentials.hasRequestMetadataOnly());
187   }
188 
189   @Test
getRequestMetadata_blocking_hasJwtAccess()190   public void getRequestMetadata_blocking_hasJwtAccess() throws IOException {
191     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
192     ServiceAccountJwtAccessCredentials credentials =
193         ServiceAccountJwtAccessCredentials.newBuilder()
194             .setClientId(SA_CLIENT_ID)
195             .setClientEmail(SA_CLIENT_EMAIL)
196             .setPrivateKey(privateKey)
197             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
198             .build();
199 
200     Map<String, List<String>> metadata = credentials.getRequestMetadata(CALL_URI);
201 
202     verifyJwtAccess(metadata, SA_CLIENT_EMAIL, CALL_URI, SA_PRIVATE_KEY_ID);
203   }
204 
205   @Test
getRequestMetadata_blocking_defaultURI_hasJwtAccess()206   public void getRequestMetadata_blocking_defaultURI_hasJwtAccess() throws IOException {
207     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
208     Credentials credentials =
209         ServiceAccountJwtAccessCredentials.newBuilder()
210             .setClientId(SA_CLIENT_ID)
211             .setClientEmail(SA_CLIENT_EMAIL)
212             .setPrivateKey(privateKey)
213             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
214             .setDefaultAudience(CALL_URI)
215             .build();
216 
217     Map<String, List<String>> metadata = credentials.getRequestMetadata();
218 
219     verifyJwtAccess(metadata, SA_CLIENT_EMAIL, CALL_URI, SA_PRIVATE_KEY_ID);
220   }
221 
222   @Test
getRequestMetadata_blocking_noURI_throws()223   public void getRequestMetadata_blocking_noURI_throws() throws IOException {
224     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
225     ServiceAccountJwtAccessCredentials credentials =
226         ServiceAccountJwtAccessCredentials.newBuilder()
227             .setClientId(SA_CLIENT_ID)
228             .setClientEmail(SA_CLIENT_EMAIL)
229             .setPrivateKey(privateKey)
230             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
231             .build();
232 
233     try {
234       credentials.getRequestMetadata();
235       fail("exception expected");
236     } catch (IOException e) {
237       // Expected
238     }
239   }
240 
241   @Test
getRequestMetadata_blocking_cached()242   public void getRequestMetadata_blocking_cached() throws IOException {
243     TestClock testClock = new TestClock();
244 
245     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
246     ServiceAccountJwtAccessCredentials credentials =
247         ServiceAccountJwtAccessCredentials.newBuilder()
248             .setClientId(SA_CLIENT_ID)
249             .setClientEmail(SA_CLIENT_EMAIL)
250             .setPrivateKey(privateKey)
251             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
252             .build();
253     credentials.clock = testClock;
254 
255     Map<String, List<String>> metadata1 = credentials.getRequestMetadata(CALL_URI);
256 
257     // Fast forward time a little
258     long lifeSpanMs = TimeUnit.SECONDS.toMillis(10);
259     testClock.setCurrentTime(lifeSpanMs);
260 
261     Map<String, List<String>> metadata2 = credentials.getRequestMetadata(CALL_URI);
262 
263     assertEquals(metadata1, metadata2);
264   }
265 
266   @Test
getRequestMetadata_blocking_cache_expired()267   public void getRequestMetadata_blocking_cache_expired() throws IOException {
268     TestClock testClock = new TestClock();
269 
270     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
271     ServiceAccountJwtAccessCredentials credentials =
272         ServiceAccountJwtAccessCredentials.newBuilder()
273             .setClientId(SA_CLIENT_ID)
274             .setClientEmail(SA_CLIENT_EMAIL)
275             .setPrivateKey(privateKey)
276             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
277             .build();
278     credentials.clock = testClock;
279 
280     Map<String, List<String>> metadata1 = credentials.getRequestMetadata(CALL_URI);
281 
282     // Fast forward time past the expiration
283     long lifeSpanMs = TimeUnit.SECONDS.toMillis(ServiceAccountJwtAccessCredentials.LIFE_SPAN_SECS);
284     testClock.setCurrentTime(lifeSpanMs);
285 
286     Map<String, List<String>> metadata2 = credentials.getRequestMetadata(CALL_URI);
287 
288     assertNotEquals(metadata1, metadata2);
289   }
290 
291   @Test
getRequestMetadata_async_hasJwtAccess()292   public void getRequestMetadata_async_hasJwtAccess() throws IOException {
293     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
294     ServiceAccountJwtAccessCredentials credentials =
295         ServiceAccountJwtAccessCredentials.newBuilder()
296             .setClientId(SA_CLIENT_ID)
297             .setClientEmail(SA_CLIENT_EMAIL)
298             .setPrivateKey(privateKey)
299             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
300             .build();
301     MockExecutor executor = new MockExecutor();
302     MockRequestMetadataCallback callback = new MockRequestMetadataCallback();
303 
304     credentials.getRequestMetadata(CALL_URI, executor, callback);
305     assertEquals(0, executor.numTasks());
306     assertNotNull(callback.metadata);
307     verifyJwtAccess(callback.metadata, SA_CLIENT_EMAIL, CALL_URI, SA_PRIVATE_KEY_ID);
308   }
309 
310   @Test
getRequestMetadata_async_defaultURI_hasJwtAccess()311   public void getRequestMetadata_async_defaultURI_hasJwtAccess() throws IOException {
312     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
313     Credentials credentials =
314         ServiceAccountJwtAccessCredentials.newBuilder()
315             .setClientId(SA_CLIENT_ID)
316             .setClientEmail(SA_CLIENT_EMAIL)
317             .setPrivateKey(privateKey)
318             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
319             .setDefaultAudience(CALL_URI)
320             .build();
321     MockExecutor executor = new MockExecutor();
322     MockRequestMetadataCallback callback = new MockRequestMetadataCallback();
323 
324     credentials.getRequestMetadata(null, executor, callback);
325     assertEquals(0, executor.numTasks());
326     assertNotNull(callback.metadata);
327     verifyJwtAccess(callback.metadata, SA_CLIENT_EMAIL, CALL_URI, SA_PRIVATE_KEY_ID);
328   }
329 
330   @Test
getRequestMetadata_async_noURI_exception()331   public void getRequestMetadata_async_noURI_exception() throws IOException {
332     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
333     ServiceAccountJwtAccessCredentials credentials =
334         ServiceAccountJwtAccessCredentials.newBuilder()
335             .setClientId(SA_CLIENT_ID)
336             .setClientEmail(SA_CLIENT_EMAIL)
337             .setPrivateKey(privateKey)
338             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
339             .build();
340     MockExecutor executor = new MockExecutor();
341     MockRequestMetadataCallback callback = new MockRequestMetadataCallback();
342 
343     credentials.getRequestMetadata(null, executor, callback);
344     assertEquals(0, executor.numTasks());
345     assertNotNull(callback.exception);
346   }
347 
348   @Test
getRequestMetadata_async_cache_expired()349   public void getRequestMetadata_async_cache_expired() throws IOException {
350     TestClock testClock = new TestClock();
351 
352     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
353     ServiceAccountJwtAccessCredentials credentials =
354         ServiceAccountJwtAccessCredentials.newBuilder()
355             .setClientId(SA_CLIENT_ID)
356             .setClientEmail(SA_CLIENT_EMAIL)
357             .setPrivateKey(privateKey)
358             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
359             .build();
360     credentials.clock = testClock;
361     MockExecutor executor = new MockExecutor();
362 
363     MockRequestMetadataCallback callback1 = new MockRequestMetadataCallback();
364     credentials.getRequestMetadata(CALL_URI, executor, callback1);
365 
366     // Fast forward time past the expiration
367     long lifeSpanMs = TimeUnit.SECONDS.toMillis(ServiceAccountJwtAccessCredentials.LIFE_SPAN_SECS);
368     testClock.setCurrentTime(lifeSpanMs);
369 
370     MockRequestMetadataCallback callback2 = new MockRequestMetadataCallback();
371     credentials.getRequestMetadata(CALL_URI, executor, callback2);
372 
373     assertNotEquals(callback1.metadata, callback2.metadata);
374   }
375 
376   @Test
getRequestMetadata_async_cached()377   public void getRequestMetadata_async_cached() throws IOException {
378     TestClock testClock = new TestClock();
379 
380     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
381     ServiceAccountJwtAccessCredentials credentials =
382         ServiceAccountJwtAccessCredentials.newBuilder()
383             .setClientId(SA_CLIENT_ID)
384             .setClientEmail(SA_CLIENT_EMAIL)
385             .setPrivateKey(privateKey)
386             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
387             .build();
388     credentials.clock = testClock;
389     MockExecutor executor = new MockExecutor();
390 
391     MockRequestMetadataCallback callback1 = new MockRequestMetadataCallback();
392     credentials.getRequestMetadata(CALL_URI, executor, callback1);
393 
394     // Fast forward time a little
395     long lifeSpanMs = TimeUnit.SECONDS.toMillis(10);
396     testClock.setCurrentTime(lifeSpanMs);
397 
398     MockRequestMetadataCallback callback2 = new MockRequestMetadataCallback();
399     credentials.getRequestMetadata(CALL_URI, executor, callback2);
400 
401     assertEquals(callback1.metadata, callback2.metadata);
402   }
403 
404   @Test
getRequestMetadata_contains_quotaProjectId()405   public void getRequestMetadata_contains_quotaProjectId() throws IOException {
406     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
407     Credentials credentials =
408         ServiceAccountJwtAccessCredentials.newBuilder()
409             .setClientId(SA_CLIENT_ID)
410             .setClientEmail(SA_CLIENT_EMAIL)
411             .setPrivateKey(privateKey)
412             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
413             .setDefaultAudience(CALL_URI)
414             .setQuotaProjectId(QUOTA_PROJECT)
415             .build();
416 
417     Map<String, List<String>> metadata = credentials.getRequestMetadata(CALL_URI);
418     assertTrue(metadata.containsKey(GoogleCredentials.QUOTA_PROJECT_ID_HEADER_KEY));
419     assertEquals(
420         metadata.get(GoogleCredentials.QUOTA_PROJECT_ID_HEADER_KEY),
421         Collections.singletonList(QUOTA_PROJECT));
422   }
423 
424   @Test
getAccount_sameAs()425   public void getAccount_sameAs() throws IOException {
426     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
427     ServiceAccountJwtAccessCredentials credentials =
428         ServiceAccountJwtAccessCredentials.newBuilder()
429             .setClientId(SA_CLIENT_ID)
430             .setClientEmail(SA_CLIENT_EMAIL)
431             .setPrivateKey(privateKey)
432             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
433             .build();
434     assertEquals(SA_CLIENT_EMAIL, credentials.getAccount());
435   }
436 
437   @Test
sign_sameAs()438   public void sign_sameAs()
439       throws IOException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
440     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
441     byte[] toSign = {0xD, 0xE, 0xA, 0xD};
442     ServiceAccountJwtAccessCredentials credentials =
443         ServiceAccountJwtAccessCredentials.newBuilder()
444             .setClientId(SA_CLIENT_ID)
445             .setClientEmail(SA_CLIENT_EMAIL)
446             .setPrivateKey(privateKey)
447             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
448             .build();
449     byte[] signedBytes = credentials.sign(toSign);
450     Signature signature = Signature.getInstance(OAuth2Utils.SIGNATURE_ALGORITHM);
451     signature.initSign(credentials.getPrivateKey());
452     signature.update(toSign);
453     assertArrayEquals(signature.sign(), signedBytes);
454   }
455 
456   @Test
equals_true()457   public void equals_true() throws IOException {
458     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
459     ServiceAccountJwtAccessCredentials credentials =
460         ServiceAccountJwtAccessCredentials.newBuilder()
461             .setClientId(SA_CLIENT_ID)
462             .setClientEmail(SA_CLIENT_EMAIL)
463             .setPrivateKey(privateKey)
464             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
465             .setDefaultAudience(CALL_URI)
466             .build();
467     ServiceAccountJwtAccessCredentials otherCredentials =
468         ServiceAccountJwtAccessCredentials.newBuilder()
469             .setClientId(SA_CLIENT_ID)
470             .setClientEmail(SA_CLIENT_EMAIL)
471             .setPrivateKey(privateKey)
472             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
473             .setDefaultAudience(CALL_URI)
474             .build();
475     assertTrue(credentials.equals(otherCredentials));
476     assertTrue(otherCredentials.equals(credentials));
477   }
478 
479   @Test
equals_false_clientId()480   public void equals_false_clientId() throws IOException {
481     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
482     ServiceAccountJwtAccessCredentials credentials =
483         ServiceAccountJwtAccessCredentials.newBuilder()
484             .setClientId(SA_CLIENT_ID)
485             .setClientEmail(SA_CLIENT_EMAIL)
486             .setPrivateKey(privateKey)
487             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
488             .setDefaultAudience(CALL_URI)
489             .build();
490     ServiceAccountJwtAccessCredentials otherCredentials =
491         ServiceAccountJwtAccessCredentials.newBuilder()
492             .setClientId("otherClientId")
493             .setClientEmail(SA_CLIENT_EMAIL)
494             .setPrivateKey(privateKey)
495             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
496             .setDefaultAudience(CALL_URI)
497             .build();
498     assertFalse(credentials.equals(otherCredentials));
499     assertFalse(otherCredentials.equals(credentials));
500   }
501 
502   @Test
equals_false_email()503   public void equals_false_email() throws IOException {
504     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
505     ServiceAccountJwtAccessCredentials credentials =
506         ServiceAccountJwtAccessCredentials.newBuilder()
507             .setClientId(SA_CLIENT_ID)
508             .setClientEmail(SA_CLIENT_EMAIL)
509             .setPrivateKey(privateKey)
510             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
511             .setDefaultAudience(CALL_URI)
512             .build();
513     ServiceAccountJwtAccessCredentials otherCredentials =
514         ServiceAccountJwtAccessCredentials.newBuilder()
515             .setClientId(SA_CLIENT_ID)
516             .setClientEmail("otherClientEmail")
517             .setPrivateKey(privateKey)
518             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
519             .setDefaultAudience(CALL_URI)
520             .build();
521     assertFalse(credentials.equals(otherCredentials));
522     assertFalse(otherCredentials.equals(credentials));
523   }
524 
525   @Test
equals_false_keyId()526   public void equals_false_keyId() throws IOException {
527     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
528     ServiceAccountJwtAccessCredentials credentials =
529         ServiceAccountJwtAccessCredentials.newBuilder()
530             .setClientId(SA_CLIENT_ID)
531             .setClientEmail(SA_CLIENT_EMAIL)
532             .setPrivateKey(privateKey)
533             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
534             .setDefaultAudience(CALL_URI)
535             .build();
536     ServiceAccountJwtAccessCredentials otherCredentials =
537         ServiceAccountJwtAccessCredentials.newBuilder()
538             .setClientId(SA_CLIENT_ID)
539             .setClientEmail(SA_CLIENT_EMAIL)
540             .setPrivateKey(privateKey)
541             .setPrivateKeyId("otherKeyId")
542             .setDefaultAudience(CALL_URI)
543             .build();
544     assertFalse(credentials.equals(otherCredentials));
545     assertFalse(otherCredentials.equals(credentials));
546   }
547 
548   @Test
equals_false_callUri()549   public void equals_false_callUri() throws IOException {
550     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
551     final URI otherCallUri = URI.create("https://foo.com/bar");
552     ServiceAccountJwtAccessCredentials credentials =
553         ServiceAccountJwtAccessCredentials.newBuilder()
554             .setClientId(SA_CLIENT_ID)
555             .setClientEmail(SA_CLIENT_EMAIL)
556             .setPrivateKey(privateKey)
557             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
558             .setDefaultAudience(CALL_URI)
559             .build();
560     ServiceAccountJwtAccessCredentials otherCredentials =
561         ServiceAccountJwtAccessCredentials.newBuilder()
562             .setClientId(SA_CLIENT_ID)
563             .setClientEmail(SA_CLIENT_EMAIL)
564             .setPrivateKey(privateKey)
565             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
566             .setDefaultAudience(otherCallUri)
567             .build();
568     assertFalse(credentials.equals(otherCredentials));
569     assertFalse(otherCredentials.equals(credentials));
570   }
571 
572   @Test
toString_containsFields()573   public void toString_containsFields() throws IOException {
574     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
575     ServiceAccountJwtAccessCredentials credentials =
576         ServiceAccountJwtAccessCredentials.newBuilder()
577             .setClientId(SA_CLIENT_ID)
578             .setClientEmail(SA_CLIENT_EMAIL)
579             .setPrivateKey(privateKey)
580             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
581             .setDefaultAudience(CALL_URI)
582             .setQuotaProjectId(QUOTA_PROJECT)
583             .build();
584     String expectedToString =
585         String.format(
586             "ServiceAccountJwtAccessCredentials{clientId=%s, clientEmail=%s, privateKeyId=%s, "
587                 + "defaultAudience=%s, quotaProjectId=%s}",
588             SA_CLIENT_ID, SA_CLIENT_EMAIL, SA_PRIVATE_KEY_ID, CALL_URI, QUOTA_PROJECT);
589     assertEquals(expectedToString, credentials.toString());
590   }
591 
592   @Test
hashCode_equals()593   public void hashCode_equals() throws IOException {
594     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
595     ServiceAccountJwtAccessCredentials credentials =
596         ServiceAccountJwtAccessCredentials.newBuilder()
597             .setClientId(SA_CLIENT_ID)
598             .setClientEmail(SA_CLIENT_EMAIL)
599             .setPrivateKey(privateKey)
600             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
601             .setDefaultAudience(CALL_URI)
602             .build();
603     ServiceAccountJwtAccessCredentials otherCredentials =
604         ServiceAccountJwtAccessCredentials.newBuilder()
605             .setClientId(SA_CLIENT_ID)
606             .setClientEmail(SA_CLIENT_EMAIL)
607             .setPrivateKey(privateKey)
608             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
609             .setDefaultAudience(CALL_URI)
610             .build();
611     assertEquals(credentials.hashCode(), otherCredentials.hashCode());
612   }
613 
614   @Test
serialize()615   public void serialize() throws IOException, ClassNotFoundException {
616     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
617     ServiceAccountJwtAccessCredentials credentials =
618         ServiceAccountJwtAccessCredentials.newBuilder()
619             .setClientId(SA_CLIENT_ID)
620             .setClientEmail(SA_CLIENT_EMAIL)
621             .setPrivateKey(privateKey)
622             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
623             .setDefaultAudience(CALL_URI)
624             .build();
625     ServiceAccountJwtAccessCredentials deserializedCredentials =
626         serializeAndDeserialize(credentials);
627     verifyJwtAccess(
628         deserializedCredentials.getRequestMetadata(), SA_CLIENT_EMAIL, CALL_URI, SA_PRIVATE_KEY_ID);
629     assertEquals(credentials, deserializedCredentials);
630     assertEquals(credentials.hashCode(), deserializedCredentials.hashCode());
631     assertEquals(credentials.toString(), deserializedCredentials.toString());
632     assertSame(deserializedCredentials.clock, Clock.SYSTEM);
633   }
634 
635   @Test
fromStream_nullStream_throws()636   public void fromStream_nullStream_throws() throws IOException {
637     MockHttpTransportFactory transportFactory = new MockHttpTransportFactory();
638     try {
639       ServiceAccountCredentials.fromStream(null, transportFactory);
640       fail("Should throw if InputStream is null");
641     } catch (NullPointerException expected) {
642       // Expected
643     }
644   }
645 
646   @Test
fromStream_hasJwtAccess()647   public void fromStream_hasJwtAccess() throws IOException {
648     InputStream serviceAccountStream =
649         ServiceAccountCredentialsTest.writeServiceAccountStream(
650             SA_CLIENT_ID, SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID);
651 
652     Credentials credentials = ServiceAccountJwtAccessCredentials.fromStream(serviceAccountStream);
653 
654     assertNotNull(credentials);
655     Map<String, List<String>> metadata = credentials.getRequestMetadata(CALL_URI);
656     verifyJwtAccess(metadata, SA_CLIENT_EMAIL, CALL_URI, SA_PRIVATE_KEY_ID);
657   }
658 
659   @Test
fromStream_defaultURI_hasJwtAccess()660   public void fromStream_defaultURI_hasJwtAccess() throws IOException {
661     InputStream serviceAccountStream =
662         ServiceAccountCredentialsTest.writeServiceAccountStream(
663             SA_CLIENT_ID, SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID);
664 
665     Credentials credentials =
666         ServiceAccountJwtAccessCredentials.fromStream(serviceAccountStream, CALL_URI);
667 
668     assertNotNull(credentials);
669     Map<String, List<String>> metadata = credentials.getRequestMetadata(null);
670     verifyJwtAccess(metadata, SA_CLIENT_EMAIL, CALL_URI, SA_PRIVATE_KEY_ID);
671   }
672 
673   @Test
fromStream_noClientId_throws()674   public void fromStream_noClientId_throws() throws IOException {
675     InputStream serviceAccountStream =
676         ServiceAccountCredentialsTest.writeServiceAccountStream(
677             null, SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID);
678 
679     testFromStreamException(serviceAccountStream, "client_id");
680   }
681 
682   @Test
fromStream_noClientEmail_throws()683   public void fromStream_noClientEmail_throws() throws IOException {
684     InputStream serviceAccountStream =
685         ServiceAccountCredentialsTest.writeServiceAccountStream(
686             SA_CLIENT_ID, null, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID);
687 
688     testFromStreamException(serviceAccountStream, "client_email");
689   }
690 
691   @Test
fromStream_noPrivateKey_throws()692   public void fromStream_noPrivateKey_throws() throws IOException {
693     InputStream serviceAccountStream =
694         ServiceAccountCredentialsTest.writeServiceAccountStream(
695             SA_CLIENT_ID, SA_CLIENT_EMAIL, null, SA_PRIVATE_KEY_ID);
696 
697     testFromStreamException(serviceAccountStream, "private_key");
698   }
699 
700   @Test
fromStream_noPrivateKeyId_throws()701   public void fromStream_noPrivateKeyId_throws() throws IOException {
702     InputStream serviceAccountStream =
703         ServiceAccountCredentialsTest.writeServiceAccountStream(
704             SA_CLIENT_ID, SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, null);
705 
706     testFromStreamException(serviceAccountStream, "private_key_id");
707   }
708 
709   @Test
jwtWithClaims_overrideAudience()710   public void jwtWithClaims_overrideAudience() throws IOException {
711     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
712     ServiceAccountJwtAccessCredentials credentials =
713         ServiceAccountJwtAccessCredentials.newBuilder()
714             .setClientId(SA_CLIENT_ID)
715             .setClientEmail(SA_CLIENT_EMAIL)
716             .setPrivateKey(privateKey)
717             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
718             .build();
719     Credentials withAudience =
720         credentials.jwtWithClaims(JwtClaims.newBuilder().setAudience("new-audience").build());
721 
722     Map<String, List<String>> metadata = withAudience.getRequestMetadata(CALL_URI);
723 
724     verifyJwtAccess(metadata, SA_CLIENT_EMAIL, URI.create("new-audience"), SA_PRIVATE_KEY_ID);
725   }
726 
727   @Test
jwtWithClaims_noAudience()728   public void jwtWithClaims_noAudience() throws IOException {
729     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
730     ServiceAccountJwtAccessCredentials credentials =
731         ServiceAccountJwtAccessCredentials.newBuilder()
732             .setClientId(SA_CLIENT_ID)
733             .setClientEmail(SA_CLIENT_EMAIL)
734             .setPrivateKey(privateKey)
735             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
736             .build();
737     try {
738       credentials.jwtWithClaims(JwtClaims.newBuilder().build());
739       fail("Expected to throw exception for missing audience");
740     } catch (IllegalStateException ex) {
741       // expected exception
742     }
743   }
744 
745   @Test
jwtWithClaims_defaultAudience()746   public void jwtWithClaims_defaultAudience() throws IOException {
747     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
748     ServiceAccountJwtAccessCredentials credentials =
749         ServiceAccountJwtAccessCredentials.newBuilder()
750             .setClientId(SA_CLIENT_ID)
751             .setClientEmail(SA_CLIENT_EMAIL)
752             .setPrivateKey(privateKey)
753             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
754             .setDefaultAudience(URI.create("default-audience"))
755             .build();
756     Credentials withAudience = credentials.jwtWithClaims(JwtClaims.newBuilder().build());
757 
758     Map<String, List<String>> metadata = withAudience.getRequestMetadata(CALL_URI);
759     verifyJwtAccess(metadata, SA_CLIENT_EMAIL, URI.create("default-audience"), SA_PRIVATE_KEY_ID);
760   }
761 
762   @Test
getRequestMetadataSetsQuotaProjectId()763   public void getRequestMetadataSetsQuotaProjectId() throws IOException {
764     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
765     ServiceAccountJwtAccessCredentials credentials =
766         ServiceAccountJwtAccessCredentials.newBuilder()
767             .setClientId(SA_CLIENT_ID)
768             .setClientEmail(SA_CLIENT_EMAIL)
769             .setPrivateKey(privateKey)
770             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
771             .setQuotaProjectId("my-quota-project-id")
772             .setDefaultAudience(URI.create("default-audience"))
773             .build();
774 
775     Map<String, List<String>> metadata = credentials.getRequestMetadata();
776     assertTrue(metadata.containsKey("x-goog-user-project"));
777     List<String> headerValues = metadata.get("x-goog-user-project");
778     assertEquals(1, headerValues.size());
779     assertEquals("my-quota-project-id", headerValues.get(0));
780   }
781 
782   @Test
getRequestMetadataNoQuotaProjectId()783   public void getRequestMetadataNoQuotaProjectId() throws IOException {
784     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
785     ServiceAccountJwtAccessCredentials credentials =
786         ServiceAccountJwtAccessCredentials.newBuilder()
787             .setClientId(SA_CLIENT_ID)
788             .setClientEmail(SA_CLIENT_EMAIL)
789             .setPrivateKey(privateKey)
790             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
791             .setDefaultAudience(URI.create("default-audience"))
792             .build();
793 
794     Map<String, List<String>> metadata = credentials.getRequestMetadata();
795     assertFalse(metadata.containsKey("x-goog-user-project"));
796   }
797 
798   @Test
getRequestMetadataWithCallback()799   public void getRequestMetadataWithCallback() throws IOException {
800     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
801     ServiceAccountJwtAccessCredentials credentials =
802         ServiceAccountJwtAccessCredentials.newBuilder()
803             .setClientId(SA_CLIENT_ID)
804             .setClientEmail(SA_CLIENT_EMAIL)
805             .setPrivateKey(privateKey)
806             .setPrivateKeyId(SA_PRIVATE_KEY_ID)
807             .setQuotaProjectId("my-quota-project-id")
808             .setDefaultAudience(URI.create("default-audience"))
809             .build();
810 
811     final Map<String, List<String>> plainMetadata = credentials.getRequestMetadata();
812     final AtomicBoolean success = new AtomicBoolean(false);
813     credentials.getRequestMetadata(
814         null,
815         null,
816         new RequestMetadataCallback() {
817           @Override
818           public void onSuccess(Map<String, List<String>> metadata) {
819             assertEquals(plainMetadata, metadata);
820             success.set(true);
821           }
822 
823           @Override
824           public void onFailure(Throwable exception) {
825             fail("Should not throw a failure.");
826           }
827         });
828 
829     assertTrue("Should have run onSuccess() callback", success.get());
830   }
831 
verifyJwtAccess( Map<String, List<String>> metadata, String expectedEmail, URI expectedAudience, String expectedKeyId)832   private void verifyJwtAccess(
833       Map<String, List<String>> metadata,
834       String expectedEmail,
835       URI expectedAudience,
836       String expectedKeyId)
837       throws IOException {
838     assertNotNull(metadata);
839     List<String> authorizations = metadata.get(AuthHttpConstants.AUTHORIZATION);
840     assertNotNull("Authorization headers not found", authorizations);
841     String assertion = null;
842     for (String authorization : authorizations) {
843       if (authorization.startsWith(JWT_ACCESS_PREFIX)) {
844         assertNull("Multiple bearer assertions found", assertion);
845         assertion = authorization.substring(JWT_ACCESS_PREFIX.length());
846       }
847     }
848     assertNotNull("Bearer assertion not found", assertion);
849     JsonWebSignature signature = JsonWebSignature.parse(JSON_FACTORY, assertion);
850     assertEquals(expectedEmail, signature.getPayload().getIssuer());
851     assertEquals(expectedEmail, signature.getPayload().getSubject());
852     assertEquals(expectedAudience.toString(), signature.getPayload().getAudience());
853     assertEquals(expectedKeyId, signature.getHeader().getKeyId());
854   }
855 
testFromStreamException(InputStream stream, String expectedMessageContent)856   private static void testFromStreamException(InputStream stream, String expectedMessageContent) {
857     try {
858       ServiceAccountJwtAccessCredentials.fromStream(stream, CALL_URI);
859       fail(
860           String.format(
861               "Should throw exception with message containing '%s'", expectedMessageContent));
862     } catch (IOException expected) {
863       assertTrue(expected.getMessage().contains(expectedMessageContent));
864     }
865   }
866 }
867