Skip to content

Commit

Permalink
feat: multi universe support, adding universe_domain field (#1282)
Browse files Browse the repository at this point in the history
* feat: adding universe_domain field

* fix: move universe_domain to very base Credential, tests cleanup

* feat: refactor ExternalAccount to use base class universe domain, update tests

* fix: deprecating a duplicate universeDomain field

* fix: docs update for Credentials.java

* getUniverseDomain throws IOExcpetion, more tests

---------

Co-authored-by: Leo <39062083+lsirac@users.noreply.github.com>
  • Loading branch information
TimurSadykov and lsirac committed Dec 20, 2023
1 parent 5f0628c commit 7eb322e
Show file tree
Hide file tree
Showing 14 changed files with 788 additions and 346 deletions.
16 changes: 16 additions & 0 deletions credentials/java/com/google/auth/Credentials.java
Expand Up @@ -43,6 +43,8 @@ public abstract class Credentials implements Serializable {

private static final long serialVersionUID = 808575179767517313L;

public static final String GOOGLE_DEFAULT_UNIVERSE = "googleapis.com";

/**
* A constant string name describing the authentication technology.
*
Expand All @@ -54,6 +56,20 @@ public abstract class Credentials implements Serializable {
*/
public abstract String getAuthenticationType();

/**
* Gets the universe domain for the credential in a blocking manner, refreshing tokens if
* required.
*
* @return a universe domain value in the format some-domain.xyz. By default, returns the Google
* universe domain googleapis.com.
* @throws IOException extending classes might have to do remote calls to determine the universe
* domain. The exception must implement {@link Retryable} and {@code isRetryable()} will
* return true if the operation may be retried.
*/
public String getUniverseDomain() throws IOException {
return GOOGLE_DEFAULT_UNIVERSE;
}

/**
* Get the current request metadata, refreshing tokens if required.
*
Expand Down
Expand Up @@ -245,6 +245,7 @@ public static Builder newBuilder() {
public int hashCode() {
return Objects.hash(
super.hashCode(),
getAccessToken(),
clientId,
clientSecret,
refreshToken,
Expand Down Expand Up @@ -281,6 +282,7 @@ public boolean equals(Object obj) {
ExternalAccountAuthorizedUserCredentials credentials =
(ExternalAccountAuthorizedUserCredentials) obj;
return super.equals(credentials)
&& Objects.equals(this.getAccessToken(), credentials.getAccessToken())
&& Objects.equals(this.clientId, credentials.clientId)
&& Objects.equals(this.clientSecret, credentials.clientSecret)
&& Objects.equals(this.refreshToken, credentials.refreshToken)
Expand Down
Expand Up @@ -95,7 +95,6 @@ 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
Expand Down Expand Up @@ -215,7 +214,6 @@ protected ExternalAccountCredentials(
this.environmentProvider =
environmentProvider == null ? SystemEnvironmentProvider.getInstance() : environmentProvider;
this.workforcePoolUserProject = null;
this.universeDomain = null;
this.serviceAccountImpersonationOptions =
new ServiceAccountImpersonationOptions(new HashMap<String, Object>());

Expand Down Expand Up @@ -269,8 +267,6 @@ 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);
Expand Down Expand Up @@ -593,11 +589,6 @@ public String getWorkforcePoolUserProject() {
return workforcePoolUserProject;
}

@Nullable
String getUniverseDomain() {
return universeDomain;
}

@Nullable
public ServiceAccountImpersonationOptions getServiceAccountImpersonationOptions() {
return serviceAccountImpersonationOptions;
Expand Down Expand Up @@ -734,7 +725,12 @@ public abstract static class Builder extends GoogleCredentials.Builder {
@Nullable protected Collection<String> scopes;
@Nullable protected String workforcePoolUserProject;
@Nullable protected ServiceAccountImpersonationOptions serviceAccountImpersonationOptions;
@Nullable protected String universeDomain;

/* The field is not being used and value not set. Superseded by the same field in the
{@link GoogleCredential.Builder}.
*/
@Nullable @Deprecated protected String universeDomain;

@Nullable protected ExternalAccountMetricsHandler metricsHandler;

protected Builder() {}
Expand All @@ -754,7 +750,6 @@ protected Builder(ExternalAccountCredentials credentials) {
this.environmentProvider = credentials.environmentProvider;
this.workforcePoolUserProject = credentials.workforcePoolUserProject;
this.serviceAccountImpersonationOptions = credentials.serviceAccountImpersonationOptions;
this.universeDomain = credentials.universeDomain;
this.metricsHandler = credentials.metricsHandler;
}

Expand Down Expand Up @@ -928,8 +923,9 @@ public Builder setServiceAccountImpersonationOptions(Map<String, Object> options
* @return this {@code Builder} object
*/
@CanIgnoreReturnValue
@Override
public Builder setUniverseDomain(String universeDomain) {
this.universeDomain = universeDomain;
super.setUniverseDomain(universeDomain);
return this;
}

Expand Down
130 changes: 124 additions & 6 deletions oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java
Expand Up @@ -35,7 +35,10 @@
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.JsonObjectParser;
import com.google.api.client.util.Preconditions;
import com.google.auth.Credentials;
import com.google.auth.http.HttpTransportFactory;
import com.google.common.base.MoreObjects;
import com.google.common.base.MoreObjects.ToStringHelper;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.IOException;
Expand All @@ -47,6 +50,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.annotation.Nullable;

/** Base type for credentials for authorizing calls to Google APIs using OAuth2. */
Expand All @@ -59,6 +63,8 @@ public class GoogleCredentials extends OAuth2Credentials implements QuotaProject
static final String SERVICE_ACCOUNT_FILE_TYPE = "service_account";
static final String GDCH_SERVICE_ACCOUNT_FILE_TYPE = "gdch_service_account";

private final String universeDomain;

protected final String quotaProjectId;

private static final DefaultCredentialsProvider defaultCredentialsProvider =
Expand All @@ -71,7 +77,10 @@ public class GoogleCredentials extends OAuth2Credentials implements QuotaProject
* @return the credentials instance
*/
public static GoogleCredentials create(AccessToken accessToken) {
return GoogleCredentials.newBuilder().setAccessToken(accessToken).build();
return GoogleCredentials.newBuilder()
.setAccessToken(accessToken)
.setUniverseDomain(Credentials.GOOGLE_DEFAULT_UNIVERSE)
.build();
}

/**
Expand Down Expand Up @@ -170,6 +179,7 @@ public static GoogleCredentials fromStream(
if (fileType == null) {
throw new IOException("Error reading credentials from stream, 'type' field not specified.");
}

if (USER_FILE_TYPE.equals(fileType)) {
return UserCredentials.fromJson(fileContents, transportFactory);
}
Expand All @@ -186,14 +196,20 @@ public static GoogleCredentials fromStream(
fileType)) {
return ExternalAccountAuthorizedUserCredentials.fromJson(fileContents, transportFactory);
}
if ("impersonated_service_account".equals(fileType)) {
if (ImpersonatedCredentials.IMPERSONATED_CREDENTIALS_FILE_TYPE.equals(fileType)) {
return ImpersonatedCredentials.fromJson(fileContents, transportFactory);
}
throw new IOException(
String.format(
"Error reading credentials from stream, 'type' value '%s' not recognized."
+ " Expecting '%s' or '%s'.",
fileType, USER_FILE_TYPE, SERVICE_ACCOUNT_FILE_TYPE));
+ " Valid values are '%s', '%s', '%s', '%s', '%s', '%s'.",
fileType,
USER_FILE_TYPE,
SERVICE_ACCOUNT_FILE_TYPE,
GDCH_SERVICE_ACCOUNT_FILE_TYPE,
ExternalAccountCredentials.EXTERNAL_ACCOUNT_FILE_TYPE,
ExternalAccountAuthorizedUserCredentials.EXTERNAL_ACCOUNT_AUTHORIZED_USER_FILE_TYPE,
ImpersonatedCredentials.IMPERSONATED_CREDENTIALS_FILE_TYPE));
}

/**
Expand All @@ -206,6 +222,27 @@ public GoogleCredentials createWithQuotaProject(String quotaProject) {
return this.toBuilder().setQuotaProjectId(quotaProject).build();
}

/**
* Gets the universe domain for the credential.
*
* @return An explicit universe domain if it was explicitly provided, invokes the super
* implementation otherwise
*/
@Override
public String getUniverseDomain() throws IOException {
return this.universeDomain;
}

/**
* Checks if universe domain equals to {@link Credentials#GOOGLE_DEFAULT_UNIVERSE}.
*
* @return true if universeDomain equals to {@link Credentials#GOOGLE_DEFAULT_UNIVERSE}, false
* otherwise
*/
boolean isDefaultUniverseDomain() {
return this.universeDomain.equals(Credentials.GOOGLE_DEFAULT_UNIVERSE);
}

/**
* Adds quota project ID to requestMetadata if present.
*
Expand Down Expand Up @@ -237,33 +274,97 @@ protected GoogleCredentials() {
this(new Builder());
}

/**
* Constructor with an explicit access token and quotaProjectId.
*
* <p>Deprecated, please use the {@link GoogleCredentials#GoogleCredentials(Builder)} constructor
* whenever possible.
*
* @param accessToken initial or temporary access token
* @param quotaProjectId a quotaProjectId, a project id to be used for billing purposes
*/
@Deprecated
protected GoogleCredentials(AccessToken accessToken, String quotaProjectId) {
super(accessToken);
this.quotaProjectId = quotaProjectId;
this.universeDomain = Credentials.GOOGLE_DEFAULT_UNIVERSE;
}

/**
* Constructor with explicit access token.
*
* @param accessToken initial or temporary access token
*/
@Deprecated
public GoogleCredentials(AccessToken accessToken) {
this(accessToken, null);
}

/**
* Constructor that relies on a {@link GoogleCredential.Builder} to provide all the necessary
* field values for initialization.
*
* @param builder an instance of a builder
*/
protected GoogleCredentials(Builder builder) {
this(builder.getAccessToken(), builder.getQuotaProjectId());
super(builder.getAccessToken());
this.quotaProjectId = builder.getQuotaProjectId();

if (builder.universeDomain == null || builder.universeDomain.trim().isEmpty()) {
this.universeDomain = Credentials.GOOGLE_DEFAULT_UNIVERSE;
} else {
this.universeDomain = builder.getUniverseDomain();
}
}

/**
* Constructor with explicit access token and refresh times
* Constructor with explicit access token and refresh margins.
*
* <p>Deprecated, please use the {@link GoogleCredentials#GoogleCredentials(Builder)} constructor
* whenever possible.
*
* @param accessToken initial or temporary access token
*/
@Deprecated
protected GoogleCredentials(
AccessToken accessToken, Duration refreshMargin, Duration expirationMargin) {
super(accessToken, refreshMargin, expirationMargin);
this.quotaProjectId = null;
this.universeDomain = Credentials.GOOGLE_DEFAULT_UNIVERSE;
}

/**
* A helper for overriding the toString() method. This allows inheritance of super class fields.
* Extending classes can override this implementation and call super implementation and add more
* fields. Same cannot be done with overriding the toString() directly.
*
* @return an instance of the ToStringHelper that has public fields added
*/
protected ToStringHelper toStringHelper() {
return MoreObjects.toStringHelper(this)
.omitNullValues()
.add("quotaProjectId", this.quotaProjectId)
.add("universeDomain", this.universeDomain);
}

@Override
public String toString() {
return toStringHelper().toString();
}

@Override
public boolean equals(Object obj) {
if (!(obj instanceof GoogleCredentials)) {
return false;
}
GoogleCredentials other = (GoogleCredentials) obj;
return Objects.equals(this.quotaProjectId, other.quotaProjectId)
&& Objects.equals(this.universeDomain, other.universeDomain);
}

@Override
public int hashCode() {
return Objects.hash(this.quotaProjectId, this.universeDomain);
}

public static Builder newBuilder() {
Expand Down Expand Up @@ -348,12 +449,20 @@ public GoogleCredentials createDelegated(String user) {

public static class Builder extends OAuth2Credentials.Builder {
@Nullable protected String quotaProjectId;
@Nullable protected String universeDomain;

protected Builder() {}

protected Builder(GoogleCredentials credentials) {
setAccessToken(credentials.getAccessToken());
this.quotaProjectId = credentials.quotaProjectId;
this.universeDomain = credentials.universeDomain;
}

protected Builder(GoogleCredentials.Builder builder) {
setAccessToken(builder.getAccessToken());
this.quotaProjectId = builder.quotaProjectId;
this.universeDomain = builder.universeDomain;
}

public GoogleCredentials build() {
Expand All @@ -366,10 +475,19 @@ public Builder setQuotaProjectId(String quotaProjectId) {
return this;
}

public Builder setUniverseDomain(String universeDomain) {
this.universeDomain = universeDomain;
return this;
}

public String getQuotaProjectId() {
return this.quotaProjectId;
}

public String getUniverseDomain() {
return this.universeDomain;
}

@Override
@CanIgnoreReturnValue
public Builder setAccessToken(AccessToken token) {
Expand Down
Expand Up @@ -93,6 +93,8 @@
public class ImpersonatedCredentials extends GoogleCredentials
implements ServiceAccountSigner, IdTokenProvider {

static final String IMPERSONATED_CREDENTIALS_FILE_TYPE = "impersonated_service_account";

private static final long serialVersionUID = -2133257318957488431L;
private static final String RFC3339 = "yyyy-MM-dd'T'HH:mm:ssX";
private static final int TWELVE_HOURS_IN_SECONDS = 43200;
Expand Down

0 comments on commit 7eb322e

Please sign in to comment.