1 /* 2 * Copyright 2021 Google LLC 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * 15 * * Neither the name of Google LLC nor the names of its 16 * contributors may be used to endorse or promote products derived from 17 * this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 package com.google.auth.oauth2; 33 34 import static com.google.auth.oauth2.MockExternalAccountCredentialsTransport.SERVICE_ACCOUNT_IMPERSONATION_URL; 35 import static org.junit.Assert.assertEquals; 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.json.GenericJson; 44 import com.google.api.client.util.Clock; 45 import com.google.auth.TestUtils; 46 import com.google.auth.http.HttpTransportFactory; 47 import com.google.auth.oauth2.ExternalAccountCredentials.SubjectTokenTypes; 48 import com.google.auth.oauth2.ExternalAccountCredentialsTest.TestExternalAccountCredentials.TestCredentialSource; 49 import java.io.ByteArrayInputStream; 50 import java.io.IOException; 51 import java.math.BigDecimal; 52 import java.net.URI; 53 import java.util.Arrays; 54 import java.util.Date; 55 import java.util.HashMap; 56 import java.util.List; 57 import java.util.Locale; 58 import java.util.Map; 59 import org.junit.Before; 60 import org.junit.Test; 61 import org.junit.runner.RunWith; 62 import org.junit.runners.JUnit4; 63 64 /** Tests for {@link ExternalAccountCredentials}. */ 65 @RunWith(JUnit4.class) 66 public class ExternalAccountCredentialsTest extends BaseSerializationTest { 67 68 private static final String STS_URL = "https://sts.googleapis.com/v1/token"; 69 private static final String GOOGLE_DEFAULT_UNIVERSE = "googleapis.com"; 70 71 private static final Map<String, Object> FILE_CREDENTIAL_SOURCE_MAP = 72 new HashMap<String, Object>() { 73 { 74 put("file", "file"); 75 } 76 }; 77 78 static class MockExternalAccountCredentialsTransportFactory implements HttpTransportFactory { 79 80 MockExternalAccountCredentialsTransport transport = 81 new MockExternalAccountCredentialsTransport(); 82 83 @Override create()84 public HttpTransport create() { 85 return transport; 86 } 87 } 88 89 private MockExternalAccountCredentialsTransportFactory transportFactory; 90 91 @Before setup()92 public void setup() { 93 transportFactory = new MockExternalAccountCredentialsTransportFactory(); 94 } 95 96 @Test fromStream_identityPoolCredentials()97 public void fromStream_identityPoolCredentials() throws IOException { 98 GenericJson json = buildJsonIdentityPoolCredential(); 99 100 ExternalAccountCredentials credential = 101 ExternalAccountCredentials.fromStream(TestUtils.jsonToInputStream(json)); 102 103 assertTrue(credential instanceof IdentityPoolCredentials); 104 } 105 106 @Test fromStream_awsCredentials()107 public void fromStream_awsCredentials() throws IOException { 108 GenericJson json = buildJsonAwsCredential(); 109 110 ExternalAccountCredentials credential = 111 ExternalAccountCredentials.fromStream(TestUtils.jsonToInputStream(json)); 112 113 assertTrue(credential instanceof AwsCredentials); 114 } 115 116 @Test fromStream_pluggableAuthCredentials()117 public void fromStream_pluggableAuthCredentials() throws IOException { 118 GenericJson json = buildJsonPluggableAuthCredential(); 119 120 ExternalAccountCredentials credential = 121 ExternalAccountCredentials.fromStream(TestUtils.jsonToInputStream(json)); 122 123 assertTrue(credential instanceof PluggableAuthCredentials); 124 } 125 126 @Test fromStream_invalidStream_throws()127 public void fromStream_invalidStream_throws() throws IOException { 128 GenericJson json = buildJsonAwsCredential(); 129 130 json.put("audience", new HashMap<>()); 131 132 try { 133 ExternalAccountCredentials.fromStream(TestUtils.jsonToInputStream(json)); 134 fail("Should fail."); 135 } catch (CredentialFormatException e) { 136 assertEquals("An invalid input stream was provided.", e.getMessage()); 137 } 138 } 139 140 @Test fromStream_nullTransport_throws()141 public void fromStream_nullTransport_throws() throws IOException { 142 try { 143 ExternalAccountCredentials.fromStream( 144 new ByteArrayInputStream("foo".getBytes()), /* transportFactory= */ null); 145 fail("NullPointerException should be thrown."); 146 } catch (NullPointerException e) { 147 // Expected. 148 } 149 } 150 151 @Test fromStream_nullStream_throws()152 public void fromStream_nullStream_throws() throws IOException { 153 try { 154 ExternalAccountCredentials.fromStream( 155 /* credentialsStream= */ null, OAuth2Utils.HTTP_TRANSPORT_FACTORY); 156 fail("NullPointerException should be thrown."); 157 } catch (NullPointerException e) { 158 // Expected. 159 } 160 } 161 162 @Test fromStream_invalidWorkloadAudience_throws()163 public void fromStream_invalidWorkloadAudience_throws() throws IOException { 164 try { 165 GenericJson json = buildJsonIdentityPoolWorkforceCredential(); 166 json.put("audience", "invalidAudience"); 167 ExternalAccountCredentials credential = 168 ExternalAccountCredentials.fromStream(TestUtils.jsonToInputStream(json)); 169 fail("CredentialFormatException should be thrown."); 170 } catch (CredentialFormatException e) { 171 assertEquals("An invalid input stream was provided.", e.getMessage()); 172 } 173 } 174 175 @Test fromJson_identityPoolCredentialsWorkload()176 public void fromJson_identityPoolCredentialsWorkload() throws IOException { 177 ExternalAccountCredentials credential = 178 ExternalAccountCredentials.fromJson( 179 buildJsonIdentityPoolCredential(), OAuth2Utils.HTTP_TRANSPORT_FACTORY); 180 181 assertTrue(credential instanceof IdentityPoolCredentials); 182 assertEquals( 183 "//iam.googleapis.com/projects/123/locations/global/workloadIdentityPools/pool/providers/provider", 184 credential.getAudience()); 185 assertEquals("subjectTokenType", credential.getSubjectTokenType()); 186 assertEquals(STS_URL, credential.getTokenUrl()); 187 assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); 188 assertNotNull(credential.getCredentialSource()); 189 assertEquals(GOOGLE_DEFAULT_UNIVERSE, credential.getUniverseDomain()); 190 } 191 192 @Test fromJson_identityPoolCredentialsWorkforce()193 public void fromJson_identityPoolCredentialsWorkforce() throws IOException { 194 ExternalAccountCredentials credential = 195 ExternalAccountCredentials.fromJson( 196 buildJsonIdentityPoolWorkforceCredential(), OAuth2Utils.HTTP_TRANSPORT_FACTORY); 197 198 assertTrue(credential instanceof IdentityPoolCredentials); 199 assertEquals( 200 "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider", 201 credential.getAudience()); 202 assertEquals("subjectTokenType", credential.getSubjectTokenType()); 203 assertEquals(STS_URL, credential.getTokenUrl()); 204 assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); 205 assertEquals("userProject", credential.getWorkforcePoolUserProject()); 206 assertNotNull(credential.getCredentialSource()); 207 assertEquals(GOOGLE_DEFAULT_UNIVERSE, credential.getUniverseDomain()); 208 } 209 210 @Test fromJson_identityPoolCredentialsWithServiceAccountImpersonationOptions()211 public void fromJson_identityPoolCredentialsWithServiceAccountImpersonationOptions() 212 throws IOException { 213 GenericJson identityPoolCredentialJson = buildJsonIdentityPoolCredential(); 214 identityPoolCredentialJson.set( 215 "service_account_impersonation", buildServiceAccountImpersonationOptions(2800)); 216 217 ExternalAccountCredentials credential = 218 ExternalAccountCredentials.fromJson( 219 identityPoolCredentialJson, OAuth2Utils.HTTP_TRANSPORT_FACTORY); 220 221 assertTrue(credential instanceof IdentityPoolCredentials); 222 assertEquals( 223 "//iam.googleapis.com/projects/123/locations/global/workloadIdentityPools/pool/providers/provider", 224 credential.getAudience()); 225 assertEquals("subjectTokenType", credential.getSubjectTokenType()); 226 assertEquals(STS_URL, credential.getTokenUrl()); 227 assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); 228 assertNotNull(credential.getCredentialSource()); 229 assertEquals(2800, credential.getServiceAccountImpersonationOptions().getLifetime()); 230 assertEquals(GOOGLE_DEFAULT_UNIVERSE, credential.getUniverseDomain()); 231 } 232 233 @Test fromJson_identityPoolCredentialsWithUniverseDomain()234 public void fromJson_identityPoolCredentialsWithUniverseDomain() throws IOException { 235 GenericJson identityPoolCredentialJson = buildJsonIdentityPoolCredential(); 236 identityPoolCredentialJson.set("universe_domain", "universeDomain"); 237 238 ExternalAccountCredentials credential = 239 ExternalAccountCredentials.fromJson( 240 identityPoolCredentialJson, OAuth2Utils.HTTP_TRANSPORT_FACTORY); 241 242 assertTrue(credential instanceof IdentityPoolCredentials); 243 assertNotNull(credential.getCredentialSource()); 244 assertEquals( 245 "//iam.googleapis.com/projects/123/locations/global/workloadIdentityPools/pool/providers/provider", 246 credential.getAudience()); 247 assertEquals("subjectTokenType", credential.getSubjectTokenType()); 248 assertEquals(STS_URL, credential.getTokenUrl()); 249 assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); 250 assertEquals("universeDomain", credential.getUniverseDomain()); 251 } 252 253 @Test fromJson_awsCredentials()254 public void fromJson_awsCredentials() throws IOException { 255 ExternalAccountCredentials credential = 256 ExternalAccountCredentials.fromJson( 257 buildJsonAwsCredential(), OAuth2Utils.HTTP_TRANSPORT_FACTORY); 258 259 assertTrue(credential instanceof AwsCredentials); 260 assertEquals("audience", credential.getAudience()); 261 assertEquals("subjectTokenType", credential.getSubjectTokenType()); 262 assertEquals(STS_URL, credential.getTokenUrl()); 263 assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); 264 assertNotNull(credential.getCredentialSource()); 265 assertEquals(GOOGLE_DEFAULT_UNIVERSE, credential.getUniverseDomain()); 266 } 267 268 @Test fromJson_awsCredentialsWithServiceAccountImpersonationOptions()269 public void fromJson_awsCredentialsWithServiceAccountImpersonationOptions() throws IOException { 270 GenericJson awsCredentialJson = buildJsonAwsCredential(); 271 awsCredentialJson.set( 272 "service_account_impersonation", buildServiceAccountImpersonationOptions(2800)); 273 274 ExternalAccountCredentials credential = 275 ExternalAccountCredentials.fromJson(awsCredentialJson, OAuth2Utils.HTTP_TRANSPORT_FACTORY); 276 277 assertTrue(credential instanceof AwsCredentials); 278 assertEquals("audience", credential.getAudience()); 279 assertEquals("subjectTokenType", credential.getSubjectTokenType()); 280 assertEquals(STS_URL, credential.getTokenUrl()); 281 assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); 282 assertNotNull(credential.getCredentialSource()); 283 assertEquals(2800, credential.getServiceAccountImpersonationOptions().getLifetime()); 284 assertEquals(GOOGLE_DEFAULT_UNIVERSE, credential.getUniverseDomain()); 285 } 286 287 @Test fromJson_awsCredentialsWithUniverseDomain()288 public void fromJson_awsCredentialsWithUniverseDomain() throws IOException { 289 GenericJson awsCredentialJson = buildJsonAwsCredential(); 290 awsCredentialJson.set("universe_domain", "universeDomain"); 291 292 ExternalAccountCredentials credential = 293 ExternalAccountCredentials.fromJson(awsCredentialJson, OAuth2Utils.HTTP_TRANSPORT_FACTORY); 294 295 assertTrue(credential instanceof AwsCredentials); 296 assertEquals("audience", credential.getAudience()); 297 assertEquals("subjectTokenType", credential.getSubjectTokenType()); 298 assertEquals(STS_URL, credential.getTokenUrl()); 299 assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); 300 assertEquals("universeDomain", credential.getUniverseDomain()); 301 assertNotNull(credential.getCredentialSource()); 302 } 303 304 @Test fromJson_pluggableAuthCredentials()305 public void fromJson_pluggableAuthCredentials() throws IOException { 306 ExternalAccountCredentials credential = 307 ExternalAccountCredentials.fromJson( 308 buildJsonPluggableAuthCredential(), OAuth2Utils.HTTP_TRANSPORT_FACTORY); 309 310 assertTrue(credential instanceof PluggableAuthCredentials); 311 assertEquals("audience", credential.getAudience()); 312 assertEquals("subjectTokenType", credential.getSubjectTokenType()); 313 assertEquals(STS_URL, credential.getTokenUrl()); 314 assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); 315 assertNotNull(credential.getCredentialSource()); 316 317 PluggableAuthCredentialSource source = 318 (PluggableAuthCredentialSource) credential.getCredentialSource(); 319 assertEquals("command", source.getCommand()); 320 assertEquals(30000, source.getTimeoutMs()); // Default timeout is 30s. 321 assertNull(source.getOutputFilePath()); 322 assertEquals(GOOGLE_DEFAULT_UNIVERSE, credential.getUniverseDomain()); 323 } 324 325 @Test fromJson_pluggableAuthCredentialsWorkforce()326 public void fromJson_pluggableAuthCredentialsWorkforce() throws IOException { 327 ExternalAccountCredentials credential = 328 ExternalAccountCredentials.fromJson( 329 buildJsonPluggableAuthWorkforceCredential(), OAuth2Utils.HTTP_TRANSPORT_FACTORY); 330 331 assertTrue(credential instanceof PluggableAuthCredentials); 332 assertEquals( 333 "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider", 334 credential.getAudience()); 335 assertEquals("subjectTokenType", credential.getSubjectTokenType()); 336 assertEquals(STS_URL, credential.getTokenUrl()); 337 assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); 338 assertEquals("userProject", credential.getWorkforcePoolUserProject()); 339 340 assertNotNull(credential.getCredentialSource()); 341 342 PluggableAuthCredentialSource source = 343 (PluggableAuthCredentialSource) credential.getCredentialSource(); 344 assertEquals("command", source.getCommand()); 345 assertEquals(30000, source.getTimeoutMs()); // Default timeout is 30s. 346 assertNull(source.getOutputFilePath()); 347 assertEquals(GOOGLE_DEFAULT_UNIVERSE, credential.getUniverseDomain()); 348 } 349 350 @Test 351 @SuppressWarnings("unchecked") fromJson_pluggableAuthCredentials_allExecutableOptionsSet()352 public void fromJson_pluggableAuthCredentials_allExecutableOptionsSet() throws IOException { 353 GenericJson json = buildJsonPluggableAuthCredential(); 354 Map<String, Object> credentialSourceMap = (Map<String, Object>) json.get("credential_source"); 355 // Add optional params to the executable config (timeout, output file path). 356 Map<String, Object> executableConfig = 357 (Map<String, Object>) credentialSourceMap.get("executable"); 358 executableConfig.put("timeout_millis", 5000); 359 executableConfig.put("output_file", "path/to/output/file"); 360 361 ExternalAccountCredentials credential = 362 ExternalAccountCredentials.fromJson(json, OAuth2Utils.HTTP_TRANSPORT_FACTORY); 363 364 assertTrue(credential instanceof PluggableAuthCredentials); 365 assertEquals("audience", credential.getAudience()); 366 assertEquals("subjectTokenType", credential.getSubjectTokenType()); 367 assertEquals(STS_URL, credential.getTokenUrl()); 368 assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); 369 assertNotNull(credential.getCredentialSource()); 370 371 PluggableAuthCredentialSource source = 372 (PluggableAuthCredentialSource) credential.getCredentialSource(); 373 assertEquals("command", source.getCommand()); 374 assertEquals("path/to/output/file", source.getOutputFilePath()); 375 assertEquals(5000, source.getTimeoutMs()); 376 assertEquals(GOOGLE_DEFAULT_UNIVERSE, credential.getUniverseDomain()); 377 } 378 379 @Test fromJson_pluggableAuthCredentialsWithServiceAccountImpersonationOptions()380 public void fromJson_pluggableAuthCredentialsWithServiceAccountImpersonationOptions() 381 throws IOException { 382 GenericJson pluggableAuthCredentialJson = buildJsonPluggableAuthCredential(); 383 pluggableAuthCredentialJson.set( 384 "service_account_impersonation", buildServiceAccountImpersonationOptions(2800)); 385 386 ExternalAccountCredentials credential = 387 ExternalAccountCredentials.fromJson( 388 pluggableAuthCredentialJson, OAuth2Utils.HTTP_TRANSPORT_FACTORY); 389 390 assertTrue(credential instanceof PluggableAuthCredentials); 391 assertEquals("audience", credential.getAudience()); 392 assertEquals("subjectTokenType", credential.getSubjectTokenType()); 393 assertEquals(STS_URL, credential.getTokenUrl()); 394 assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); 395 assertNotNull(credential.getCredentialSource()); 396 assertEquals(2800, credential.getServiceAccountImpersonationOptions().getLifetime()); 397 398 PluggableAuthCredentialSource source = 399 (PluggableAuthCredentialSource) credential.getCredentialSource(); 400 assertEquals("command", source.getCommand()); 401 assertEquals(30000, source.getTimeoutMs()); // Default timeout is 30s. 402 assertNull(source.getOutputFilePath()); 403 assertEquals(GOOGLE_DEFAULT_UNIVERSE, credential.getUniverseDomain()); 404 } 405 406 @Test 407 @SuppressWarnings("unchecked") fromJson_pluggableAuthCredentials_withUniverseDomain()408 public void fromJson_pluggableAuthCredentials_withUniverseDomain() throws IOException { 409 GenericJson json = buildJsonPluggableAuthCredential(); 410 json.set("universe_domain", "universeDomain"); 411 412 Map<String, Object> credentialSourceMap = (Map<String, Object>) json.get("credential_source"); 413 // Add optional params to the executable config (timeout, output file path). 414 Map<String, Object> executableConfig = 415 (Map<String, Object>) credentialSourceMap.get("executable"); 416 executableConfig.put("timeout_millis", 5000); 417 executableConfig.put("output_file", "path/to/output/file"); 418 419 ExternalAccountCredentials credential = 420 ExternalAccountCredentials.fromJson(json, OAuth2Utils.HTTP_TRANSPORT_FACTORY); 421 422 assertTrue(credential instanceof PluggableAuthCredentials); 423 assertEquals("audience", credential.getAudience()); 424 assertEquals("subjectTokenType", credential.getSubjectTokenType()); 425 assertEquals(STS_URL, credential.getTokenUrl()); 426 assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); 427 assertNotNull(credential.getCredentialSource()); 428 429 PluggableAuthCredentialSource source = 430 (PluggableAuthCredentialSource) credential.getCredentialSource(); 431 assertEquals("command", source.getCommand()); 432 assertEquals("path/to/output/file", source.getOutputFilePath()); 433 assertEquals(5000, source.getTimeoutMs()); 434 assertEquals("universeDomain", credential.getUniverseDomain()); 435 } 436 437 @Test fromJson_pluggableAuthCredentialsWithUniverseDomain()438 public void fromJson_pluggableAuthCredentialsWithUniverseDomain() throws IOException { 439 GenericJson pluggableAuthCredentialJson = buildJsonPluggableAuthCredential(); 440 pluggableAuthCredentialJson.set("universe_domain", "universeDomain"); 441 442 ExternalAccountCredentials credential = 443 ExternalAccountCredentials.fromJson( 444 pluggableAuthCredentialJson, OAuth2Utils.HTTP_TRANSPORT_FACTORY); 445 446 assertTrue(credential instanceof PluggableAuthCredentials); 447 assertEquals("audience", credential.getAudience()); 448 assertEquals("subjectTokenType", credential.getSubjectTokenType()); 449 assertEquals(STS_URL, credential.getTokenUrl()); 450 assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); 451 assertNotNull(credential.getCredentialSource()); 452 assertEquals("universeDomain", credential.getUniverseDomain()); 453 454 PluggableAuthCredentialSource source = 455 (PluggableAuthCredentialSource) credential.getCredentialSource(); 456 assertEquals("command", source.getCommand()); 457 assertEquals(30000, source.getTimeoutMs()); // Default timeout is 30s. 458 assertNull(source.getOutputFilePath()); 459 } 460 461 @Test fromJson_nullJson_throws()462 public void fromJson_nullJson_throws() throws IOException { 463 try { 464 ExternalAccountCredentials.fromJson(/* json= */ null, OAuth2Utils.HTTP_TRANSPORT_FACTORY); 465 fail("Exception should be thrown."); 466 } catch (NullPointerException e) { 467 // Expected. 468 } 469 } 470 471 @Test fromJson_nullTransport_throws()472 public void fromJson_nullTransport_throws() throws IOException { 473 try { 474 ExternalAccountCredentials.fromJson( 475 new HashMap<String, Object>(), /* transportFactory= */ null); 476 fail("Exception should be thrown."); 477 } catch (NullPointerException e) { 478 // Expected. 479 } 480 } 481 482 @Test fromJson_invalidWorkforceAudiences_throws()483 public void fromJson_invalidWorkforceAudiences_throws() throws IOException { 484 List<String> invalidAudiences = 485 Arrays.asList( 486 "//iam.googleapis.com/locations/global/workloadIdentityPools/pool/providers/provider", 487 "//iam.googleapis.com/locations/global/workforcepools/pool/providers/provider", 488 "//iam.googleapis.com/locations/global/workforcePools/providers/provider", 489 "//iam.googleapis.com/locations/global/workforcePools/providers", 490 "//iam.googleapis.com/locations/global/workforcePools/", 491 "//iam.googleapis.com/locations//workforcePools/providers", 492 "//iam.googleapis.com/notlocations/global/workforcePools/providers", 493 "//iam.googleapis.com/locations/global/workforce/providers"); 494 495 for (String audience : invalidAudiences) { 496 try { 497 GenericJson json = buildJsonIdentityPoolCredential(); 498 json.put("audience", audience); 499 json.put("workforce_pool_user_project", "userProject"); 500 501 ExternalAccountCredentials.fromJson(json, OAuth2Utils.HTTP_TRANSPORT_FACTORY); 502 fail("Exception should be thrown."); 503 } catch (IllegalArgumentException e) { 504 assertEquals( 505 "The workforce_pool_user_project parameter should only be provided for a Workforce Pool configuration.", 506 e.getMessage()); 507 } 508 } 509 } 510 511 @Test constructor_builder()512 public void constructor_builder() throws IOException { 513 HashMap<String, Object> credentialSource = new HashMap<>(); 514 credentialSource.put("file", "file"); 515 516 ExternalAccountCredentials credentials = 517 IdentityPoolCredentials.newBuilder() 518 .setHttpTransportFactory(transportFactory) 519 .setAudience( 520 "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider") 521 .setSubjectTokenType("subjectTokenType") 522 .setTokenUrl(STS_URL) 523 .setTokenInfoUrl("https://tokeninfo.com") 524 .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) 525 .setCredentialSource(new TestCredentialSource(credentialSource)) 526 .setScopes(Arrays.asList("scope1", "scope2")) 527 .setQuotaProjectId("projectId") 528 .setClientId("clientId") 529 .setClientSecret("clientSecret") 530 .setWorkforcePoolUserProject("workforcePoolUserProject") 531 .setUniverseDomain("universeDomain") 532 .build(); 533 534 assertEquals( 535 "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider", 536 credentials.getAudience()); 537 assertEquals("subjectTokenType", credentials.getSubjectTokenType()); 538 assertEquals(STS_URL, credentials.getTokenUrl()); 539 assertEquals("https://tokeninfo.com", credentials.getTokenInfoUrl()); 540 assertEquals( 541 SERVICE_ACCOUNT_IMPERSONATION_URL, credentials.getServiceAccountImpersonationUrl()); 542 assertEquals(Arrays.asList("scope1", "scope2"), credentials.getScopes()); 543 assertEquals("projectId", credentials.getQuotaProjectId()); 544 assertEquals("clientId", credentials.getClientId()); 545 assertEquals("clientSecret", credentials.getClientSecret()); 546 assertEquals("workforcePoolUserProject", credentials.getWorkforcePoolUserProject()); 547 assertEquals("universeDomain", credentials.getUniverseDomain()); 548 assertNotNull(credentials.getCredentialSource()); 549 } 550 551 @Test constructor_builder_defaultTokenUrl()552 public void constructor_builder_defaultTokenUrl() { 553 HashMap<String, Object> credentialSource = new HashMap<>(); 554 credentialSource.put("file", "file"); 555 556 ExternalAccountCredentials credentials = 557 IdentityPoolCredentials.newBuilder() 558 .setHttpTransportFactory(transportFactory) 559 .setAudience( 560 "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider") 561 .setSubjectTokenType("subjectTokenType") 562 .setCredentialSource(new TestCredentialSource(credentialSource)) 563 .build(); 564 565 assertEquals(STS_URL, credentials.getTokenUrl()); 566 } 567 568 @Test constructor_builder_subjectTokenTypeEnum()569 public void constructor_builder_subjectTokenTypeEnum() { 570 HashMap<String, Object> credentialSource = new HashMap<>(); 571 credentialSource.put("file", "file"); 572 573 ExternalAccountCredentials credentials = 574 IdentityPoolCredentials.newBuilder() 575 .setHttpTransportFactory(transportFactory) 576 .setAudience( 577 "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider") 578 .setSubjectTokenType(SubjectTokenTypes.SAML2) 579 .setTokenUrl(STS_URL) 580 .setCredentialSource(new TestCredentialSource(credentialSource)) 581 .build(); 582 583 assertEquals(SubjectTokenTypes.SAML2.value, credentials.getSubjectTokenType()); 584 } 585 586 @Test constructor_builder_invalidTokenUrl()587 public void constructor_builder_invalidTokenUrl() { 588 try { 589 ExternalAccountCredentials.Builder builder = 590 TestExternalAccountCredentials.newBuilder() 591 .setHttpTransportFactory(transportFactory) 592 .setAudience("audience") 593 .setSubjectTokenType("subjectTokenType") 594 .setTokenUrl("tokenUrl") 595 .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)); 596 new TestExternalAccountCredentials(builder); 597 fail("Should not be able to continue without exception."); 598 } catch (IllegalArgumentException exception) { 599 assertEquals("The provided token URL is invalid.", exception.getMessage()); 600 } 601 } 602 603 @Test constructor_builder_invalidServiceAccountImpersonationUrl()604 public void constructor_builder_invalidServiceAccountImpersonationUrl() { 605 try { 606 ExternalAccountCredentials.Builder builder = 607 TestExternalAccountCredentials.newBuilder() 608 .setHttpTransportFactory(transportFactory) 609 .setAudience("audience") 610 .setSubjectTokenType("subjectTokenType") 611 .setTokenUrl("tokenUrl") 612 .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) 613 .setServiceAccountImpersonationUrl("serviceAccountImpersonationUrl"); 614 new TestExternalAccountCredentials(builder); 615 fail("Should not be able to continue without exception."); 616 } catch (IllegalArgumentException exception) { 617 assertEquals("The provided token URL is invalid.", exception.getMessage()); 618 } 619 } 620 621 @Test constructor_builderWithInvalidWorkforceAudiences_throws()622 public void constructor_builderWithInvalidWorkforceAudiences_throws() { 623 List<String> invalidAudiences = 624 Arrays.asList( 625 "", 626 "//iam.googleapis.com/projects/x23/locations/global/workloadIdentityPools/pool/providers/provider", 627 "//iam.googleapis.com/locations/global/workforcepools/pool/providers/provider", 628 "//iam.googleapis.com/locations/global/workforcePools/providers/provider", 629 "//iam.googleapis.com/locations/global/workforcePools/providers", 630 "//iam.googleapis.com/locations/global/workforcePools/", 631 "//iam.googleapis.com/locations//workforcePools/providers", 632 "//iam.googleapis.com/notlocations/global/workforcePools/providers", 633 "//iam.googleapis.com/locations/global/workforce/providers"); 634 635 HashMap<String, Object> credentialSource = new HashMap<>(); 636 credentialSource.put("file", "file"); 637 for (String audience : invalidAudiences) { 638 try { 639 TestExternalAccountCredentials.newBuilder() 640 .setWorkforcePoolUserProject("workforcePoolUserProject") 641 .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) 642 .setAudience(audience) 643 .setSubjectTokenType("subjectTokenType") 644 .setTokenUrl(STS_URL) 645 .setCredentialSource(new TestCredentialSource(credentialSource)) 646 .build(); 647 fail("Should not be able to continue without exception."); 648 } catch (IllegalArgumentException exception) { 649 assertEquals( 650 "The workforce_pool_user_project parameter should only be provided for a Workforce Pool configuration.", 651 exception.getMessage()); 652 } 653 } 654 } 655 656 @Test constructor_builderWithEmptyWorkforceUserProjectAndWorkforceAudience()657 public void constructor_builderWithEmptyWorkforceUserProjectAndWorkforceAudience() { 658 HashMap<String, Object> credentialSource = new HashMap<>(); 659 credentialSource.put("file", "file"); 660 // No exception should be thrown. 661 TestExternalAccountCredentials.newBuilder() 662 .setWorkforcePoolUserProject("") 663 .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) 664 .setAudience("//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider") 665 .setSubjectTokenType("subjectTokenType") 666 .setTokenUrl(STS_URL) 667 .setCredentialSource(new TestCredentialSource(credentialSource)) 668 .build(); 669 } 670 671 @Test constructor_builder_invalidTokenLifetime_throws()672 public void constructor_builder_invalidTokenLifetime_throws() { 673 Map<String, Object> invalidOptionsMap = new HashMap<String, Object>(); 674 invalidOptionsMap.put("token_lifetime_seconds", "thisIsAString"); 675 676 try { 677 IdentityPoolCredentials.newBuilder() 678 .setHttpTransportFactory(transportFactory) 679 .setAudience( 680 "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider") 681 .setSubjectTokenType("subjectTokenType") 682 .setTokenUrl(STS_URL) 683 .setTokenInfoUrl("https://tokeninfo.com") 684 .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) 685 .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) 686 .setScopes(Arrays.asList("scope1", "scope2")) 687 .setQuotaProjectId("projectId") 688 .setClientId("clientId") 689 .setClientSecret("clientSecret") 690 .setWorkforcePoolUserProject("workforcePoolUserProject") 691 .setUniverseDomain("universeDomain") 692 .setServiceAccountImpersonationOptions(invalidOptionsMap) 693 .build(); 694 fail("Should not be able to continue without exception."); 695 } catch (IllegalArgumentException exception) { 696 assertEquals( 697 "Value of \"token_lifetime_seconds\" field could not be parsed into an integer.", 698 exception.getMessage()); 699 assertEquals(NumberFormatException.class, exception.getCause().getClass()); 700 } 701 } 702 703 @Test constructor_builder_stringTokenLifetime()704 public void constructor_builder_stringTokenLifetime() { 705 Map<String, Object> optionsMap = new HashMap<String, Object>(); 706 optionsMap.put("token_lifetime_seconds", "2800"); 707 708 ExternalAccountCredentials credentials = 709 IdentityPoolCredentials.newBuilder() 710 .setHttpTransportFactory(transportFactory) 711 .setAudience( 712 "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider") 713 .setSubjectTokenType("subjectTokenType") 714 .setTokenUrl(STS_URL) 715 .setTokenInfoUrl("https://tokeninfo.com") 716 .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) 717 .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) 718 .setScopes(Arrays.asList("scope1", "scope2")) 719 .setQuotaProjectId("projectId") 720 .setClientId("clientId") 721 .setClientSecret("clientSecret") 722 .setWorkforcePoolUserProject("workforcePoolUserProject") 723 .setUniverseDomain("universeDomain") 724 .setServiceAccountImpersonationOptions(optionsMap) 725 .build(); 726 727 assertEquals(2800, credentials.getServiceAccountImpersonationOptions().getLifetime()); 728 } 729 730 @Test constructor_builder_bigDecimalTokenLifetime()731 public void constructor_builder_bigDecimalTokenLifetime() { 732 Map<String, Object> optionsMap = new HashMap<String, Object>(); 733 optionsMap.put("token_lifetime_seconds", new BigDecimal("2800")); 734 735 ExternalAccountCredentials credentials = 736 IdentityPoolCredentials.newBuilder() 737 .setHttpTransportFactory(transportFactory) 738 .setAudience( 739 "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider") 740 .setSubjectTokenType("subjectTokenType") 741 .setTokenUrl(STS_URL) 742 .setTokenInfoUrl("https://tokeninfo.com") 743 .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) 744 .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) 745 .setScopes(Arrays.asList("scope1", "scope2")) 746 .setQuotaProjectId("projectId") 747 .setClientId("clientId") 748 .setClientSecret("clientSecret") 749 .setWorkforcePoolUserProject("workforcePoolUserProject") 750 .setUniverseDomain("universeDomain") 751 .setServiceAccountImpersonationOptions(optionsMap) 752 .build(); 753 754 assertEquals(2800, credentials.getServiceAccountImpersonationOptions().getLifetime()); 755 } 756 757 @Test constructor_builder_integerTokenLifetime()758 public void constructor_builder_integerTokenLifetime() { 759 Map<String, Object> optionsMap = new HashMap<String, Object>(); 760 optionsMap.put("token_lifetime_seconds", Integer.valueOf(2800)); 761 762 ExternalAccountCredentials credentials = 763 IdentityPoolCredentials.newBuilder() 764 .setHttpTransportFactory(transportFactory) 765 .setAudience( 766 "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider") 767 .setSubjectTokenType("subjectTokenType") 768 .setTokenUrl(STS_URL) 769 .setTokenInfoUrl("https://tokeninfo.com") 770 .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) 771 .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) 772 .setScopes(Arrays.asList("scope1", "scope2")) 773 .setQuotaProjectId("projectId") 774 .setClientId("clientId") 775 .setClientSecret("clientSecret") 776 .setWorkforcePoolUserProject("workforcePoolUserProject") 777 .setUniverseDomain("universeDomain") 778 .setServiceAccountImpersonationOptions(optionsMap) 779 .build(); 780 781 assertEquals(2800, credentials.getServiceAccountImpersonationOptions().getLifetime()); 782 } 783 784 @Test constructor_builder_lowTokenLifetime_throws()785 public void constructor_builder_lowTokenLifetime_throws() { 786 Map<String, Object> optionsMap = new HashMap<String, Object>(); 787 optionsMap.put("token_lifetime_seconds", 599); 788 789 try { 790 IdentityPoolCredentials.newBuilder() 791 .setHttpTransportFactory(transportFactory) 792 .setAudience( 793 "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider") 794 .setSubjectTokenType("subjectTokenType") 795 .setTokenUrl(STS_URL) 796 .setTokenInfoUrl("https://tokeninfo.com") 797 .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) 798 .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) 799 .setScopes(Arrays.asList("scope1", "scope2")) 800 .setQuotaProjectId("projectId") 801 .setClientId("clientId") 802 .setClientSecret("clientSecret") 803 .setWorkforcePoolUserProject("workforcePoolUserProject") 804 .setUniverseDomain("universeDomain") 805 .setServiceAccountImpersonationOptions(optionsMap) 806 .build(); 807 } catch (IllegalArgumentException e) { 808 assertEquals( 809 "The \"token_lifetime_seconds\" field must be between 600 and 43200 seconds.", 810 e.getMessage()); 811 } 812 } 813 814 @Test constructor_builder_highTokenLifetime_throws()815 public void constructor_builder_highTokenLifetime_throws() { 816 Map<String, Object> optionsMap = new HashMap<String, Object>(); 817 optionsMap.put("token_lifetime_seconds", 43201); 818 819 try { 820 IdentityPoolCredentials.newBuilder() 821 .setHttpTransportFactory(transportFactory) 822 .setAudience( 823 "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider") 824 .setSubjectTokenType("subjectTokenType") 825 .setTokenUrl(STS_URL) 826 .setTokenInfoUrl("https://tokeninfo.com") 827 .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) 828 .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) 829 .setScopes(Arrays.asList("scope1", "scope2")) 830 .setQuotaProjectId("projectId") 831 .setClientId("clientId") 832 .setClientSecret("clientSecret") 833 .setWorkforcePoolUserProject("workforcePoolUserProject") 834 .setUniverseDomain("universeDomain") 835 .setServiceAccountImpersonationOptions(optionsMap) 836 .build(); 837 } catch (IllegalArgumentException e) { 838 assertEquals( 839 "The \"token_lifetime_seconds\" field must be between 600 and 43200 seconds.", 840 e.getMessage()); 841 } 842 } 843 844 @Test exchangeExternalCredentialForAccessToken()845 public void exchangeExternalCredentialForAccessToken() throws IOException { 846 ExternalAccountCredentials credential = 847 ExternalAccountCredentials.fromJson(buildJsonIdentityPoolCredential(), transportFactory); 848 849 StsTokenExchangeRequest stsTokenExchangeRequest = 850 StsTokenExchangeRequest.newBuilder("credential", "subjectTokenType").build(); 851 852 AccessToken accessToken = 853 credential.exchangeExternalCredentialForAccessToken(stsTokenExchangeRequest); 854 855 assertEquals(transportFactory.transport.getAccessToken(), accessToken.getTokenValue()); 856 857 // Validate no internal options set. 858 Map<String, String> query = 859 TestUtils.parseQuery(transportFactory.transport.getLastRequest().getContentAsString()); 860 assertNull(query.get("options")); 861 862 // Validate metrics header is set correctly on the sts request. 863 Map<String, List<String>> headers = 864 transportFactory.transport.getRequests().get(0).getHeaders(); 865 validateMetricsHeader(headers, "file", false, false); 866 } 867 868 @Test exchangeExternalCredentialForAccessToken_withInternalOptions()869 public void exchangeExternalCredentialForAccessToken_withInternalOptions() throws IOException { 870 ExternalAccountCredentials credential = 871 ExternalAccountCredentials.fromJson(buildJsonIdentityPoolCredential(), transportFactory); 872 873 GenericJson internalOptions = new GenericJson(); 874 internalOptions.setFactory(OAuth2Utils.JSON_FACTORY); 875 internalOptions.put("key", "value"); 876 StsTokenExchangeRequest stsTokenExchangeRequest = 877 StsTokenExchangeRequest.newBuilder("credential", "subjectTokenType") 878 .setInternalOptions(internalOptions.toString()) 879 .build(); 880 881 AccessToken accessToken = 882 credential.exchangeExternalCredentialForAccessToken(stsTokenExchangeRequest); 883 884 assertEquals(transportFactory.transport.getAccessToken(), accessToken.getTokenValue()); 885 886 // Validate internal options set. 887 Map<String, String> query = 888 TestUtils.parseQuery(transportFactory.transport.getLastRequest().getContentAsString()); 889 assertNotNull(query.get("options")); 890 assertEquals(internalOptions.toString(), query.get("options")); 891 } 892 893 @Test exchangeExternalCredentialForAccessToken_workforceCred_expectUserProjectPassedToSts()894 public void exchangeExternalCredentialForAccessToken_workforceCred_expectUserProjectPassedToSts() 895 throws IOException { 896 ExternalAccountCredentials identityPoolCredential = 897 ExternalAccountCredentials.fromJson( 898 buildJsonIdentityPoolWorkforceCredential(), transportFactory); 899 900 ExternalAccountCredentials pluggableAuthCredential = 901 ExternalAccountCredentials.fromJson( 902 buildJsonPluggableAuthWorkforceCredential(), transportFactory); 903 904 List<ExternalAccountCredentials> credentials = 905 Arrays.asList(identityPoolCredential, pluggableAuthCredential); 906 907 for (int i = 0; i < credentials.size(); i++) { 908 StsTokenExchangeRequest stsTokenExchangeRequest = 909 StsTokenExchangeRequest.newBuilder("credential", "subjectTokenType").build(); 910 911 AccessToken accessToken = 912 credentials.get(i).exchangeExternalCredentialForAccessToken(stsTokenExchangeRequest); 913 914 assertEquals(transportFactory.transport.getAccessToken(), accessToken.getTokenValue()); 915 916 // Validate internal options set. 917 Map<String, String> query = 918 TestUtils.parseQuery(transportFactory.transport.getLastRequest().getContentAsString()); 919 GenericJson internalOptions = new GenericJson(); 920 internalOptions.setFactory(OAuth2Utils.JSON_FACTORY); 921 internalOptions.put("userProject", "userProject"); 922 assertEquals(internalOptions.toString(), query.get("options")); 923 assertEquals(i + 1, transportFactory.transport.getRequests().size()); 924 } 925 } 926 927 @Test 928 public void exchangeExternalCredentialForAccessToken_workforceCredWithInternalOptions_expectOverridden()929 exchangeExternalCredentialForAccessToken_workforceCredWithInternalOptions_expectOverridden() 930 throws IOException { 931 ExternalAccountCredentials credential = 932 ExternalAccountCredentials.fromJson( 933 buildJsonIdentityPoolWorkforceCredential(), transportFactory); 934 935 GenericJson internalOptions = new GenericJson(); 936 internalOptions.put("key", "value"); 937 StsTokenExchangeRequest stsTokenExchangeRequest = 938 StsTokenExchangeRequest.newBuilder("credential", "subjectTokenType") 939 .setInternalOptions(internalOptions.toString()) 940 .build(); 941 942 AccessToken accessToken = 943 credential.exchangeExternalCredentialForAccessToken(stsTokenExchangeRequest); 944 945 assertEquals(transportFactory.transport.getAccessToken(), accessToken.getTokenValue()); 946 947 // Validate internal options set. 948 Map<String, String> query = 949 TestUtils.parseQuery(transportFactory.transport.getLastRequest().getContentAsString()); 950 assertNotNull(query.get("options")); 951 assertEquals(internalOptions.toString(), query.get("options")); 952 } 953 954 @Test exchangeExternalCredentialForAccessToken_withServiceAccountImpersonation()955 public void exchangeExternalCredentialForAccessToken_withServiceAccountImpersonation() 956 throws IOException { 957 transportFactory.transport.setExpireTime(TestUtils.getDefaultExpireTime()); 958 959 ExternalAccountCredentials credential = 960 ExternalAccountCredentials.fromStream( 961 IdentityPoolCredentialsTest.writeIdentityPoolCredentialsStream( 962 transportFactory.transport.getStsUrl(), 963 transportFactory.transport.getMetadataUrl(), 964 transportFactory.transport.getServiceAccountImpersonationUrl(), 965 /* serviceAccountImpersonationOptionsMap= */ null), 966 transportFactory); 967 968 StsTokenExchangeRequest stsTokenExchangeRequest = 969 StsTokenExchangeRequest.newBuilder("credential", "subjectTokenType").build(); 970 971 AccessToken returnedToken = 972 credential.exchangeExternalCredentialForAccessToken(stsTokenExchangeRequest); 973 974 assertEquals( 975 transportFactory.transport.getServiceAccountAccessToken(), returnedToken.getTokenValue()); 976 977 // Validate that default lifetime was set correctly on the request. 978 GenericJson query = 979 OAuth2Utils.JSON_FACTORY 980 .createJsonParser(transportFactory.transport.getLastRequest().getContentAsString()) 981 .parseAndClose(GenericJson.class); 982 983 assertEquals("3600s", query.get("lifetime")); 984 985 // Validate metrics header is set correctly on the sts request. 986 Map<String, List<String>> headers = 987 transportFactory.transport.getRequests().get(1).getHeaders(); 988 validateMetricsHeader(headers, "url", true, false); 989 } 990 991 @Test exchangeExternalCredentialForAccessToken_withServiceAccountImpersonationOptions()992 public void exchangeExternalCredentialForAccessToken_withServiceAccountImpersonationOptions() 993 throws IOException { 994 transportFactory.transport.setExpireTime(TestUtils.getDefaultExpireTime()); 995 996 ExternalAccountCredentials credential = 997 ExternalAccountCredentials.fromStream( 998 IdentityPoolCredentialsTest.writeIdentityPoolCredentialsStream( 999 transportFactory.transport.getStsUrl(), 1000 transportFactory.transport.getMetadataUrl(), 1001 transportFactory.transport.getServiceAccountImpersonationUrl(), 1002 buildServiceAccountImpersonationOptions(2800)), 1003 transportFactory); 1004 1005 StsTokenExchangeRequest stsTokenExchangeRequest = 1006 StsTokenExchangeRequest.newBuilder("credential", "subjectTokenType").build(); 1007 1008 AccessToken returnedToken = 1009 credential.exchangeExternalCredentialForAccessToken(stsTokenExchangeRequest); 1010 1011 assertEquals( 1012 transportFactory.transport.getServiceAccountAccessToken(), returnedToken.getTokenValue()); 1013 1014 // Validate that lifetime was set correctly on the request. 1015 GenericJson query = 1016 OAuth2Utils.JSON_FACTORY 1017 .createJsonParser(transportFactory.transport.getLastRequest().getContentAsString()) 1018 .parseAndClose(GenericJson.class); 1019 1020 // Validate metrics header is set correctly on the sts request. 1021 Map<String, List<String>> headers = 1022 transportFactory.transport.getRequests().get(1).getHeaders(); 1023 validateMetricsHeader(headers, "url", true, true); 1024 assertEquals("2800s", query.get("lifetime")); 1025 } 1026 1027 @Test exchangeExternalCredentialForAccessToken_throws()1028 public void exchangeExternalCredentialForAccessToken_throws() throws IOException { 1029 ExternalAccountCredentials credential = 1030 ExternalAccountCredentials.fromJson(buildJsonIdentityPoolCredential(), transportFactory); 1031 1032 String errorCode = "invalidRequest"; 1033 String errorDescription = "errorDescription"; 1034 String errorUri = "errorUri"; 1035 transportFactory.transport.addResponseErrorSequence( 1036 TestUtils.buildHttpResponseException(errorCode, errorDescription, errorUri)); 1037 1038 StsTokenExchangeRequest stsTokenExchangeRequest = 1039 StsTokenExchangeRequest.newBuilder("credential", "subjectTokenType").build(); 1040 1041 try { 1042 credential.exchangeExternalCredentialForAccessToken(stsTokenExchangeRequest); 1043 fail("Exception should be thrown."); 1044 } catch (OAuthException e) { 1045 assertEquals(errorCode, e.getErrorCode()); 1046 assertEquals(errorDescription, e.getErrorDescription()); 1047 assertEquals(errorUri, e.getErrorUri()); 1048 } 1049 } 1050 1051 @Test exchangeExternalCredentialForAccessToken_invalidImpersonatedCredentialsThrows()1052 public void exchangeExternalCredentialForAccessToken_invalidImpersonatedCredentialsThrows() 1053 throws IOException { 1054 GenericJson json = buildJsonIdentityPoolCredential(); 1055 json.put("service_account_impersonation_url", "https://iamcredentials.googleapis.com"); 1056 ExternalAccountCredentials credential = 1057 ExternalAccountCredentials.fromJson(json, transportFactory); 1058 1059 StsTokenExchangeRequest stsTokenExchangeRequest = 1060 StsTokenExchangeRequest.newBuilder("credential", "subjectTokenType").build(); 1061 1062 try { 1063 credential.exchangeExternalCredentialForAccessToken(stsTokenExchangeRequest); 1064 fail("Exception should be thrown."); 1065 } catch (IllegalArgumentException e) { 1066 assertEquals( 1067 "Unable to determine target principal from service account impersonation URL.", 1068 e.getMessage()); 1069 } 1070 } 1071 1072 @Test getRequestMetadata_withQuotaProjectId()1073 public void getRequestMetadata_withQuotaProjectId() throws IOException { 1074 TestExternalAccountCredentials testCredentials = 1075 (TestExternalAccountCredentials) 1076 TestExternalAccountCredentials.newBuilder() 1077 .setHttpTransportFactory(transportFactory) 1078 .setAudience("audience") 1079 .setSubjectTokenType("subjectTokenType") 1080 .setTokenUrl(STS_URL) 1081 .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) 1082 .setQuotaProjectId("quotaProjectId") 1083 .build(); 1084 1085 Map<String, List<String>> requestMetadata = 1086 testCredentials.getRequestMetadata(URI.create("http://googleapis.com/foo/bar")); 1087 1088 assertEquals("quotaProjectId", requestMetadata.get("x-goog-user-project").get(0)); 1089 } 1090 1091 @Test serialize()1092 public void serialize() throws IOException, ClassNotFoundException { 1093 Map<String, Object> impersonationOpts = 1094 new HashMap<String, Object>() { 1095 { 1096 put("token_lifetime_seconds", 1000); 1097 } 1098 }; 1099 1100 TestExternalAccountCredentials testCredentials = 1101 (TestExternalAccountCredentials) 1102 TestExternalAccountCredentials.newBuilder() 1103 .setHttpTransportFactory(transportFactory) 1104 .setAudience("audience") 1105 .setSubjectTokenType("subjectTokenType") 1106 .setTokenUrl(STS_URL) 1107 .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) 1108 .setServiceAccountImpersonationOptions(impersonationOpts) 1109 .build(); 1110 1111 TestExternalAccountCredentials deserializedCredentials = 1112 serializeAndDeserialize(testCredentials); 1113 assertEquals(testCredentials, deserializedCredentials); 1114 assertEquals(testCredentials.hashCode(), deserializedCredentials.hashCode()); 1115 assertEquals(testCredentials.toString(), deserializedCredentials.toString()); 1116 assertEquals( 1117 testCredentials.getServiceAccountImpersonationOptions().getLifetime(), 1118 deserializedCredentials.getServiceAccountImpersonationOptions().getLifetime()); 1119 assertSame(deserializedCredentials.clock, Clock.SYSTEM); 1120 } 1121 1122 @Test validateTokenUrl_validUrls()1123 public void validateTokenUrl_validUrls() { 1124 List<String> validUrls = 1125 Arrays.asList( 1126 "https://sts.googleapis.com", 1127 "https://us-east-1.sts.googleapis.com", 1128 "https://US-EAST-1.sts.googleapis.com", 1129 "https://sts.us-east-1.googleapis.com", 1130 "https://sts.US-WEST-1.googleapis.com", 1131 "https://us-east-1-sts.googleapis.com", 1132 "https://US-WEST-1-sts.googleapis.com", 1133 "https://us-west-1-sts.googleapis.com/path?query", 1134 "https://sts-xyz123.p.googleapis.com/path?query", 1135 "https://sts-xyz123.p.googleapis.com", 1136 "https://sts-xyz-123.p.googleapis.com"); 1137 1138 for (String url : validUrls) { 1139 ExternalAccountCredentials.validateTokenUrl(url); 1140 ExternalAccountCredentials.validateTokenUrl(url.toUpperCase(Locale.US)); 1141 } 1142 } 1143 1144 @Test validateTokenUrl_invalidUrls()1145 public void validateTokenUrl_invalidUrls() { 1146 List<String> invalidUrls = 1147 Arrays.asList( 1148 "sts.googleapis.com", 1149 "https://", 1150 "http://sts.googleapis.com", 1151 "https://us-eas\\t-1.sts.googleapis.com", 1152 "https:/us-east-1.sts.googleapis.com", 1153 "testhttps://us-east-1.sts.googleapis.com", 1154 "hhttps://us-east-1.sts.googleapis.com", 1155 "https://us- -1.sts.googleapis.com"); 1156 1157 for (String url : invalidUrls) { 1158 try { 1159 ExternalAccountCredentials.validateTokenUrl(url); 1160 fail("Should have failed since an invalid URL was passed."); 1161 } catch (IllegalArgumentException e) { 1162 assertEquals("The provided token URL is invalid.", e.getMessage()); 1163 } 1164 } 1165 } 1166 1167 @Test validateServiceAccountImpersonationUrls_validUrls()1168 public void validateServiceAccountImpersonationUrls_validUrls() { 1169 List<String> validUrls = 1170 Arrays.asList( 1171 "https://iamcredentials.googleapis.com", 1172 "https://us-east-1.iamcredentials.googleapis.com", 1173 "https://US-EAST-1.iamcredentials.googleapis.com", 1174 "https://iamcredentials.us-east-1.googleapis.com", 1175 "https://iamcredentials.US-WEST-1.googleapis.com", 1176 "https://us-east-1-iamcredentials.googleapis.com", 1177 "https://US-WEST-1-iamcredentials.googleapis.com", 1178 "https://us-west-1-iamcredentials.googleapis.com/path?query", 1179 "https://iamcredentials-xyz123.p.googleapis.com/path?query", 1180 "https://iamcredentials-xyz123.p.googleapis.com", 1181 "https://iamcredentials-xyz-123.p.googleapis.com"); 1182 1183 for (String url : validUrls) { 1184 ExternalAccountCredentials.validateServiceAccountImpersonationInfoUrl(url); 1185 ExternalAccountCredentials.validateServiceAccountImpersonationInfoUrl( 1186 url.toUpperCase(Locale.US)); 1187 } 1188 } 1189 1190 @Test validateServiceAccountImpersonationUrls_invalidUrls()1191 public void validateServiceAccountImpersonationUrls_invalidUrls() { 1192 List<String> invalidUrls = 1193 Arrays.asList( 1194 "iamcredentials.googleapis.com", 1195 "https://", 1196 "http://iamcredentials.googleapis.com", 1197 "https:/iamcredentials.googleapis.com", 1198 "https://us-eas\t-1.iamcredentials.googleapis.com", 1199 "testhttps://us-east-1.iamcredentials.googleapis.com", 1200 "hhttps://us-east-1.iamcredentials.googleapis.com", 1201 "https://us- -1.iamcredentials.googleapis.com"); 1202 1203 for (String url : invalidUrls) { 1204 try { 1205 ExternalAccountCredentials.validateServiceAccountImpersonationInfoUrl(url); 1206 fail("Should have failed since an invalid URL was passed."); 1207 } catch (IllegalArgumentException e) { 1208 assertEquals("The provided service account impersonation URL is invalid.", e.getMessage()); 1209 } 1210 } 1211 } 1212 buildJsonIdentityPoolCredential()1213 private GenericJson buildJsonIdentityPoolCredential() { 1214 GenericJson json = new GenericJson(); 1215 json.put( 1216 "audience", 1217 "//iam.googleapis.com/projects/123/locations/global/workloadIdentityPools/pool/providers/provider"); 1218 json.put("subject_token_type", "subjectTokenType"); 1219 json.put("token_url", STS_URL); 1220 json.put("token_info_url", "tokenInfoUrl"); 1221 1222 Map<String, String> map = new HashMap<>(); 1223 map.put("file", "file"); 1224 json.put("credential_source", map); 1225 return json; 1226 } 1227 buildJsonIdentityPoolWorkforceCredential()1228 private GenericJson buildJsonIdentityPoolWorkforceCredential() { 1229 GenericJson json = buildJsonIdentityPoolCredential(); 1230 json.put( 1231 "audience", "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider"); 1232 json.put("workforce_pool_user_project", "userProject"); 1233 return json; 1234 } 1235 buildJsonAwsCredential()1236 private GenericJson buildJsonAwsCredential() { 1237 GenericJson json = new GenericJson(); 1238 json.put("audience", "audience"); 1239 json.put("subject_token_type", "subjectTokenType"); 1240 json.put("token_url", STS_URL); 1241 json.put("token_info_url", "tokenInfoUrl"); 1242 1243 Map<String, String> map = new HashMap<>(); 1244 map.put("environment_id", "aws1"); 1245 map.put("region_url", "https://169.254.169.254/region"); 1246 map.put("url", "https://169.254.169.254/"); 1247 map.put("regional_cred_verification_url", "regionalCredVerificationUrl"); 1248 json.put("credential_source", map); 1249 1250 return json; 1251 } 1252 buildJsonPluggableAuthCredential()1253 private GenericJson buildJsonPluggableAuthCredential() { 1254 GenericJson json = new GenericJson(); 1255 json.put("audience", "audience"); 1256 json.put("subject_token_type", "subjectTokenType"); 1257 json.put("token_url", STS_URL); 1258 json.put("token_info_url", "tokenInfoUrl"); 1259 1260 Map<String, Map<String, Object>> credentialSource = new HashMap<>(); 1261 1262 Map<String, Object> executableConfig = new HashMap<>(); 1263 executableConfig.put("command", "command"); 1264 1265 credentialSource.put("executable", executableConfig); 1266 json.put("credential_source", credentialSource); 1267 1268 return json; 1269 } 1270 buildJsonPluggableAuthWorkforceCredential()1271 private GenericJson buildJsonPluggableAuthWorkforceCredential() { 1272 GenericJson json = buildJsonPluggableAuthCredential(); 1273 json.put( 1274 "audience", "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider"); 1275 json.put("workforce_pool_user_project", "userProject"); 1276 return json; 1277 } 1278 buildServiceAccountImpersonationOptions(Integer lifetime)1279 static Map<String, Object> buildServiceAccountImpersonationOptions(Integer lifetime) { 1280 Map<String, Object> map = new HashMap<String, Object>(); 1281 map.put("token_lifetime_seconds", lifetime); 1282 1283 return map; 1284 } 1285 validateMetricsHeader( Map<String, List<String>> headers, String source, boolean saImpersonationUsed, boolean configLifetimeUsed)1286 static void validateMetricsHeader( 1287 Map<String, List<String>> headers, 1288 String source, 1289 boolean saImpersonationUsed, 1290 boolean configLifetimeUsed) { 1291 assertTrue(headers.containsKey(MetricsUtils.API_CLIENT_HEADER)); 1292 String actualMetricsValue = headers.get(MetricsUtils.API_CLIENT_HEADER).get(0); 1293 String expectedMetricsValue = 1294 String.format( 1295 "%s google-byoid-sdk source/%s sa-impersonation/%s config-lifetime/%s", 1296 MetricsUtils.getLanguageAndAuthLibraryVersions(), 1297 source, 1298 saImpersonationUsed, 1299 configLifetimeUsed); 1300 assertEquals(expectedMetricsValue, actualMetricsValue); 1301 } 1302 1303 static class TestExternalAccountCredentials extends ExternalAccountCredentials { 1304 static class TestCredentialSource extends IdentityPoolCredentialSource { TestCredentialSource(Map<String, Object> credentialSourceMap)1305 protected TestCredentialSource(Map<String, Object> credentialSourceMap) { 1306 super(credentialSourceMap); 1307 } 1308 } 1309 newBuilder()1310 public static Builder newBuilder() { 1311 return new Builder(); 1312 } 1313 1314 static class Builder extends ExternalAccountCredentials.Builder { Builder()1315 Builder() {} 1316 1317 @Override build()1318 public TestExternalAccountCredentials build() { 1319 return new TestExternalAccountCredentials(this); 1320 } 1321 } 1322 TestExternalAccountCredentials(ExternalAccountCredentials.Builder builder)1323 protected TestExternalAccountCredentials(ExternalAccountCredentials.Builder builder) { 1324 super(builder); 1325 } 1326 1327 @Override refreshAccessToken()1328 public AccessToken refreshAccessToken() { 1329 return new AccessToken("accessToken", new Date()); 1330 } 1331 1332 @Override retrieveSubjectToken()1333 public String retrieveSubjectToken() { 1334 return "subjectToken"; 1335 } 1336 } 1337 } 1338