20
20
import com .google .api .client .util .ObjectParser ;
21
21
import com .google .api .client .util .Preconditions ;
22
22
import com .google .api .client .util .Strings ;
23
+ import com .google .auth .Credentials ;
24
+ import com .google .auth .http .HttpCredentialsAdapter ;
25
+ import com .google .common .annotations .VisibleForTesting ;
23
26
import java .io .IOException ;
24
27
import java .util .logging .Logger ;
28
+ import java .util .regex .Matcher ;
29
+ import java .util .regex .Pattern ;
25
30
26
31
/**
27
32
* Abstract thread-safe Google client.
@@ -33,6 +38,8 @@ public abstract class AbstractGoogleClient {
33
38
34
39
private static final Logger logger = Logger .getLogger (AbstractGoogleClient .class .getName ());
35
40
41
+ private static final String GOOGLE_CLOUD_UNIVERSE_DOMAIN = "GOOGLE_CLOUD_UNIVERSE_DOMAIN" ;
42
+
36
43
/** The request factory for connections to the server. */
37
44
private final HttpRequestFactory requestFactory ;
38
45
@@ -68,13 +75,18 @@ public abstract class AbstractGoogleClient {
68
75
/** Whether discovery required parameter checks should be suppressed. */
69
76
private final boolean suppressRequiredParameterChecks ;
70
77
78
+ private final String universeDomain ;
79
+
80
+ private final HttpRequestInitializer httpRequestInitializer ;
81
+
71
82
/**
72
83
* @param builder builder
73
84
* @since 1.14
74
85
*/
75
86
protected AbstractGoogleClient (Builder builder ) {
76
87
googleClientRequestInitializer = builder .googleClientRequestInitializer ;
77
- rootUrl = normalizeRootUrl (builder .rootUrl );
88
+ universeDomain = determineUniverseDomain (builder );
89
+ rootUrl = normalizeRootUrl (determineEndpoint (builder ));
78
90
servicePath = normalizeServicePath (builder .servicePath );
79
91
batchPath = builder .batchPath ;
80
92
if (Strings .isNullOrEmpty (builder .applicationName )) {
@@ -88,6 +100,75 @@ protected AbstractGoogleClient(Builder builder) {
88
100
objectParser = builder .objectParser ;
89
101
suppressPatternChecks = builder .suppressPatternChecks ;
90
102
suppressRequiredParameterChecks = builder .suppressRequiredParameterChecks ;
103
+ httpRequestInitializer = builder .httpRequestInitializer ;
104
+ }
105
+
106
+ /**
107
+ * Resolve the Universe Domain to be used when resolving the endpoint. The logic for resolving the
108
+ * universe domain is the following order: 1. Use the user configured value is set, 2. Use the
109
+ * Universe Domain Env Var if set, 3. Default to the Google Default Universe
110
+ */
111
+ private String determineUniverseDomain (Builder builder ) {
112
+ String resolvedUniverseDomain = builder .universeDomain ;
113
+ if (resolvedUniverseDomain == null ) {
114
+ resolvedUniverseDomain = System .getenv (GOOGLE_CLOUD_UNIVERSE_DOMAIN );
115
+ }
116
+ return resolvedUniverseDomain == null
117
+ ? Credentials .GOOGLE_DEFAULT_UNIVERSE
118
+ : resolvedUniverseDomain ;
119
+ }
120
+
121
+ /**
122
+ * Resolve the endpoint based on user configurations. If the user has configured a custom rootUrl,
123
+ * use that value. Otherwise, construct the endpoint based on the serviceName and the
124
+ * universeDomain.
125
+ */
126
+ private String determineEndpoint (Builder builder ) {
127
+ boolean mtlsEnabled = builder .rootUrl .contains (".mtls." );
128
+ if (mtlsEnabled && !universeDomain .equals (Credentials .GOOGLE_DEFAULT_UNIVERSE )) {
129
+ throw new IllegalStateException (
130
+ "mTLS is not supported in any universe other than googleapis.com" );
131
+ }
132
+ // If the serviceName is null, we cannot construct a valid resolved endpoint. Simply return
133
+ // the rootUrl as this was custom rootUrl passed in.
134
+ if (builder .isUserConfiguredEndpoint || builder .serviceName == null ) {
135
+ return builder .rootUrl ;
136
+ }
137
+ if (mtlsEnabled ) {
138
+ return "https://" + builder .serviceName + ".mtls." + universeDomain + "/" ;
139
+ }
140
+ return "https://" + builder .serviceName + "." + universeDomain + "/" ;
141
+ }
142
+
143
+ /**
144
+ * Check that the User configured universe domain matches the Credentials' universe domain. This
145
+ * uses the HttpRequestInitializer to get the Credentials and is enforced that the
146
+ * HttpRequestInitializer is of the {@see <a
147
+ * href="https://github.com/googleapis/google-auth-library-java/blob/main/oauth2_http/java/com/google/auth/http/HttpCredentialsAdapter.java">HttpCredentialsAdapter</a>}
148
+ * from the google-auth-library.
149
+ *
150
+ * <p>To use a non-GDU Credentials, you must use the HttpCredentialsAdapter class.
151
+ *
152
+ * @throws IOException if there is an error reading the Universe Domain from the credentials
153
+ * @throws IllegalStateException if the configured Universe Domain does not match the Universe
154
+ * Domain in the Credentials
155
+ */
156
+ public void validateUniverseDomain () throws IOException {
157
+ if (!(httpRequestInitializer instanceof HttpCredentialsAdapter )) {
158
+ return ;
159
+ }
160
+ Credentials credentials = ((HttpCredentialsAdapter ) httpRequestInitializer ).getCredentials ();
161
+ // No need for a null check as HttpCredentialsAdapter cannot be initialized with null
162
+ // Credentials
163
+ String expectedUniverseDomain = credentials .getUniverseDomain ();
164
+ if (!expectedUniverseDomain .equals (getUniverseDomain ())) {
165
+ throw new IllegalStateException (
166
+ String .format (
167
+ "The configured universe domain (%s) does not match the universe domain found"
168
+ + " in the credentials (%s). If you haven't configured the universe domain"
169
+ + " explicitly, `googleapis.com` is the default." ,
170
+ getUniverseDomain (), expectedUniverseDomain ));
171
+ }
91
172
}
92
173
93
174
/**
@@ -139,6 +220,18 @@ public final GoogleClientRequestInitializer getGoogleClientRequestInitializer()
139
220
return googleClientRequestInitializer ;
140
221
}
141
222
223
+ /**
224
+ * Universe Domain is the domain for Google Cloud Services. It follows the format of
225
+ * `{ServiceName}.{UniverseDomain}`. For example, speech.googleapis.com would have a Universe
226
+ * Domain value of `googleapis.com` and cloudasset.test.com would have a Universe Domain of
227
+ * `test.com`. If this value is not set, this will default to `googleapis.com`.
228
+ *
229
+ * @return The configured Universe Domain or the Google Default Universe (googleapis.com)
230
+ */
231
+ public final String getUniverseDomain () {
232
+ return universeDomain ;
233
+ }
234
+
142
235
/**
143
236
* Returns the object parser or {@code null} for none.
144
237
*
@@ -173,6 +266,7 @@ public ObjectParser getObjectParser() {
173
266
* @param httpClientRequest Google client request type
174
267
*/
175
268
protected void initialize (AbstractGoogleClientRequest <?> httpClientRequest ) throws IOException {
269
+ validateUniverseDomain ();
176
270
if (getGoogleClientRequestInitializer () != null ) {
177
271
getGoogleClientRequestInitializer ().initialize (httpClientRequest );
178
272
}
@@ -311,6 +405,33 @@ public abstract static class Builder {
311
405
/** Whether discovery required parameter checks should be suppressed. */
312
406
boolean suppressRequiredParameterChecks ;
313
407
408
+ /** User configured Universe Domain. Defaults to `googleapis.com`. */
409
+ String universeDomain ;
410
+
411
+ /**
412
+ * Regex pattern to check if the URL passed in matches the default endpoint configured from a
413
+ * discovery doc. Follows the format of `https://{serviceName}(.mtls).googleapis.com/`
414
+ */
415
+ Pattern defaultEndpointRegex =
416
+ Pattern .compile ("https://([a-zA-Z]*)(\\ .mtls)?\\ .googleapis.com/?" );
417
+
418
+ /**
419
+ * Whether the user has configured an endpoint via {@link #setRootUrl(String)}. This is added in
420
+ * because the rootUrl is set in the Builder's constructor. ,
421
+ *
422
+ * <p>Apiary clients don't allow user configurations to this Builder's constructor, so this
423
+ * would be set to false by default for Apiary libraries. User configuration to the rootUrl is
424
+ * done via {@link #setRootUrl(String)}.
425
+ *
426
+ * <p>For other uses cases that touch this Builder's constructor directly, check if the rootUrl
427
+ * passed matches the default endpoint regex. If it doesn't match, it is a user configured
428
+ * endpoint.
429
+ */
430
+ boolean isUserConfiguredEndpoint ;
431
+
432
+ /** The parsed serviceName value from the rootUrl from the Discovery Doc. */
433
+ String serviceName ;
434
+
314
435
/**
315
436
* Returns an instance of a new builder.
316
437
*
@@ -328,9 +449,15 @@ protected Builder(
328
449
HttpRequestInitializer httpRequestInitializer ) {
329
450
this .transport = Preconditions .checkNotNull (transport );
330
451
this .objectParser = objectParser ;
331
- setRootUrl (rootUrl );
332
- setServicePath (servicePath );
452
+ this . rootUrl = normalizeRootUrl (rootUrl );
453
+ this . servicePath = normalizeServicePath (servicePath );
333
454
this .httpRequestInitializer = httpRequestInitializer ;
455
+ Matcher matcher = defaultEndpointRegex .matcher (rootUrl );
456
+ boolean matches = matcher .matches ();
457
+ // Checked here for the use case where users extend this class and may pass in
458
+ // a custom endpoint
459
+ this .isUserConfiguredEndpoint = !matches ;
460
+ this .serviceName = matches ? matcher .group (1 ) : null ;
334
461
}
335
462
336
463
/** Builds a new instance of {@link AbstractGoogleClient}. */
@@ -371,6 +498,7 @@ public final String getRootUrl() {
371
498
* changing the return type, but nothing else.
372
499
*/
373
500
public Builder setRootUrl (String rootUrl ) {
501
+ this .isUserConfiguredEndpoint = true ;
374
502
this .rootUrl = normalizeRootUrl (rootUrl );
375
503
return this ;
376
504
}
@@ -515,5 +643,24 @@ public Builder setSuppressRequiredParameterChecks(boolean suppressRequiredParame
515
643
public Builder setSuppressAllChecks (boolean suppressAllChecks ) {
516
644
return setSuppressPatternChecks (true ).setSuppressRequiredParameterChecks (true );
517
645
}
646
+
647
+ /**
648
+ * Sets the user configured Universe Domain value. This value will be used to try and construct
649
+ * the endpoint to connect to GCP services.
650
+ *
651
+ * @throws IllegalArgumentException if universeDomain is passed in with an empty string ("")
652
+ */
653
+ public Builder setUniverseDomain (String universeDomain ) {
654
+ if (universeDomain != null && universeDomain .isEmpty ()) {
655
+ throw new IllegalArgumentException ("The universe domain value cannot be empty." );
656
+ }
657
+ this .universeDomain = universeDomain ;
658
+ return this ;
659
+ }
660
+
661
+ @ VisibleForTesting
662
+ String getServiceName () {
663
+ return serviceName ;
664
+ }
518
665
}
519
666
}
0 commit comments