diff --git a/.github/workflows/auto-release.yaml b/.github/workflows/auto-release.yaml index 7c8816a7d..9b4fd4d83 100644 --- a/.github/workflows/auto-release.yaml +++ b/.github/workflows/auto-release.yaml @@ -16,8 +16,8 @@ jobs: return; } - // only approve PRs like "chore(master): release " - if ( !context.payload.pull_request.title.startsWith("chore(master): release") ) { + // only approve PRs like "chore: release " + if ( !context.payload.pull_request.title.startsWith("chore: release") ) { return; } diff --git a/.kokoro/release/publish_javadoc.cfg b/.kokoro/release/publish_javadoc.cfg index 713676594..49b734164 100644 --- a/.kokoro/release/publish_javadoc.cfg +++ b/.kokoro/release/publish_javadoc.cfg @@ -7,12 +7,6 @@ env_vars: { value: "docs-staging" } -# cloud-rad staging -env_vars: { - key: "STAGING_BUCKET_V2" - value: "docs-staging-v2-staging" -} - env_vars: { key: "TRAMPOLINE_BUILD_FILE" value: "github/google-auth-library-java/.kokoro/release/publish_javadoc.sh" @@ -26,4 +20,4 @@ before_action { keyname: "docuploader_service_account" } } -} \ No newline at end of file +} diff --git a/.kokoro/release/publish_javadoc.sh b/.kokoro/release/publish_javadoc.sh index ba83b7f23..b16006b35 100755 --- a/.kokoro/release/publish_javadoc.sh +++ b/.kokoro/release/publish_javadoc.sh @@ -56,22 +56,3 @@ python3 -m docuploader create-metadata \ python3 -m docuploader upload . \ --credentials ${CREDENTIALS} \ --staging-bucket ${STAGING_BUCKET} - -popd - -# V2 due to problems w/ the released javadoc plugin doclava, Java 8 is required. Beware of accidental updates. - -mvn clean site -B -q -Ddevsite.template="${KOKORO_GFILE_DIR}/java/" - -pushd target/devsite/reference - -# create metadata -python3 -m docuploader create-metadata \ - --name ${NAME} \ - --version ${VERSION} \ - --language java - -# upload docs to staging bucket -python3 -m docuploader upload . \ - --credentials ${CREDENTIALS} \ - --staging-bucket ${STAGING_BUCKET_V2} diff --git a/.kokoro/release/publish_javadoc11.sh b/.kokoro/release/publish_javadoc11.sh index 83beb9a06..e4096f3e7 100755 --- a/.kokoro/release/publish_javadoc11.sh +++ b/.kokoro/release/publish_javadoc11.sh @@ -40,6 +40,9 @@ export VERSION=$(grep ${NAME}: versions.txt | cut -d: -f3) # generate yml mvn clean site -B -q -P docFX +# copy README to docfx-yml dir and rename index.md +cp README.md target/docfx-yml/index.md + pushd target/docfx-yml # create metadata @@ -52,4 +55,4 @@ python3 -m docuploader create-metadata \ python3 -m docuploader upload . \ --credentials ${CREDENTIALS} \ --staging-bucket ${STAGING_BUCKET_V2} \ - --destination-prefix docfx- + --destination-prefix docfx diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e4d1f32d..41c26310a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.25.0](https://www.github.com/googleapis/google-auth-library-java/compare/v0.24.1...v0.25.0) (2021-03-16) + + +### Features + +* add self signed jwt support ([#572](https://www.github.com/googleapis/google-auth-library-java/issues/572)) ([efe103a](https://www.github.com/googleapis/google-auth-library-java/commit/efe103a2e688ca915ec9925a72c49bb2a1b3c3b5)) + ### [0.24.1](https://www.github.com/googleapis/google-auth-library-java/compare/v0.24.0...v0.24.1) (2021-02-25) diff --git a/README.md b/README.md index 78430a80f..b328b59a4 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ If you are using Maven, add this to your pom.xml file (notice that you can repla com.google.auth google-auth-library-oauth2-http - 0.24.1 + 0.25.0 ``` [//]: # ({x-version-update-end}) @@ -42,7 +42,7 @@ If you are using Gradle, add this to your dependencies [//]: # ({x-version-update-start:google-auth-library-oauth2-http:released}) ```Groovy -compile 'com.google.auth:google-auth-library-oauth2-http:0.24.1' +compile 'com.google.auth:google-auth-library-oauth2-http:0.25.0' ``` [//]: # ({x-version-update-end}) @@ -50,7 +50,7 @@ If you are using SBT, add this to your dependencies [//]: # ({x-version-update-start:google-auth-library-oauth2-http:released}) ```Scala -libraryDependencies += "com.google.auth" % "google-auth-library-oauth2-http" % "0.24.1" +libraryDependencies += "com.google.auth" % "google-auth-library-oauth2-http" % "0.25.0" ``` [//]: # ({x-version-update-end}) diff --git a/appengine/pom.xml b/appengine/pom.xml index db0a45519..9bc70c605 100644 --- a/appengine/pom.xml +++ b/appengine/pom.xml @@ -5,7 +5,7 @@ com.google.auth google-auth-library-parent - 0.24.1 + 0.25.0 ../pom.xml diff --git a/bom/pom.xml b/bom/pom.xml index daffccd04..b52cc4d20 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.auth google-auth-library-bom - 0.24.1 + 0.25.0 pom Google Auth Library for Java BOM diff --git a/credentials/pom.xml b/credentials/pom.xml index d40022109..4a70f86bb 100644 --- a/credentials/pom.xml +++ b/credentials/pom.xml @@ -4,7 +4,7 @@ com.google.auth google-auth-library-parent - 0.24.1 + 0.25.0 ../pom.xml diff --git a/oauth2_http/java/com/google/auth/oauth2/AppEngineCredentials.java b/oauth2_http/java/com/google/auth/oauth2/AppEngineCredentials.java index baa2f6530..861adba16 100644 --- a/oauth2_http/java/com/google/auth/oauth2/AppEngineCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/AppEngineCredentials.java @@ -79,18 +79,32 @@ class AppEngineCredentials extends GoogleCredentials implements ServiceAccountSi private transient Method getSignature; private transient String account; - AppEngineCredentials(Collection scopes) throws IOException { - this.scopes = scopes == null ? ImmutableSet.of() : ImmutableList.copyOf(scopes); + AppEngineCredentials(Collection scopes, Collection defaultScopes) + throws IOException { + // Use defaultScopes only when scopes don't exist. + if (scopes == null || scopes.isEmpty()) { + this.scopes = + defaultScopes == null ? ImmutableList.of() : ImmutableList.copyOf(defaultScopes); + } else { + this.scopes = ImmutableList.copyOf(scopes); + } this.scopesRequired = this.scopes.isEmpty(); init(); } - AppEngineCredentials(Collection scopes, AppEngineCredentials unscoped) { + AppEngineCredentials( + Collection scopes, Collection defaultScopes, AppEngineCredentials unscoped) { this.appIdentityService = unscoped.appIdentityService; this.getAccessToken = unscoped.getAccessToken; this.getAccessTokenResult = unscoped.getAccessTokenResult; this.getExpirationTime = unscoped.getExpirationTime; - this.scopes = scopes == null ? ImmutableSet.of() : ImmutableList.copyOf(scopes); + // Use defaultScopes only when scopes don't exist. + if (scopes == null || scopes.isEmpty()) { + this.scopes = + defaultScopes == null ? ImmutableSet.of() : ImmutableList.copyOf(defaultScopes); + } else { + this.scopes = ImmutableList.copyOf(scopes); + } this.scopesRequired = this.scopes.isEmpty(); } @@ -145,7 +159,13 @@ public boolean createScopedRequired() { @Override public GoogleCredentials createScoped(Collection scopes) { - return new AppEngineCredentials(scopes, this); + return new AppEngineCredentials(scopes, null, this); + } + + @Override + public GoogleCredentials createScoped( + Collection scopes, Collection defaultScopes) { + return new AppEngineCredentials(scopes, defaultScopes, this); } @Override diff --git a/oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java index ede42cee9..01988d1f4 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java @@ -109,14 +109,22 @@ public class ComputeEngineCredentials extends GoogleCredentials * @param transportFactory HTTP transport factory, creates the transport used to get access * tokens. * @param scopes scope strings for the APIs to be called. May be null or an empty collection. + * @param defaultScopes default scope strings for the APIs to be called. May be null or an empty + * collection. Default scopes are ignored if scopes are provided. */ private ComputeEngineCredentials( - HttpTransportFactory transportFactory, Collection scopes) { + HttpTransportFactory transportFactory, + Collection scopes, + Collection defaultScopes) { this.transportFactory = firstNonNull( transportFactory, getFromServiceLoader(HttpTransportFactory.class, OAuth2Utils.HTTP_TRANSPORT_FACTORY)); this.transportFactoryClassName = this.transportFactory.getClass().getName(); + // Use defaultScopes only when scopes don't exist. + if (scopes == null || scopes.isEmpty()) { + scopes = defaultScopes; + } if (scopes == null) { this.scopes = ImmutableSet.of(); } else { @@ -129,7 +137,14 @@ private ComputeEngineCredentials( /** Clones the compute engine account with the specified scopes. */ @Override public GoogleCredentials createScoped(Collection newScopes) { - return new ComputeEngineCredentials(this.transportFactory, newScopes); + return new ComputeEngineCredentials(this.transportFactory, newScopes, null); + } + + /** Clones the compute engine account with the specified scopes. */ + @Override + public GoogleCredentials createScoped( + Collection newScopes, Collection newDefaultScopes) { + return new ComputeEngineCredentials(this.transportFactory, newScopes, newDefaultScopes); } /** @@ -138,7 +153,7 @@ public GoogleCredentials createScoped(Collection newScopes) { * @return new ComputeEngineCredentials */ public static ComputeEngineCredentials create() { - return new ComputeEngineCredentials(null, null); + return new ComputeEngineCredentials(null, null, null); } public final Collection getScopes() { @@ -465,7 +480,7 @@ public Collection getScopes() { } public ComputeEngineCredentials build() { - return new ComputeEngineCredentials(transportFactory, scopes); + return new ComputeEngineCredentials(transportFactory, scopes, null); } } } diff --git a/oauth2_http/java/com/google/auth/oauth2/DefaultCredentialsProvider.java b/oauth2_http/java/com/google/auth/oauth2/DefaultCredentialsProvider.java index 78ad73066..12fff6a37 100644 --- a/oauth2_http/java/com/google/auth/oauth2/DefaultCredentialsProvider.java +++ b/oauth2_http/java/com/google/auth/oauth2/DefaultCredentialsProvider.java @@ -301,7 +301,8 @@ private GoogleCredentials tryGetAppEngineCredential() throws IOException { if (!onAppEngine) { return null; } - return new AppEngineCredentials(Collections.emptyList()); + return new AppEngineCredentials( + Collections.emptyList(), Collections.emptyList()); } private final GoogleCredentials tryGetComputeCredentials(HttpTransportFactory transportFactory) { diff --git a/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java b/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java index 3e61e5d60..5a215322f 100644 --- a/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java @@ -235,6 +235,20 @@ public GoogleCredentials createScoped(Collection scopes) { return this; } + /** + * If the credentials support scopes, creates a copy of the the identity with the specified scopes + * and default scopes; otherwise, returns the same instance. This is mainly used by client + * libraries. + * + * @param scopes Collection of scopes to request. + * @param defaultScopes Collection of default scopes to request. + * @return GoogleCredentials with requested scopes. + */ + public GoogleCredentials createScoped( + Collection scopes, Collection defaultScopes) { + return this; + } + /** * If the credentials support scopes, creates a copy of the the identity with the specified * scopes; otherwise, returns the same instance. diff --git a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java index e12f8d412..aa9043611 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java @@ -53,6 +53,7 @@ import com.google.api.client.util.PemReader.Section; import com.google.api.client.util.Preconditions; import com.google.api.client.util.SecurityUtils; +import com.google.auth.RequestMetadataCallback; import com.google.auth.ServiceAccountSigner; import com.google.auth.http.HttpTransportFactory; import com.google.common.annotations.VisibleForTesting; @@ -80,6 +81,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.Executor; /** * OAuth2 credentials representing a Service Account for calling Google APIs. @@ -104,27 +106,29 @@ public class ServiceAccountCredentials extends GoogleCredentials private final String transportFactoryClassName; private final URI tokenServerUri; private final Collection scopes; + private final Collection defaultScopes; private final String quotaProjectId; private final int lifetime; private transient HttpTransportFactory transportFactory; + private transient ServiceAccountJwtAccessCredentials jwtCredentials = null; /** * Constructor with minimum identifying information and custom HTTP transport. * - * @param clientId Client ID of the service account from the console. May be null. - * @param clientEmail Client email address of the service account from the console. - * @param privateKey RSA private key object for the service account. - * @param privateKeyId Private key identifier for the service account. May be null. - * @param scopes Scope strings for the APIs to be called. May be null or an empty collection, - * which results in a credential that must have createScoped called before use. + * @param clientId client ID of the service account from the console. May be null. + * @param clientEmail client email address of the service account from the console + * @param privateKey RSA private key object for the service account + * @param privateKeyId private key identifier for the service account. May be null. + * @param scopes scope strings for the APIs to be called. May be null or an empty collection. + * @param defaultScopes default scope strings for the APIs to be called. May be null or an empty. * @param transportFactory HTTP transport factory, creates the transport used to get access * tokens. * @param tokenServerUri URI of the end point that provides tokens. - * @param serviceAccountUser Email of the user account to impersonate, if delegating domain-wide + * @param serviceAccountUser email of the user account to impersonate, if delegating domain-wide * authority to the service account. * @param projectId the project used for billing - * @param quotaProjectId The project used for quota and billing purposes. May be null. + * @param quotaProjectId the project used for quota and billing purposes. May be null. * @param lifetime number of seconds the access token should be valid for. The value should be at * most 43200 (12 hours). If the token is used for calling a Google API, then the value should * be at most 3600 (1 hour). If the given value is 0, then the default value 3600 will be used @@ -136,6 +140,7 @@ public class ServiceAccountCredentials extends GoogleCredentials PrivateKey privateKey, String privateKeyId, Collection scopes, + Collection defaultScopes, HttpTransportFactory transportFactory, URI tokenServerUri, String serviceAccountUser, @@ -147,6 +152,8 @@ public class ServiceAccountCredentials extends GoogleCredentials this.privateKey = Preconditions.checkNotNull(privateKey); this.privateKeyId = privateKeyId; this.scopes = (scopes == null) ? ImmutableSet.of() : ImmutableSet.copyOf(scopes); + this.defaultScopes = + (defaultScopes == null) ? ImmutableSet.of() : ImmutableSet.copyOf(defaultScopes); this.transportFactory = firstNonNull( transportFactory, @@ -160,6 +167,18 @@ public class ServiceAccountCredentials extends GoogleCredentials throw new IllegalStateException("lifetime must be less than or equal to 43200"); } this.lifetime = lifetime; + + // Use self signed JWT if scopes is not set, see https://google.aip.dev/auth/4111. + if (this.scopes.isEmpty()) { + jwtCredentials = + new ServiceAccountJwtAccessCredentials.Builder() + .setClientEmail(clientEmail) + .setClientId(clientId) + .setPrivateKey(privateKey) + .setPrivateKeyId(privateKeyId) + .setQuotaProjectId(quotaProjectId) + .build(); + } } /** @@ -204,6 +223,7 @@ static ServiceAccountCredentials fromJson( privateKeyPkcs8, privateKeyId, null, + null, transportFactory, tokenServerUriFromCreds, null, @@ -231,7 +251,51 @@ public static ServiceAccountCredentials fromPkcs8( Collection scopes) throws IOException { return fromPkcs8( - clientId, clientEmail, privateKeyPkcs8, privateKeyId, scopes, null, null, null, null, null); + clientId, + clientEmail, + privateKeyPkcs8, + privateKeyId, + scopes, + null, + null, + null, + null, + null, + null); + } + + /** + * Factory with minimum identifying information using PKCS#8 for the private key. + * + * @param clientId client ID of the service account from the console. May be null. + * @param clientEmail client email address of the service account from the console + * @param privateKeyPkcs8 RSA private key object for the service account in PKCS#8 format. + * @param privateKeyId private key identifier for the service account. May be null. + * @param scopes scope strings for the APIs to be called. May be null or an empty collection. + * @param defaultScopes default scope strings for the APIs to be called. May be null or an empty. + * @return new ServiceAccountCredentials created from a private key + * @throws IOException if the credential cannot be created from the private key + */ + public static ServiceAccountCredentials fromPkcs8( + String clientId, + String clientEmail, + String privateKeyPkcs8, + String privateKeyId, + Collection scopes, + Collection defaultScopes) + throws IOException { + return fromPkcs8( + clientId, + clientEmail, + privateKeyPkcs8, + privateKeyId, + scopes, + defaultScopes, + null, + null, + null, + null, + null); } /** @@ -265,6 +329,49 @@ public static ServiceAccountCredentials fromPkcs8( privateKeyPkcs8, privateKeyId, scopes, + null, + transportFactory, + tokenServerUri, + null, + null, + null); + } + + /** + * Factory with minimum identifying information and custom transport using PKCS#8 for the private + * key. + * + * @param clientId client ID of the service account from the console. May be null. + * @param clientEmail client email address of the service account from the console + * @param privateKeyPkcs8 RSA private key object for the service account in PKCS#8 format. + * @param privateKeyId private key identifier for the service account. May be null. + * @param scopes scope strings for the APIs to be called. May be null or an empty collection, + * which results in a credential that must have createScoped called before use. + * @param defaultScopes default scope strings for the APIs to be called. May be null or an empty + * collection, which results in a credential that must have createScoped called before use. + * @param transportFactory HTTP transport factory, creates the transport used to get access + * tokens. + * @param tokenServerUri URI of the end point that provides tokens + * @return new ServiceAccountCredentials created from a private key + * @throws IOException if the credential cannot be created from the private key + */ + public static ServiceAccountCredentials fromPkcs8( + String clientId, + String clientEmail, + String privateKeyPkcs8, + String privateKeyId, + Collection scopes, + Collection defaultScopes, + HttpTransportFactory transportFactory, + URI tokenServerUri) + throws IOException { + return fromPkcs8( + clientId, + clientEmail, + privateKeyPkcs8, + privateKeyId, + scopes, + defaultScopes, transportFactory, tokenServerUri, null, @@ -306,6 +413,52 @@ public static ServiceAccountCredentials fromPkcs8( privateKeyPkcs8, privateKeyId, scopes, + null, + transportFactory, + tokenServerUri, + serviceAccountUser, + null, + null); + } + + /** + * Factory with minimum identifying information and custom transport using PKCS#8 for the private + * key. + * + * @param clientId client ID of the service account from the console. May be null. + * @param clientEmail client email address of the service account from the console + * @param privateKeyPkcs8 RSA private key object for the service account in PKCS#8 format. + * @param privateKeyId private key identifier for the service account. May be null. + * @param scopes scope strings for the APIs to be called. May be null or an empty collection, + * which results in a credential that must have createScoped called before use. + * @param defaultScopes default scope strings for the APIs to be called. May be null or an empty + * collection, which results in a credential that must have createScoped called before use. + * @param transportFactory HTTP transport factory, creates the transport used to get access + * tokens. + * @param tokenServerUri URI of the end point that provides tokens + * @param serviceAccountUser the email of the user account to impersonate, if delegating + * domain-wide authority to the service account. + * @return new ServiceAccountCredentials created from a private key + * @throws IOException if the credential cannot be created from the private key + */ + public static ServiceAccountCredentials fromPkcs8( + String clientId, + String clientEmail, + String privateKeyPkcs8, + String privateKeyId, + Collection scopes, + Collection defaultScopes, + HttpTransportFactory transportFactory, + URI tokenServerUri, + String serviceAccountUser) + throws IOException { + return fromPkcs8( + clientId, + clientEmail, + privateKeyPkcs8, + privateKeyId, + scopes, + defaultScopes, transportFactory, tokenServerUri, serviceAccountUser, @@ -319,6 +472,7 @@ static ServiceAccountCredentials fromPkcs8( String privateKeyPkcs8, String privateKeyId, Collection scopes, + Collection defaultScopes, HttpTransportFactory transportFactory, URI tokenServerUri, String serviceAccountUser, @@ -332,6 +486,7 @@ static ServiceAccountCredentials fromPkcs8( privateKey, privateKeyId, scopes, + defaultScopes, transportFactory, tokenServerUri, serviceAccountUser, @@ -411,12 +566,6 @@ public static ServiceAccountCredentials fromStream( */ @Override public AccessToken refreshAccessToken() throws IOException { - if (createScopedRequired()) { - throw new IOException( - "Scopes not configured for service account. Scoped should be specified" - + " by calling createScoped or passing scopes to constructor."); - } - JsonFactory jsonFactory = OAuth2Utils.JSON_FACTORY; long currentTime = clock.currentTimeMillis(); String assertion = createAssertion(jsonFactory, currentTime, tokenServerUri.toString()); @@ -501,10 +650,14 @@ public IdToken idTokenWithAudience(String targetAudience, List