diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java index 227603957..8ac4ec401 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java @@ -95,6 +95,7 @@ abstract static class CredentialSource implements java.io.Serializable { @Nullable private final String serviceAccountImpersonationUrl; @Nullable private final String clientId; @Nullable private final String clientSecret; + @Nullable private final String universeDomain; // This is used for Workforce Pools. It is passed to the Security Token Service during token // exchange in the `options` param and will be embedded in the token by the Security Token @@ -214,6 +215,7 @@ protected ExternalAccountCredentials( this.environmentProvider = environmentProvider == null ? SystemEnvironmentProvider.getInstance() : environmentProvider; this.workforcePoolUserProject = null; + this.universeDomain = null; this.serviceAccountImpersonationOptions = new ServiceAccountImpersonationOptions(new HashMap()); @@ -265,6 +267,8 @@ protected ExternalAccountCredentials(ExternalAccountCredentials.Builder builder) "The workforce_pool_user_project parameter should only be provided for a Workforce Pool configuration."); } + this.universeDomain = builder.universeDomain; + validateTokenUrl(tokenUrl); if (serviceAccountImpersonationUrl != null) { validateServiceAccountImpersonationInfoUrl(serviceAccountImpersonationUrl); @@ -403,6 +407,7 @@ static ExternalAccountCredentials fromJson( String clientSecret = (String) json.get("client_secret"); String quotaProjectId = (String) json.get("quota_project_id"); String userProject = (String) json.get("workforce_pool_user_project"); + String universeDomain = (String) json.get("universe_domain"); Map impersonationOptionsMap = (Map) json.get("service_account_impersonation"); @@ -423,6 +428,7 @@ static ExternalAccountCredentials fromJson( .setClientId(clientId) .setClientSecret(clientSecret) .setServiceAccountImpersonationOptions(impersonationOptionsMap) + .setUniverseDomain(universeDomain) .build(); } else if (isPluggableAuthCredential(credentialSourceMap)) { return PluggableAuthCredentials.newBuilder() @@ -438,6 +444,7 @@ static ExternalAccountCredentials fromJson( .setClientSecret(clientSecret) .setWorkforcePoolUserProject(userProject) .setServiceAccountImpersonationOptions(impersonationOptionsMap) + .setUniverseDomain(universeDomain) .build(); } return IdentityPoolCredentials.newBuilder() @@ -453,6 +460,7 @@ static ExternalAccountCredentials fromJson( .setClientSecret(clientSecret) .setWorkforcePoolUserProject(userProject) .setServiceAccountImpersonationOptions(impersonationOptionsMap) + .setUniverseDomain(universeDomain) .build(); } @@ -571,6 +579,11 @@ public String getWorkforcePoolUserProject() { return workforcePoolUserProject; } + @Nullable + public String getUniverseDomain() { + return universeDomain; + } + @Nullable public ServiceAccountImpersonationOptions getServiceAccountImpersonationOptions() { return serviceAccountImpersonationOptions; @@ -700,6 +713,7 @@ public abstract static class Builder extends GoogleCredentials.Builder { @Nullable protected Collection scopes; @Nullable protected String workforcePoolUserProject; @Nullable protected ServiceAccountImpersonationOptions serviceAccountImpersonationOptions; + @Nullable protected String universeDomain; protected Builder() {} @@ -718,6 +732,7 @@ protected Builder(ExternalAccountCredentials credentials) { this.environmentProvider = credentials.environmentProvider; this.workforcePoolUserProject = credentials.workforcePoolUserProject; this.serviceAccountImpersonationOptions = credentials.serviceAccountImpersonationOptions; + this.universeDomain = credentials.universeDomain; } /** @@ -870,6 +885,17 @@ public Builder setServiceAccountImpersonationOptions(Map options return this; } + /** + * Sets the optional universe domain. + * + * @param universeDomain the universe domain to set + * @return this {@code Builder} object + */ + public Builder setUniverseDomain(String universeDomain) { + this.universeDomain = universeDomain; + return this; + } + /** * Sets the optional Environment Provider. * diff --git a/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java index 7cf3ce3bc..248bc92df 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java @@ -746,6 +746,7 @@ public void createdScoped_clonedCredentialWithAddedScopes() { .setQuotaProjectId("quotaProjectId") .setClientId("clientId") .setClientSecret("clientSecret") + .setUniverseDomain("universeDomain") .build(); List newScopes = Arrays.asList("scope1", "scope2"); @@ -764,6 +765,8 @@ public void createdScoped_clonedCredentialWithAddedScopes() { assertEquals(credentials.getClientId(), newCredentials.getClientId()); assertEquals(credentials.getClientSecret(), newCredentials.getClientSecret()); assertEquals(newScopes, newCredentials.getScopes()); + assertEquals(credentials.getUniverseDomain(), newCredentials.getUniverseDomain()); + assertEquals("universeDomain", newCredentials.getUniverseDomain()); } @Test @@ -991,6 +994,7 @@ public void serialize() throws IOException, ClassNotFoundException { .setQuotaProjectId("quotaProjectId") .setClientId("clientId") .setClientSecret("clientSecret") + .setUniverseDomain("universeDomain") .setScopes(scopes) .build(); diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java index a12faa104..6e0f1efd3 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java @@ -186,6 +186,7 @@ public void fromJson_identityPoolCredentialsWorkload() { assertEquals(STS_URL, credential.getTokenUrl()); assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); assertNotNull(credential.getCredentialSource()); + assertNull(credential.getUniverseDomain()); } @Test @@ -203,6 +204,7 @@ public void fromJson_identityPoolCredentialsWorkforce() { assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); assertEquals("userProject", credential.getWorkforcePoolUserProject()); assertNotNull(credential.getCredentialSource()); + assertNull(credential.getUniverseDomain()); } @Test @@ -224,6 +226,27 @@ public void fromJson_identityPoolCredentialsWithServiceAccountImpersonationOptio assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); assertNotNull(credential.getCredentialSource()); assertEquals(2800, credential.getServiceAccountImpersonationOptions().getLifetime()); + assertNull(credential.getUniverseDomain()); + } + + @Test + public void fromJson_identityPoolCredentialsWithUniverseDomain() { + GenericJson identityPoolCredentialJson = buildJsonIdentityPoolCredential(); + identityPoolCredentialJson.set("universe_domain", "universeDomain"); + + ExternalAccountCredentials credential = + ExternalAccountCredentials.fromJson( + identityPoolCredentialJson, OAuth2Utils.HTTP_TRANSPORT_FACTORY); + + assertTrue(credential instanceof IdentityPoolCredentials); + assertNotNull(credential.getCredentialSource()); + assertEquals( + "//iam.googleapis.com/projects/123/locations/global/workloadIdentityPools/pool/providers/provider", + credential.getAudience()); + assertEquals("subjectTokenType", credential.getSubjectTokenType()); + assertEquals(STS_URL, credential.getTokenUrl()); + assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); + assertEquals("universeDomain", credential.getUniverseDomain()); } @Test @@ -238,6 +261,7 @@ public void fromJson_awsCredentials() throws IOException { assertEquals(STS_URL, credential.getTokenUrl()); assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); assertNotNull(credential.getCredentialSource()); + assertNull(credential.getUniverseDomain()); } @Test @@ -256,6 +280,24 @@ public void fromJson_awsCredentialsWithServiceAccountImpersonationOptions() thro assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); assertNotNull(credential.getCredentialSource()); assertEquals(2800, credential.getServiceAccountImpersonationOptions().getLifetime()); + assertNull(credential.getUniverseDomain()); + } + + @Test + public void fromJson_awsCredentialsWithUniverseDomain() { + GenericJson awsCredentialJson = buildJsonAwsCredential(); + awsCredentialJson.set("universe_domain", "universeDomain"); + + ExternalAccountCredentials credential = + ExternalAccountCredentials.fromJson(awsCredentialJson, OAuth2Utils.HTTP_TRANSPORT_FACTORY); + + assertTrue(credential instanceof AwsCredentials); + assertEquals("audience", credential.getAudience()); + assertEquals("subjectTokenType", credential.getSubjectTokenType()); + assertEquals(STS_URL, credential.getTokenUrl()); + assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); + assertEquals("universeDomain", credential.getUniverseDomain()); + assertNotNull(credential.getCredentialSource()); } @Test @@ -276,6 +318,7 @@ public void fromJson_pluggableAuthCredentials() { assertEquals("command", source.getCommand()); assertEquals(30000, source.getTimeoutMs()); // Default timeout is 30s. assertNull(source.getOutputFilePath()); + assertNull(credential.getUniverseDomain()); } @Test @@ -300,6 +343,7 @@ public void fromJson_pluggableAuthCredentialsWorkforce() { assertEquals("command", source.getCommand()); assertEquals(30000, source.getTimeoutMs()); // Default timeout is 30s. assertNull(source.getOutputFilePath()); + assertNull(credential.getUniverseDomain()); } @Test @@ -327,6 +371,7 @@ public void fromJson_pluggableAuthCredentials_allExecutableOptionsSet() { assertEquals("command", source.getCommand()); assertEquals("path/to/output/file", source.getOutputFilePath()); assertEquals(5000, source.getTimeoutMs()); + assertNull(credential.getUniverseDomain()); } @Test @@ -347,6 +392,61 @@ public void fromJson_pluggableAuthCredentialsWithServiceAccountImpersonationOpti assertNotNull(credential.getCredentialSource()); assertEquals(2800, credential.getServiceAccountImpersonationOptions().getLifetime()); + PluggableAuthCredentialSource source = + (PluggableAuthCredentialSource) credential.getCredentialSource(); + assertEquals("command", source.getCommand()); + assertEquals(30000, source.getTimeoutMs()); // Default timeout is 30s. + assertNull(source.getOutputFilePath()); + assertNull(credential.getUniverseDomain()); + } + + @Test + public void fromJson_pluggableAuthCredentials_withUniverseDomain() { + GenericJson json = buildJsonPluggableAuthCredential(); + json.set("universe_domain", "universeDomain"); + + Map credentialSourceMap = (Map) json.get("credential_source"); + // Add optional params to the executable config (timeout, output file path). + Map executableConfig = + (Map) credentialSourceMap.get("executable"); + executableConfig.put("timeout_millis", 5000); + executableConfig.put("output_file", "path/to/output/file"); + + ExternalAccountCredentials credential = + ExternalAccountCredentials.fromJson(json, OAuth2Utils.HTTP_TRANSPORT_FACTORY); + + assertTrue(credential instanceof PluggableAuthCredentials); + assertEquals("audience", credential.getAudience()); + assertEquals("subjectTokenType", credential.getSubjectTokenType()); + assertEquals(STS_URL, credential.getTokenUrl()); + assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); + assertNotNull(credential.getCredentialSource()); + + PluggableAuthCredentialSource source = + (PluggableAuthCredentialSource) credential.getCredentialSource(); + assertEquals("command", source.getCommand()); + assertEquals("path/to/output/file", source.getOutputFilePath()); + assertEquals(5000, source.getTimeoutMs()); + assertEquals("universeDomain", credential.getUniverseDomain()); + } + + @Test + public void fromJson_pluggableAuthCredentialsWithUniverseDomain() { + GenericJson pluggableAuthCredentialJson = buildJsonPluggableAuthCredential(); + pluggableAuthCredentialJson.set("universe_domain", "universeDomain"); + + ExternalAccountCredentials credential = + ExternalAccountCredentials.fromJson( + pluggableAuthCredentialJson, OAuth2Utils.HTTP_TRANSPORT_FACTORY); + + assertTrue(credential instanceof PluggableAuthCredentials); + assertEquals("audience", credential.getAudience()); + assertEquals("subjectTokenType", credential.getSubjectTokenType()); + assertEquals(STS_URL, credential.getTokenUrl()); + assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); + assertNotNull(credential.getCredentialSource()); + assertEquals("universeDomain", credential.getUniverseDomain()); + PluggableAuthCredentialSource source = (PluggableAuthCredentialSource) credential.getCredentialSource(); assertEquals("command", source.getCommand()); @@ -439,6 +539,7 @@ public void constructor_builder() { .setClientId("clientId") .setClientSecret("clientSecret") .setWorkforcePoolUserProject("workforcePoolUserProject") + .setUniverseDomain("universeDomain") .build(); assertEquals( @@ -454,6 +555,7 @@ public void constructor_builder() { assertEquals("clientId", credentials.getClientId()); assertEquals("clientSecret", credentials.getClientSecret()); assertEquals("workforcePoolUserProject", credentials.getWorkforcePoolUserProject()); + assertEquals("universeDomain", credentials.getUniverseDomain()); assertNotNull(credentials.getCredentialSource()); } @@ -562,6 +664,7 @@ public void constructor_builder_invalidTokenLifetime_throws() { .setClientId("clientId") .setClientSecret("clientSecret") .setWorkforcePoolUserProject("workforcePoolUserProject") + .setUniverseDomain("universeDomain") .setServiceAccountImpersonationOptions(invalidOptionsMap) .build(); fail("Should not be able to continue without exception."); @@ -593,6 +696,7 @@ public void constructor_builder_stringTokenLifetime() { .setClientId("clientId") .setClientSecret("clientSecret") .setWorkforcePoolUserProject("workforcePoolUserProject") + .setUniverseDomain("universeDomain") .setServiceAccountImpersonationOptions(optionsMap) .build(); @@ -619,6 +723,7 @@ public void constructor_builder_bigDecimalTokenLifetime() { .setClientId("clientId") .setClientSecret("clientSecret") .setWorkforcePoolUserProject("workforcePoolUserProject") + .setUniverseDomain("universeDomain") .setServiceAccountImpersonationOptions(optionsMap) .build(); @@ -645,6 +750,7 @@ public void constructor_builder_integerTokenLifetime() { .setClientId("clientId") .setClientSecret("clientSecret") .setWorkforcePoolUserProject("workforcePoolUserProject") + .setUniverseDomain("universeDomain") .setServiceAccountImpersonationOptions(optionsMap) .build(); @@ -657,23 +763,23 @@ public void constructor_builder_lowTokenLifetime_throws() { optionsMap.put("token_lifetime_seconds", 599); try { - ExternalAccountCredentials credentials = - IdentityPoolCredentials.newBuilder() - .setHttpTransportFactory(transportFactory) - .setAudience( - "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider") - .setSubjectTokenType("subjectTokenType") - .setTokenUrl(STS_URL) - .setTokenInfoUrl("https://tokeninfo.com") - .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) - .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) - .setScopes(Arrays.asList("scope1", "scope2")) - .setQuotaProjectId("projectId") - .setClientId("clientId") - .setClientSecret("clientSecret") - .setWorkforcePoolUserProject("workforcePoolUserProject") - .setServiceAccountImpersonationOptions(optionsMap) - .build(); + IdentityPoolCredentials.newBuilder() + .setHttpTransportFactory(transportFactory) + .setAudience( + "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider") + .setSubjectTokenType("subjectTokenType") + .setTokenUrl(STS_URL) + .setTokenInfoUrl("https://tokeninfo.com") + .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) + .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) + .setScopes(Arrays.asList("scope1", "scope2")) + .setQuotaProjectId("projectId") + .setClientId("clientId") + .setClientSecret("clientSecret") + .setWorkforcePoolUserProject("workforcePoolUserProject") + .setUniverseDomain("universeDomain") + .setServiceAccountImpersonationOptions(optionsMap) + .build(); } catch (IllegalArgumentException e) { assertEquals( "The \"token_lifetime_seconds\" field must be between 600 and 43200 seconds.", @@ -687,23 +793,23 @@ public void constructor_builder_highTokenLifetime_throws() { optionsMap.put("token_lifetime_seconds", 43201); try { - ExternalAccountCredentials credentials = - IdentityPoolCredentials.newBuilder() - .setHttpTransportFactory(transportFactory) - .setAudience( - "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider") - .setSubjectTokenType("subjectTokenType") - .setTokenUrl(STS_URL) - .setTokenInfoUrl("https://tokeninfo.com") - .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) - .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) - .setScopes(Arrays.asList("scope1", "scope2")) - .setQuotaProjectId("projectId") - .setClientId("clientId") - .setClientSecret("clientSecret") - .setWorkforcePoolUserProject("workforcePoolUserProject") - .setServiceAccountImpersonationOptions(optionsMap) - .build(); + IdentityPoolCredentials.newBuilder() + .setHttpTransportFactory(transportFactory) + .setAudience( + "//iam.googleapis.com/locations/global/workforcePools/pool/providers/provider") + .setSubjectTokenType("subjectTokenType") + .setTokenUrl(STS_URL) + .setTokenInfoUrl("https://tokeninfo.com") + .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) + .setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP)) + .setScopes(Arrays.asList("scope1", "scope2")) + .setQuotaProjectId("projectId") + .setClientId("clientId") + .setClientSecret("clientSecret") + .setWorkforcePoolUserProject("workforcePoolUserProject") + .setUniverseDomain("universeDomain") + .setServiceAccountImpersonationOptions(optionsMap) + .build(); } catch (IllegalArgumentException e) { assertEquals( "The \"token_lifetime_seconds\" field must be between 600 and 43200 seconds.", diff --git a/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java index 8771ca9ff..560334965 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java @@ -103,6 +103,7 @@ public void createdScoped_clonedCredentialWithAddedScopes() { .setQuotaProjectId("quotaProjectId") .setClientId("clientId") .setClientSecret("clientSecret") + .setUniverseDomain("universeDomain") .build(); List newScopes = Arrays.asList("scope1", "scope2"); @@ -121,6 +122,8 @@ public void createdScoped_clonedCredentialWithAddedScopes() { assertEquals(credentials.getQuotaProjectId(), newCredentials.getQuotaProjectId()); assertEquals(credentials.getClientId(), newCredentials.getClientId()); assertEquals(credentials.getClientSecret(), newCredentials.getClientSecret()); + assertEquals(credentials.getUniverseDomain(), newCredentials.getUniverseDomain()); + assertEquals("universeDomain", newCredentials.getUniverseDomain()); } @Test @@ -739,6 +742,7 @@ public void serialize() throws IOException, ClassNotFoundException { .setQuotaProjectId("quotaProjectId") .setClientId("clientId") .setClientSecret("clientSecret") + .setUniverseDomain("universeDomain") .build(); IdentityPoolCredentials deserializedCredentials = serializeAndDeserialize(testCredentials); diff --git a/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java index ef66e90cd..ddc321fdd 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java @@ -414,6 +414,7 @@ public void createdScoped_clonedCredentialWithAddedScopes() { .setQuotaProjectId("quotaProjectId") .setClientId("clientId") .setClientSecret("clientSecret") + .setUniverseDomain("universeDomain") .build(); List newScopes = Arrays.asList("scope1", "scope2"); @@ -433,6 +434,8 @@ public void createdScoped_clonedCredentialWithAddedScopes() { assertEquals(credentials.getClientId(), newCredentials.getClientId()); assertEquals(credentials.getClientSecret(), newCredentials.getClientSecret()); assertEquals(credentials.getExecutableHandler(), newCredentials.getExecutableHandler()); + assertEquals(credentials.getUniverseDomain(), newCredentials.getUniverseDomain()); + assertEquals("universeDomain", newCredentials.getUniverseDomain()); } @Test @@ -445,6 +448,7 @@ public void serialize() throws IOException, ClassNotFoundException { .setQuotaProjectId("quotaProjectId") .setClientId("clientId") .setClientSecret("clientSecret") + .setUniverseDomain("universeDomain") .build(); // PluggableAuthCredentials are not serializable