Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adds universe domain for DownscopedCredentials and ExternalAccountAuthorizedUserCredentials #1355

Merged
merged 11 commits into from
Jan 25, 2024
Original file line number Diff line number Diff line change
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,44 @@
*/
public final class DownscopedCredentials extends OAuth2Credentials {
lsirac marked this conversation as resolved.
Show resolved Hide resolved

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()) {
lsirac marked this conversation as resolved.
Show resolved Hide resolved
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) {
throw new IllegalStateException(
lsirac marked this conversation as resolved.
Show resolved Hide resolved
lsirac marked this conversation as resolved.
Show resolved Hide resolved
"Error occurred when attempting to retrieve source credential universe domain.");
}
this.tokenExchangeEndpoint =
TOKEN_EXCHANGE_URL_FORMAT.replace("{universe_domain}", universeDomain);
}

@Override
Expand All @@ -122,7 +145,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 +173,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() {
lsirac marked this conversation as resolved.
Show resolved Hide resolved
return universeDomain;
}

@VisibleForTesting
HttpTransportFactory getTransportFactory() {
return transportFactory;
Expand All @@ -164,31 +198,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);
}
}
}
Original file line number Diff line number Diff line change
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)
lsirac marked this conversation as resolved.
Show resolved Hide resolved
.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
Original file line number Diff line number Diff line change
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());
}
lsirac marked this conversation as resolved.
Show resolved Hide resolved

@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