To use a non-GDU Credentials, you must use the HttpCredentialsAdapter class. + * + * @throws IOException if there is an error reading the Universe Domain from the credentials + * @throws IllegalStateException if the configured Universe Domain does not match the Universe + * Domain in the Credentials + */ + public void validateUniverseDomain() throws IOException { + if (!(httpRequestInitializer instanceof HttpCredentialsAdapter)) { + return; + } + Credentials credentials = ((HttpCredentialsAdapter) httpRequestInitializer).getCredentials(); + // No need for a null check as HttpCredentialsAdapter cannot be initialized with null + // Credentials + String expectedUniverseDomain = credentials.getUniverseDomain(); + if (!expectedUniverseDomain.equals(getUniverseDomain())) { + throw new IllegalStateException( + String.format( + "The configured universe domain (%s) does not match the universe domain found" + + " in the credentials (%s). If you haven't configured the universe domain" + + " explicitly, `googleapis.com` is the default.", + getUniverseDomain(), expectedUniverseDomain)); + } } /** @@ -139,6 +220,18 @@ public final GoogleClientRequestInitializer getGoogleClientRequestInitializer() return googleClientRequestInitializer; } + /** + * Universe Domain is the domain for Google Cloud Services. It follows the format of + * `{ServiceName}.{UniverseDomain}`. For example, speech.googleapis.com would have a Universe + * Domain value of `googleapis.com` and cloudasset.test.com would have a Universe Domain of + * `test.com`. If this value is not set, this will default to `googleapis.com`. + * + * @return The configured Universe Domain or the Google Default Universe (googleapis.com) + */ + public final String getUniverseDomain() { + return universeDomain; + } + /** * Returns the object parser or {@code null} for none. * @@ -173,6 +266,7 @@ public ObjectParser getObjectParser() { * @param httpClientRequest Google client request type */ protected void initialize(AbstractGoogleClientRequest> httpClientRequest) throws IOException { + validateUniverseDomain(); if (getGoogleClientRequestInitializer() != null) { getGoogleClientRequestInitializer().initialize(httpClientRequest); } @@ -311,6 +405,33 @@ public abstract static class Builder { /** Whether discovery required parameter checks should be suppressed. */ boolean suppressRequiredParameterChecks; + /** User configured Universe Domain. Defaults to `googleapis.com`. */ + String universeDomain; + + /** + * Regex pattern to check if the URL passed in matches the default endpoint configured from a + * discovery doc. Follows the format of `https://{serviceName}(.mtls).googleapis.com/` + */ + Pattern defaultEndpointRegex = + Pattern.compile("https://([a-zA-Z]*)(\\.mtls)?\\.googleapis.com/?"); + + /** + * Whether the user has configured an endpoint via {@link #setRootUrl(String)}. This is added in + * because the rootUrl is set in the Builder's constructor. , + * + *
Apiary clients don't allow user configurations to this Builder's constructor, so this + * would be set to false by default for Apiary libraries. User configuration to the rootUrl is + * done via {@link #setRootUrl(String)}. + * + *
For other uses cases that touch this Builder's constructor directly, check if the rootUrl
+ * passed matches the default endpoint regex. If it doesn't match, it is a user configured
+ * endpoint.
+ */
+ boolean isUserConfiguredEndpoint;
+
+ /** The parsed serviceName value from the rootUrl from the Discovery Doc. */
+ String serviceName;
+
/**
* Returns an instance of a new builder.
*
@@ -328,9 +449,15 @@ protected Builder(
HttpRequestInitializer httpRequestInitializer) {
this.transport = Preconditions.checkNotNull(transport);
this.objectParser = objectParser;
- setRootUrl(rootUrl);
- setServicePath(servicePath);
+ this.rootUrl = normalizeRootUrl(rootUrl);
+ this.servicePath = normalizeServicePath(servicePath);
this.httpRequestInitializer = httpRequestInitializer;
+ Matcher matcher = defaultEndpointRegex.matcher(rootUrl);
+ boolean matches = matcher.matches();
+ // Checked here for the use case where users extend this class and may pass in
+ // a custom endpoint
+ this.isUserConfiguredEndpoint = !matches;
+ this.serviceName = matches ? matcher.group(1) : null;
}
/** Builds a new instance of {@link AbstractGoogleClient}. */
@@ -371,6 +498,7 @@ public final String getRootUrl() {
* changing the return type, but nothing else.
*/
public Builder setRootUrl(String rootUrl) {
+ this.isUserConfiguredEndpoint = true;
this.rootUrl = normalizeRootUrl(rootUrl);
return this;
}
@@ -515,5 +643,24 @@ public Builder setSuppressRequiredParameterChecks(boolean suppressRequiredParame
public Builder setSuppressAllChecks(boolean suppressAllChecks) {
return setSuppressPatternChecks(true).setSuppressRequiredParameterChecks(true);
}
+
+ /**
+ * Sets the user configured Universe Domain value. This value will be used to try and construct
+ * the endpoint to connect to GCP services.
+ *
+ * @throws IllegalArgumentException if universeDomain is passed in with an empty string ("")
+ */
+ public Builder setUniverseDomain(String universeDomain) {
+ if (universeDomain != null && universeDomain.isEmpty()) {
+ throw new IllegalArgumentException("The universe domain value cannot be empty.");
+ }
+ this.universeDomain = universeDomain;
+ return this;
+ }
+
+ @VisibleForTesting
+ String getServiceName() {
+ return serviceName;
+ }
}
}
diff --git a/google-api-client/src/test/java/com/google/api/client/googleapis/services/AbstractGoogleClientTest.java b/google-api-client/src/test/java/com/google/api/client/googleapis/services/AbstractGoogleClientTest.java
index 7d2ff6383..bcb6a3775 100644
--- a/google-api-client/src/test/java/com/google/api/client/googleapis/services/AbstractGoogleClientTest.java
+++ b/google-api-client/src/test/java/com/google/api/client/googleapis/services/AbstractGoogleClientTest.java
@@ -12,6 +12,8 @@
package com.google.api.client.googleapis.services;
+import static org.junit.Assert.assertThrows;
+
import com.google.api.client.googleapis.media.MediaHttpUploader;
import com.google.api.client.googleapis.testing.services.MockGoogleClient;
import com.google.api.client.googleapis.testing.services.MockGoogleClientRequest;
@@ -32,23 +34,46 @@
import com.google.api.client.testing.http.MockLowLevelHttpRequest;
import com.google.api.client.testing.http.MockLowLevelHttpResponse;
import com.google.api.client.util.Key;
+import com.google.auth.Credentials;
+import com.google.auth.http.HttpCredentialsAdapter;
+import com.google.auth.oauth2.GoogleCredentials;
import java.io.ByteArrayInputStream;
+import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import junit.framework.TestCase;
+import org.junit.Test;
+import org.junit.function.ThrowingRunnable;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
/**
* Tests {@link AbstractGoogleClient}.
*
* @author Yaniv Inbar
*/
+@RunWith(MockitoJUnitRunner.class)
public class AbstractGoogleClientTest extends TestCase {
+ @Mock private GoogleCredentials googleCredentials;
+
+ @Mock private HttpCredentialsAdapter httpCredentialsAdapter;
+
private static final JsonFactory JSON_FACTORY = new GsonFactory();
private static final JsonObjectParser JSON_OBJECT_PARSER = new JsonObjectParser(JSON_FACTORY);
private static final HttpTransport TRANSPORT = new MockHttpTransport();
+ private static class TestHttpRequestInitializer implements HttpRequestInitializer {
+
+ @Override
+ public void initialize(HttpRequest httpRequest) {
+ // no-op
+ }
+ }
+
private static class TestRemoteRequestInitializer implements GoogleClientRequestInitializer {
boolean isCalled;
@@ -60,9 +85,10 @@ public void initialize(AbstractGoogleClientRequest> request) {
}
}
+ @Test
public void testGoogleClientBuilder() {
- String rootUrl = "http://www.testgoogleapis.com/test/";
- String servicePath = "path/v1/";
+ String rootUrl = "https://test.googleapis.com/";
+ String servicePath = "test/path/v1/";
GoogleClientRequestInitializer jsonHttpRequestInitializer = new TestRemoteRequestInitializer();
String applicationName = "Test Application";
@@ -82,6 +108,142 @@ public void testGoogleClientBuilder() {
assertTrue(client.getSuppressRequiredParameterChecks());
}
+ @Test
+ public void testGoogleClientBuilder_setsCorrectRootUrl_nonMtlsUrl() {
+ String rootUrl = "https://test.googleapis.com/";
+ String applicationName = "Test Application";
+ String servicePath = "test/";
+
+ AbstractGoogleClient client =
+ new MockGoogleClient.Builder(TRANSPORT, rootUrl, servicePath, JSON_OBJECT_PARSER, null)
+ .setApplicationName(applicationName)
+ .build();
+ assertEquals(rootUrl, client.getRootUrl());
+ assertEquals(Credentials.GOOGLE_DEFAULT_UNIVERSE, client.getUniverseDomain());
+ }
+
+ @Test
+ public void testGoogleClientBuilder_setsCorrectRootUrl_mtlsUrl() {
+ String rootUrl = "https://test.mtls.googleapis.com/";
+ String applicationName = "Test Application";
+ String servicePath = "test/";
+
+ AbstractGoogleClient client =
+ new MockGoogleClient.Builder(TRANSPORT, rootUrl, servicePath, JSON_OBJECT_PARSER, null)
+ .setApplicationName(applicationName)
+ .build();
+ assertEquals(rootUrl, client.getRootUrl());
+ assertEquals(Credentials.GOOGLE_DEFAULT_UNIVERSE, client.getUniverseDomain());
+ }
+
+ @Test
+ public void testGoogleClientBuilder_customUniverseDomain_nonMtlsUrl() {
+ String rootUrl = "https://test.googleapis.com/";
+ String applicationName = "Test Application";
+ String servicePath = "test/";
+ String universeDomain = "random.com";
+
+ AbstractGoogleClient client =
+ new MockGoogleClient.Builder(TRANSPORT, rootUrl, servicePath, JSON_OBJECT_PARSER, null)
+ .setApplicationName(applicationName)
+ .setUniverseDomain(universeDomain)
+ .build();
+ assertEquals("https://test.random.com/", client.getRootUrl());
+ assertEquals(universeDomain, client.getUniverseDomain());
+ }
+
+ @Test
+ public void testGoogleClientBuilder_customUniverseDomain_mtlsUrl() {
+ String rootUrl = "https://test.mtls.googleapis.com/";
+ String applicationName = "Test Application";
+ String servicePath = "test/";
+ final AbstractGoogleClient.Builder builder =
+ new MockGoogleClient.Builder(TRANSPORT, rootUrl, servicePath, JSON_OBJECT_PARSER, null)
+ .setApplicationName(applicationName)
+ .setUniverseDomain("random.com");
+
+ IllegalStateException exception =
+ assertThrows(
+ IllegalStateException.class,
+ new ThrowingRunnable() {
+ @Override
+ public void run() {
+ builder.build();
+ }
+ });
+ assertEquals(
+ "mTLS is not supported in any universe other than googleapis.com", exception.getMessage());
+ }
+
+ @Test
+ public void testGoogleClientBuilder_customEndpoint_defaultUniverseDomain() {
+ String rootUrl = "https://test.googleapis.com/";
+ String applicationName = "Test Application";
+ String servicePath = "test/";
+
+ AbstractGoogleClient client =
+ new MockGoogleClient.Builder(TRANSPORT, rootUrl, servicePath, JSON_OBJECT_PARSER, null)
+ .setApplicationName(applicationName)
+ .setRootUrl("https://randomendpoint.com/")
+ .build();
+ assertEquals("https://randomendpoint.com/", client.getRootUrl());
+ assertEquals(Credentials.GOOGLE_DEFAULT_UNIVERSE, client.getUniverseDomain());
+ }
+
+ @Test
+ public void testGoogleClientBuilder_customEndpoint_customUniverseDomain() {
+ String rootUrl = "https://test.googleapis.com/";
+ String applicationName = "Test Application";
+ String servicePath = "test/";
+ String universeDomain = "random.com";
+ String customRootUrl = "https://randomendpoint.com/";
+
+ AbstractGoogleClient client =
+ new MockGoogleClient.Builder(TRANSPORT, rootUrl, servicePath, JSON_OBJECT_PARSER, null)
+ .setApplicationName(applicationName)
+ .setRootUrl(customRootUrl)
+ .setUniverseDomain(universeDomain)
+ .build();
+ assertEquals(customRootUrl, client.getRootUrl());
+ assertEquals(universeDomain, client.getUniverseDomain());
+ }
+
+ @Test
+ public void testGoogleClientBuilder_noCustomUniverseDomain_universeDomainEnvVar() {
+ String rootUrl = "https://test.googleapis.com/";
+ String applicationName = "Test Application";
+ String servicePath = "test/";
+ // Env Var Universe Domain is `random.com`
+ String envVarUniverseDomain = "random.com";
+ String expectedRootUrl = "https://test.random.com/";
+
+ AbstractGoogleClient client =
+ new MockGoogleClient.Builder(TRANSPORT, rootUrl, servicePath, JSON_OBJECT_PARSER, null)
+ .setApplicationName(applicationName)
+ .build();
+ assertEquals(expectedRootUrl, client.getRootUrl());
+ assertEquals(envVarUniverseDomain, client.getUniverseDomain());
+ }
+
+ @Test
+ public void testGoogleClientBuilder_customUniverseDomain_universeDomainEnvVar() {
+ String rootUrl = "https://test.googleapis.com/";
+ String applicationName = "Test Application";
+ String servicePath = "test/";
+ // Env Var Universe Domain is `random.com`
+ String customUniverseDomain = "test.com";
+ String expectedRootUrl = "https://test.test.com/";
+
+ AbstractGoogleClient client =
+ new MockGoogleClient.Builder(TRANSPORT, rootUrl, servicePath, JSON_OBJECT_PARSER, null)
+ .setApplicationName(applicationName)
+ .setUniverseDomain(customUniverseDomain)
+ .build();
+ assertEquals(expectedRootUrl, client.getRootUrl());
+ assertEquals(customUniverseDomain, client.getUniverseDomain());
+ }
+
+ @Test
public void testGoogleClientSuppressionDefaults() {
String rootUrl = "http://www.testgoogleapis.com/test/";
String servicePath = "path/v1/";
@@ -97,6 +259,7 @@ public void testGoogleClientSuppressionDefaults() {
assertFalse(googleClient.getSuppressRequiredParameterChecks());
}
+ @Test
public void testBaseServerAndBasePathBuilder() {
AbstractGoogleClient client =
new MockGoogleClient.Builder(
@@ -113,6 +276,7 @@ public void testBaseServerAndBasePathBuilder() {
assertEquals("http://www.googleapis.com/test/path/v2/", client.getBaseUrl());
}
+ @Test
public void testInitialize() throws Exception {
TestRemoteRequestInitializer remoteRequestInitializer = new TestRemoteRequestInitializer();
AbstractGoogleClient client =
@@ -125,6 +289,138 @@ public void testInitialize() throws Exception {
assertTrue(remoteRequestInitializer.isCalled);
}
+ @Test
+ public void testParseServiceName_nonMtlsRootUrl() {
+ AbstractGoogleClient.Builder clientBuilder =
+ new MockGoogleClient.Builder(
+ TRANSPORT, "https://random.googleapis.com/", "", JSON_OBJECT_PARSER, null)
+ .setApplicationName("Test Application");
+ assertEquals(clientBuilder.getServiceName(), "random");
+ }
+
+ @Test
+ public void testParseServiceName_mtlsRootUrl() {
+ AbstractGoogleClient.Builder clientBuilder =
+ new MockGoogleClient.Builder(
+ TRANSPORT, "https://test.mtls.googleapis.com/", "", JSON_OBJECT_PARSER, null)
+ .setApplicationName("Test Application");
+ assertEquals(clientBuilder.getServiceName(), "test");
+ }
+
+ @Test
+ public void testParseServiceName_nonGDURootUrl() {
+ AbstractGoogleClient.Builder clientBuilder =
+ new MockGoogleClient.Builder(
+ TRANSPORT, "https://test.random.com/", "", JSON_OBJECT_PARSER, null)
+ .setApplicationName("Test Application");
+ assertNull(clientBuilder.getServiceName());
+ }
+
+ @Test
+ public void testIsUserSetEndpoint_nonMtlsRootUrl() {
+ AbstractGoogleClient.Builder clientBuilder =
+ new MockGoogleClient.Builder(
+ TRANSPORT, "https://random.googleapis.com/", "", JSON_OBJECT_PARSER, null)
+ .setApplicationName("Test Application");
+ assertFalse(clientBuilder.isUserConfiguredEndpoint);
+ }
+
+ @Test
+ public void testIsUserSetEndpoint_mtlsRootUrl() {
+ AbstractGoogleClient.Builder clientBuilder =
+ new MockGoogleClient.Builder(
+ TRANSPORT, "https://test.mtls.googleapis.com/", "", JSON_OBJECT_PARSER, null)
+ .setApplicationName("Test Application");
+ assertFalse(clientBuilder.isUserConfiguredEndpoint);
+ }
+
+ @Test
+ public void testIsUserSetEndpoint_nonGDURootUrl() {
+ AbstractGoogleClient.Builder clientBuilder =
+ new MockGoogleClient.Builder(
+ TRANSPORT, "https://test.random.com/", "", JSON_OBJECT_PARSER, null)
+ .setApplicationName("Test Application");
+ assertTrue(clientBuilder.isUserConfiguredEndpoint);
+ }
+
+ @Test
+ public void testIsUserSetEndpoint_regionalEndpoint() {
+ AbstractGoogleClient.Builder clientBuilder =
+ new MockGoogleClient.Builder(
+ TRANSPORT,
+ "https://us-east-4.coolservice.googleapis.com/",
+ "",
+ JSON_OBJECT_PARSER,
+ null)
+ .setApplicationName("Test Application");
+ assertTrue(clientBuilder.isUserConfiguredEndpoint);
+ }
+
+ @Test
+ public void validateUniverseDomain_validUniverseDomain() throws IOException {
+ String rootUrl = "https://test.googleapis.com/";
+ String applicationName = "Test Application";
+ String servicePath = "test/";
+
+ Mockito.when(httpCredentialsAdapter.getCredentials()).thenReturn(googleCredentials);
+ Mockito.when(googleCredentials.getUniverseDomain())
+ .thenReturn(Credentials.GOOGLE_DEFAULT_UNIVERSE);
+
+ AbstractGoogleClient client =
+ new MockGoogleClient.Builder(
+ TRANSPORT, rootUrl, servicePath, JSON_OBJECT_PARSER, httpCredentialsAdapter)
+ .setApplicationName(applicationName)
+ .build();
+
+ // Nothing throws
+ client.validateUniverseDomain();
+ }
+
+ @Test
+ public void validateUniverseDomain_invalidUniverseDomain() throws IOException {
+ String rootUrl = "https://test.googleapis.com/";
+ String applicationName = "Test Application";
+ String servicePath = "test/";
+
+ Mockito.when(httpCredentialsAdapter.getCredentials()).thenReturn(googleCredentials);
+ Mockito.when(googleCredentials.getUniverseDomain()).thenReturn("invalid.universe.domain");
+
+ final AbstractGoogleClient client =
+ new MockGoogleClient.Builder(
+ TRANSPORT, rootUrl, servicePath, JSON_OBJECT_PARSER, httpCredentialsAdapter)
+ .setApplicationName(applicationName)
+ .build();
+ assertThrows(
+ IllegalStateException.class,
+ new ThrowingRunnable() {
+ @Override
+ public void run() throws IOException {
+ client.validateUniverseDomain();
+ }
+ });
+ }
+
+ @Test
+ public void validateUniverseDomain_notUsingHttpCredentialsAdapter_defaultUniverseDomain()
+ throws IOException {
+ String rootUrl = "https://test.googleapis.com/";
+ String applicationName = "Test Application";
+ String servicePath = "test/";
+
+ AbstractGoogleClient client =
+ new MockGoogleClient.Builder(
+ TRANSPORT,
+ rootUrl,
+ servicePath,
+ JSON_OBJECT_PARSER,
+ new TestHttpRequestInitializer())
+ .setApplicationName(applicationName)
+ .build();
+
+ // Nothing throws
+ client.validateUniverseDomain();
+ }
+
private static final String TEST_RESUMABLE_REQUEST_URL =
"http://www.test.com/request/url?uploadType=resumable";
private static final String TEST_UPLOAD_URL = "http://www.test.com/media/upload/location";
diff --git a/pom.xml b/pom.xml
index 62c35db41..58803a85e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -102,6 +102,13 @@