1 /* 2 * Copyright 2022 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.Credentials.GOOGLE_DEFAULT_UNIVERSE; 35 import static com.google.auth.oauth2.MockExternalAccountCredentialsTransport.SERVICE_ACCOUNT_IMPERSONATION_URL; 36 import static org.junit.Assert.*; 37 38 import com.google.api.client.http.HttpTransport; 39 import com.google.api.client.json.GenericJson; 40 import com.google.auth.TestUtils; 41 import com.google.auth.http.HttpTransportFactory; 42 import com.google.auth.oauth2.ExecutableHandler.ExecutableOptions; 43 import java.io.IOException; 44 import java.io.InputStream; 45 import java.io.NotSerializableException; 46 import java.math.BigDecimal; 47 import java.util.Arrays; 48 import java.util.HashMap; 49 import java.util.List; 50 import java.util.Map; 51 import javax.annotation.Nullable; 52 import org.junit.Test; 53 54 /** Tests for {@link PluggableAuthCredentials}. */ 55 public class PluggableAuthCredentialsTest extends BaseSerializationTest { 56 // The default timeout for waiting for the executable to finish (30 seconds). 57 private static final int DEFAULT_EXECUTABLE_TIMEOUT_MS = 30 * 1000; 58 // The minimum timeout for waiting for the executable to finish (5 seconds). 59 private static final int MINIMUM_EXECUTABLE_TIMEOUT_MS = 5 * 1000; 60 // The maximum timeout for waiting for the executable to finish (120 seconds). 61 private static final int MAXIMUM_EXECUTABLE_TIMEOUT_MS = 120 * 1000; 62 private static final String STS_URL = "https://sts.googleapis.com"; 63 64 private static final PluggableAuthCredentials CREDENTIAL = 65 PluggableAuthCredentials.newBuilder() 66 .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) 67 .setAudience( 68 "//iam.googleapis.com/projects/123/locations/global/workloadIdentityPools/pool/providers/provider") 69 .setSubjectTokenType("subjectTokenType") 70 .setTokenUrl(STS_URL) 71 .setTokenInfoUrl("tokenInfoUrl") 72 .setCredentialSource(buildCredentialSource()) 73 .build(); 74 75 static class MockExternalAccountCredentialsTransportFactory implements HttpTransportFactory { 76 77 MockExternalAccountCredentialsTransport transport = 78 new MockExternalAccountCredentialsTransport(); 79 80 @Override create()81 public HttpTransport create() { 82 return transport; 83 } 84 } 85 86 @Test retrieveSubjectToken_shouldDelegateToHandler()87 public void retrieveSubjectToken_shouldDelegateToHandler() throws IOException { 88 PluggableAuthCredentials credential = 89 PluggableAuthCredentials.newBuilder(CREDENTIAL) 90 .setExecutableHandler(options -> "pluggableAuthToken") 91 .build(); 92 String subjectToken = credential.retrieveSubjectToken(); 93 assertEquals(subjectToken, "pluggableAuthToken"); 94 } 95 96 @Test retrieveSubjectToken_shouldPassAllOptionsToHandler()97 public void retrieveSubjectToken_shouldPassAllOptionsToHandler() throws IOException { 98 String command = "/path/to/executable"; 99 String timeout = "5000"; 100 String outputFile = "/path/to/output/file"; 101 102 final ExecutableOptions[] providedOptions = {null}; 103 ExecutableHandler executableHandler = 104 options -> { 105 providedOptions[0] = options; 106 return "pluggableAuthToken"; 107 }; 108 109 PluggableAuthCredentials credential = 110 PluggableAuthCredentials.newBuilder(CREDENTIAL) 111 .setExecutableHandler(executableHandler) 112 .setCredentialSource(buildCredentialSource(command, timeout, outputFile)) 113 .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) 114 .build(); 115 116 String subjectToken = credential.retrieveSubjectToken(); 117 118 assertEquals(subjectToken, "pluggableAuthToken"); 119 120 // Validate that the correct options were passed to the executable handler. 121 ExecutableOptions options = providedOptions[0]; 122 assertEquals(options.getExecutableCommand(), command); 123 assertEquals(options.getExecutableTimeoutMs(), Integer.parseInt(timeout)); 124 assertEquals(options.getOutputFilePath(), outputFile); 125 126 Map<String, String> envMap = options.getEnvironmentMap(); 127 assertEquals(envMap.size(), 5); 128 assertEquals(envMap.get("GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE"), credential.getAudience()); 129 assertEquals( 130 envMap.get("GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE"), credential.getSubjectTokenType()); 131 assertEquals(envMap.get("GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE"), "0"); 132 assertEquals( 133 envMap.get("GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL"), 134 credential.getServiceAccountEmail()); 135 assertEquals(envMap.get("GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE"), outputFile); 136 } 137 138 @Test retrieveSubjectToken_shouldPassMinimalOptionsToHandler()139 public void retrieveSubjectToken_shouldPassMinimalOptionsToHandler() throws IOException { 140 String command = "/path/to/executable"; 141 142 final ExecutableOptions[] providedOptions = {null}; 143 ExecutableHandler executableHandler = 144 options -> { 145 providedOptions[0] = options; 146 return "pluggableAuthToken"; 147 }; 148 149 PluggableAuthCredentials credential = 150 PluggableAuthCredentials.newBuilder(CREDENTIAL) 151 .setExecutableHandler(executableHandler) 152 .setCredentialSource( 153 buildCredentialSource(command, /* timeoutMs= */ null, /* outputFile= */ null)) 154 .build(); 155 156 String subjectToken = credential.retrieveSubjectToken(); 157 158 assertEquals(subjectToken, "pluggableAuthToken"); 159 160 // Validate that the correct options were passed to the executable handler. 161 ExecutableOptions options = providedOptions[0]; 162 assertEquals(options.getExecutableCommand(), command); 163 assertEquals(options.getExecutableTimeoutMs(), DEFAULT_EXECUTABLE_TIMEOUT_MS); 164 assertNull(options.getOutputFilePath()); 165 166 Map<String, String> envMap = options.getEnvironmentMap(); 167 assertEquals(envMap.size(), 3); 168 assertEquals(envMap.get("GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE"), credential.getAudience()); 169 assertEquals( 170 envMap.get("GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE"), credential.getSubjectTokenType()); 171 assertEquals(envMap.get("GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE"), "0"); 172 assertNull(envMap.get("GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL")); 173 assertNull(envMap.get("GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE")); 174 } 175 176 @Test refreshAccessToken_withoutServiceAccountImpersonation()177 public void refreshAccessToken_withoutServiceAccountImpersonation() throws IOException { 178 MockExternalAccountCredentialsTransportFactory transportFactory = 179 new MockExternalAccountCredentialsTransportFactory(); 180 181 transportFactory.transport.setExpireTime(TestUtils.getDefaultExpireTime()); 182 183 PluggableAuthCredentials credential = 184 PluggableAuthCredentials.newBuilder(CREDENTIAL) 185 .setExecutableHandler(options -> "pluggableAuthToken") 186 .setTokenUrl(transportFactory.transport.getStsUrl()) 187 .setHttpTransportFactory(transportFactory) 188 .build(); 189 190 AccessToken accessToken = credential.refreshAccessToken(); 191 192 assertEquals(transportFactory.transport.getAccessToken(), accessToken.getTokenValue()); 193 194 // Validate that the correct subject token was passed to STS. 195 Map<String, String> query = 196 TestUtils.parseQuery(transportFactory.transport.getRequests().get(0).getContentAsString()); 197 assertEquals(query.get("subject_token"), "pluggableAuthToken"); 198 199 // Validate metrics header is set correctly on the sts request. 200 Map<String, List<String>> headers = 201 transportFactory.transport.getRequests().get(0).getHeaders(); 202 ExternalAccountCredentialsTest.validateMetricsHeader(headers, "executable", false, false); 203 } 204 205 @Test refreshAccessToken_withServiceAccountImpersonation()206 public void refreshAccessToken_withServiceAccountImpersonation() throws IOException { 207 MockExternalAccountCredentialsTransportFactory transportFactory = 208 new MockExternalAccountCredentialsTransportFactory(); 209 210 transportFactory.transport.setExpireTime(TestUtils.getDefaultExpireTime()); 211 212 PluggableAuthCredentials credential = 213 PluggableAuthCredentials.newBuilder() 214 .setAudience( 215 "//iam.googleapis.com/projects/123/locations/global/workloadIdentityPools/pool/providers/provider") 216 .setSubjectTokenType("subjectTokenType") 217 .setTokenInfoUrl("tokenInfoUrl") 218 .setTokenUrl(transportFactory.transport.getStsUrl()) 219 .setCredentialSource(buildCredentialSource()) 220 .setServiceAccountImpersonationUrl( 221 transportFactory.transport.getServiceAccountImpersonationUrl()) 222 .setHttpTransportFactory(transportFactory) 223 .build(); 224 225 credential = 226 PluggableAuthCredentials.newBuilder(credential) 227 .setExecutableHandler(options -> "pluggableAuthToken") 228 .build(); 229 230 AccessToken accessToken = credential.refreshAccessToken(); 231 232 assertEquals( 233 transportFactory.transport.getServiceAccountAccessToken(), accessToken.getTokenValue()); 234 235 // Validate that the correct subject token was passed to STS. 236 Map<String, String> query = 237 TestUtils.parseQuery(transportFactory.transport.getRequests().get(0).getContentAsString()); 238 assertEquals(query.get("subject_token"), "pluggableAuthToken"); 239 240 // Validate metrics header is set correctly on the sts request. 241 Map<String, List<String>> headers = 242 transportFactory.transport.getRequests().get(0).getHeaders(); 243 ExternalAccountCredentialsTest.validateMetricsHeader(headers, "executable", true, false); 244 } 245 246 @Test refreshAccessToken_withServiceAccountImpersonationOptions()247 public void refreshAccessToken_withServiceAccountImpersonationOptions() throws IOException { 248 MockExternalAccountCredentialsTransportFactory transportFactory = 249 new MockExternalAccountCredentialsTransportFactory(); 250 251 transportFactory.transport.setExpireTime(TestUtils.getDefaultExpireTime()); 252 253 PluggableAuthCredentials credential = 254 PluggableAuthCredentials.newBuilder() 255 .setAudience( 256 "//iam.googleapis.com/projects/123/locations/global/workloadIdentityPools/pool/providers/provider") 257 .setSubjectTokenType("subjectTokenType") 258 .setTokenInfoUrl("tokenInfoUrl") 259 .setTokenUrl(transportFactory.transport.getStsUrl()) 260 .setCredentialSource(buildCredentialSource()) 261 .setServiceAccountImpersonationUrl( 262 transportFactory.transport.getServiceAccountImpersonationUrl()) 263 .setServiceAccountImpersonationOptions( 264 ExternalAccountCredentialsTest.buildServiceAccountImpersonationOptions(2800)) 265 .setHttpTransportFactory(transportFactory) 266 .build(); 267 268 credential = 269 PluggableAuthCredentials.newBuilder(credential) 270 .setExecutableHandler(options -> "pluggableAuthToken") 271 .build(); 272 273 AccessToken accessToken = credential.refreshAccessToken(); 274 275 assertEquals( 276 transportFactory.transport.getServiceAccountAccessToken(), accessToken.getTokenValue()); 277 278 // Validate that default lifetime was set correctly on the request. 279 GenericJson query = 280 OAuth2Utils.JSON_FACTORY 281 .createJsonParser(transportFactory.transport.getLastRequest().getContentAsString()) 282 .parseAndClose(GenericJson.class); 283 284 assertEquals("2800s", query.get("lifetime")); 285 286 // Validate metrics header is set correctly on the sts request. 287 Map<String, List<String>> headers = 288 transportFactory.transport.getRequests().get(0).getHeaders(); 289 ExternalAccountCredentialsTest.validateMetricsHeader(headers, "executable", true, true); 290 } 291 292 @Test pluggableAuthCredentialSource_allFields()293 public void pluggableAuthCredentialSource_allFields() { 294 Map<String, Object> source = new HashMap<>(); 295 Map<String, Object> executable = new HashMap<>(); 296 source.put("executable", executable); 297 executable.put("command", "/path/to/executable"); 298 executable.put("timeout_millis", "10000"); 299 executable.put("output_file", "/path/to/output/file"); 300 301 PluggableAuthCredentialSource credentialSource = new PluggableAuthCredentialSource(source); 302 303 assertEquals(credentialSource.getCommand(), "/path/to/executable"); 304 assertEquals(credentialSource.getTimeoutMs(), 10000); 305 assertEquals(credentialSource.getOutputFilePath(), "/path/to/output/file"); 306 } 307 308 @Test pluggableAuthCredentialSource_noTimeoutProvided_setToDefault()309 public void pluggableAuthCredentialSource_noTimeoutProvided_setToDefault() { 310 Map<String, Object> source = new HashMap<>(); 311 Map<String, Object> executable = new HashMap<>(); 312 source.put("executable", executable); 313 executable.put("command", "command"); 314 PluggableAuthCredentialSource credentialSource = new PluggableAuthCredentialSource(source); 315 316 assertEquals(credentialSource.getCommand(), "command"); 317 assertEquals(credentialSource.getTimeoutMs(), DEFAULT_EXECUTABLE_TIMEOUT_MS); 318 assertNull(credentialSource.getOutputFilePath()); 319 } 320 321 @Test pluggableAuthCredentialSource_timeoutProvidedOutOfRange_throws()322 public void pluggableAuthCredentialSource_timeoutProvidedOutOfRange_throws() { 323 Map<String, Object> source = new HashMap<>(); 324 Map<String, Object> executable = new HashMap<>(); 325 source.put("executable", executable); 326 327 executable.put("command", "command"); 328 329 int[] possibleOutOfRangeValues = new int[] {0, 4 * 1000, 121 * 1000}; 330 331 for (int value : possibleOutOfRangeValues) { 332 executable.put("timeout_millis", value); 333 334 try { 335 new PluggableAuthCredentialSource(source); 336 fail("Should not be able to continue without exception."); 337 } catch (IllegalArgumentException exception) { 338 assertEquals( 339 String.format( 340 "The executable timeout must be between %s and %s milliseconds.", 341 MINIMUM_EXECUTABLE_TIMEOUT_MS, MAXIMUM_EXECUTABLE_TIMEOUT_MS), 342 exception.getMessage()); 343 } 344 } 345 } 346 347 @Test pluggableAuthCredentialSource_validTimeoutProvided()348 public void pluggableAuthCredentialSource_validTimeoutProvided() { 349 Map<String, Object> source = new HashMap<>(); 350 Map<String, Object> executable = new HashMap<>(); 351 source.put("executable", executable); 352 353 executable.put("command", "command"); 354 355 Object[] possibleValues = new Object[] {"10000", 10000, BigDecimal.valueOf(10000L)}; 356 357 for (Object value : possibleValues) { 358 executable.put("timeout_millis", value); 359 PluggableAuthCredentialSource credentialSource = new PluggableAuthCredentialSource(source); 360 361 assertEquals(credentialSource.getCommand(), "command"); 362 assertEquals(credentialSource.getTimeoutMs(), 10000); 363 assertNull(credentialSource.getOutputFilePath()); 364 } 365 } 366 367 @Test pluggableAuthCredentialSource_missingExecutableField_throws()368 public void pluggableAuthCredentialSource_missingExecutableField_throws() { 369 try { 370 new PluggableAuthCredentialSource(new HashMap<>()); 371 fail("Should not be able to continue without exception."); 372 } catch (IllegalArgumentException exception) { 373 assertEquals( 374 "Invalid credential source for PluggableAuth credentials.", exception.getMessage()); 375 } 376 } 377 378 @Test pluggableAuthCredentialSource_missingExecutableCommandField_throws()379 public void pluggableAuthCredentialSource_missingExecutableCommandField_throws() { 380 Map<String, Object> source = new HashMap<>(); 381 Map<String, Object> executable = new HashMap<>(); 382 source.put("executable", executable); 383 384 try { 385 new PluggableAuthCredentialSource(source); 386 fail("Should not be able to continue without exception."); 387 } catch (IllegalArgumentException exception) { 388 assertEquals( 389 "The PluggableAuthCredentialSource is missing the required 'command' field.", 390 exception.getMessage()); 391 } 392 } 393 394 @Test builder_allFields()395 public void builder_allFields() throws IOException { 396 List<String> scopes = Arrays.asList("scope1", "scope2"); 397 398 PluggableAuthCredentialSource source = buildCredentialSource(); 399 ExecutableHandler handler = options -> "Token"; 400 401 PluggableAuthCredentials credentials = 402 PluggableAuthCredentials.newBuilder() 403 .setExecutableHandler(handler) 404 .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) 405 .setAudience("audience") 406 .setSubjectTokenType("subjectTokenType") 407 .setTokenUrl(STS_URL) 408 .setTokenInfoUrl("tokenInfoUrl") 409 .setCredentialSource(source) 410 .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) 411 .setQuotaProjectId("quotaProjectId") 412 .setClientId("clientId") 413 .setClientSecret("clientSecret") 414 .setScopes(scopes) 415 .setUniverseDomain("universeDomain") 416 .build(); 417 418 assertEquals(handler, credentials.getExecutableHandler()); 419 assertEquals("audience", credentials.getAudience()); 420 assertEquals("subjectTokenType", credentials.getSubjectTokenType()); 421 assertEquals(STS_URL, credentials.getTokenUrl()); 422 assertEquals("tokenInfoUrl", credentials.getTokenInfoUrl()); 423 assertEquals( 424 SERVICE_ACCOUNT_IMPERSONATION_URL, credentials.getServiceAccountImpersonationUrl()); 425 assertEquals(source, credentials.getCredentialSource()); 426 assertEquals("quotaProjectId", credentials.getQuotaProjectId()); 427 assertEquals("clientId", credentials.getClientId()); 428 assertEquals("clientSecret", credentials.getClientSecret()); 429 assertEquals(scopes, credentials.getScopes()); 430 assertEquals(SystemEnvironmentProvider.getInstance(), credentials.getEnvironmentProvider()); 431 assertEquals("universeDomain", credentials.getUniverseDomain()); 432 } 433 434 @Test builder_missingUniverseDomain_defaults()435 public void builder_missingUniverseDomain_defaults() throws IOException { 436 List<String> scopes = Arrays.asList("scope1", "scope2"); 437 438 PluggableAuthCredentialSource source = buildCredentialSource(); 439 ExecutableHandler handler = options -> "Token"; 440 441 PluggableAuthCredentials credentials = 442 PluggableAuthCredentials.newBuilder() 443 .setExecutableHandler(handler) 444 .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) 445 .setAudience("audience") 446 .setSubjectTokenType("subjectTokenType") 447 .setTokenUrl(STS_URL) 448 .setTokenInfoUrl("tokenInfoUrl") 449 .setCredentialSource(source) 450 .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) 451 .setQuotaProjectId("quotaProjectId") 452 .setClientId("clientId") 453 .setClientSecret("clientSecret") 454 .setScopes(scopes) 455 .build(); 456 457 assertEquals(handler, credentials.getExecutableHandler()); 458 assertEquals("audience", credentials.getAudience()); 459 assertEquals("subjectTokenType", credentials.getSubjectTokenType()); 460 assertEquals(STS_URL, credentials.getTokenUrl()); 461 assertEquals("tokenInfoUrl", credentials.getTokenInfoUrl()); 462 assertEquals( 463 SERVICE_ACCOUNT_IMPERSONATION_URL, credentials.getServiceAccountImpersonationUrl()); 464 assertEquals(source, credentials.getCredentialSource()); 465 assertEquals("quotaProjectId", credentials.getQuotaProjectId()); 466 assertEquals("clientId", credentials.getClientId()); 467 assertEquals("clientSecret", credentials.getClientSecret()); 468 assertEquals(scopes, credentials.getScopes()); 469 assertEquals(SystemEnvironmentProvider.getInstance(), credentials.getEnvironmentProvider()); 470 assertEquals(GOOGLE_DEFAULT_UNIVERSE, credentials.getUniverseDomain()); 471 } 472 473 @Test newBuilder_allFields()474 public void newBuilder_allFields() throws IOException { 475 List<String> scopes = Arrays.asList("scope1", "scope2"); 476 477 PluggableAuthCredentialSource source = buildCredentialSource(); 478 ExecutableHandler handler = options -> "Token"; 479 480 PluggableAuthCredentials credentials = 481 PluggableAuthCredentials.newBuilder() 482 .setExecutableHandler(handler) 483 .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) 484 .setAudience("audience") 485 .setSubjectTokenType("subjectTokenType") 486 .setTokenUrl(STS_URL) 487 .setTokenInfoUrl("tokenInfoUrl") 488 .setCredentialSource(source) 489 .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) 490 .setQuotaProjectId("quotaProjectId") 491 .setClientId("clientId") 492 .setClientSecret("clientSecret") 493 .setScopes(scopes) 494 .setUniverseDomain("universeDomain") 495 .build(); 496 497 PluggableAuthCredentials newBuilderCreds = 498 PluggableAuthCredentials.newBuilder(credentials).build(); 499 assertEquals(credentials.getAudience(), newBuilderCreds.getAudience()); 500 assertEquals(credentials.getSubjectTokenType(), newBuilderCreds.getSubjectTokenType()); 501 assertEquals(credentials.getTokenUrl(), newBuilderCreds.getTokenUrl()); 502 assertEquals(credentials.getTokenInfoUrl(), newBuilderCreds.getTokenInfoUrl()); 503 assertEquals( 504 credentials.getServiceAccountImpersonationUrl(), 505 newBuilderCreds.getServiceAccountImpersonationUrl()); 506 assertEquals(credentials.getCredentialSource(), newBuilderCreds.getCredentialSource()); 507 assertEquals(credentials.getQuotaProjectId(), newBuilderCreds.getQuotaProjectId()); 508 assertEquals(credentials.getClientId(), newBuilderCreds.getClientId()); 509 assertEquals(credentials.getClientSecret(), newBuilderCreds.getClientSecret()); 510 assertEquals(credentials.getScopes(), newBuilderCreds.getScopes()); 511 assertEquals(credentials.getEnvironmentProvider(), newBuilderCreds.getEnvironmentProvider()); 512 assertEquals(credentials.getUniverseDomain(), newBuilderCreds.getUniverseDomain()); 513 } 514 515 @Test newBuilder_noUniverseDomain_defaults()516 public void newBuilder_noUniverseDomain_defaults() throws IOException { 517 List<String> scopes = Arrays.asList("scope1", "scope2"); 518 519 PluggableAuthCredentialSource source = buildCredentialSource(); 520 ExecutableHandler handler = options -> "Token"; 521 522 PluggableAuthCredentials credentials = 523 PluggableAuthCredentials.newBuilder() 524 .setExecutableHandler(handler) 525 .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) 526 .setAudience("audience") 527 .setSubjectTokenType("subjectTokenType") 528 .setTokenUrl(STS_URL) 529 .setTokenInfoUrl("tokenInfoUrl") 530 .setCredentialSource(source) 531 .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) 532 .setQuotaProjectId("quotaProjectId") 533 .setClientId("clientId") 534 .setClientSecret("clientSecret") 535 .setScopes(scopes) 536 .build(); 537 538 PluggableAuthCredentials newBuilderCreds = 539 PluggableAuthCredentials.newBuilder(credentials).build(); 540 assertEquals(credentials.getAudience(), newBuilderCreds.getAudience()); 541 assertEquals(credentials.getSubjectTokenType(), newBuilderCreds.getSubjectTokenType()); 542 assertEquals(credentials.getTokenUrl(), newBuilderCreds.getTokenUrl()); 543 assertEquals(credentials.getTokenInfoUrl(), newBuilderCreds.getTokenInfoUrl()); 544 assertEquals( 545 credentials.getServiceAccountImpersonationUrl(), 546 newBuilderCreds.getServiceAccountImpersonationUrl()); 547 assertEquals(credentials.getCredentialSource(), newBuilderCreds.getCredentialSource()); 548 assertEquals(credentials.getQuotaProjectId(), newBuilderCreds.getQuotaProjectId()); 549 assertEquals(credentials.getClientId(), newBuilderCreds.getClientId()); 550 assertEquals(credentials.getClientSecret(), newBuilderCreds.getClientSecret()); 551 assertEquals(credentials.getScopes(), newBuilderCreds.getScopes()); 552 assertEquals(credentials.getEnvironmentProvider(), newBuilderCreds.getEnvironmentProvider()); 553 assertEquals(GOOGLE_DEFAULT_UNIVERSE, newBuilderCreds.getUniverseDomain()); 554 } 555 556 @Test createdScoped_clonedCredentialWithAddedScopes()557 public void createdScoped_clonedCredentialWithAddedScopes() throws IOException { 558 PluggableAuthCredentials credentials = 559 PluggableAuthCredentials.newBuilder(CREDENTIAL) 560 .setExecutableHandler(options -> "pluggableAuthToken") 561 .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) 562 .setQuotaProjectId("quotaProjectId") 563 .setClientId("clientId") 564 .setClientSecret("clientSecret") 565 .setUniverseDomain("universeDomain") 566 .build(); 567 568 List<String> newScopes = Arrays.asList("scope1", "scope2"); 569 570 PluggableAuthCredentials newCredentials = credentials.createScoped(newScopes); 571 572 assertEquals(credentials.getAudience(), newCredentials.getAudience()); 573 assertEquals(credentials.getSubjectTokenType(), newCredentials.getSubjectTokenType()); 574 assertEquals(credentials.getTokenUrl(), newCredentials.getTokenUrl()); 575 assertEquals(credentials.getTokenInfoUrl(), newCredentials.getTokenInfoUrl()); 576 assertEquals( 577 credentials.getServiceAccountImpersonationUrl(), 578 newCredentials.getServiceAccountImpersonationUrl()); 579 assertEquals(credentials.getCredentialSource(), newCredentials.getCredentialSource()); 580 assertEquals(newScopes, newCredentials.getScopes()); 581 assertEquals(credentials.getQuotaProjectId(), newCredentials.getQuotaProjectId()); 582 assertEquals(credentials.getClientId(), newCredentials.getClientId()); 583 assertEquals(credentials.getClientSecret(), newCredentials.getClientSecret()); 584 assertEquals(credentials.getExecutableHandler(), newCredentials.getExecutableHandler()); 585 assertEquals(credentials.getUniverseDomain(), newCredentials.getUniverseDomain()); 586 assertEquals("universeDomain", newCredentials.getUniverseDomain()); 587 } 588 589 @Test serialize()590 public void serialize() throws IOException, ClassNotFoundException { 591 PluggableAuthCredentials testCredentials = 592 PluggableAuthCredentials.newBuilder(CREDENTIAL) 593 .setExecutableHandler(options -> "pluggableAuthToken") 594 .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) 595 .setQuotaProjectId("quotaProjectId") 596 .setClientId("clientId") 597 .setClientSecret("clientSecret") 598 .setUniverseDomain("universeDomain") 599 .build(); 600 601 // PluggableAuthCredentials are not serializable 602 assertThrows(NotSerializableException.class, () -> serializeAndDeserialize(testCredentials)); 603 } 604 buildCredentialSource()605 private static PluggableAuthCredentialSource buildCredentialSource() { 606 return buildCredentialSource("command", null, null); 607 } 608 buildCredentialSource( String command, @Nullable String timeoutMs, @Nullable String outputFile)609 private static PluggableAuthCredentialSource buildCredentialSource( 610 String command, @Nullable String timeoutMs, @Nullable String outputFile) { 611 Map<String, Object> source = new HashMap<>(); 612 Map<String, Object> executable = new HashMap<>(); 613 source.put("executable", executable); 614 executable.put("command", command); 615 if (timeoutMs != null) { 616 executable.put("timeout_millis", timeoutMs); 617 } 618 if (outputFile != null) { 619 executable.put("output_file", outputFile); 620 } 621 622 return new PluggableAuthCredentialSource(source); 623 } 624 writeCredentialsStream(String tokenUrl)625 static InputStream writeCredentialsStream(String tokenUrl) throws IOException { 626 GenericJson json = new GenericJson(); 627 json.put("audience", "audience"); 628 json.put("subject_token_type", "subjectTokenType"); 629 json.put("token_url", tokenUrl); 630 json.put("token_info_url", "tokenInfoUrl"); 631 json.put("type", ExternalAccountCredentials.EXTERNAL_ACCOUNT_FILE_TYPE); 632 633 GenericJson credentialSource = new GenericJson(); 634 GenericJson executable = new GenericJson(); 635 executable.put("command", "/path/to/executable"); 636 credentialSource.put("executable", executable); 637 638 json.put("credential_source", credentialSource); 639 return TestUtils.jsonToInputStream(json); 640 } 641 } 642