diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4b1d0bbb0..37fc04345 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -20,6 +20,13 @@ jobs: - run: .kokoro/build.sh env: JOB_TYPE: test + # The `envVarTest` profile runs tests that require an environment variable + - name: Env Var Tests + run: | + mvn test -B -ntp -Dclirr.skip=true -Denforcer.skip=true -PenvVarTest + # Set the Env Var for this step only + env: + GOOGLE_CLOUD_UNIVERSE_DOMAIN: random.com windows: runs-on: windows-latest steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index ca060d8d1..d88f74fa5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## [2.4.0](https://github.com/googleapis/google-api-java-client/compare/v2.3.0...v2.4.0) (2024-03-13) + + +### Features + +* Add Universe Domain Support ([#2435](https://github.com/googleapis/google-api-java-client/issues/2435)) ([4adfed9](https://github.com/googleapis/google-api-java-client/commit/4adfed9e0ce65a82d0d7229611f43a34c5cb3a3d)) + + +### Bug Fixes + +* **deps:** Update dependency com.google.api-client:google-api-client to v2.3.0 ([#2427](https://github.com/googleapis/google-api-java-client/issues/2427)) ([a075e39](https://github.com/googleapis/google-api-java-client/commit/a075e3914e4ac1de97ee55f2942bf354dfa13fb5)) +* **deps:** Update dependency com.google.appengine:appengine-api-1.0-sdk to v2.0.25 ([#2438](https://github.com/googleapis/google-api-java-client/issues/2438)) ([830d021](https://github.com/googleapis/google-api-java-client/commit/830d021a7cf94178d5f9b1a2bc92228654ba5d6e)) +* **deps:** Update dependency com.google.auth:google-auth-library-bom to v1.23.0 ([#2443](https://github.com/googleapis/google-api-java-client/issues/2443)) ([690a21a](https://github.com/googleapis/google-api-java-client/commit/690a21a4435d8af4953b54a9aff5e5fa432cd8d9)) +* **deps:** Update dependency com.google.cloud:libraries-bom to v26.34.0 ([#2425](https://github.com/googleapis/google-api-java-client/issues/2425)) ([94cd2a3](https://github.com/googleapis/google-api-java-client/commit/94cd2a370df2768dba3d3b902d29fa4f3badddfa)) +* **deps:** Update dependency com.google.guava:guava to v33 ([#2410](https://github.com/googleapis/google-api-java-client/issues/2410)) ([d6adc9b](https://github.com/googleapis/google-api-java-client/commit/d6adc9b64d3d4af688785b3739ab9cdedbc231a5)) +* **deps:** Update dependency commons-codec:commons-codec to v1.16.1 ([#2432](https://github.com/googleapis/google-api-java-client/issues/2432)) ([81bdcd1](https://github.com/googleapis/google-api-java-client/commit/81bdcd156f03c3bc25b81ceb60e74547b36ec759)) + ## [2.3.0](https://github.com/googleapis/google-api-java-client/compare/v2.2.0...v2.3.0) (2024-01-29) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b65dd279c..ee2c5ce00 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,10 @@ # How to Contribute -We'd love to accept your patches and contributions to this project. There are -just a few small guidelines you need to follow. +Please follow the guidelines below before opening an issue or a PR: +1. Ensure the issue was not already reported. +2. Open a new issue if you are unable to find an existing issue addressing your problem. Make sure to include a title and clear description, as much relevant information as possible, and a code sample or an executable test case demonstrating the expected behavior that is not occurring. +3. Discuss the priority and potential solutions with the maintainers in the issue. The maintainers would review the issue and add a label "Accepting Contributions" once the issue is ready for accepting contributions. +4. Open a PR only if the issue is labeled with "Accepting Contributions", ensure the PR description clearly describes the problem and solution. Note that an open PR without an issues labeled with "Accepting Contributions" will not be accepted. ## Contributor License Agreement @@ -89,4 +92,4 @@ mvn com.coveo:fmt-maven-plugin:format [1]: https://cloud.google.com/docs/authentication/getting-started#creating_a_service_account [2]: https://maven.apache.org/settings.html#Active_Profiles -[3]: https://github.com/GoogleCloudPlatform/java-docs-samples/blob/main/SAMPLE_FORMAT.md \ No newline at end of file +[3]: https://github.com/GoogleCloudPlatform/java-docs-samples/blob/main/SAMPLE_FORMAT.md diff --git a/google-api-client-android/pom.xml b/google-api-client-android/pom.xml index 6974294d6..3060ddb19 100644 --- a/google-api-client-android/pom.xml +++ b/google-api-client-android/pom.xml @@ -4,7 +4,7 @@ com.google.api-client google-api-client-parent - 2.3.0 + 2.4.0 ../pom.xml google-api-client-android diff --git a/google-api-client-appengine/pom.xml b/google-api-client-appengine/pom.xml index d33b5378a..56191d06a 100644 --- a/google-api-client-appengine/pom.xml +++ b/google-api-client-appengine/pom.xml @@ -4,7 +4,7 @@ com.google.api-client google-api-client-parent - 2.3.0 + 2.4.0 ../pom.xml google-api-client-appengine diff --git a/google-api-client-assembly/pom.xml b/google-api-client-assembly/pom.xml index b244fe505..20e09c8e7 100644 --- a/google-api-client-assembly/pom.xml +++ b/google-api-client-assembly/pom.xml @@ -4,7 +4,7 @@ com.google.api-client google-api-client-parent - 2.3.0 + 2.4.0 ../pom.xml com.google.api-client diff --git a/google-api-client-bom/pom.xml b/google-api-client-bom/pom.xml index 1b46c98ab..fac7dbd0d 100644 --- a/google-api-client-bom/pom.xml +++ b/google-api-client-bom/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.api-client google-api-client-bom - 2.3.0 + 2.4.0 pom Google API Client Library for Java BOM @@ -63,48 +63,48 @@ com.google.api-client google-api-client - 2.3.0 + 2.4.0 com.google.api-client google-api-client-android - 2.3.0 + 2.4.0 com.google.api-client google-api-client-appengine - 2.3.0 + 2.4.0 com.google.api-client google-api-client-assembly - 2.3.0 + 2.4.0 pom com.google.api-client google-api-client-gson - 2.3.0 + 2.4.0 com.google.api-client google-api-client-jackson2 - 2.3.0 + 2.4.0 com.google.api-client google-api-client-protobuf - 2.3.0 + 2.4.0 com.google.api-client google-api-client-servlet - 2.3.0 + 2.4.0 com.google.api-client google-api-client-xml - 2.3.0 + 2.4.0 @@ -170,7 +170,7 @@ org.apache.maven.plugins maven-gpg-plugin - 3.1.0 + 3.2.0 sign-artifacts diff --git a/google-api-client-gson/pom.xml b/google-api-client-gson/pom.xml index 7ca80980c..702e35a83 100644 --- a/google-api-client-gson/pom.xml +++ b/google-api-client-gson/pom.xml @@ -4,7 +4,7 @@ com.google.api-client google-api-client-parent - 2.3.0 + 2.4.0 ../pom.xml google-api-client-gson diff --git a/google-api-client-jackson2/pom.xml b/google-api-client-jackson2/pom.xml index 2de030f94..c349d81c1 100644 --- a/google-api-client-jackson2/pom.xml +++ b/google-api-client-jackson2/pom.xml @@ -4,7 +4,7 @@ com.google.api-client google-api-client-parent - 2.3.0 + 2.4.0 ../pom.xml google-api-client-jackson2 diff --git a/google-api-client-protobuf/pom.xml b/google-api-client-protobuf/pom.xml index b84b8a74e..fcd79ef7e 100644 --- a/google-api-client-protobuf/pom.xml +++ b/google-api-client-protobuf/pom.xml @@ -4,7 +4,7 @@ com.google.api-client google-api-client-parent - 2.3.0 + 2.4.0 ../pom.xml google-api-client-protobuf diff --git a/google-api-client-servlet/pom.xml b/google-api-client-servlet/pom.xml index 85bf22cb0..bf5775dc2 100644 --- a/google-api-client-servlet/pom.xml +++ b/google-api-client-servlet/pom.xml @@ -4,7 +4,7 @@ com.google.api-client google-api-client-parent - 2.3.0 + 2.4.0 ../pom.xml google-api-client-servlet diff --git a/google-api-client-xml/pom.xml b/google-api-client-xml/pom.xml index 96515eac5..30c630d1c 100644 --- a/google-api-client-xml/pom.xml +++ b/google-api-client-xml/pom.xml @@ -4,7 +4,7 @@ com.google.api-client google-api-client-parent - 2.3.0 + 2.4.0 ../pom.xml google-api-client-xml diff --git a/google-api-client/pom.xml b/google-api-client/pom.xml index f311d3168..9482ab069 100644 --- a/google-api-client/pom.xml +++ b/google-api-client/pom.xml @@ -4,7 +4,7 @@ com.google.api-client google-api-client-parent - 2.3.0 + 2.4.0 ../pom.xml google-api-client @@ -111,6 +111,14 @@ commons-codec:commons-codec + + org.apache.maven.plugins + maven-surefire-plugin + + + !AbstractGoogleClientTest#testGoogleClientBuilder_noCustomUniverseDomain_universeDomainEnvVar+testGoogleClientBuilder_customUniverseDomain_universeDomainEnvVar + + @@ -135,6 +143,14 @@ com.google.oauth-client google-oauth-client + + com.google.auth + google-auth-library-credentials + + + com.google.auth + google-auth-library-oauth2-http + com.google.http-client google-http-client-gson @@ -179,6 +195,27 @@ junit test - + + org.mockito + mockito-core + test + + + + + envVarTest + + + + org.apache.maven.plugins + maven-surefire-plugin + + AbstractGoogleClientTest#testGoogleClientBuilder_noCustomUniverseDomain_universeDomainEnvVar+testGoogleClientBuilder_customUniverseDomain_universeDomainEnvVar + + + + + + diff --git a/google-api-client/src/main/java/com/google/api/client/googleapis/services/AbstractGoogleClient.java b/google-api-client/src/main/java/com/google/api/client/googleapis/services/AbstractGoogleClient.java index 421dc0a46..53cc57fa7 100644 --- a/google-api-client/src/main/java/com/google/api/client/googleapis/services/AbstractGoogleClient.java +++ b/google-api-client/src/main/java/com/google/api/client/googleapis/services/AbstractGoogleClient.java @@ -20,8 +20,13 @@ import com.google.api.client.util.ObjectParser; import com.google.api.client.util.Preconditions; import com.google.api.client.util.Strings; +import com.google.auth.Credentials; +import com.google.auth.http.HttpCredentialsAdapter; +import com.google.common.annotations.VisibleForTesting; import java.io.IOException; import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Abstract thread-safe Google client. @@ -33,6 +38,8 @@ public abstract class AbstractGoogleClient { private static final Logger logger = Logger.getLogger(AbstractGoogleClient.class.getName()); + private static final String GOOGLE_CLOUD_UNIVERSE_DOMAIN = "GOOGLE_CLOUD_UNIVERSE_DOMAIN"; + /** The request factory for connections to the server. */ private final HttpRequestFactory requestFactory; @@ -68,13 +75,18 @@ public abstract class AbstractGoogleClient { /** Whether discovery required parameter checks should be suppressed. */ private final boolean suppressRequiredParameterChecks; + private final String universeDomain; + + private final HttpRequestInitializer httpRequestInitializer; + /** * @param builder builder * @since 1.14 */ protected AbstractGoogleClient(Builder builder) { googleClientRequestInitializer = builder.googleClientRequestInitializer; - rootUrl = normalizeRootUrl(builder.rootUrl); + universeDomain = determineUniverseDomain(builder); + rootUrl = normalizeRootUrl(determineEndpoint(builder)); servicePath = normalizeServicePath(builder.servicePath); batchPath = builder.batchPath; if (Strings.isNullOrEmpty(builder.applicationName)) { @@ -88,6 +100,75 @@ protected AbstractGoogleClient(Builder builder) { objectParser = builder.objectParser; suppressPatternChecks = builder.suppressPatternChecks; suppressRequiredParameterChecks = builder.suppressRequiredParameterChecks; + httpRequestInitializer = builder.httpRequestInitializer; + } + + /** + * Resolve the Universe Domain to be used when resolving the endpoint. The logic for resolving the + * universe domain is the following order: 1. Use the user configured value is set, 2. Use the + * Universe Domain Env Var if set, 3. Default to the Google Default Universe + */ + private String determineUniverseDomain(Builder builder) { + String resolvedUniverseDomain = builder.universeDomain; + if (resolvedUniverseDomain == null) { + resolvedUniverseDomain = System.getenv(GOOGLE_CLOUD_UNIVERSE_DOMAIN); + } + return resolvedUniverseDomain == null + ? Credentials.GOOGLE_DEFAULT_UNIVERSE + : resolvedUniverseDomain; + } + + /** + * Resolve the endpoint based on user configurations. If the user has configured a custom rootUrl, + * use that value. Otherwise, construct the endpoint based on the serviceName and the + * universeDomain. + */ + private String determineEndpoint(Builder builder) { + boolean mtlsEnabled = builder.rootUrl.contains(".mtls."); + if (mtlsEnabled && !universeDomain.equals(Credentials.GOOGLE_DEFAULT_UNIVERSE)) { + throw new IllegalStateException( + "mTLS is not supported in any universe other than googleapis.com"); + } + // If the serviceName is null, we cannot construct a valid resolved endpoint. Simply return + // the rootUrl as this was custom rootUrl passed in. + if (builder.isUserConfiguredEndpoint || builder.serviceName == null) { + return builder.rootUrl; + } + if (mtlsEnabled) { + return "https://" + builder.serviceName + ".mtls." + universeDomain + "/"; + } + return "https://" + builder.serviceName + "." + universeDomain + "/"; + } + + /** + * Check that the User configured universe domain matches the Credentials' universe domain. This + * uses the HttpRequestInitializer to get the Credentials and is enforced that the + * HttpRequestInitializer is of the {@see HttpCredentialsAdapter} + * from the google-auth-library. + * + *

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/owlbot.py b/owlbot.py index d6da82cca..1e97fea99 100644 --- a/owlbot.py +++ b/owlbot.py @@ -24,6 +24,7 @@ java.common_templates( excludes=[ "README.md", + "CONTRIBUTING.md", "java.header", "checkstyle.xml", "renovate.json", diff --git a/pom.xml b/pom.xml index 6b30b24e6..900f384eb 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.google.api-client google-api-client-parent - 2.3.0 + 2.4.0 pom Parent for the Google API Client Library for Java The Google APIs Client Library for Java is a Java client library @@ -102,6 +102,13 @@ junit junit 4.13.2 + test + + + org.mockito + mockito-core + 4.11.0 + test com.google.appengine @@ -157,6 +164,13 @@ pom import + + com.google.auth + google-auth-library-bom + ${project.auth.version} + pom + import + com.google.api-client google-api-client @@ -242,7 +256,7 @@ maven-assembly-plugin - 3.6.0 + 3.7.0 maven-compiler-plugin @@ -497,13 +511,14 @@ 1.44.1 4.4.16 4.5.14 - 1.16.0 + 1.16.1 1.35.0 + 1.23.0 3.0.2 2.8.6 - 3.25.2 - 32.0.0-jre - 2.0.24 + 3.25.3 + 33.0.0-jre + 2.0.25 1.1.4c 2.3-20090302111651 3.2.2 @@ -528,7 +543,7 @@ org.apache.maven.plugins maven-gpg-plugin - 3.1.0 + 3.2.0 sign-artifacts @@ -645,5 +660,23 @@ + + + envVarTest + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + **/*.java + + + + + + diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index dc2b466f9..69008cc8b 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -29,7 +29,7 @@ com.google.api-client google-api-client - 2.2.0 + 2.3.0 @@ -42,7 +42,7 @@ com.google.truth truth - 1.3.0 + 1.4.2 test diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index 7798d5fc4..ba42d268f 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -28,7 +28,7 @@ com.google.api-client google-api-client - 2.2.0 + 2.3.0 @@ -41,7 +41,7 @@ com.google.truth truth - 1.3.0 + 1.4.2 test diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index 3dc9c14a1..a006534e0 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -30,7 +30,7 @@ com.google.cloud libraries-bom - 26.30.0 + 26.34.0 pom import @@ -53,7 +53,7 @@ com.google.truth truth - 1.3.0 + 1.4.2 test diff --git a/versions.txt b/versions.txt index 8afb4909f..b7f824d0d 100644 --- a/versions.txt +++ b/versions.txt @@ -1,4 +1,4 @@ # Format: # module:released-version:current-version -google-api-client:2.3.0:2.3.0 +google-api-client:2.4.0:2.4.0