diff --git a/credentials/java/com/google/auth/Credentials.java b/credentials/java/com/google/auth/Credentials.java index 416de9d5a..493b970ad 100644 --- a/credentials/java/com/google/auth/Credentials.java +++ b/credentials/java/com/google/auth/Credentials.java @@ -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. * @@ -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. * diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java index 0f87464bd..e713f7452 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java @@ -245,6 +245,7 @@ public static Builder newBuilder() { public int hashCode() { return Objects.hash( super.hashCode(), + getAccessToken(), clientId, clientSecret, refreshToken, @@ -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) diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java index 45d7dda9c..bccd943eb 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java @@ -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 @@ -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()); @@ -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); @@ -593,11 +589,6 @@ public String getWorkforcePoolUserProject() { return workforcePoolUserProject; } - @Nullable - String getUniverseDomain() { - return universeDomain; - } - @Nullable public ServiceAccountImpersonationOptions getServiceAccountImpersonationOptions() { return serviceAccountImpersonationOptions; @@ -734,7 +725,12 @@ public abstract static class Builder extends GoogleCredentials.Builder { @Nullable protected Collection 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() {} @@ -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; } @@ -928,8 +923,9 @@ public Builder setServiceAccountImpersonationOptions(Map options * @return this {@code Builder} object */ @CanIgnoreReturnValue + @Override public Builder setUniverseDomain(String universeDomain) { - this.universeDomain = universeDomain; + super.setUniverseDomain(universeDomain); return this; } diff --git a/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java b/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java index 258a347c3..531c43e1b 100644 --- a/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java @@ -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; @@ -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. */ @@ -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 = @@ -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(); } /** @@ -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); } @@ -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)); } /** @@ -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. * @@ -237,9 +274,20 @@ protected GoogleCredentials() { this(new Builder()); } + /** + * Constructor with an explicit access token and quotaProjectId. + * + *

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; } /** @@ -247,23 +295,76 @@ protected GoogleCredentials(AccessToken accessToken, String quotaProjectId) { * * @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. + * + *

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() { @@ -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() { @@ -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) { diff --git a/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java index d54d5b547..641ddc462 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java @@ -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; diff --git a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java index c83c8ff97..69361e00c 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java @@ -41,7 +41,6 @@ import com.google.api.client.http.HttpResponse; import com.google.api.client.http.HttpResponseException; import com.google.api.client.http.UrlEncodedContent; -import com.google.api.client.json.GenericJson; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.JsonObjectParser; import com.google.api.client.json.webtoken.JsonWebSignature; @@ -50,11 +49,12 @@ import com.google.api.client.util.GenericData; import com.google.api.client.util.Joiner; import com.google.api.client.util.Preconditions; +import com.google.auth.Credentials; import com.google.auth.RequestMetadataCallback; import com.google.auth.ServiceAccountSigner; import com.google.auth.http.HttpTransportFactory; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.MoreObjects; +import com.google.common.base.MoreObjects.ToStringHelper; import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.IOException; @@ -62,7 +62,6 @@ import java.io.ObjectInputStream; import java.net.URI; import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -167,6 +166,7 @@ static ServiceAccountCredentials fromJson( String projectId = (String) json.get("project_id"); String tokenServerUriStringFromCreds = (String) json.get("token_uri"); String quotaProjectId = (String) json.get("quota_project_id"); + String universeDomain = (String) json.get("universe_domain"); URI tokenServerUriFromCreds = null; try { if (tokenServerUriStringFromCreds != null) { @@ -193,7 +193,8 @@ static ServiceAccountCredentials fromJson( .setHttpTransportFactory(transportFactory) .setTokenServerUri(tokenServerUriFromCreds) .setProjectId(projectId) - .setQuotaProjectId(quotaProjectId); + .setQuotaProjectId(quotaProjectId) + .setUniverseDomain(universeDomain); return fromPkcs8(privateKeyPkcs8, builder); } @@ -463,26 +464,15 @@ public static ServiceAccountCredentials fromStream(InputStream credentialsStream */ public static ServiceAccountCredentials fromStream( InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException { - Preconditions.checkNotNull(credentialsStream); - Preconditions.checkNotNull(transportFactory); - - JsonFactory jsonFactory = OAuth2Utils.JSON_FACTORY; - JsonObjectParser parser = new JsonObjectParser(jsonFactory); - GenericJson fileContents = - parser.parseAndClose(credentialsStream, StandardCharsets.UTF_8, GenericJson.class); - - String fileType = (String) fileContents.get("type"); - if (fileType == null) { - throw new IOException("Error reading credentials from stream, 'type' field not specified."); - } - if (SERVICE_ACCOUNT_FILE_TYPE.equals(fileType)) { - return fromJson(fileContents, transportFactory); + ServiceAccountCredentials credential = + (ServiceAccountCredentials) + GoogleCredentials.fromStream(credentialsStream, transportFactory); + if (credential == null) { + throw new IOException( + String.format( + "Error reading credentials from stream, ServiceAccountCredentials type is not recognized.")); } - throw new IOException( - String.format( - "Error reading credentials from stream, 'type' value '%s' not recognized." - + " Expecting '%s'.", - fileType, SERVICE_ACCOUNT_FILE_TYPE)); + return credential; } /** Returns whether the scopes are empty, meaning createScoped must be called before use. */ @@ -491,6 +481,12 @@ public boolean createScopedRequired() { return scopes.isEmpty() && defaultScopes.isEmpty(); } + /** Returns true if credential is configured domain wide delegation */ + @VisibleForTesting + boolean isConfiguredForDomainWideDelegation() { + return serviceAccountUser != null && serviceAccountUser.length() > 0; + } + /** * Refreshes the OAuth2 access token by getting a new access token using a JSON Web Token (JWT). */ @@ -644,9 +640,10 @@ public ServiceAccountCredentials createWithCustomLifetime(int lifetime) { } /** - * Clones the service account with a new useJwtAccessWithScope value. + * Clones the service account with a new useJwtAccessWithScope value. This flag will be ignored if + * universeDomain field is different from {@link Credentials.GOOGLE_DEFAULT_UNIVERSE}. * - * @param useJwtAccessWithScope whether self signed JWT with scopes should be used + * @param useJwtAccessWithScope whether self-signed JWT with scopes should be used * @return the cloned service account credentials with the given useJwtAccessWithScope */ public ServiceAccountCredentials createWithUseJwtAccessWithScope(boolean useJwtAccessWithScope) { @@ -732,8 +729,7 @@ public byte[] sign(byte[] toSign) { /** * Returns a new JwtCredentials instance with modified claims. * - * @param newClaims new claims. Any unspecified claim fields will default to the the current - * values. + * @param newClaims new claims. Any unspecified claim fields will default to the current values. * @return new credentials */ @Override @@ -759,15 +755,15 @@ public int hashCode() { tokenServerUri, scopes, defaultScopes, - quotaProjectId, lifetime, useJwtAccessWithScope, - defaultRetriesEnabled); + defaultRetriesEnabled, + super.hashCode()); } @Override - public String toString() { - return MoreObjects.toStringHelper(this) + protected ToStringHelper toStringHelper() { + return super.toStringHelper() .add("clientId", clientId) .add("clientEmail", clientEmail) .add("privateKeyId", privateKeyId) @@ -776,11 +772,9 @@ public String toString() { .add("scopes", scopes) .add("defaultScopes", defaultScopes) .add("serviceAccountUser", serviceAccountUser) - .add("quotaProjectId", quotaProjectId) .add("lifetime", lifetime) .add("useJwtAccessWithScope", useJwtAccessWithScope) - .add("defaultRetriesEnabled", defaultRetriesEnabled) - .toString(); + .add("defaultRetriesEnabled", defaultRetriesEnabled); } @Override @@ -788,6 +782,10 @@ public boolean equals(Object obj) { if (!(obj instanceof ServiceAccountCredentials)) { return false; } + if (!super.equals(obj)) { + return false; + } + ServiceAccountCredentials other = (ServiceAccountCredentials) obj; return Objects.equals(this.clientId, other.clientId) && Objects.equals(this.clientEmail, other.clientEmail) @@ -797,7 +795,6 @@ public boolean equals(Object obj) { && Objects.equals(this.tokenServerUri, other.tokenServerUri) && Objects.equals(this.scopes, other.scopes) && Objects.equals(this.defaultScopes, other.defaultScopes) - && Objects.equals(this.quotaProjectId, other.quotaProjectId) && Objects.equals(this.lifetime, other.lifetime) && Objects.equals(this.useJwtAccessWithScope, other.useJwtAccessWithScope) && Objects.equals(this.defaultRetriesEnabled, other.defaultRetriesEnabled); @@ -866,7 +863,7 @@ String createAssertionForIdToken( } /** - * Self signed JWT uses uri as audience, which should have the "https://{host}/" format. For + * Self-signed JWT uses uri as audience, which should have the "https://{host}/" format. For * instance, if the uri is "https://compute.googleapis.com/compute/v1/projects/", then this * function returns "https://compute.googleapis.com/". */ @@ -884,7 +881,7 @@ static URI getUriForSelfSignedJWT(URI uri) { @VisibleForTesting JwtCredentials createSelfSignedJwtCredentials(final URI uri) { - // Create a JwtCredentials for self signed JWT. See https://google.aip.dev/auth/4111. + // Create a JwtCredentials for self-signed JWT. See https://google.aip.dev/auth/4111. JwtClaims.Builder claimsBuilder = JwtClaims.newBuilder().setIssuer(clientEmail).setSubject(clientEmail); @@ -912,9 +909,12 @@ JwtCredentials createSelfSignedJwtCredentials(final URI uri) { @Override public void getRequestMetadata( final URI uri, Executor executor, final RequestMetadataCallback callback) { - if (useJwtAccessWithScope) { - // This will call getRequestMetadata(URI uri), which handles self signed JWT logic. - // Self signed JWT doesn't use network, so here we do a blocking call to improve + // For default universe Self-signed JWT could be explicitly disabled with + // {@code ServiceAccountCredentials.useJwtAccessWithScope} flag. + // If universe is non-default, it only supports self-signed JWT, and it is always allowed. + if (this.useJwtAccessWithScope || !isDefaultUniverseDomain()) { + // This will call getRequestMetadata(URI uri), which handles self-signed JWT logic. + // Self-signed JWT doesn't use network, so here we do a blocking call to improve // efficiency. executor will be ignored since it is intended for async operation. blockingGetToCallback(uri, callback); } else { @@ -932,17 +932,45 @@ public Map> getRequestMetadata(URI uri) throws IOException + " providing uri to getRequestMetadata."); } - // If scopes are provided but we cannot use self signed JWT, then use scopes to get access - // token. + if (isDefaultUniverseDomain()) { + return getRequestMetadataForGdu(uri); + } else { + return getRequestMetadataForNonGdu(uri); + } + } + + private Map> getRequestMetadataForGdu(URI uri) throws IOException { + // If scopes are provided, but we cannot use self-signed JWT or domain-wide delegation is + // configured then use scopes to get access token. if ((!createScopedRequired() && !useJwtAccessWithScope) - || (serviceAccountUser != null && serviceAccountUser.length() > 0)) { + || isConfiguredForDomainWideDelegation()) { return super.getRequestMetadata(uri); } - // If scopes are provided and self signed JWT can be used, use self signed JWT with scopes. - // Otherwise, use self signed JWT with uri as the audience. + return getRequestMetadataWithSelfSignedJwt(uri); + } + + private Map> getRequestMetadataForNonGdu(URI uri) throws IOException { + // Self Signed JWT is not supported for domain-wide delegation for non-GDU universes + if (isConfiguredForDomainWideDelegation()) { + throw new IOException( + String.format( + "Service Account user is configured for the credential. " + + "Domain-wide delegation is not supported in universes different than %s.", + Credentials.GOOGLE_DEFAULT_UNIVERSE)); + } + + return getRequestMetadataWithSelfSignedJwt(uri); + } + + /** Provide the access JWT for scopes if provided, for uri as aud otherwise */ + @VisibleForTesting + private Map> getRequestMetadataWithSelfSignedJwt(URI uri) + throws IOException { + // If scopes are provided and self-signed JWT can be used, use self-signed JWT with scopes. + // Otherwise, use self-signed JWT with uri as the audience. JwtCredentials jwtCredentials; - if (!createScopedRequired() && useJwtAccessWithScope) { + if (!createScopedRequired()) { // Create selfSignedJwtCredentialsWithScope when needed and reuse it for better performance. if (selfSignedJwtCredentialsWithScope == null) { selfSignedJwtCredentialsWithScope = createSelfSignedJwtCredentials(null); @@ -952,6 +980,7 @@ public Map> getRequestMetadata(URI uri) throws IOException // Create JWT credentials with the uri as audience. jwtCredentials = createSelfSignedJwtCredentials(uri); } + Map> requestMetadata = jwtCredentials.getRequestMetadata(null); return addQuotaProjectIdToRequestMetadata(quotaProjectId, requestMetadata); } @@ -1086,6 +1115,10 @@ public Builder setLifetime(int lifetime) { return this; } + /** + * Sets the useJwtAccessWithScope flag. This flag will be ignored if universeDomain field is + * different from {@link Credentials.GOOGLE_DEFAULT_UNIVERSE}. + */ @CanIgnoreReturnValue public Builder setUseJwtAccessWithScope(boolean useJwtAccessWithScope) { this.useJwtAccessWithScope = useJwtAccessWithScope; @@ -1098,6 +1131,11 @@ public Builder setDefaultRetriesEnabled(boolean defaultRetriesEnabled) { return this; } + public Builder setUniverseDomain(String universeDomain) { + super.universeDomain = universeDomain; + return this; + } + public String getClientId() { return clientId; } diff --git a/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java b/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java index abc040f25..d4358d93b 100644 --- a/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java @@ -319,8 +319,12 @@ public void save(String filePath) throws IOException { @Override public int hashCode() { + // We include access token explicitly here for backwards compatibility. + // For the rest of the credentials we don't include it because Credentials are + // equivalent with different valid active tokens if main and parent fields are equal. return Objects.hash( super.hashCode(), + getAccessToken(), clientId, clientSecret, refreshToken, @@ -347,8 +351,10 @@ public boolean equals(Object obj) { if (!(obj instanceof UserCredentials)) { return false; } + UserCredentials other = (UserCredentials) obj; return super.equals(other) + && Objects.equals(this.getAccessToken(), other.getAccessToken()) && Objects.equals(this.clientId, other.clientId) && Objects.equals(this.clientSecret, other.clientSecret) && Objects.equals(this.refreshToken, other.refreshToken) diff --git a/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java index b6e73fcce..6a70e3138 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java @@ -760,7 +760,7 @@ public void getAwsRegion_metadataServer() throws IOException { } @Test - public void createdScoped_clonedCredentialWithAddedScopes() { + public void createdScoped_clonedCredentialWithAddedScopes() throws IOException { AwsCredentials credentials = (AwsCredentials) AwsCredentials.newBuilder(AWS_CREDENTIAL) diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java index 6d2fe38dc..372cb5380 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java @@ -66,6 +66,7 @@ public class ExternalAccountCredentialsTest extends BaseSerializationTest { private static final String STS_URL = "https://sts.googleapis.com"; + private static final String GOOGLE_DEFAULT_UNIVERSE = "googleapis.com"; private static final Map FILE_CREDENTIAL_SOURCE_MAP = new HashMap() { @@ -172,7 +173,7 @@ public void fromStream_invalidWorkloadAudience_throws() throws IOException { } @Test - public void fromJson_identityPoolCredentialsWorkload() { + public void fromJson_identityPoolCredentialsWorkload() throws IOException { ExternalAccountCredentials credential = ExternalAccountCredentials.fromJson( buildJsonIdentityPoolCredential(), OAuth2Utils.HTTP_TRANSPORT_FACTORY); @@ -185,11 +186,11 @@ public void fromJson_identityPoolCredentialsWorkload() { assertEquals(STS_URL, credential.getTokenUrl()); assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); assertNotNull(credential.getCredentialSource()); - assertNull(credential.getUniverseDomain()); + assertEquals(GOOGLE_DEFAULT_UNIVERSE, credential.getUniverseDomain()); } @Test - public void fromJson_identityPoolCredentialsWorkforce() { + public void fromJson_identityPoolCredentialsWorkforce() throws IOException { ExternalAccountCredentials credential = ExternalAccountCredentials.fromJson( buildJsonIdentityPoolWorkforceCredential(), OAuth2Utils.HTTP_TRANSPORT_FACTORY); @@ -203,11 +204,12 @@ public void fromJson_identityPoolCredentialsWorkforce() { assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); assertEquals("userProject", credential.getWorkforcePoolUserProject()); assertNotNull(credential.getCredentialSource()); - assertNull(credential.getUniverseDomain()); + assertEquals(GOOGLE_DEFAULT_UNIVERSE, credential.getUniverseDomain()); } @Test - public void fromJson_identityPoolCredentialsWithServiceAccountImpersonationOptions() { + public void fromJson_identityPoolCredentialsWithServiceAccountImpersonationOptions() + throws IOException { GenericJson identityPoolCredentialJson = buildJsonIdentityPoolCredential(); identityPoolCredentialJson.set( "service_account_impersonation", buildServiceAccountImpersonationOptions(2800)); @@ -225,11 +227,11 @@ public void fromJson_identityPoolCredentialsWithServiceAccountImpersonationOptio assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); assertNotNull(credential.getCredentialSource()); assertEquals(2800, credential.getServiceAccountImpersonationOptions().getLifetime()); - assertNull(credential.getUniverseDomain()); + assertEquals(GOOGLE_DEFAULT_UNIVERSE, credential.getUniverseDomain()); } @Test - public void fromJson_identityPoolCredentialsWithUniverseDomain() { + public void fromJson_identityPoolCredentialsWithUniverseDomain() throws IOException { GenericJson identityPoolCredentialJson = buildJsonIdentityPoolCredential(); identityPoolCredentialJson.set("universe_domain", "universeDomain"); @@ -260,7 +262,7 @@ public void fromJson_awsCredentials() throws IOException { assertEquals(STS_URL, credential.getTokenUrl()); assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); assertNotNull(credential.getCredentialSource()); - assertNull(credential.getUniverseDomain()); + assertEquals(GOOGLE_DEFAULT_UNIVERSE, credential.getUniverseDomain()); } @Test @@ -279,11 +281,11 @@ public void fromJson_awsCredentialsWithServiceAccountImpersonationOptions() thro assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); assertNotNull(credential.getCredentialSource()); assertEquals(2800, credential.getServiceAccountImpersonationOptions().getLifetime()); - assertNull(credential.getUniverseDomain()); + assertEquals(GOOGLE_DEFAULT_UNIVERSE, credential.getUniverseDomain()); } @Test - public void fromJson_awsCredentialsWithUniverseDomain() { + public void fromJson_awsCredentialsWithUniverseDomain() throws IOException { GenericJson awsCredentialJson = buildJsonAwsCredential(); awsCredentialJson.set("universe_domain", "universeDomain"); @@ -300,7 +302,7 @@ public void fromJson_awsCredentialsWithUniverseDomain() { } @Test - public void fromJson_pluggableAuthCredentials() { + public void fromJson_pluggableAuthCredentials() throws IOException { ExternalAccountCredentials credential = ExternalAccountCredentials.fromJson( buildJsonPluggableAuthCredential(), OAuth2Utils.HTTP_TRANSPORT_FACTORY); @@ -317,11 +319,11 @@ public void fromJson_pluggableAuthCredentials() { assertEquals("command", source.getCommand()); assertEquals(30000, source.getTimeoutMs()); // Default timeout is 30s. assertNull(source.getOutputFilePath()); - assertNull(credential.getUniverseDomain()); + assertEquals(GOOGLE_DEFAULT_UNIVERSE, credential.getUniverseDomain()); } @Test - public void fromJson_pluggableAuthCredentialsWorkforce() { + public void fromJson_pluggableAuthCredentialsWorkforce() throws IOException { ExternalAccountCredentials credential = ExternalAccountCredentials.fromJson( buildJsonPluggableAuthWorkforceCredential(), OAuth2Utils.HTTP_TRANSPORT_FACTORY); @@ -342,12 +344,12 @@ public void fromJson_pluggableAuthCredentialsWorkforce() { assertEquals("command", source.getCommand()); assertEquals(30000, source.getTimeoutMs()); // Default timeout is 30s. assertNull(source.getOutputFilePath()); - assertNull(credential.getUniverseDomain()); + assertEquals(GOOGLE_DEFAULT_UNIVERSE, credential.getUniverseDomain()); } @Test @SuppressWarnings("unchecked") - public void fromJson_pluggableAuthCredentials_allExecutableOptionsSet() { + public void fromJson_pluggableAuthCredentials_allExecutableOptionsSet() throws IOException { GenericJson json = buildJsonPluggableAuthCredential(); Map credentialSourceMap = (Map) json.get("credential_source"); // Add optional params to the executable config (timeout, output file path). @@ -371,11 +373,12 @@ public void fromJson_pluggableAuthCredentials_allExecutableOptionsSet() { assertEquals("command", source.getCommand()); assertEquals("path/to/output/file", source.getOutputFilePath()); assertEquals(5000, source.getTimeoutMs()); - assertNull(credential.getUniverseDomain()); + assertEquals(GOOGLE_DEFAULT_UNIVERSE, credential.getUniverseDomain()); } @Test - public void fromJson_pluggableAuthCredentialsWithServiceAccountImpersonationOptions() { + public void fromJson_pluggableAuthCredentialsWithServiceAccountImpersonationOptions() + throws IOException { GenericJson pluggableAuthCredentialJson = buildJsonPluggableAuthCredential(); pluggableAuthCredentialJson.set( "service_account_impersonation", buildServiceAccountImpersonationOptions(2800)); @@ -397,12 +400,12 @@ public void fromJson_pluggableAuthCredentialsWithServiceAccountImpersonationOpti assertEquals("command", source.getCommand()); assertEquals(30000, source.getTimeoutMs()); // Default timeout is 30s. assertNull(source.getOutputFilePath()); - assertNull(credential.getUniverseDomain()); + assertEquals(GOOGLE_DEFAULT_UNIVERSE, credential.getUniverseDomain()); } @Test @SuppressWarnings("unchecked") - public void fromJson_pluggableAuthCredentials_withUniverseDomain() { + public void fromJson_pluggableAuthCredentials_withUniverseDomain() throws IOException { GenericJson json = buildJsonPluggableAuthCredential(); json.set("universe_domain", "universeDomain"); @@ -432,7 +435,7 @@ public void fromJson_pluggableAuthCredentials_withUniverseDomain() { } @Test - public void fromJson_pluggableAuthCredentialsWithUniverseDomain() { + public void fromJson_pluggableAuthCredentialsWithUniverseDomain() throws IOException { GenericJson pluggableAuthCredentialJson = buildJsonPluggableAuthCredential(); pluggableAuthCredentialJson.set("universe_domain", "universeDomain"); @@ -456,7 +459,7 @@ public void fromJson_pluggableAuthCredentialsWithUniverseDomain() { } @Test - public void fromJson_nullJson_throws() { + public void fromJson_nullJson_throws() throws IOException { try { ExternalAccountCredentials.fromJson(/* json= */ null, OAuth2Utils.HTTP_TRANSPORT_FACTORY); fail("Exception should be thrown."); @@ -466,7 +469,7 @@ public void fromJson_nullJson_throws() { } @Test - public void fromJson_invalidServiceAccountImpersonationUrl_throws() { + public void fromJson_invalidServiceAccountImpersonationUrl_throws() throws IOException { GenericJson json = buildJsonIdentityPoolCredential(); json.put("service_account_impersonation_url", "https://iamcredentials.googleapis.com"); @@ -481,7 +484,7 @@ public void fromJson_invalidServiceAccountImpersonationUrl_throws() { } @Test - public void fromJson_nullTransport_throws() { + public void fromJson_nullTransport_throws() throws IOException { try { ExternalAccountCredentials.fromJson( new HashMap(), /* transportFactory= */ null); @@ -492,7 +495,7 @@ public void fromJson_nullTransport_throws() { } @Test - public void fromJson_invalidWorkforceAudiences_throws() { + public void fromJson_invalidWorkforceAudiences_throws() throws IOException { List invalidAudiences = Arrays.asList( "//iam.googleapis.com/locations/global/workloadIdentityPools/pool/providers/provider", @@ -521,7 +524,7 @@ public void fromJson_invalidWorkforceAudiences_throws() { } @Test - public void constructor_builder() { + public void constructor_builder() throws IOException { HashMap credentialSource = new HashMap<>(); credentialSource.put("file", "file"); diff --git a/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java index 80e28b3ec..df1ba9216 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java @@ -33,7 +33,9 @@ import static org.junit.Assert.*; +import com.google.api.client.json.GenericJson; import com.google.api.client.util.Clock; +import com.google.auth.Credentials; import com.google.auth.TestUtils; import com.google.auth.http.HttpTransportFactory; import com.google.auth.oauth2.ExternalAccountAuthorizedUserCredentialsTest.MockExternalAccountAuthorizedUserCredentialsTransportFactory; @@ -90,6 +92,8 @@ public class GoogleCredentialsTest extends BaseSerializationTest { Collections.unmodifiableCollection(Arrays.asList("scope1", "scope2")); private static final Collection DEFAULT_SCOPES = Collections.unmodifiableCollection(Arrays.asList("scope3")); + private static final String GOOGLE_DEFAULT_UNIVERSE = "googleapis.com"; + private static final String TPC_UNIVERSE = "foo.bar"; @Test public void getApplicationDefault_nullTransport_throws() throws IOException { @@ -101,6 +105,25 @@ public void getApplicationDefault_nullTransport_throws() throws IOException { } } + @Test + public void fromStream_unknownType_throws() throws IOException { + MockHttpTransportFactory transportFactory = new MockHttpTransportFactory(); + GenericJson json = new GenericJson(); + json.put("type", "unsupported_credential"); + InputStream stream = TestUtils.jsonToInputStream(json); + try { + GoogleCredentials.fromStream(stream, transportFactory); + fail("Should throw if type is unknown."); + } catch (IOException expected) { + String expectedError = + "Error reading credentials from stream, 'type' value " + + "'unsupported_credential' not recognized. Valid values are 'authorized_user', " + + "'service_account', 'gdch_service_account', 'external_account', " + + "'external_account_authorized_user', 'impersonated_service_account'."; + assertTrue(expected.getMessage().contains(expectedError)); + } + } + @Test public void fromStream_nullTransport_throws() throws IOException { InputStream stream = new ByteArrayInputStream("foo".getBytes()); @@ -112,6 +135,23 @@ public void fromStream_nullTransport_throws() throws IOException { } } + @Test + public void fromStream_noType_throws() throws IOException { + MockHttpTransportFactory transportFactory = new MockHttpTransportFactory(); + GenericJson json = + ServiceAccountCredentialsTest.writeServiceAccountJson( + "project_id", QUOTA_PROJECT, "universe"); + json.remove("type"); + InputStream stream = TestUtils.jsonToInputStream(json); + try { + GoogleCredentials.fromStream(stream, transportFactory); + fail("Should throw if type is unknown."); + } catch (IOException expected) { + String expectedError = "Error reading credentials from stream, 'type' field not specified."; + assertEquals(expectedError, expected.getMessage()); + } + } + @Test public void fromStream_nullStream_throws() throws IOException { MockHttpTransportFactory transportFactory = new MockHttpTransportFactory(); @@ -124,7 +164,7 @@ public void fromStream_nullStream_throws() throws IOException { } @Test - public void fromStream_serviceAccount_providesToken() throws IOException { + public void fromStream_serviceAccount_noUniverse_providesToken() throws IOException { MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); transportFactory.transport.addServiceAccount(SA_CLIENT_EMAIL, ACCESS_TOKEN); InputStream serviceAccountStream = @@ -135,6 +175,7 @@ public void fromStream_serviceAccount_providesToken() throws IOException { GoogleCredentials.fromStream(serviceAccountStream, transportFactory); assertNotNull(credentials); + assertEquals(GOOGLE_DEFAULT_UNIVERSE, credentials.getUniverseDomain()); credentials = credentials.createScoped(SCOPES); Map> metadata = credentials.getRequestMetadata(CALL_URI); TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN); @@ -144,6 +185,24 @@ public void fromStream_serviceAccount_providesToken() throws IOException { TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN); } + @Test + public void fromStream_serviceAccount_Universe_noToken() throws IOException { + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + transportFactory.transport.addServiceAccount(SA_CLIENT_EMAIL, ACCESS_TOKEN); + InputStream serviceAccountStream = + ServiceAccountCredentialsTest.writeServiceAccountStream( + SA_CLIENT_ID, SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID, TPC_UNIVERSE); + + GoogleCredentials credentials = + GoogleCredentials.fromStream(serviceAccountStream, transportFactory); + + assertNotNull(credentials); + assertEquals(TPC_UNIVERSE, credentials.getUniverseDomain()); + credentials = credentials.createScoped(SCOPES); + Map> metadata = credentials.getRequestMetadata(CALL_URI); + assertNotNull(((ServiceAccountCredentials) credentials).getSelfSignedJwtCredentialsWithScope()); + } + @Test public void fromStream_serviceAccountNoClientId_throws() throws IOException { InputStream serviceAccountStream = @@ -202,6 +261,7 @@ public void fromStream_gdchServiceAccount_correct() throws IOException { GDCH_SA_SERVICE_IDENTITY_NAME, ((GdchCredentials) credentials).getServiceIdentityName()); assertEquals(GDCH_SA_TOKEN_SERVER_URI, ((GdchCredentials) credentials).getTokenServerUri()); assertEquals(GDCH_SA_CA_CERT_PATH, ((GdchCredentials) credentials).getCaCertPath()); + assertEquals(Credentials.GOOGLE_DEFAULT_UNIVERSE, credentials.getUniverseDomain()); assertNull(((GdchCredentials) credentials).getApiAudience()); credentials = ((GdchCredentials) credentials).createWithGdchAudience(GDCH_API_AUDIENCE); @@ -340,7 +400,7 @@ public void fromStream_gdchServiceAccountInvalidCaCertPath_throws() throws IOExc } @Test - public void fromStream_user_providesToken() throws IOException { + public void fromStream_userCredentials_providesToken() throws IOException { MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); transportFactory.transport.addClient(USER_CLIENT_ID, USER_CLIENT_SECRET); transportFactory.transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN); @@ -356,7 +416,19 @@ public void fromStream_user_providesToken() throws IOException { } @Test - public void fromStream_userNoClientId_throws() throws IOException { + public void fromStream_userCredentials_defaultUniverse() throws IOException { + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + InputStream userStream = + UserCredentialsTest.writeUserStream( + USER_CLIENT_ID, USER_CLIENT_SECRET, REFRESH_TOKEN, null); + + GoogleCredentials credentials = GoogleCredentials.fromStream(userStream, transportFactory); + + assertEquals(Credentials.GOOGLE_DEFAULT_UNIVERSE, credentials.getUniverseDomain()); + } + + @Test + public void fromStream_userCredentials_NoClientId_throws() throws IOException { InputStream userStream = UserCredentialsTest.writeUserStream(null, USER_CLIENT_SECRET, REFRESH_TOKEN, QUOTA_PROJECT); @@ -364,7 +436,7 @@ public void fromStream_userNoClientId_throws() throws IOException { } @Test - public void fromStream_userNoClientSecret_throws() throws IOException { + public void fromStream_userCredentials_NoClientSecret_throws() throws IOException { InputStream userStream = UserCredentialsTest.writeUserStream(USER_CLIENT_ID, null, REFRESH_TOKEN, QUOTA_PROJECT); @@ -372,7 +444,7 @@ public void fromStream_userNoClientSecret_throws() throws IOException { } @Test - public void fromStream_userNoRefreshToken_throws() throws IOException { + public void fromStream_userCredentials_NoRefreshToken_throws() throws IOException { InputStream userStream = UserCredentialsTest.writeUserStream( USER_CLIENT_ID, USER_CLIENT_SECRET, null, QUOTA_PROJECT); @@ -400,6 +472,23 @@ public void fromStream_identityPoolCredentials_providesToken() throws IOExceptio TestUtils.assertContainsBearerToken(metadata, transportFactory.transport.getAccessToken()); } + @Test + public void fromStream_identityPoolCredentials_defaultUniverse() throws IOException { + MockExternalAccountCredentialsTransportFactory transportFactory = + new MockExternalAccountCredentialsTransportFactory(); + InputStream identityPoolCredentialStream = + IdentityPoolCredentialsTest.writeIdentityPoolCredentialsStream( + transportFactory.transport.getStsUrl(), + transportFactory.transport.getMetadataUrl(), + /* serviceAccountImpersonationUrl= */ null, + /* serviceAccountImpersonationOptionsMap= */ null); + + GoogleCredentials credentials = + GoogleCredentials.fromStream(identityPoolCredentialStream, transportFactory); + + assertEquals(Credentials.GOOGLE_DEFAULT_UNIVERSE, credentials.getUniverseDomain()); + } + @Test public void fromStream_awsCredentials_providesToken() throws IOException { MockExternalAccountCredentialsTransportFactory transportFactory = @@ -420,6 +509,23 @@ public void fromStream_awsCredentials_providesToken() throws IOException { TestUtils.assertContainsBearerToken(metadata, transportFactory.transport.getAccessToken()); } + @Test + public void fromStream_awsCredentials_defaultUniverse() throws IOException { + MockExternalAccountCredentialsTransportFactory transportFactory = + new MockExternalAccountCredentialsTransportFactory(); + + InputStream awsCredentialStream = + AwsCredentialsTest.writeAwsCredentialsStream( + transportFactory.transport.getStsUrl(), + transportFactory.transport.getAwsRegionUrl(), + transportFactory.transport.getAwsCredentialsUrl()); + + GoogleCredentials credentials = + GoogleCredentials.fromStream(awsCredentialStream, transportFactory); + + assertEquals(Credentials.GOOGLE_DEFAULT_UNIVERSE, credentials.getUniverseDomain()); + } + @Test public void fromStream_pluggableAuthCredentials_providesToken() throws IOException { MockExternalAccountCredentialsTransportFactory transportFactory = @@ -443,6 +549,19 @@ public void fromStream_pluggableAuthCredentials_providesToken() throws IOExcepti TestUtils.assertContainsBearerToken(metadata, transportFactory.transport.getAccessToken()); } + @Test + public void fromStream_pluggableAuthCredentials_defaultUniverse() throws IOException { + MockExternalAccountCredentialsTransportFactory transportFactory = + new MockExternalAccountCredentialsTransportFactory(); + + InputStream stream = + PluggableAuthCredentialsTest.writeCredentialsStream(transportFactory.transport.getStsUrl()); + + GoogleCredentials credentials = GoogleCredentials.fromStream(stream, transportFactory); + + assertEquals(Credentials.GOOGLE_DEFAULT_UNIVERSE, credentials.getUniverseDomain()); + } + @Test public void fromStream_externalAccountAuthorizedUserCredentials_providesToken() throws IOException { @@ -458,6 +577,20 @@ public void fromStream_externalAccountAuthorizedUserCredentials_providesToken() TestUtils.assertContainsBearerToken(metadata, transportFactory.transport.getAccessToken()); } + @Test + public void fromStream_externalAccountAuthorizedUserCredentials_defaultUniverse() + throws IOException { + MockExternalAccountAuthorizedUserCredentialsTransportFactory transportFactory = + new MockExternalAccountAuthorizedUserCredentialsTransportFactory(); + InputStream stream = + TestUtils.jsonToInputStream( + ExternalAccountAuthorizedUserCredentialsTest.buildJsonCredentials()); + + GoogleCredentials credentials = GoogleCredentials.fromStream(stream, transportFactory); + + assertEquals(Credentials.GOOGLE_DEFAULT_UNIVERSE, credentials.getUniverseDomain()); + } + @Test public void fromStream_Impersonation_providesToken_WithQuotaProject() throws IOException { MockTokenServerTransportFactory transportFactoryForSource = @@ -494,6 +627,30 @@ public void fromStream_Impersonation_providesToken_WithQuotaProject() throws IOE assertEquals(ImpersonatedCredentialsTest.QUOTA_PROJECT_ID, headerValues.get(0)); } + @Test + public void fromStream_Impersonation_defaultUniverse() throws IOException { + MockTokenServerTransportFactory transportFactoryForSource = + new MockTokenServerTransportFactory(); + transportFactoryForSource.transport.addServiceAccount( + ImpersonatedCredentialsTest.SA_CLIENT_EMAIL, ImpersonatedCredentialsTest.ACCESS_TOKEN); + + MockIAMCredentialsServiceTransportFactory transportFactory = + new MockIAMCredentialsServiceTransportFactory(); + + InputStream impersonationCredentialsStream = + ImpersonatedCredentialsTest.writeImpersonationCredentialsStream( + ImpersonatedCredentialsTest.IMPERSONATION_URL, + ImpersonatedCredentialsTest.DELEGATES, + ImpersonatedCredentialsTest.QUOTA_PROJECT_ID); + + ImpersonatedCredentials credentials = + (ImpersonatedCredentials) + GoogleCredentials.fromStream(impersonationCredentialsStream, transportFactoryForSource); + credentials.setTransportFactory(transportFactory); + + assertEquals(Credentials.GOOGLE_DEFAULT_UNIVERSE, credentials.getUniverseDomain()); + } + @Test public void fromStream_Impersonation_providesToken_WithoutQuotaProject() throws IOException { MockTokenServerTransportFactory transportFactoryForSource = @@ -563,6 +720,22 @@ public void createWithQuotaProject() { assertEquals(null, sameCredentials.getQuotaProjectId()); } + @Test + public void buildWithUniverseDomain() throws IOException { + final GoogleCredentials original = + new GoogleCredentials.Builder().setUniverseDomain("universe1").build(); + GoogleCredentials updated = original.toBuilder().setUniverseDomain("universe2").build(); + + assertEquals("universe1", original.getUniverseDomain()); + assertEquals("universe2", updated.getUniverseDomain()); + + GoogleCredentials withEmpty = original.toBuilder().setUniverseDomain("").build(); + assertEquals(GOOGLE_DEFAULT_UNIVERSE, withEmpty.getUniverseDomain()); + + GoogleCredentials withNull = original.toBuilder().setUniverseDomain(null).build(); + assertEquals(GOOGLE_DEFAULT_UNIVERSE, withNull.getUniverseDomain()); + } + @Test public void serialize() throws IOException, ClassNotFoundException { final GoogleCredentials testCredentials = new GoogleCredentials.Builder().build(); diff --git a/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java index 387430d42..7bb64ab1e 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java @@ -94,7 +94,7 @@ public HttpTransport create() { } @Test - public void createdScoped_clonedCredentialWithAddedScopes() { + public void createdScoped_clonedCredentialWithAddedScopes() throws IOException { IdentityPoolCredentials credentials = (IdentityPoolCredentials) IdentityPoolCredentials.newBuilder(FILE_SOURCED_CREDENTIAL) diff --git a/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java index 395fe6cd6..3ffa2c24f 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java @@ -437,7 +437,7 @@ public void builder_allFields() { } @Test - public void createdScoped_clonedCredentialWithAddedScopes() { + public void createdScoped_clonedCredentialWithAddedScopes() throws IOException { PluggableAuthCredentials credentials = (PluggableAuthCredentials) PluggableAuthCredentials.newBuilder(CREDENTIAL) diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java index 6da93d409..174db6e76 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java @@ -51,14 +51,17 @@ import com.google.api.client.testing.http.MockLowLevelHttpResponse; import com.google.api.client.util.Clock; import com.google.api.client.util.Joiner; +import com.google.auth.Credentials; import com.google.auth.RequestMetadataCallback; import com.google.auth.TestUtils; import com.google.auth.http.AuthHttpConstants; import com.google.auth.http.HttpTransportFactory; import com.google.common.collect.ImmutableSet; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.ObjectOutputStream; import java.net.URI; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -120,17 +123,37 @@ public class ServiceAccountCredentialsTest extends BaseSerializationTest { private static final int DEFAULT_LIFETIME_IN_SECONDS = 3600; private static final int INVALID_LIFETIME = 43210; private static final String JWT_ACCESS_PREFIX = "Bearer "; + private static final String GOOGLE_DEFAULT_UNIVERSE = "googleapis.com"; + + private ServiceAccountCredentials.Builder createDefaultBuilderWithToken(String accessToken) + throws IOException { + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + transportFactory.transport.addServiceAccount(CLIENT_EMAIL, accessToken); + return createDefaultBuilder().setHttpTransportFactory(transportFactory); + } + + private ServiceAccountCredentials.Builder createDefaultBuilderWithScopes( + Collection scopes) throws IOException { + return createDefaultBuilder().setScopes(scopes); + } + + private ServiceAccountCredentials.Builder createDefaultBuilderWithKey(PrivateKey privateKey) { + ServiceAccountCredentials.Builder builder = + ServiceAccountCredentials.newBuilder() + .setClientId(CLIENT_ID) + .setClientEmail(CLIENT_EMAIL) + .setPrivateKey(privateKey) + .setPrivateKeyId(PRIVATE_KEY_ID) + .setProjectId(PROJECT_ID) + .setQuotaProjectId(QUOTA_PROJECT) + .setHttpTransportFactory(new MockHttpTransportFactory()); + + return builder; + } private ServiceAccountCredentials.Builder createDefaultBuilder() throws IOException { PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); - return ServiceAccountCredentials.newBuilder() - .setClientId(CLIENT_ID) - .setClientEmail(CLIENT_EMAIL) - .setPrivateKey(privateKey) - .setPrivateKeyId(PRIVATE_KEY_ID) - .setScopes(SCOPES) - .setServiceAccountUser(USER) - .setProjectId(PROJECT_ID); + return createDefaultBuilderWithKey(privateKey); } @Test @@ -170,15 +193,10 @@ public void createWithCustomLifetime() throws IOException { @Test public void createdScoped_clones() throws IOException { PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); - GoogleCredentials credentials = - ServiceAccountCredentials.newBuilder() - .setClientId(CLIENT_ID) - .setClientEmail(CLIENT_EMAIL) - .setPrivateKey(privateKey) - .setPrivateKeyId(PRIVATE_KEY_ID) - .setScopes(SCOPES) + ServiceAccountCredentials credentials = + createDefaultBuilderWithKey(privateKey) .setServiceAccountUser(USER) - .setProjectId(PROJECT_ID) + .setScopes(SCOPES) .build(); List newScopes = Arrays.asList("scope1", "scope2"); @@ -192,6 +210,7 @@ public void createdScoped_clones() throws IOException { assertArrayEquals(newScopes.toArray(), newCredentials.getScopes().toArray()); assertEquals(USER, newCredentials.getServiceAccountUser()); assertEquals(PROJECT_ID, newCredentials.getProjectId()); + assertEquals(GOOGLE_DEFAULT_UNIVERSE, newCredentials.getUniverseDomain()); assertArrayEquals( SCOPES.toArray(), ((ServiceAccountCredentials) credentials).getScopes().toArray()); @@ -201,15 +220,9 @@ public void createdScoped_clones() throws IOException { public void createdDelegated_clones() throws IOException { PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); ServiceAccountCredentials credentials = - ServiceAccountCredentials.newBuilder() - .setClientId(CLIENT_ID) - .setClientEmail(CLIENT_EMAIL) - .setPrivateKey(privateKey) - .setPrivateKeyId(PRIVATE_KEY_ID) + createDefaultBuilderWithKey(privateKey) .setScopes(SCOPES) .setServiceAccountUser(USER) - .setProjectId(PROJECT_ID) - .setQuotaProjectId(QUOTA_PROJECT) .build(); String newServiceAccountUser = "stranger@other.org"; @@ -230,18 +243,9 @@ public void createdDelegated_clones() throws IOException { @Test public void createAssertion_correct() throws IOException { - PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); List scopes = Arrays.asList("scope1", "scope2"); - ServiceAccountCredentials credentials = - ServiceAccountCredentials.newBuilder() - .setClientId(CLIENT_ID) - .setClientEmail(CLIENT_EMAIL) - .setPrivateKey(privateKey) - .setPrivateKeyId(PRIVATE_KEY_ID) - .setScopes(scopes) - .setServiceAccountUser(USER) - .setProjectId(PROJECT_ID) - .build(); + ServiceAccountCredentials.Builder builder = createDefaultBuilderWithScopes(scopes); + ServiceAccountCredentials credentials = builder.setServiceAccountUser(USER).build(); JsonFactory jsonFactory = OAuth2Utils.JSON_FACTORY; long currentTimeMillis = Clock.SYSTEM.currentTimeMillis(); @@ -259,17 +263,10 @@ public void createAssertion_correct() throws IOException { @Test public void createAssertion_defaultScopes_correct() throws IOException { - PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); - List scopes = Arrays.asList("scope1", "scope2"); - ServiceAccountCredentials.Builder builder = - ServiceAccountCredentials.newBuilder() - .setClientId(CLIENT_ID) - .setClientEmail(CLIENT_EMAIL) - .setPrivateKey(privateKey) - .setPrivateKeyId(PRIVATE_KEY_ID) - .setScopes(null, scopes) - .setServiceAccountUser(USER) - .setProjectId(PROJECT_ID); + List defaultScopes = Arrays.asList("scope1", "scope2"); + ServiceAccountCredentials.Builder builder = createDefaultBuilder(); + builder.setScopes(null, defaultScopes).setServiceAccountUser(USER); + assertEquals(2, builder.getDefaultScopes().size()); ServiceAccountCredentials credentials = builder.build(); @@ -284,7 +281,7 @@ public void createAssertion_defaultScopes_correct() throws IOException { assertEquals(currentTimeMillis / 1000, (long) payload.getIssuedAtTimeSeconds()); assertEquals(currentTimeMillis / 1000 + 3600, (long) payload.getExpirationTimeSeconds()); assertEquals(USER, payload.getSubject()); - assertEquals(Joiner.on(' ').join(scopes), payload.get("scope")); + assertEquals(Joiner.on(' ').join(defaultScopes), payload.get("scope")); } @Test @@ -302,13 +299,9 @@ public void createAssertion_custom_lifetime() throws IOException { @Test public void createAssertionForIdToken_correct() throws IOException { - PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); ServiceAccountCredentials credentials = - ServiceAccountCredentials.newBuilder() - .setClientId(CLIENT_ID) - .setClientEmail(CLIENT_EMAIL) - .setPrivateKey(privateKey) + createDefaultBuilder() .setPrivateKeyId(PRIVATE_KEY_ID) .setServiceAccountUser(USER) .setProjectId(PROJECT_ID) @@ -331,7 +324,6 @@ public void createAssertionForIdToken_correct() throws IOException { @Test public void createAssertionForIdToken_custom_lifetime() throws IOException { - ServiceAccountCredentials credentials = createDefaultBuilder().setLifetime(4000).build(); JsonFactory jsonFactory = OAuth2Utils.JSON_FACTORY; @@ -347,7 +339,6 @@ public void createAssertionForIdToken_custom_lifetime() throws IOException { @Test public void createAssertionForIdToken_incorrect() throws IOException { - PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); ServiceAccountCredentials credentials = ServiceAccountCredentials.newBuilder() @@ -376,19 +367,11 @@ public void createAssertionForIdToken_incorrect() throws IOException { } @Test - public void createdScoped_enablesAccessTokens() throws IOException { - MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); - transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN); - GoogleCredentials credentials = - ServiceAccountCredentials.fromPkcs8( - CLIENT_ID, - CLIENT_EMAIL, - PRIVATE_KEY_PKCS8, - PRIVATE_KEY_ID, - null, - transportFactory, - null); + public void createdScoped_withAud_noUniverse_jwtWithScopesDisabled_accessToken() + throws IOException { + GoogleCredentials credentials = createDefaultBuilderWithToken(ACCESS_TOKEN).build(); + // No aud, no scopes gives an exception. try { credentials.getRequestMetadata(null); fail("Should not be able to get token without scopes"); @@ -403,12 +386,65 @@ public void createdScoped_enablesAccessTokens() throws IOException { TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN); } + @Test + public void createdScoped_withUniverse_selfSignedJwt() throws IOException { + ServiceAccountCredentials credentials = + createDefaultBuilder().setUniverseDomain("foo.bar").build(); + + try { + credentials.getRequestMetadata(null); + fail("Should not be able to get token without scopes"); + } catch (IOException e) { + assertTrue( + "expected to fail with exception", + e.getMessage().contains("Scopes and uri are not configured for service account")); + } + + GoogleCredentials scopedCredentials = credentials.createScoped("dummy.scope"); + Map> metadata = scopedCredentials.getRequestMetadata(null); + verifyJwtAccess(metadata, "dummy.scope"); + + // Recreate to avoid jwt caching. + scopedCredentials = credentials.createScoped("dummy.scope2"); + metadata = scopedCredentials.getRequestMetadata(CALL_URI); + verifyJwtAccess(metadata, "dummy.scope2"); + + // Recreate to avoid jwt caching. + scopedCredentials = + credentials.createScoped( + Collections.emptyList(), Arrays.asList("dummy.default.scope")); + metadata = scopedCredentials.getRequestMetadata(null); + verifyJwtAccess(metadata, "dummy.default.scope"); + + // Recreate to avoid jwt caching. + scopedCredentials = + credentials.createScoped( + Collections.emptyList(), Arrays.asList("dummy.default.scope2")); + metadata = scopedCredentials.getRequestMetadata(CALL_URI); + verifyJwtAccess(metadata, "dummy.default.scope2"); + } + + @Test + public void noScopes_withUniverse_selfSignedJwt() throws IOException { + GoogleCredentials credentials = createDefaultBuilder().setUniverseDomain("foo.bar").build(); + + try { + credentials.getRequestMetadata(null); + fail("Should not be able to get token without scopes"); + } catch (IOException e) { + assertTrue( + "expected to fail with exception", + e.getMessage().contains("Scopes and uri are not configured for service account")); + } + + Map> metadata = credentials.getRequestMetadata(CALL_URI); + assertNull(((ServiceAccountCredentials) credentials).getSelfSignedJwtCredentialsWithScope()); + verifyJwtAccess(metadata, null); + } + @Test public void createdScoped_defaultScopes() throws IOException { - final URI TOKEN_SERVER = URI.create("https://foo.com/bar"); MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); - transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN); - transportFactory.transport.setTokenServerUri(TOKEN_SERVER); ServiceAccountCredentials credentials = ServiceAccountCredentials.fromPkcs8( @@ -425,7 +461,7 @@ public void createdScoped_defaultScopes() throws IOException { SCOPES, DEFAULT_SCOPES, transportFactory, - TOKEN_SERVER); + null); assertEquals(1, credentials.getDefaultScopes().size()); assertEquals("dummy.default.scope", credentials.getDefaultScopes().toArray()[0]); @@ -438,7 +474,7 @@ public void createdScoped_defaultScopes() throws IOException { SCOPES, DEFAULT_SCOPES, transportFactory, - TOKEN_SERVER, + null, "service_account_user"); assertEquals(1, credentials.getDefaultScopes().size()); assertEquals("dummy.default.scope", credentials.getDefaultScopes().toArray()[0]); @@ -473,24 +509,28 @@ public void createScopedRequired_nonEmptyDefaultScopes() throws IOException { @Test public void fromJSON_getProjectId() throws IOException { - MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); - transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN); - GenericJson json = - writeServiceAccountJson( - CLIENT_ID, CLIENT_EMAIL, PRIVATE_KEY_PKCS8, PRIVATE_KEY_ID, PROJECT_ID, null); + GenericJson json = writeServiceAccountJson(PROJECT_ID, null, null); ServiceAccountCredentials credentials = - ServiceAccountCredentials.fromJson(json, transportFactory); + ServiceAccountCredentials.fromJson(json, new MockTokenServerTransportFactory()); assertEquals(PROJECT_ID, credentials.getProjectId()); + assertEquals(GOOGLE_DEFAULT_UNIVERSE, credentials.getUniverseDomain()); + } + + @Test + public void fromJSON_Universe_getUniverseDomain() throws IOException { + GenericJson json = writeServiceAccountJson(PROJECT_ID, null, "foo.bar"); + + ServiceAccountCredentials credentials = + ServiceAccountCredentials.fromJson(json, new MockTokenServerTransportFactory()); + assertEquals("foo.bar", credentials.getUniverseDomain()); } @Test public void fromJSON_getProjectIdNull() throws IOException { MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN); - GenericJson json = - writeServiceAccountJson( - CLIENT_ID, CLIENT_EMAIL, PRIVATE_KEY_PKCS8, PRIVATE_KEY_ID, null, null); + GenericJson json = writeServiceAccountJson(null, null, null); ServiceAccountCredentials credentials = ServiceAccountCredentials.fromJson(json, transportFactory); @@ -501,9 +541,7 @@ public void fromJSON_getProjectIdNull() throws IOException { public void fromJSON_hasAccessToken() throws IOException { MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN); - GenericJson json = - writeServiceAccountJson( - CLIENT_ID, CLIENT_EMAIL, PRIVATE_KEY_PKCS8, PRIVATE_KEY_ID, PROJECT_ID, null); + GenericJson json = writeServiceAccountJson(PROJECT_ID, null, null); GoogleCredentials credentials = ServiceAccountCredentials.fromJson(json, transportFactory); @@ -512,14 +550,25 @@ public void fromJSON_hasAccessToken() throws IOException { TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN); } + @Test + public void fromJSON_withUniverse_selfSignedJwt() throws IOException { + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN); + GenericJson json = writeServiceAccountJson(PROJECT_ID, null, "foo.bar"); + + GoogleCredentials credentials = ServiceAccountCredentials.fromJson(json, transportFactory); + + credentials = credentials.createScoped(SCOPES); + Map> metadata = credentials.getRequestMetadata(null); + verifyJwtAccess(metadata, "dummy.scope"); + } + @Test public void fromJSON_tokenServerUri() throws IOException { final String tokenServerUri = "https://foo.com/bar"; MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN); - GenericJson json = - writeServiceAccountJson( - CLIENT_ID, CLIENT_EMAIL, PRIVATE_KEY_PKCS8, PRIVATE_KEY_ID, PROJECT_ID, null); + GenericJson json = writeServiceAccountJson(PROJECT_ID, null, null); json.put("token_uri", tokenServerUri); ServiceAccountCredentials credentials = ServiceAccountCredentials.fromJson(json, transportFactory); @@ -530,11 +579,10 @@ public void fromJSON_tokenServerUri() throws IOException { public void fromJson_hasQuotaProjectId() throws IOException { MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN); - GenericJson json = - writeServiceAccountJson( - CLIENT_ID, CLIENT_EMAIL, PRIVATE_KEY_PKCS8, PRIVATE_KEY_ID, PROJECT_ID, QUOTA_PROJECT); + GenericJson json = writeServiceAccountJson(PROJECT_ID, QUOTA_PROJECT, null); GoogleCredentials credentials = ServiceAccountCredentials.fromJson(json, transportFactory); credentials = credentials.createScoped(SCOPES); + Map> metadata = credentials.getRequestMetadata(CALL_URI); assertTrue(metadata.containsKey(GoogleCredentials.QUOTA_PROJECT_ID_HEADER_KEY)); @@ -545,20 +593,9 @@ public void fromJson_hasQuotaProjectId() throws IOException { @Test public void getRequestMetadata_hasAccessToken() throws IOException { - MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); - transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN); - OAuth2Credentials credentials = - ServiceAccountCredentials.fromPkcs8( - CLIENT_ID, - CLIENT_EMAIL, - PRIVATE_KEY_PKCS8, - PRIVATE_KEY_ID, - SCOPES, - transportFactory, - null); - + GoogleCredentials credentials = + createDefaultBuilderWithToken(ACCESS_TOKEN).setScopes(SCOPES).build(); Map> metadata = credentials.getRequestMetadata(CALL_URI); - TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN); } @@ -569,20 +606,22 @@ public void getRequestMetadata_customTokenServer_hasAccessToken() throws IOExcep transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN); transportFactory.transport.setTokenServerUri(TOKEN_SERVER); OAuth2Credentials credentials = - ServiceAccountCredentials.fromPkcs8( - CLIENT_ID, - CLIENT_EMAIL, - PRIVATE_KEY_PKCS8, - PRIVATE_KEY_ID, - SCOPES, - transportFactory, - TOKEN_SERVER); - + createDefaultBuilder() + .setScopes(SCOPES) + .setHttpTransportFactory(transportFactory) + .setTokenServerUri(TOKEN_SERVER) + .build(); Map> metadata = credentials.getRequestMetadata(CALL_URI); TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN); } + @Test + public void getUniverseDomain_defaultUniverse() throws IOException { + ServiceAccountCredentials credentials = createDefaultBuilder().build(); + assertEquals(GOOGLE_DEFAULT_UNIVERSE, credentials.getUniverseDomain()); + } + @Test public void refreshAccessToken_refreshesToken() throws IOException { final String accessToken1 = "1/MkSJoj1xsli0AccessToken_NKPY2"; @@ -590,15 +629,7 @@ public void refreshAccessToken_refreshesToken() throws IOException { MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); MockTokenServerTransport transport = transportFactory.transport; ServiceAccountCredentials credentials = - ServiceAccountCredentials.fromPkcs8( - CLIENT_ID, - CLIENT_EMAIL, - PRIVATE_KEY_PKCS8, - PRIVATE_KEY_ID, - SCOPES, - transportFactory, - null); - + createDefaultBuilder().setScopes(SCOPES).setHttpTransportFactory(transportFactory).build(); transport.addServiceAccount(CLIENT_EMAIL, accessToken1); TestUtils.assertContainsBearerToken(credentials.getRequestMetadata(CALL_URI), accessToken1); @@ -609,29 +640,21 @@ public void refreshAccessToken_refreshesToken() throws IOException { @Test public void refreshAccessToken_tokenExpiry() throws IOException { - final String tokenString = "1/MkSJoj1xsli0AccessToken_NKPY2"; MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); MockTokenServerTransport transport = transportFactory.transport; + transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN); ServiceAccountCredentials credentials = - ServiceAccountCredentials.fromPkcs8( - CLIENT_ID, - CLIENT_EMAIL, - PRIVATE_KEY_PKCS8, - PRIVATE_KEY_ID, - SCOPES, - transportFactory, - null); + createDefaultBuilder().setScopes(SCOPES).setHttpTransportFactory(transportFactory).build(); credentials.clock = new FixedClock(0L); - transport.addServiceAccount(CLIENT_EMAIL, tokenString); AccessToken accessToken = credentials.refreshAccessToken(); - assertEquals(tokenString, accessToken.getTokenValue()); + assertEquals(ACCESS_TOKEN, accessToken.getTokenValue()); assertEquals(3600 * 1000L, accessToken.getExpirationTimeMillis().longValue()); // Test for large expires_in values (should not overflow). transport.setExpiresInSeconds(3600 * 1000); accessToken = credentials.refreshAccessToken(); - assertEquals(tokenString, accessToken.getTokenValue()); + assertEquals(ACCESS_TOKEN, accessToken.getTokenValue()); assertEquals(3600 * 1000 * 1000L, accessToken.getExpirationTimeMillis().longValue()); } @@ -642,14 +665,8 @@ public void refreshAccessToken_IOException_Retry() throws IOException { MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); MockTokenServerTransport transport = transportFactory.transport; ServiceAccountCredentials credentials = - ServiceAccountCredentials.fromPkcs8( - CLIENT_ID, - CLIENT_EMAIL, - PRIVATE_KEY_PKCS8, - PRIVATE_KEY_ID, - SCOPES, - transportFactory, - null); + createDefaultBuilder().setScopes(SCOPES).setHttpTransportFactory(transportFactory).build(); + ; transport.addServiceAccount(CLIENT_EMAIL, accessToken1); TestUtils.assertContainsBearerToken(credentials.getRequestMetadata(CALL_URI), accessToken1); @@ -667,14 +684,7 @@ public void refreshAccessToken_retriesServerErrors() throws IOException { MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); MockTokenServerTransport transport = transportFactory.transport; ServiceAccountCredentials credentials = - ServiceAccountCredentials.fromPkcs8( - CLIENT_ID, - CLIENT_EMAIL, - PRIVATE_KEY_PKCS8, - PRIVATE_KEY_ID, - SCOPES, - transportFactory, - null); + createDefaultBuilder().setScopes(SCOPES).setHttpTransportFactory(transportFactory).build(); transport.addServiceAccount(CLIENT_EMAIL, accessToken1); TestUtils.assertContainsBearerToken(credentials.getRequestMetadata(CALL_URI), accessToken1); @@ -694,14 +704,7 @@ public void refreshAccessToken_retriesTimeoutAndThrottled() throws IOException { MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); MockTokenServerTransport transport = transportFactory.transport; ServiceAccountCredentials credentials = - ServiceAccountCredentials.fromPkcs8( - CLIENT_ID, - CLIENT_EMAIL, - PRIVATE_KEY_PKCS8, - PRIVATE_KEY_ID, - SCOPES, - transportFactory, - null); + createDefaultBuilder().setScopes(SCOPES).setHttpTransportFactory(transportFactory).build(); transport.addServiceAccount(CLIENT_EMAIL, accessToken1); TestUtils.assertContainsBearerToken(credentials.getRequestMetadata(CALL_URI), accessToken1); @@ -721,14 +724,10 @@ public void refreshAccessToken_defaultRetriesDisabled() throws IOException { MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); MockTokenServerTransport transport = transportFactory.transport; ServiceAccountCredentials credentials = - ServiceAccountCredentials.fromPkcs8( - CLIENT_ID, - CLIENT_EMAIL, - PRIVATE_KEY_PKCS8, - PRIVATE_KEY_ID, - SCOPES, - transportFactory, - null) + createDefaultBuilder() + .setScopes(SCOPES) + .setHttpTransportFactory(transportFactory) + .build() .createWithCustomRetryStrategy(false); transport.addServiceAccount(CLIENT_EMAIL, accessToken1); @@ -751,21 +750,13 @@ public void refreshAccessToken_defaultRetriesDisabled() throws IOException { @Test public void refreshAccessToken_maxRetries_maxDelay() throws IOException { - final String accessToken1 = "1/MkSJoj1xsli0AccessToken_NKPY2"; MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); MockTokenServerTransport transport = transportFactory.transport; ServiceAccountCredentials credentials = - ServiceAccountCredentials.fromPkcs8( - CLIENT_ID, - CLIENT_EMAIL, - PRIVATE_KEY_PKCS8, - PRIVATE_KEY_ID, - SCOPES, - transportFactory, - null); + createDefaultBuilder().setScopes(SCOPES).setHttpTransportFactory(transportFactory).build(); - transport.addServiceAccount(CLIENT_EMAIL, accessToken1); - TestUtils.assertContainsBearerToken(credentials.getRequestMetadata(CALL_URI), accessToken1); + transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN); + TestUtils.assertContainsBearerToken(credentials.getRequestMetadata(CALL_URI), ACCESS_TOKEN); MockLowLevelHttpResponse response408 = new MockLowLevelHttpResponse().setStatusCode(408); MockLowLevelHttpResponse response429 = new MockLowLevelHttpResponse().setStatusCode(429); @@ -792,21 +783,13 @@ public void refreshAccessToken_maxRetries_maxDelay() throws IOException { @Test public void refreshAccessToken_RequestFailure_retried() throws IOException { - final String accessToken1 = "1/MkSJoj1xsli0AccessToken_NKPY2"; MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); MockTokenServerTransport transport = transportFactory.transport; ServiceAccountCredentials credentials = - ServiceAccountCredentials.fromPkcs8( - CLIENT_ID, - CLIENT_EMAIL, - PRIVATE_KEY_PKCS8, - PRIVATE_KEY_ID, - SCOPES, - transportFactory, - null); + createDefaultBuilder().setScopes(SCOPES).setHttpTransportFactory(transportFactory).build(); - transport.addServiceAccount(CLIENT_EMAIL, accessToken1); - TestUtils.assertContainsBearerToken(credentials.getRequestMetadata(CALL_URI), accessToken1); + transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN); + TestUtils.assertContainsBearerToken(credentials.getRequestMetadata(CALL_URI), ACCESS_TOKEN); IOException error = new IOException("Invalid grant: Account not found"); MockLowLevelHttpResponse response503 = new MockLowLevelHttpResponse().setStatusCode(503); @@ -839,14 +822,7 @@ public void refreshAccessToken_4xx_5xx_NonRetryableFails() throws IOException { MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); MockTokenServerTransport transport = transportFactory.transport; ServiceAccountCredentials credentials = - ServiceAccountCredentials.fromPkcs8( - CLIENT_ID, - CLIENT_EMAIL, - PRIVATE_KEY_PKCS8, - PRIVATE_KEY_ID, - SCOPES, - transportFactory, - null); + createDefaultBuilder().setScopes(SCOPES).setHttpTransportFactory(transportFactory).build(); transport.addServiceAccount(CLIENT_EMAIL, accessToken1); TestUtils.assertContainsBearerToken(credentials.getRequestMetadata(CALL_URI), accessToken1); @@ -875,14 +851,7 @@ public void idTokenWithAudience_correct() throws IOException { MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); MockTokenServerTransport transport = transportFactory.transport; ServiceAccountCredentials credentials = - ServiceAccountCredentials.fromPkcs8( - CLIENT_ID, - CLIENT_EMAIL, - PRIVATE_KEY_PKCS8, - PRIVATE_KEY_ID, - SCOPES, - transportFactory, - null); + createDefaultBuilder().setScopes(SCOPES).setHttpTransportFactory(transportFactory).build(); transport.addServiceAccount(CLIENT_EMAIL, accessToken1); TestUtils.assertContainsBearerToken(credentials.getRequestMetadata(CALL_URI), accessToken1); @@ -907,14 +876,7 @@ public void idTokenWithAudience_incorrect() throws IOException { MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); MockTokenServerTransport transport = transportFactory.transport; ServiceAccountCredentials credentials = - ServiceAccountCredentials.fromPkcs8( - CLIENT_ID, - CLIENT_EMAIL, - PRIVATE_KEY_PKCS8, - PRIVATE_KEY_ID, - SCOPES, - transportFactory, - null); + createDefaultBuilder().setScopes(SCOPES).setHttpTransportFactory(transportFactory).build(); transport.addServiceAccount(CLIENT_EMAIL, accessToken1); TestUtils.assertContainsBearerToken(credentials.getRequestMetadata(CALL_URI), accessToken1); @@ -933,10 +895,7 @@ public void idTokenWithAudience_incorrect() throws IOException { @Test public void getScopes_nullReturnsEmpty() throws IOException { - ServiceAccountCredentials credentials = - ServiceAccountCredentials.fromPkcs8( - CLIENT_ID, CLIENT_EMAIL, PRIVATE_KEY_PKCS8, PRIVATE_KEY_ID, null); - + ServiceAccountCredentials credentials = createDefaultBuilder().build(); Collection scopes = credentials.getScopes(); assertNotNull(scopes); @@ -945,9 +904,7 @@ public void getScopes_nullReturnsEmpty() throws IOException { @Test public void getAccount_sameAs() throws IOException { - ServiceAccountCredentials credentials = - ServiceAccountCredentials.fromPkcs8( - CLIENT_ID, CLIENT_EMAIL, PRIVATE_KEY_PKCS8, PRIVATE_KEY_ID, null); + ServiceAccountCredentials credentials = createDefaultBuilder().build(); assertEquals(CLIENT_EMAIL, credentials.getAccount()); } @@ -955,13 +912,13 @@ public void getAccount_sameAs() throws IOException { public void sign_sameAs() throws IOException, NoSuchAlgorithmException, InvalidKeyException, SignatureException { byte[] toSign = {0xD, 0xE, 0xA, 0xD}; - ServiceAccountCredentials credentials = - ServiceAccountCredentials.fromPkcs8( - CLIENT_ID, CLIENT_EMAIL, PRIVATE_KEY_PKCS8, PRIVATE_KEY_ID, null); + ServiceAccountCredentials credentials = createDefaultBuilder().build(); + byte[] signedBytes = credentials.sign(toSign); Signature signature = Signature.getInstance(OAuth2Utils.SIGNATURE_ALGORITHM); signature.initSign(credentials.getPrivateKey()); signature.update(toSign); + assertArrayEquals(signature.sign(), signedBytes); } @@ -1043,6 +1000,35 @@ public void equals_false_email() throws IOException { assertFalse(otherCredentials.equals(credentials)); } + @Test + public void equals_false_super() throws IOException { + final URI tokenServer1 = URI.create("https://foo1.com/bar"); + MockTokenServerTransportFactory serverTransportFactory = new MockTokenServerTransportFactory(); + OAuth2Credentials credentials = + ServiceAccountCredentials.fromPkcs8( + CLIENT_ID, + CLIENT_EMAIL, + PRIVATE_KEY_PKCS8, + PRIVATE_KEY_ID, + SCOPES, + serverTransportFactory, + tokenServer1); + OAuth2Credentials otherCredentials = + ServiceAccountCredentials.fromPkcs8( + CLIENT_ID, + CLIENT_EMAIL, + PRIVATE_KEY_PKCS8, + PRIVATE_KEY_ID, + SCOPES, + serverTransportFactory, + tokenServer1) + .toBuilder() + .setUniverseDomain("universe.com") + .build(); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + } + @Test public void equals_false_keyId() throws IOException { final URI tokenServer1 = URI.create("https://foo1.com/bar"); @@ -1168,9 +1154,11 @@ public void toString_containsFields() throws IOException { OAuth2Credentials credentials = ServiceAccountCredentials.fromPkcs8(PRIVATE_KEY_PKCS8, builder); String expectedToString = String.format( - "ServiceAccountCredentials{clientId=%s, clientEmail=%s, privateKeyId=%s, " - + "transportFactoryClassName=%s, tokenServerUri=%s, scopes=%s, defaultScopes=%s, serviceAccountUser=%s, " - + "quotaProjectId=%s, lifetime=3600, useJwtAccessWithScope=false, defaultRetriesEnabled=true}", + "ServiceAccountCredentials{quotaProjectId=%s, universeDomain=%s, clientId=%s, clientEmail=%s, " + + "privateKeyId=%s, transportFactoryClassName=%s, tokenServerUri=%s, scopes=%s, defaultScopes=%s, " + + "serviceAccountUser=%s, lifetime=3600, useJwtAccessWithScope=false, defaultRetriesEnabled=true}", + QUOTA_PROJECT, + Credentials.GOOGLE_DEFAULT_UNIVERSE, CLIENT_ID, CLIENT_EMAIL, PRIVATE_KEY_ID, @@ -1178,8 +1166,7 @@ public void toString_containsFields() throws IOException { tokenServer, SCOPES, DEFAULT_SCOPES, - USER, - QUOTA_PROJECT); + USER); assertEquals(expectedToString, credentials.toString()); } @@ -1189,14 +1176,38 @@ public void hashCode_equals() throws IOException { MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); OAuth2Credentials credentials = ServiceAccountCredentials.fromPkcs8( - CLIENT_ID, - CLIENT_EMAIL, - PRIVATE_KEY_PKCS8, - PRIVATE_KEY_ID, - SCOPES, - transportFactory, - tokenServer); + CLIENT_ID, + CLIENT_EMAIL, + PRIVATE_KEY_PKCS8, + PRIVATE_KEY_ID, + SCOPES, + transportFactory, + tokenServer) + .createWithQuotaProject(QUOTA_PROJECT) + .toBuilder() + .setUniverseDomain("universe.com") + .build(); OAuth2Credentials otherCredentials = + ServiceAccountCredentials.fromPkcs8( + CLIENT_ID, + CLIENT_EMAIL, + PRIVATE_KEY_PKCS8, + PRIVATE_KEY_ID, + SCOPES, + transportFactory, + tokenServer) + .createWithQuotaProject(QUOTA_PROJECT) + .toBuilder() + .setUniverseDomain("universe.com") + .build(); + assertEquals(credentials.hashCode(), otherCredentials.hashCode()); + } + + @Test + public void hashCode_not_equals_quota() throws IOException { + final URI tokenServer = URI.create("https://foo.com/bar"); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + OAuth2Credentials credentials = ServiceAccountCredentials.fromPkcs8( CLIENT_ID, CLIENT_EMAIL, @@ -1205,7 +1216,17 @@ public void hashCode_equals() throws IOException { SCOPES, transportFactory, tokenServer); - assertEquals(credentials.hashCode(), otherCredentials.hashCode()); + OAuth2Credentials otherCredentials = + ServiceAccountCredentials.fromPkcs8( + CLIENT_ID, + CLIENT_EMAIL, + PRIVATE_KEY_PKCS8, + PRIVATE_KEY_ID, + SCOPES, + transportFactory, + tokenServer) + .createWithQuotaProject("some_quota"); + assertNotEquals(credentials.hashCode(), otherCredentials.hashCode()); } @Test @@ -1221,6 +1242,12 @@ public void serialize() throws IOException, ClassNotFoundException { SCOPES, transportFactory, tokenServer); + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + try (ObjectOutputStream output = new ObjectOutputStream(bytes)) { + output.writeObject(credentials); + String s = output.toString(); + } ServiceAccountCredentials deserializedCredentials = serializeAndDeserialize(credentials); assertEquals(credentials, deserializedCredentials); assertEquals(credentials.hashCode(), deserializedCredentials.hashCode()); @@ -1291,14 +1318,7 @@ public void getIdTokenWithAudience_badEmailError_issClaimTraced() throws IOExcep MockTokenServerTransport transport = transportFactory.transport; transport.setError(new IOException("Invalid grant: Account not found")); ServiceAccountCredentials credentials = - ServiceAccountCredentials.fromPkcs8( - CLIENT_ID, - CLIENT_EMAIL, - PRIVATE_KEY_PKCS8, - PRIVATE_KEY_ID, - SCOPES, - transportFactory, - null); + createDefaultBuilder().setScopes(SCOPES).setHttpTransportFactory(transportFactory).build(); String targetAudience = "https://bar"; IdTokenCredentials tokenCredential = @@ -1357,7 +1377,7 @@ public void getUriForSelfSignedJWT_forStaticAudience_returnsURI() { } @Test - public void getRequestMetadataSetsQuotaProjectId() throws IOException { + public void getRequestMetadata_setsQuotaProjectId() throws IOException { MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); transportFactory.transport.addClient(CLIENT_ID, "unused-client-secret"); transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN); @@ -1384,7 +1404,7 @@ public void getRequestMetadataSetsQuotaProjectId() throws IOException { } @Test - public void getRequestMetadataNoQuotaProjectId() throws IOException { + public void getRequestMetadata_noQuotaProjectId() throws IOException { MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); transportFactory.transport.addClient(CLIENT_ID, "unused-client-secret"); transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN); @@ -1407,7 +1427,7 @@ public void getRequestMetadataNoQuotaProjectId() throws IOException { } @Test - public void getRequestMetadataWithCallback() throws IOException { + public void getRequestMetadata_withCallback() throws IOException { MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); transportFactory.transport.addClient(CLIENT_ID, "unused-client-secret"); transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN); @@ -1420,7 +1440,6 @@ public void getRequestMetadataWithCallback() throws IOException { .setPrivateKey(privateKey) .setPrivateKeyId(PRIVATE_KEY_ID) .setScopes(SCOPES) - .setServiceAccountUser(USER) .setProjectId(PROJECT_ID) .setQuotaProjectId("my-quota-project-id") .setHttpTransportFactory(transportFactory) @@ -1448,7 +1467,47 @@ public void onFailure(Throwable exception) { } @Test - public void getRequestMetadata_selfSignedJWT_withScopes() throws IOException { + public void getRequestMetadata_withScopes_withUniverseDomain_SelfSignedJwt() throws IOException { + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + transportFactory.transport.addClient(CLIENT_ID, "unused-client-secret"); + transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN); + + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); + GoogleCredentials credentials = + ServiceAccountCredentials.newBuilder() + .setClientId(CLIENT_ID) + .setClientEmail(CLIENT_EMAIL) + .setPrivateKey(privateKey) + .setPrivateKeyId(PRIVATE_KEY_ID) + .setScopes(SCOPES) + .setProjectId(PROJECT_ID) + .setHttpTransportFactory(transportFactory) + .setUniverseDomain("foo.bar") + .build(); + + final Map> plainMetadata = credentials.getRequestMetadata(); + final AtomicBoolean success = new AtomicBoolean(false); + credentials.getRequestMetadata( + null, + null, + new RequestMetadataCallback() { + @Override + public void onSuccess(Map> metadata) { + assertEquals(plainMetadata, metadata); + success.set(true); + } + + @Override + public void onFailure(Throwable exception) { + fail("Should not throw a failure."); + } + }); + + assertTrue("Should have run onSuccess() callback", success.get()); + } + + @Test + public void getRequestMetadata_withScopes_selfSignedJWT() throws IOException { PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); GoogleCredentials credentials = ServiceAccountCredentials.newBuilder() @@ -1504,7 +1563,7 @@ public void refreshAccessToken_withDomainDelegation_selfSignedJWT_disabled() thr } @Test - public void getRequestMetadata_selfSignedJWT_withAudience() throws IOException { + public void getRequestMetadata_withAudience_selfSignedJWT() throws IOException { PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); GoogleCredentials credentials = ServiceAccountCredentials.newBuilder() @@ -1522,7 +1581,7 @@ public void getRequestMetadata_selfSignedJWT_withAudience() throws IOException { } @Test - public void getRequestMetadata_selfSignedJWT_withDefaultScopes() throws IOException { + public void getRequestMetadata_withDefaultScopes_selfSignedJWT() throws IOException { PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); GoogleCredentials credentials = ServiceAccountCredentials.newBuilder() @@ -1607,13 +1666,26 @@ private void verifyJwtAccess(Map> metadata, String expected assertEquals(PRIVATE_KEY_ID, signature.getHeader().getKeyId()); } + static GenericJson writeServiceAccountJson( + String projectId, String quotaProjectId, String universeDomain) { + return writeServiceAccountJson( + CLIENT_ID, + CLIENT_EMAIL, + PRIVATE_KEY_PKCS8, + PRIVATE_KEY_ID, + projectId, + quotaProjectId, + universeDomain); + } + static GenericJson writeServiceAccountJson( String clientId, String clientEmail, String privateKeyPkcs8, String privateKeyId, String projectId, - String quotaProjectId) { + String quotaProjectId, + String universeDomain) { GenericJson json = new GenericJson(); if (clientId != null) { json.put("client_id", clientId); @@ -1633,6 +1705,9 @@ static GenericJson writeServiceAccountJson( if (quotaProjectId != null) { json.put("quota_project_id", quotaProjectId); } + if (universeDomain != null) { + json.put("universe_domain", universeDomain); + } json.put("type", GoogleCredentials.SERVICE_ACCOUNT_FILE_TYPE); return json; } @@ -1640,8 +1715,19 @@ static GenericJson writeServiceAccountJson( static InputStream writeServiceAccountStream( String clientId, String clientEmail, String privateKeyPkcs8, String privateKeyId) throws IOException { + return writeServiceAccountStream(clientId, clientEmail, privateKeyPkcs8, privateKeyId, null); + } + + static InputStream writeServiceAccountStream( + String clientId, + String clientEmail, + String privateKeyPkcs8, + String privateKeyId, + String universeDomain) + throws IOException { GenericJson json = - writeServiceAccountJson(clientId, clientEmail, privateKeyPkcs8, privateKeyId, null, null); + writeServiceAccountJson( + clientId, clientEmail, privateKeyPkcs8, privateKeyId, null, null, universeDomain); return TestUtils.jsonToInputStream(json); } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/UserCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/UserCredentialsTest.java index a5c666936..7fc0a256f 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/UserCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/UserCredentialsTest.java @@ -33,6 +33,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; @@ -368,6 +369,7 @@ public void equals_false_accessToken() throws IOException { .build(); assertFalse(credentials.equals(otherCredentials)); assertFalse(otherCredentials.equals(credentials)); + assertNotEquals(credentials.hashCode(), otherAccessToken.hashCode()); } @Test