diff --git a/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryOptions.java b/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryOptions.java index 8ac4c622a91c..b8b7994f8775 100644 --- a/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryOptions.java +++ b/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryOptions.java @@ -77,7 +77,9 @@ public static class Builder extends ServiceOptions.Builder resultRetryAlgorithm; - private Builder() {} + private Builder() { + setUseJwtAccessWithScope(false); + } private Builder(BigQueryOptions options) { super(options); diff --git a/java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/BigQueryOptionsTest.java b/java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/BigQueryOptionsTest.java index 050deba4af16..49d6f6fe031f 100644 --- a/java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/BigQueryOptionsTest.java +++ b/java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/BigQueryOptionsTest.java @@ -92,4 +92,11 @@ void dataFormatOptionsSetterHasPrecedence() { assertTrue(options.getDataFormatOptions().useInt64Timestamp()); } + + @Test + void testUseJwtAccessWithScope_defaultsToFalse() { + BigQueryOptions options = BigQueryOptions.newBuilder().setProjectId("project-id").build(); + + assertFalse(options.getUseJwtAccessWithScope()); + } } diff --git a/java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageOptions.java b/java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageOptions.java index 723a11dc34ce..97eecedaeef1 100644 --- a/java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageOptions.java +++ b/java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageOptions.java @@ -112,7 +112,9 @@ public DefaultStorageRpcFactory() { public abstract static class Builder extends ServiceOptions.Builder { - Builder() {} + Builder() { + setUseJwtAccessWithScope(false); + } Builder(StorageOptions options) { super(options); diff --git a/java-storage/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageOptionsBuilderTest.java b/java-storage/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageOptionsBuilderTest.java index 4601a3b2e8df..240040519635 100644 --- a/java-storage/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageOptionsBuilderTest.java +++ b/java-storage/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageOptionsBuilderTest.java @@ -69,6 +69,15 @@ public void grpc() throws Exception { () -> assertThat(rebuilt.hashCode()).isEqualTo(base.hashCode())); } + @Test + public void useJwtAccessWithScope_defaultsToFalse() { + HttpStorageOptions httpOptions = HttpStorageOptions.http().build(); + GrpcStorageOptions grpcOptions = GrpcStorageOptions.grpc().build(); + + assertThat(httpOptions.getUseJwtAccessWithScope()).isFalse(); + assertThat(grpcOptions.getUseJwtAccessWithScope()).isFalse(); + } + private static class MyStorageRetryStrategy implements StorageRetryStrategy { @Override diff --git a/sdk-platform-java/java-core/google-cloud-core/src/main/java/com/google/cloud/ServiceOptions.java b/sdk-platform-java/java-core/google-cloud-core/src/main/java/com/google/cloud/ServiceOptions.java index 92aaa9d6a9e1..89f96f4e38d8 100644 --- a/sdk-platform-java/java-core/google-cloud-core/src/main/java/com/google/cloud/ServiceOptions.java +++ b/sdk-platform-java/java-core/google-cloud-core/src/main/java/com/google/cloud/ServiceOptions.java @@ -106,6 +106,7 @@ public abstract class ServiceOptions< private final TransportOptions transportOptions; private final HeaderProvider headerProvider; private final String quotaProjectId; + private final boolean useJwtAccessWithScope; private transient ServiceRpcFactory serviceRpcFactory; private transient ServiceFactory serviceFactory; @@ -140,6 +141,7 @@ public abstract static class Builder< private HeaderProvider headerProvider; private String clientLibToken = ServiceOptions.getGoogApiClientLibName(); private String quotaProjectId; + private boolean useJwtAccessWithScope = true; private ApiTracerFactory apiTracerFactory; @@ -159,6 +161,7 @@ protected Builder(ServiceOptions options) { transportOptions = options.transportOptions; clientLibToken = options.clientLibToken; quotaProjectId = options.quotaProjectId; + useJwtAccessWithScope = options.useJwtAccessWithScope; apiTracerFactory = options.apiTracerFactory; } @@ -313,6 +316,18 @@ public B setQuotaProjectId(String quotaProjectId) { return self(); } + /** + * Sets the configuration determining whether self-signed JWT with scopes are used for service + * account credentials. + * + * @param useJwtAccessWithScope whether to use self-signed JWT with scopes + * @return the builder + */ + public B setUseJwtAccessWithScope(final boolean useJwtAccessWithScope) { + this.useJwtAccessWithScope = useJwtAccessWithScope; + return self(); + } + /** * Sets the {@link ApiTracerFactory}. It will be used to create an {@link ApiTracer} that is * annotated throughout the lifecycle of an RPC operation. @@ -365,6 +380,7 @@ protected ServiceOptions( builder.quotaProjectId != null ? builder.quotaProjectId : getValueFromCredentialsFile(getCredentialsPath(), "quota_project_id"); + useJwtAccessWithScope = builder.useJwtAccessWithScope; apiTracerFactory = builder.apiTracerFactory; } @@ -650,6 +666,11 @@ public Credentials getScopedCredentials() { && ((GoogleCredentials) credentials).createScopedRequired()) { credentialsToReturn = ((GoogleCredentials) credentials).createScoped(getScopes()); } + if (credentialsToReturn instanceof ServiceAccountCredentials) { + credentialsToReturn = + ((ServiceAccountCredentials) credentialsToReturn) + .createWithUseJwtAccessWithScope(getUseJwtAccessWithScope()); + } return credentialsToReturn; } @@ -823,6 +844,15 @@ public String getQuotaProjectId() { return quotaProjectId; } + /** + * Returns true when self-signed JWT with scopes are used for service account credentials. + * + * @return true when self-signed JWT with scopes are used + */ + public boolean getUseJwtAccessWithScope() { + return useJwtAccessWithScope; + } + /** * Returns the resolved host for the Service to connect to Google Cloud * diff --git a/sdk-platform-java/java-core/google-cloud-core/src/test/java/com/google/cloud/ServiceOptionsTest.java b/sdk-platform-java/java-core/google-cloud-core/src/test/java/com/google/cloud/ServiceOptionsTest.java index eb8eab37b2a6..3c11ba0eeefa 100644 --- a/sdk-platform-java/java-core/google-cloud-core/src/test/java/com/google/cloud/ServiceOptionsTest.java +++ b/sdk-platform-java/java-core/google-cloud-core/src/test/java/com/google/cloud/ServiceOptionsTest.java @@ -630,6 +630,46 @@ void testIsValidUniverseDomain_userUniverseDomainConfig_nonGDUCredentials() thro assertThat(options.hasValidUniverseDomain()).isTrue(); } + @Test + void testGetScopedCredentials_useJwtAccessWithScope_enablesByDefault() { + TestServiceOptions options = + TestServiceOptions.newBuilder() + .setProjectId("project-id") + .setCredentials(credentials) + .build(); + com.google.auth.Credentials scoped = options.getScopedCredentials(); + assertThat(scoped).isInstanceOf(ServiceAccountCredentials.class); + assertThat(((ServiceAccountCredentials) scoped).getUseJwtAccessWithScope()).isTrue(); + } + + @Test + void testGetScopedCredentials_useJwtAccessWithScope_canBeDisabled() { + TestServiceOptions options = + new TestServiceOptions.Builder() + .setProjectId("project-id") + .setCredentials(credentials) + .setUseJwtAccessWithScope(false) + .build(); + com.google.auth.Credentials scoped = options.getScopedCredentials(); + assertThat(scoped).isInstanceOf(ServiceAccountCredentials.class); + assertThat(((ServiceAccountCredentials) scoped).getUseJwtAccessWithScope()).isFalse(); + } + + @Test + void testGetScopedCredentials_useJwtAccessWithScope_overridesCredentials() { + ServiceAccountCredentials trueCredentials = + ((ServiceAccountCredentials) credentials).createWithUseJwtAccessWithScope(true); + TestServiceOptions options = + new TestServiceOptions.Builder() + .setProjectId("project-id") + .setCredentials(trueCredentials) + .setUseJwtAccessWithScope(false) + .build(); + com.google.auth.Credentials scoped = options.getScopedCredentials(); + assertThat(scoped).isInstanceOf(ServiceAccountCredentials.class); + assertThat(((ServiceAccountCredentials) scoped).getUseJwtAccessWithScope()).isFalse(); + } + private HttpResponse createHttpResponseWithHeader(final Multimap headers) throws Exception { HttpTransport mockHttpTransport =