Skip to content

Commit

Permalink
feat: adds universe domain for DownscopedCredentials and ExternalAcco…
Browse files Browse the repository at this point in the history
…untAuthorizedUserCredentials (#1355)

* feat: adds universe domain to DownscopedCredentials

* feat: adds universe domain to external account authorized user creds

* fix: adds more coverage for universe domain accross tests

* fix tests

* fix: more tests

* fix: add source credential universe domain check

* fix: add source credential universe domain check

* fix: review comments
  • Loading branch information
lsirac committed Jan 25, 2024
1 parent e3a2e9c commit 17ef707
Show file tree
Hide file tree
Showing 9 changed files with 729 additions and 52 deletions.
90 changes: 78 additions & 12 deletions oauth2_http/java/com/google/auth/oauth2/DownscopedCredentials.java
Expand Up @@ -34,6 +34,7 @@
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkNotNull;

import com.google.auth.Credentials;
import com.google.auth.http.HttpTransportFactory;
import com.google.common.annotations.VisibleForTesting;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
Expand Down Expand Up @@ -87,22 +88,45 @@
*/
public final class DownscopedCredentials extends OAuth2Credentials {

private static final String TOKEN_EXCHANGE_ENDPOINT = "https://sts.googleapis.com/v1/token";

private final String TOKEN_EXCHANGE_URL_FORMAT = "https://sts.{universe_domain}/v1/token";
private final GoogleCredentials sourceCredential;
private final CredentialAccessBoundary credentialAccessBoundary;
private final String universeDomain;

private final transient HttpTransportFactory transportFactory;

private DownscopedCredentials(
GoogleCredentials sourceCredential,
CredentialAccessBoundary credentialAccessBoundary,
HttpTransportFactory transportFactory) {
private final String tokenExchangeEndpoint;

/** Internal constructor. See {@link Builder}. */
private DownscopedCredentials(Builder builder) {
this.transportFactory =
firstNonNull(
transportFactory,
builder.transportFactory,
getFromServiceLoader(HttpTransportFactory.class, OAuth2Utils.HTTP_TRANSPORT_FACTORY));
this.sourceCredential = checkNotNull(sourceCredential);
this.credentialAccessBoundary = checkNotNull(credentialAccessBoundary);
this.sourceCredential = checkNotNull(builder.sourceCredential);
this.credentialAccessBoundary = checkNotNull(builder.credentialAccessBoundary);

// Default to GDU when not supplied.
if (builder.universeDomain == null || builder.universeDomain.trim().isEmpty()) {
this.universeDomain = Credentials.GOOGLE_DEFAULT_UNIVERSE;
} else {
this.universeDomain = builder.universeDomain;
}

// Ensure source credential's universe domain matches.
try {
if (!this.universeDomain.equals(sourceCredential.getUniverseDomain())) {
throw new IllegalArgumentException(
"The downscoped credential's universe domain must be the same as the source "
+ "credential.");
}
} catch (IOException e) {
// Throwing an IOException would be a breaking change, so wrap it here.
throw new IllegalStateException(
"Error occurred when attempting to retrieve source credential universe domain.", e);
}
this.tokenExchangeEndpoint =
TOKEN_EXCHANGE_URL_FORMAT.replace("{universe_domain}", universeDomain);
}

@Override
Expand All @@ -122,7 +146,7 @@ public AccessToken refreshAccessToken() throws IOException {

StsRequestHandler handler =
StsRequestHandler.newBuilder(
TOKEN_EXCHANGE_ENDPOINT, request, transportFactory.create().createRequestFactory())
tokenExchangeEndpoint, request, transportFactory.create().createRequestFactory())
.setInternalOptions(credentialAccessBoundary.toJson())
.build();

Expand Down Expand Up @@ -150,6 +174,17 @@ public CredentialAccessBoundary getCredentialAccessBoundary() {
return credentialAccessBoundary;
}

/**
* Returns the universe domain for the credential.
*
* @return An explicit universe domain if it was explicitly provided, otherwise the default Google
* universe will be returned.
*/
@Override
public String getUniverseDomain() {
return universeDomain;
}

@VisibleForTesting
HttpTransportFactory getTransportFactory() {
return transportFactory;
Expand All @@ -164,31 +199,62 @@ public static class Builder extends OAuth2Credentials.Builder {
private GoogleCredentials sourceCredential;
private CredentialAccessBoundary credentialAccessBoundary;
private HttpTransportFactory transportFactory;
private String universeDomain;

private Builder() {}

/**
* Sets the required source credential used to acquire the downscoped credential.
*
* @param sourceCredential the {@code GoogleCredentials} to set
* @return this {@code Builder} object
*/
@CanIgnoreReturnValue
public Builder setSourceCredential(GoogleCredentials sourceCredential) {
this.sourceCredential = sourceCredential;
return this;
}

/**
* Sets the required credential access boundary which specifies the upper bound of permissions
* that the credential can access. See {@link CredentialAccessBoundary} for more information.
*
* @param credentialAccessBoundary the {@code CredentialAccessBoundary} to set
* @return this {@code Builder} object
*/
@CanIgnoreReturnValue
public Builder setCredentialAccessBoundary(CredentialAccessBoundary credentialAccessBoundary) {
this.credentialAccessBoundary = credentialAccessBoundary;
return this;
}

/**
* Sets the HTTP transport factory.
*
* @param transportFactory the {@code HttpTransportFactory} to set
* @return this {@code Builder} object
*/
@CanIgnoreReturnValue
public Builder setHttpTransportFactory(HttpTransportFactory transportFactory) {
this.transportFactory = transportFactory;
return this;
}

/**
* Sets the optional universe domain.
*
* @param universeDomain the universe domain to set
* @return this {@code Builder} object
*/
@CanIgnoreReturnValue
public Builder setUniverseDomain(String universeDomain) {
this.universeDomain = universeDomain;
return this;
}

@Override
public DownscopedCredentials build() {
return new DownscopedCredentials(
sourceCredential, credentialAccessBoundary, transportFactory);
return new DownscopedCredentials(this);
}
}
}
Expand Up @@ -317,6 +317,7 @@ static ExternalAccountAuthorizedUserCredentials fromJson(
String clientId = (String) json.get("client_id");
String clientSecret = (String) json.get("client_secret");
String quotaProjectId = (String) json.get("quota_project_id");
String universeDomain = (String) json.get("universe_domain");

return ExternalAccountAuthorizedUserCredentials.newBuilder()
.setAudience(audience)
Expand All @@ -329,6 +330,7 @@ static ExternalAccountAuthorizedUserCredentials fromJson(
.setRefreshToken(refreshToken)
.setHttpTransportFactory(transportFactory)
.setQuotaProjectId(quotaProjectId)
.setUniverseDomain(universeDomain)
.build();
}

Expand Down Expand Up @@ -522,6 +524,19 @@ public Builder setAccessToken(AccessToken accessToken) {
return this;
}

/**
* Sets the optional universe domain. The Google Default Universe is used when not provided.
*
* @param universeDomain the universe domain to set
* @return this {@code Builder} object
*/
@CanIgnoreReturnValue
@Override
public Builder setUniverseDomain(String universeDomain) {
super.setUniverseDomain(universeDomain);
return this;
}

@Override
public ExternalAccountAuthorizedUserCredentials build() {
return new ExternalAccountAuthorizedUserCredentials(this);
Expand Down
136 changes: 126 additions & 10 deletions oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java
Expand Up @@ -31,6 +31,7 @@

package com.google.auth.oauth2;

import static com.google.auth.Credentials.GOOGLE_DEFAULT_UNIVERSE;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
Expand Down Expand Up @@ -964,7 +965,44 @@ public void shouldUseMetadataServer_noEnvironmentVars() {
}

@Test
public void builder() {
public void builder_allFields() throws IOException {
List<String> scopes = Arrays.asList("scope1", "scope2");

AwsCredentials credentials =
(AwsCredentials)
AwsCredentials.newBuilder()
.setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY)
.setAudience("audience")
.setSubjectTokenType("subjectTokenType")
.setTokenUrl(STS_URL)
.setTokenInfoUrl("tokenInfoUrl")
.setCredentialSource(AWS_CREDENTIAL_SOURCE)
.setTokenInfoUrl("tokenInfoUrl")
.setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL)
.setQuotaProjectId("quotaProjectId")
.setClientId("clientId")
.setClientSecret("clientSecret")
.setScopes(scopes)
.setUniverseDomain("universeDomain")
.build();

assertEquals("audience", credentials.getAudience());
assertEquals("subjectTokenType", credentials.getSubjectTokenType());
assertEquals(STS_URL, credentials.getTokenUrl());
assertEquals("tokenInfoUrl", credentials.getTokenInfoUrl());
assertEquals(
SERVICE_ACCOUNT_IMPERSONATION_URL, credentials.getServiceAccountImpersonationUrl());
assertEquals(AWS_CREDENTIAL_SOURCE, credentials.getCredentialSource());
assertEquals("quotaProjectId", credentials.getQuotaProjectId());
assertEquals("clientId", credentials.getClientId());
assertEquals("clientSecret", credentials.getClientSecret());
assertEquals(scopes, credentials.getScopes());
assertEquals(SystemEnvironmentProvider.getInstance(), credentials.getEnvironmentProvider());
assertEquals("universeDomain", credentials.getUniverseDomain());
}

@Test
public void builder_missingUniverseDomain_defaults() throws IOException {
List<String> scopes = Arrays.asList("scope1", "scope2");

AwsCredentials credentials =
Expand All @@ -986,16 +1024,94 @@ public void builder() {

assertEquals("audience", credentials.getAudience());
assertEquals("subjectTokenType", credentials.getSubjectTokenType());
assertEquals(credentials.getTokenUrl(), STS_URL);
assertEquals(credentials.getTokenInfoUrl(), "tokenInfoUrl");
assertEquals(STS_URL, credentials.getTokenUrl());
assertEquals("tokenInfoUrl", credentials.getTokenInfoUrl());
assertEquals(
credentials.getServiceAccountImpersonationUrl(), SERVICE_ACCOUNT_IMPERSONATION_URL);
assertEquals(credentials.getCredentialSource(), AWS_CREDENTIAL_SOURCE);
assertEquals(credentials.getQuotaProjectId(), "quotaProjectId");
assertEquals(credentials.getClientId(), "clientId");
assertEquals(credentials.getClientSecret(), "clientSecret");
assertEquals(credentials.getScopes(), scopes);
assertEquals(credentials.getEnvironmentProvider(), SystemEnvironmentProvider.getInstance());
SERVICE_ACCOUNT_IMPERSONATION_URL, credentials.getServiceAccountImpersonationUrl());
assertEquals(AWS_CREDENTIAL_SOURCE, credentials.getCredentialSource());
assertEquals("quotaProjectId", credentials.getQuotaProjectId());
assertEquals("clientId", credentials.getClientId());
assertEquals("clientSecret", credentials.getClientSecret());
assertEquals(scopes, credentials.getScopes());
assertEquals(SystemEnvironmentProvider.getInstance(), credentials.getEnvironmentProvider());
assertEquals(GOOGLE_DEFAULT_UNIVERSE, credentials.getUniverseDomain());
}

@Test
public void newBuilder_allFields() throws IOException {
List<String> scopes = Arrays.asList("scope1", "scope2");

AwsCredentials credentials =
(AwsCredentials)
AwsCredentials.newBuilder()
.setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY)
.setAudience("audience")
.setSubjectTokenType("subjectTokenType")
.setTokenUrl(STS_URL)
.setTokenInfoUrl("tokenInfoUrl")
.setCredentialSource(AWS_CREDENTIAL_SOURCE)
.setTokenInfoUrl("tokenInfoUrl")
.setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL)
.setQuotaProjectId("quotaProjectId")
.setClientId("clientId")
.setClientSecret("clientSecret")
.setScopes(scopes)
.setUniverseDomain("universeDomain")
.build();

AwsCredentials newBuilderCreds = AwsCredentials.newBuilder(credentials).build();
assertEquals(credentials.getAudience(), newBuilderCreds.getAudience());
assertEquals(credentials.getSubjectTokenType(), newBuilderCreds.getSubjectTokenType());
assertEquals(credentials.getTokenUrl(), newBuilderCreds.getTokenUrl());
assertEquals(credentials.getTokenInfoUrl(), newBuilderCreds.getTokenInfoUrl());
assertEquals(
credentials.getServiceAccountImpersonationUrl(),
newBuilderCreds.getServiceAccountImpersonationUrl());
assertEquals(credentials.getCredentialSource(), newBuilderCreds.getCredentialSource());
assertEquals(credentials.getQuotaProjectId(), newBuilderCreds.getQuotaProjectId());
assertEquals(credentials.getClientId(), newBuilderCreds.getClientId());
assertEquals(credentials.getClientSecret(), newBuilderCreds.getClientSecret());
assertEquals(credentials.getScopes(), newBuilderCreds.getScopes());
assertEquals(credentials.getEnvironmentProvider(), newBuilderCreds.getEnvironmentProvider());
assertEquals(credentials.getUniverseDomain(), newBuilderCreds.getUniverseDomain());
}

@Test
public void newBuilder_noUniverseDomain_defaults() throws IOException {
List<String> scopes = Arrays.asList("scope1", "scope2");

AwsCredentials credentials =
(AwsCredentials)
AwsCredentials.newBuilder()
.setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY)
.setAudience("audience")
.setSubjectTokenType("subjectTokenType")
.setTokenUrl(STS_URL)
.setTokenInfoUrl("tokenInfoUrl")
.setCredentialSource(AWS_CREDENTIAL_SOURCE)
.setTokenInfoUrl("tokenInfoUrl")
.setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL)
.setQuotaProjectId("quotaProjectId")
.setClientId("clientId")
.setClientSecret("clientSecret")
.setScopes(scopes)
.build();

AwsCredentials newBuilderCreds = AwsCredentials.newBuilder(credentials).build();
assertEquals(credentials.getAudience(), newBuilderCreds.getAudience());
assertEquals(credentials.getSubjectTokenType(), newBuilderCreds.getSubjectTokenType());
assertEquals(credentials.getTokenUrl(), newBuilderCreds.getTokenUrl());
assertEquals(credentials.getTokenInfoUrl(), newBuilderCreds.getTokenInfoUrl());
assertEquals(
credentials.getServiceAccountImpersonationUrl(),
newBuilderCreds.getServiceAccountImpersonationUrl());
assertEquals(credentials.getCredentialSource(), newBuilderCreds.getCredentialSource());
assertEquals(credentials.getQuotaProjectId(), newBuilderCreds.getQuotaProjectId());
assertEquals(credentials.getClientId(), newBuilderCreds.getClientId());
assertEquals(credentials.getClientSecret(), newBuilderCreds.getClientSecret());
assertEquals(credentials.getScopes(), newBuilderCreds.getScopes());
assertEquals(credentials.getEnvironmentProvider(), newBuilderCreds.getEnvironmentProvider());
assertEquals(GOOGLE_DEFAULT_UNIVERSE, newBuilderCreds.getUniverseDomain());
}

@Test
Expand Down

0 comments on commit 17ef707

Please sign in to comment.