Skip to content

Commit 7eb322e

Browse files
Timur Sadykovlsirac
andauthored
feat: multi universe support, adding universe_domain field (#1282)
* feat: adding universe_domain field * fix: move universe_domain to very base Credential, tests cleanup * feat: refactor ExternalAccount to use base class universe domain, update tests * fix: deprecating a duplicate universeDomain field * fix: docs update for Credentials.java * getUniverseDomain throws IOExcpetion, more tests --------- Co-authored-by: Leo <39062083+lsirac@users.noreply.github.com>
1 parent 5f0628c commit 7eb322e

14 files changed

+788
-346
lines changed

credentials/java/com/google/auth/Credentials.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ public abstract class Credentials implements Serializable {
4343

4444
private static final long serialVersionUID = 808575179767517313L;
4545

46+
public static final String GOOGLE_DEFAULT_UNIVERSE = "googleapis.com";
47+
4648
/**
4749
* A constant string name describing the authentication technology.
4850
*
@@ -54,6 +56,20 @@ public abstract class Credentials implements Serializable {
5456
*/
5557
public abstract String getAuthenticationType();
5658

59+
/**
60+
* Gets the universe domain for the credential in a blocking manner, refreshing tokens if
61+
* required.
62+
*
63+
* @return a universe domain value in the format some-domain.xyz. By default, returns the Google
64+
* universe domain googleapis.com.
65+
* @throws IOException extending classes might have to do remote calls to determine the universe
66+
* domain. The exception must implement {@link Retryable} and {@code isRetryable()} will
67+
* return true if the operation may be retried.
68+
*/
69+
public String getUniverseDomain() throws IOException {
70+
return GOOGLE_DEFAULT_UNIVERSE;
71+
}
72+
5773
/**
5874
* Get the current request metadata, refreshing tokens if required.
5975
*

oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ public static Builder newBuilder() {
245245
public int hashCode() {
246246
return Objects.hash(
247247
super.hashCode(),
248+
getAccessToken(),
248249
clientId,
249250
clientSecret,
250251
refreshToken,
@@ -281,6 +282,7 @@ public boolean equals(Object obj) {
281282
ExternalAccountAuthorizedUserCredentials credentials =
282283
(ExternalAccountAuthorizedUserCredentials) obj;
283284
return super.equals(credentials)
285+
&& Objects.equals(this.getAccessToken(), credentials.getAccessToken())
284286
&& Objects.equals(this.clientId, credentials.clientId)
285287
&& Objects.equals(this.clientSecret, credentials.clientSecret)
286288
&& Objects.equals(this.refreshToken, credentials.refreshToken)

oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@ abstract static class CredentialSource implements java.io.Serializable {
9595
@Nullable private final String serviceAccountImpersonationUrl;
9696
@Nullable private final String clientId;
9797
@Nullable private final String clientSecret;
98-
@Nullable private final String universeDomain;
9998

10099
// This is used for Workforce Pools. It is passed to the Security Token Service during token
101100
// exchange in the `options` param and will be embedded in the token by the Security Token
@@ -215,7 +214,6 @@ protected ExternalAccountCredentials(
215214
this.environmentProvider =
216215
environmentProvider == null ? SystemEnvironmentProvider.getInstance() : environmentProvider;
217216
this.workforcePoolUserProject = null;
218-
this.universeDomain = null;
219217
this.serviceAccountImpersonationOptions =
220218
new ServiceAccountImpersonationOptions(new HashMap<String, Object>());
221219

@@ -269,8 +267,6 @@ protected ExternalAccountCredentials(ExternalAccountCredentials.Builder builder)
269267
"The workforce_pool_user_project parameter should only be provided for a Workforce Pool configuration.");
270268
}
271269

272-
this.universeDomain = builder.universeDomain;
273-
274270
validateTokenUrl(tokenUrl);
275271
if (serviceAccountImpersonationUrl != null) {
276272
validateServiceAccountImpersonationInfoUrl(serviceAccountImpersonationUrl);
@@ -593,11 +589,6 @@ public String getWorkforcePoolUserProject() {
593589
return workforcePoolUserProject;
594590
}
595591

596-
@Nullable
597-
String getUniverseDomain() {
598-
return universeDomain;
599-
}
600-
601592
@Nullable
602593
public ServiceAccountImpersonationOptions getServiceAccountImpersonationOptions() {
603594
return serviceAccountImpersonationOptions;
@@ -734,7 +725,12 @@ public abstract static class Builder extends GoogleCredentials.Builder {
734725
@Nullable protected Collection<String> scopes;
735726
@Nullable protected String workforcePoolUserProject;
736727
@Nullable protected ServiceAccountImpersonationOptions serviceAccountImpersonationOptions;
737-
@Nullable protected String universeDomain;
728+
729+
/* The field is not being used and value not set. Superseded by the same field in the
730+
{@link GoogleCredential.Builder}.
731+
*/
732+
@Nullable @Deprecated protected String universeDomain;
733+
738734
@Nullable protected ExternalAccountMetricsHandler metricsHandler;
739735

740736
protected Builder() {}
@@ -754,7 +750,6 @@ protected Builder(ExternalAccountCredentials credentials) {
754750
this.environmentProvider = credentials.environmentProvider;
755751
this.workforcePoolUserProject = credentials.workforcePoolUserProject;
756752
this.serviceAccountImpersonationOptions = credentials.serviceAccountImpersonationOptions;
757-
this.universeDomain = credentials.universeDomain;
758753
this.metricsHandler = credentials.metricsHandler;
759754
}
760755

@@ -928,8 +923,9 @@ public Builder setServiceAccountImpersonationOptions(Map<String, Object> options
928923
* @return this {@code Builder} object
929924
*/
930925
@CanIgnoreReturnValue
926+
@Override
931927
public Builder setUniverseDomain(String universeDomain) {
932-
this.universeDomain = universeDomain;
928+
super.setUniverseDomain(universeDomain);
933929
return this;
934930
}
935931

oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java

Lines changed: 124 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@
3535
import com.google.api.client.json.JsonFactory;
3636
import com.google.api.client.json.JsonObjectParser;
3737
import com.google.api.client.util.Preconditions;
38+
import com.google.auth.Credentials;
3839
import com.google.auth.http.HttpTransportFactory;
40+
import com.google.common.base.MoreObjects;
41+
import com.google.common.base.MoreObjects.ToStringHelper;
3942
import com.google.common.collect.ImmutableList;
4043
import com.google.errorprone.annotations.CanIgnoreReturnValue;
4144
import java.io.IOException;
@@ -47,6 +50,7 @@
4750
import java.util.HashMap;
4851
import java.util.List;
4952
import java.util.Map;
53+
import java.util.Objects;
5054
import javax.annotation.Nullable;
5155

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

66+
private final String universeDomain;
67+
6268
protected final String quotaProjectId;
6369

6470
private static final DefaultCredentialsProvider defaultCredentialsProvider =
@@ -71,7 +77,10 @@ public class GoogleCredentials extends OAuth2Credentials implements QuotaProject
7177
* @return the credentials instance
7278
*/
7379
public static GoogleCredentials create(AccessToken accessToken) {
74-
return GoogleCredentials.newBuilder().setAccessToken(accessToken).build();
80+
return GoogleCredentials.newBuilder()
81+
.setAccessToken(accessToken)
82+
.setUniverseDomain(Credentials.GOOGLE_DEFAULT_UNIVERSE)
83+
.build();
7584
}
7685

7786
/**
@@ -170,6 +179,7 @@ public static GoogleCredentials fromStream(
170179
if (fileType == null) {
171180
throw new IOException("Error reading credentials from stream, 'type' field not specified.");
172181
}
182+
173183
if (USER_FILE_TYPE.equals(fileType)) {
174184
return UserCredentials.fromJson(fileContents, transportFactory);
175185
}
@@ -186,14 +196,20 @@ public static GoogleCredentials fromStream(
186196
fileType)) {
187197
return ExternalAccountAuthorizedUserCredentials.fromJson(fileContents, transportFactory);
188198
}
189-
if ("impersonated_service_account".equals(fileType)) {
199+
if (ImpersonatedCredentials.IMPERSONATED_CREDENTIALS_FILE_TYPE.equals(fileType)) {
190200
return ImpersonatedCredentials.fromJson(fileContents, transportFactory);
191201
}
192202
throw new IOException(
193203
String.format(
194204
"Error reading credentials from stream, 'type' value '%s' not recognized."
195-
+ " Expecting '%s' or '%s'.",
196-
fileType, USER_FILE_TYPE, SERVICE_ACCOUNT_FILE_TYPE));
205+
+ " Valid values are '%s', '%s', '%s', '%s', '%s', '%s'.",
206+
fileType,
207+
USER_FILE_TYPE,
208+
SERVICE_ACCOUNT_FILE_TYPE,
209+
GDCH_SERVICE_ACCOUNT_FILE_TYPE,
210+
ExternalAccountCredentials.EXTERNAL_ACCOUNT_FILE_TYPE,
211+
ExternalAccountAuthorizedUserCredentials.EXTERNAL_ACCOUNT_AUTHORIZED_USER_FILE_TYPE,
212+
ImpersonatedCredentials.IMPERSONATED_CREDENTIALS_FILE_TYPE));
197213
}
198214

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

225+
/**
226+
* Gets the universe domain for the credential.
227+
*
228+
* @return An explicit universe domain if it was explicitly provided, invokes the super
229+
* implementation otherwise
230+
*/
231+
@Override
232+
public String getUniverseDomain() throws IOException {
233+
return this.universeDomain;
234+
}
235+
236+
/**
237+
* Checks if universe domain equals to {@link Credentials#GOOGLE_DEFAULT_UNIVERSE}.
238+
*
239+
* @return true if universeDomain equals to {@link Credentials#GOOGLE_DEFAULT_UNIVERSE}, false
240+
* otherwise
241+
*/
242+
boolean isDefaultUniverseDomain() {
243+
return this.universeDomain.equals(Credentials.GOOGLE_DEFAULT_UNIVERSE);
244+
}
245+
209246
/**
210247
* Adds quota project ID to requestMetadata if present.
211248
*
@@ -237,33 +274,97 @@ protected GoogleCredentials() {
237274
this(new Builder());
238275
}
239276

277+
/**
278+
* Constructor with an explicit access token and quotaProjectId.
279+
*
280+
* <p>Deprecated, please use the {@link GoogleCredentials#GoogleCredentials(Builder)} constructor
281+
* whenever possible.
282+
*
283+
* @param accessToken initial or temporary access token
284+
* @param quotaProjectId a quotaProjectId, a project id to be used for billing purposes
285+
*/
286+
@Deprecated
240287
protected GoogleCredentials(AccessToken accessToken, String quotaProjectId) {
241288
super(accessToken);
242289
this.quotaProjectId = quotaProjectId;
290+
this.universeDomain = Credentials.GOOGLE_DEFAULT_UNIVERSE;
243291
}
244292

245293
/**
246294
* Constructor with explicit access token.
247295
*
248296
* @param accessToken initial or temporary access token
249297
*/
298+
@Deprecated
250299
public GoogleCredentials(AccessToken accessToken) {
251300
this(accessToken, null);
252301
}
253302

303+
/**
304+
* Constructor that relies on a {@link GoogleCredential.Builder} to provide all the necessary
305+
* field values for initialization.
306+
*
307+
* @param builder an instance of a builder
308+
*/
254309
protected GoogleCredentials(Builder builder) {
255-
this(builder.getAccessToken(), builder.getQuotaProjectId());
310+
super(builder.getAccessToken());
311+
this.quotaProjectId = builder.getQuotaProjectId();
312+
313+
if (builder.universeDomain == null || builder.universeDomain.trim().isEmpty()) {
314+
this.universeDomain = Credentials.GOOGLE_DEFAULT_UNIVERSE;
315+
} else {
316+
this.universeDomain = builder.getUniverseDomain();
317+
}
256318
}
257319

258320
/**
259-
* Constructor with explicit access token and refresh times
321+
* Constructor with explicit access token and refresh margins.
322+
*
323+
* <p>Deprecated, please use the {@link GoogleCredentials#GoogleCredentials(Builder)} constructor
324+
* whenever possible.
260325
*
261326
* @param accessToken initial or temporary access token
262327
*/
328+
@Deprecated
263329
protected GoogleCredentials(
264330
AccessToken accessToken, Duration refreshMargin, Duration expirationMargin) {
265331
super(accessToken, refreshMargin, expirationMargin);
266332
this.quotaProjectId = null;
333+
this.universeDomain = Credentials.GOOGLE_DEFAULT_UNIVERSE;
334+
}
335+
336+
/**
337+
* A helper for overriding the toString() method. This allows inheritance of super class fields.
338+
* Extending classes can override this implementation and call super implementation and add more
339+
* fields. Same cannot be done with overriding the toString() directly.
340+
*
341+
* @return an instance of the ToStringHelper that has public fields added
342+
*/
343+
protected ToStringHelper toStringHelper() {
344+
return MoreObjects.toStringHelper(this)
345+
.omitNullValues()
346+
.add("quotaProjectId", this.quotaProjectId)
347+
.add("universeDomain", this.universeDomain);
348+
}
349+
350+
@Override
351+
public String toString() {
352+
return toStringHelper().toString();
353+
}
354+
355+
@Override
356+
public boolean equals(Object obj) {
357+
if (!(obj instanceof GoogleCredentials)) {
358+
return false;
359+
}
360+
GoogleCredentials other = (GoogleCredentials) obj;
361+
return Objects.equals(this.quotaProjectId, other.quotaProjectId)
362+
&& Objects.equals(this.universeDomain, other.universeDomain);
363+
}
364+
365+
@Override
366+
public int hashCode() {
367+
return Objects.hash(this.quotaProjectId, this.universeDomain);
267368
}
268369

269370
public static Builder newBuilder() {
@@ -348,12 +449,20 @@ public GoogleCredentials createDelegated(String user) {
348449

349450
public static class Builder extends OAuth2Credentials.Builder {
350451
@Nullable protected String quotaProjectId;
452+
@Nullable protected String universeDomain;
351453

352454
protected Builder() {}
353455

354456
protected Builder(GoogleCredentials credentials) {
355457
setAccessToken(credentials.getAccessToken());
356458
this.quotaProjectId = credentials.quotaProjectId;
459+
this.universeDomain = credentials.universeDomain;
460+
}
461+
462+
protected Builder(GoogleCredentials.Builder builder) {
463+
setAccessToken(builder.getAccessToken());
464+
this.quotaProjectId = builder.quotaProjectId;
465+
this.universeDomain = builder.universeDomain;
357466
}
358467

359468
public GoogleCredentials build() {
@@ -366,10 +475,19 @@ public Builder setQuotaProjectId(String quotaProjectId) {
366475
return this;
367476
}
368477

478+
public Builder setUniverseDomain(String universeDomain) {
479+
this.universeDomain = universeDomain;
480+
return this;
481+
}
482+
369483
public String getQuotaProjectId() {
370484
return this.quotaProjectId;
371485
}
372486

487+
public String getUniverseDomain() {
488+
return this.universeDomain;
489+
}
490+
373491
@Override
374492
@CanIgnoreReturnValue
375493
public Builder setAccessToken(AccessToken token) {

oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@
9393
public class ImpersonatedCredentials extends GoogleCredentials
9494
implements ServiceAccountSigner, IdTokenProvider {
9595

96+
static final String IMPERSONATED_CREDENTIALS_FILE_TYPE = "impersonated_service_account";
97+
9698
private static final long serialVersionUID = -2133257318957488431L;
9799
private static final String RFC3339 = "yyyy-MM-dd'T'HH:mm:ssX";
98100
private static final int TWELVE_HOURS_IN_SECONDS = 43200;

0 commit comments

Comments
 (0)