diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml index 9786771c4..3473042c0 100644 --- a/.github/.OwlBot.lock.yaml +++ b/.github/.OwlBot.lock.yaml @@ -13,4 +13,4 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-java:latest - digest: sha256:3c950ed12391ebaffd1ee66d0374766a1c50144ebe6a7a0042300b2e6bb5856b + digest: sha256:df8d7b2cc0dbc65871e7edd86601901a0612b272fa3f7f0eb590c5c53aa5f92e diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 0b5bc917c..b1925b41c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -5,3 +5,6 @@ Thank you for opening a Pull Request! Before submitting your PR, there are a few - [ ] Appropriate docs were updated (if necessary) Fixes # ☕️ + +If you write sample code, please follow the [samples format]( +https://github.com/GoogleCloudPlatform/java-docs-samples/blob/main/SAMPLE_FORMAT.md). diff --git a/.github/workflows/auto-release.yaml b/.github/workflows/auto-release.yaml index 18e23230d..7a106d007 100644 --- a/.github/workflows/auto-release.yaml +++ b/.github/workflows/auto-release.yaml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest if: contains(github.head_ref, 'release-please') steps: - - uses: actions/github-script@v5 + - uses: actions/github-script@v6 with: github-token: ${{secrets.YOSHI_APPROVER_TOKEN}} debug: true diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6b5e56aaa..83ef7f9c2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,8 +27,8 @@ jobs: matrix: java: [8, 11, 17] steps: - - uses: actions/checkout@v2 - - uses: actions/setup-java@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 with: distribution: zulu java-version: ${{matrix.java}} @@ -39,8 +39,8 @@ jobs: windows: runs-on: windows-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-java@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 with: distribution: zulu java-version: 8 @@ -54,8 +54,8 @@ jobs: matrix: java: [8, 11, 17] steps: - - uses: actions/checkout@v2 - - uses: actions/setup-java@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 with: distribution: zulu java-version: ${{matrix.java}} @@ -64,8 +64,8 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-java@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 with: distribution: zulu java-version: 11 @@ -76,8 +76,8 @@ jobs: clirr: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-java@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 with: distribution: zulu java-version: 8 diff --git a/.kokoro/build.bat b/.kokoro/build.bat index cc602c9eb..067cf4a4c 100644 --- a/.kokoro/build.bat +++ b/.kokoro/build.bat @@ -1,18 +1,18 @@ +:: Copyright 2022 Google LLC +:: +:: Licensed under the Apache License, Version 2.0 (the "License"); +:: you may not use this file except in compliance with the License. +:: You may obtain a copy of the License at +:: +:: http://www.apache.org/licenses/LICENSE-2.0 +:: +:: Unless required by applicable law or agreed to in writing, software +:: distributed under the License is distributed on an "AS IS" BASIS, +:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +:: See the License for the specific language governing permissions and +:: limitations under the License. +:: Github action job to test core java library features on +:: downstream client libraries before they are released. :: See documentation in type-shell-output.bat -# Copyright 2022 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# Github action job to test core java library features on -# downstream client libraries before they are released. "C:\Program Files\Git\bin\bash.exe" %~dp0build.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index b45b0a790..4582e8ef6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## [1.6.0](https://github.com/googleapis/google-auth-library-java/compare/v1.5.3...v1.6.0) (2022-03-15) + + +### Features + +* Add AWS Session Token to Metadata Requests ([#850](https://github.com/googleapis/google-auth-library-java/issues/850)) ([577e9a5](https://github.com/googleapis/google-auth-library-java/commit/577e9a52204b0d6026a302bb7efe2c6162d57945)) + + +### Bug Fixes + +* ImmutableSet converted to List for Impersonated Credentials ([#732](https://github.com/googleapis/google-auth-library-java/issues/732)) ([7dcd549](https://github.com/googleapis/google-auth-library-java/commit/7dcd549c4ef0617e657315b7a718368fbd162997)) +* update library docs ([#868](https://github.com/googleapis/google-auth-library-java/issues/868)) ([a081015](https://github.com/googleapis/google-auth-library-java/commit/a081015cb72ade91c022b58261c8d253e46a7793)) + ### [1.5.3](https://github.com/googleapis/google-auth-library-java/compare/v1.5.2...v1.5.3) (2022-02-24) diff --git a/README.md b/README.md index 90f92a06d..9370e67e8 100644 --- a/README.md +++ b/README.md @@ -2,22 +2,39 @@ Open source authentication client library for Java. -[![unstable](http://badges.github.io/stability-badges/dist/unstable.svg)](http://github.com/badges/stability-badges) +[![stable](http://badges.github.io/stability-badges/dist/stable.svg)](http://github.com/badges/stability-badges) [![Maven](https://img.shields.io/maven-central/v/com.google.auth/google-auth-library-credentials.svg)](https://img.shields.io/maven-central/v/com.google.auth/google-auth-library-credentials.svg) - [API Documentation](https://googleapis.dev/java/google-auth-library/latest) This project consists of 3 artifacts: -- [*google-auth-library-credentials*](#google-auth-library-credentials): contains base classes and +- [*google-auth-library-credentials*](#google-auth-library-credentials): contains base classes and interfaces for Google credentials -- [*google-auth-library-appengine*](#google-auth-library-appengine): contains App Engine +- [*google-auth-library-appengine*](#google-auth-library-appengine): contains App Engine credentials. This artifact depends on the App Engine SDK. -- [*google-auth-library-oauth2-http*](#google-auth-library-oauth2-http): contains a wide variety of +- [*google-auth-library-oauth2-http*](#google-auth-library-oauth2-http): contains a wide variety of credentials as well as utility methods to create them and to get Application Default Credentials -> Note: This client is a work-in-progress, and may occasionally -> make backwards-incompatible changes. +**Table of contents:** + + +* [Quickstart](#quickstart) + +* [google-auth-library-oauth2-http](#google-auth-library-oauth2-http) + * [Application Default Credentials](#application-default-credentials) + * [ImpersonatedCredentials](#impersonatedcredentials) + * [Workload Identity Federation](#workload-identity-federation) + * [Downscoping with Credential Access Boundaries](#downscoping-with-credential-access-boundaries) + * [Configuring a Proxy](#configuring-a-proxy) + * [Using Credentials with google-http-client](#using-credentials-with-google-http-client) + * [Verifying JWT Tokens](#verifying-a-signature) +* [google-auth-library-credentials](#google-auth-library-credentials) +* [google-auth-library-appengine](#google-auth-library-appengine) +* [CI Status](#ci-status) +* [Contributing](#contributing) +* [License](#license) + ## Quickstart @@ -53,64 +70,27 @@ libraryDependencies += "com.google.auth" % "google-auth-library-oauth2-http" % " ``` [//]: # ({x-version-update-end}) -## google-auth-library-credentials - -This artifact contains base classes and interfaces for Google credentials: -- `Credentials`: base class for an authorized identity. Implementations of this class can be used to -authorize your application -- `RequestMetadataCallback`: interface for the callback that receives the result of the asynchronous -`Credentials.getRequestMetadata(URI, Executor, RequestMetadataCallback)` -- `ServiceAccountSigner`: interface for a service account signer. Implementations of this class are -capable of signing byte arrays using the credentials associated to a Google Service Account - -## google-auth-library-appengine - -This artifact depends on the App Engine SDK (`appengine-api-1.0-sdk`) and should be used only by -applications running on App Engine environments that use urlfetch. The `AppEngineCredentials` class -allows you to authorize your App Engine application given an instance of -[AppIdentityService][appengine-app-identity-service]. - -Usage: - -```java -import com.google.appengine.api.appidentity.AppIdentityService; -import com.google.appengine.api.appidentity.AppIdentityServiceFactory; -import com.google.auth.Credentials; -import com.google.auth.appengine.AppEngineCredentials; - -AppIdentityService appIdentityService = AppIdentityServiceFactory.getAppIdentityService(); +## google-auth-library-oauth2-http -Credentials credentials = - AppEngineCredentials.newBuilder() - .setScopes(...) - .setAppIdentityService(appIdentityService) - .build(); -``` +### Application Default Credentials -**Important: `com.google.auth.appengine.AppEngineCredentials` is a separate class from -`com.google.auth.oauth2.AppEngineCredentials`.** +This library provides an implementation of [Application Default Credentials](https://google.aip.dev/auth/4110) +for Java. The [Application Default Credentials](https://google.aip.dev/auth/4110) +provide a simple way to get authorization credentials for use in calling Google APIs. -## google-auth-library-oauth2-http +They are best suited for cases when the call needs to have the same identity and +authorization level for the application independent of the user. This is the recommended +approach to authorize calls to Cloud APIs, particularly when you're building an application +that uses Google Cloud Platform. -### Application Default Credentials +Application Default Credentials also support workload identity federation to access +Google Cloud resources from non-Google Cloud platforms including Amazon Web Services (AWS), +Microsoft Azure or any identity provider that supports OpenID Connect (OIDC). Workload +identity federation is recommended for non-Google Cloud environments as it avoids the +need to download, manage and store service account private keys locally, see: +[Workload Identity Federation](#workload-identity-federation). -This artifact contains a wide variety of credentials as well as utility methods to create them and -to get Application Default Credentials. -Credentials classes contained in this artifact are: -- `CloudShellCredentials`: credentials for Google Cloud Shell built-in service account -- `ComputeEngineCredentials`: credentials for Google Compute Engine built-in service account -- `OAuth2Credentials`: base class for OAuth2-based credentials -- `ServiceAccountCredentials`: credentials for a Service Account - use a JSON Web Token (JWT) to get -access tokens -- `ServiceAccountJwtAccessCredentials`: credentials for a Service Account - use JSON Web Token (JWT) -directly in the request metadata to provide authorization -- `UserCredentials`: credentials for a user identity and consent -- `ExternalAccountCredentials`: base class for credentials using workload identity federation to -access Google Cloud resources from non-Google Cloud platforms -- `IdentityPoolCredentials`: credentials using workload identity federation to access Google Cloud -resources from Microsoft Azure or any identity provider that supports OpenID Connect (OIDC) -- `AwsCredentials`: credentials using workload identity federation to access Google Cloud resources -from Amazon Web Services (AWS) +#### Getting Application Default Credentials To get Application Default Credentials use `GoogleCredentials.getApplicationDefault()` or `GoogleCredentials.getApplicationDefault(HttpTransportFactory)`. These methods return the @@ -125,7 +105,7 @@ following are searched (in order) to find the Application Default Credentials: - Skip this check by setting the environment variable `NO_GCE_CHECK=true` - Customize the GCE metadata server address by setting the environment variable `GCE_METADATA_HOST=` -### Explicit Credential Loading +#### Explicit Credential Loading To get Credentials from a Service Account JSON key use `GoogleCredentials.fromStream(InputStream)` or `GoogleCredentials.fromStream(InputStream, HttpTransportFactory)`. Note that the credentials must @@ -211,6 +191,11 @@ Where the following variables need to be substituted: This generates the configuration file in the specified output file. +If you want to use the AWS IMDSv2 flow, you can add the field below to the credential_source in your AWS ADC configuration file: +"imdsv2_session_token_url": "http://169.254.169.254/latest/api/token" + +The gcloud create-cred-config command will be updated to support this soon. + You can now [use the Auth library](#using-external-identities) to call Google Cloud resources from AWS. @@ -647,11 +632,48 @@ try { For more options, see the [`TokenVerifier.Builder`][token-verifier-builder] documentation. + +## google-auth-library-credentials + +This artifact contains base classes and interfaces for Google credentials: +- `Credentials`: base class for an authorized identity. Implementations of this class can be used to + authorize your application +- `RequestMetadataCallback`: interface for the callback that receives the result of the asynchronous + `Credentials.getRequestMetadata(URI, Executor, RequestMetadataCallback)` +- `ServiceAccountSigner`: interface for a service account signer. Implementations of this class are + capable of signing byte arrays using the credentials associated to a Google Service Account + +## google-auth-library-appengine + +This artifact depends on the App Engine SDK (`appengine-api-1.0-sdk`) and should be used only by +applications running on App Engine environments that use urlfetch. The `AppEngineCredentials` class +allows you to authorize your App Engine application given an instance of +[AppIdentityService][appengine-app-identity-service]. + +Usage: + +```java +import com.google.appengine.api.appidentity.AppIdentityService; +import com.google.appengine.api.appidentity.AppIdentityServiceFactory; +import com.google.auth.Credentials; +import com.google.auth.appengine.AppEngineCredentials; + +AppIdentityService appIdentityService = AppIdentityServiceFactory.getAppIdentityService(); + +Credentials credentials = + AppEngineCredentials.newBuilder() + .setScopes(...) + .setAppIdentityService(appIdentityService) + .build(); +``` + +**Important: `com.google.auth.appengine.AppEngineCredentials` is a separate class from +`com.google.auth.oauth2.AppEngineCredentials`.** + ## CI Status Java Version | Status ------------ | ------ -Java 7 | [![Kokoro CI](http://storage.googleapis.com/cloud-devrel-public/java/badges/google-auth-library-java/java7.svg)](http://storage.googleapis.com/cloud-devrel-public/java/badges/google-auth-library-java/java7.html) Java 8 | [![Kokoro CI](http://storage.googleapis.com/cloud-devrel-public/java/badges/google-auth-library-java/java8.svg)](http://storage.googleapis.com/cloud-devrel-public/java/badges/google-auth-library-java/java8.html) Java 8 OSX | [![Kokoro CI](http://storage.googleapis.com/cloud-devrel-public/java/badges/google-auth-library-java/java8-osx.svg)](http://storage.googleapis.com/cloud-devrel-public/java/badges/google-auth-library-java/java8-osx.html) Java 8 Windows | [![Kokoro CI](http://storage.googleapis.com/cloud-devrel-public/java/badges/google-auth-library-java/java8-win.svg)](http://storage.googleapis.com/cloud-devrel-public/java/badges/google-auth-library-java/java8-win.html) diff --git a/appengine/pom.xml b/appengine/pom.xml index 625df3848..e5154125e 100644 --- a/appengine/pom.xml +++ b/appengine/pom.xml @@ -5,7 +5,7 @@ com.google.auth google-auth-library-parent - 1.5.3 + 1.6.0 ../pom.xml diff --git a/bom/pom.xml b/bom/pom.xml index 864a3b9e3..fe9057003 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.auth google-auth-library-bom - 1.5.3 + 1.6.0 pom Google Auth Library for Java BOM @@ -72,7 +72,7 @@ org.sonatype.plugins nexus-staging-maven-plugin - 1.6.8 + 1.6.12 true ossrh diff --git a/credentials/pom.xml b/credentials/pom.xml index 2f90eba96..e3a3de0b2 100644 --- a/credentials/pom.xml +++ b/credentials/pom.xml @@ -4,7 +4,7 @@ com.google.auth google-auth-library-parent - 1.5.3 + 1.6.0 ../pom.xml diff --git a/oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java b/oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java index 54c7a13e9..168af3054 100644 --- a/oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java @@ -32,6 +32,9 @@ package com.google.auth.oauth2; import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpContent; +import com.google.api.client.http.HttpHeaders; +import com.google.api.client.http.HttpMethods; import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpRequestFactory; import com.google.api.client.http.HttpResponse; @@ -48,6 +51,7 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.annotation.Nullable; /** * AWS credentials representing a third-party identity for calling Google APIs. @@ -56,15 +60,22 @@ */ public class AwsCredentials extends ExternalAccountCredentials { + static final String AWS_IMDSV2_SESSION_TOKEN_HEADER = "x-aws-ec2-metadata-token"; + static final String AWS_IMDSV2_SESSION_TOKEN_TTL_HEADER = "x-aws-ec2-metadata-token-ttl-seconds"; + static final String AWS_IMDSV2_SESSION_TOKEN_TTL = "300"; + /** * The AWS credential source. Stores data required to retrieve the AWS credential from the AWS * metadata server. */ static class AwsCredentialSource extends CredentialSource { + private static final String IMDSV2_SESSION_TOKEN_URL_FIELD_NAME = "imdsv2_session_token_url"; + private final String regionUrl; private final String url; private final String regionalCredentialVerificationUrl; + private final String imdsv2SessionTokenUrl; /** * The source of the AWS credential. The credential source map must contain the @@ -107,6 +118,13 @@ static class AwsCredentialSource extends CredentialSource { this.url = (String) credentialSourceMap.get("url"); this.regionalCredentialVerificationUrl = (String) credentialSourceMap.get("regional_cred_verification_url"); + + if (credentialSourceMap.containsKey(IMDSV2_SESSION_TOKEN_URL_FIELD_NAME)) { + this.imdsv2SessionTokenUrl = + (String) credentialSourceMap.get(IMDSV2_SESSION_TOKEN_URL_FIELD_NAME); + } else { + this.imdsv2SessionTokenUrl = null; + } } } @@ -135,11 +153,13 @@ public AccessToken refreshAccessToken() throws IOException { @Override public String retrieveSubjectToken() throws IOException { + Map metadataRequestHeaders = createMetadataRequestHeaders(awsCredentialSource); + // The targeted region is required to generate the signed request. The regional // endpoint must also be used. - String region = getAwsRegion(); + String region = getAwsRegion(metadataRequestHeaders); - AwsSecurityCredentials credentials = getAwsSecurityCredentials(); + AwsSecurityCredentials credentials = getAwsSecurityCredentials(metadataRequestHeaders); // Generate the signed request to the AWS STS GetCallerIdentity API. Map headers = new HashMap<>(); @@ -164,10 +184,28 @@ public GoogleCredentials createScoped(Collection newScopes) { return new AwsCredentials((AwsCredentials.Builder) newBuilder(this).setScopes(newScopes)); } - private String retrieveResource(String url, String resourceName) throws IOException { + private String retrieveResource(String url, String resourceName, Map headers) + throws IOException { + return retrieveResource(url, resourceName, HttpMethods.GET, headers, /* content= */ null); + } + + private String retrieveResource( + String url, + String resourceName, + String requestMethod, + Map headers, + @Nullable HttpContent content) + throws IOException { try { HttpRequestFactory requestFactory = transportFactory.create().createRequestFactory(); - HttpRequest request = requestFactory.buildGetRequest(new GenericUrl(url)); + HttpRequest request = + requestFactory.buildRequest(requestMethod, new GenericUrl(url), content); + + HttpHeaders requestHeaders = request.getHeaders(); + for (Map.Entry header : headers.entrySet()) { + requestHeaders.set(header.getKey(), header.getValue()); + } + HttpResponse response = request.execute(); return response.parseAsString(); } catch (IOException e) { @@ -200,8 +238,42 @@ private String buildSubjectToken(AwsRequestSignature signature) return URLEncoder.encode(token.toString(), "UTF-8"); } + Map createMetadataRequestHeaders(AwsCredentialSource awsCredentialSource) + throws IOException { + Map metadataRequestHeaders = new HashMap<>(); + + // AWS IDMSv2 introduced a requirement for a session token to be present + // with the requests made to metadata endpoints. This requirement is to help + // prevent SSRF attacks. + // Presence of "imdsv2_session_token_url" in Credential Source of config file + // will trigger a flow with session token, else there will not be a session + // token with the metadata requests. + // Both flows work for IDMS v1 and v2. But if IDMSv2 is enabled, then if + // session token is not present, Unauthorized exception will be thrown. + if (awsCredentialSource.imdsv2SessionTokenUrl != null) { + Map tokenRequestHeaders = + new HashMap() { + { + put(AWS_IMDSV2_SESSION_TOKEN_TTL_HEADER, AWS_IMDSV2_SESSION_TOKEN_TTL); + } + }; + + String imdsv2SessionToken = + retrieveResource( + awsCredentialSource.imdsv2SessionTokenUrl, + "Session Token", + HttpMethods.PUT, + tokenRequestHeaders, + /* content= */ null); + + metadataRequestHeaders.put(AWS_IMDSV2_SESSION_TOKEN_HEADER, imdsv2SessionToken); + } + + return metadataRequestHeaders; + } + @VisibleForTesting - String getAwsRegion() throws IOException { + String getAwsRegion(Map metadataRequestHeaders) throws IOException { // For AWS Lambda, the region is retrieved through the AWS_REGION environment variable. String region = getEnvironmentProvider().getEnv("AWS_REGION"); if (region != null) { @@ -218,7 +290,7 @@ String getAwsRegion() throws IOException { "Unable to determine the AWS region. The credential source does not contain the region URL."); } - region = retrieveResource(awsCredentialSource.regionUrl, "region"); + region = retrieveResource(awsCredentialSource.regionUrl, "region", metadataRequestHeaders); // There is an extra appended character that must be removed. If `us-east-1b` is returned, // we want `us-east-1`. @@ -226,7 +298,8 @@ String getAwsRegion() throws IOException { } @VisibleForTesting - AwsSecurityCredentials getAwsSecurityCredentials() throws IOException { + AwsSecurityCredentials getAwsSecurityCredentials(Map metadataRequestHeaders) + throws IOException { // Check environment variables for credentials first. String accessKeyId = getEnvironmentProvider().getEnv("AWS_ACCESS_KEY_ID"); String secretAccessKey = getEnvironmentProvider().getEnv("AWS_SECRET_ACCESS_KEY"); @@ -243,12 +316,13 @@ AwsSecurityCredentials getAwsSecurityCredentials() throws IOException { "Unable to determine the AWS IAM role name. The credential source does not contain the" + " url field."); } - String roleName = retrieveResource(awsCredentialSource.url, "IAM role"); + String roleName = retrieveResource(awsCredentialSource.url, "IAM role", metadataRequestHeaders); // Retrieve the AWS security credentials by calling the endpoint specified by the credential // source. String awsCredentials = - retrieveResource(awsCredentialSource.url + "/" + roleName, "credentials"); + retrieveResource( + awsCredentialSource.url + "/" + roleName, "credentials", metadataRequestHeaders); JsonParser parser = OAuth2Utils.JSON_FACTORY.createJsonParser(awsCredentials); GenericJson genericJson = parser.parseAndClose(GenericJson.class); diff --git a/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java index 700ad2117..962c105cd 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java @@ -365,7 +365,7 @@ public boolean createScopedRequired() { @Override public GoogleCredentials createScoped(Collection scopes) { return toBuilder() - .setScopes((List) scopes) + .setScopes(new ArrayList(scopes)) .setLifetime(this.lifetime) .setDelegates(this.delegates) .setHttpTransportFactory(this.transportFactory) diff --git a/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java index c36bdf3bc..e52365c80 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java @@ -39,6 +39,7 @@ import com.google.api.client.json.GenericJson; import com.google.api.client.json.JsonParser; +import com.google.api.client.testing.http.MockLowLevelHttpRequest; import com.google.auth.TestUtils; import com.google.auth.oauth2.AwsCredentials.AwsCredentialSource; import com.google.auth.oauth2.ExternalAccountCredentialsTest.MockExternalAccountCredentialsTransportFactory; @@ -47,6 +48,7 @@ import java.net.URI; import java.net.URLDecoder; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -56,6 +58,12 @@ class AwsCredentialsTest { private static final String STS_URL = "https://sts.googleapis.com"; + private static final String AWS_CREDENTIALS_URL = "https://www.aws-credentials.com"; + private static final String AWS_CREDENTIALS_URL_WITH_ROLE = + "https://www.aws-credentials.com/roleName"; + private static final String AWS_REGION_URL = "https://www.aws-region.com"; + private static final String AWS_IMDSV2_SESSION_TOKEN_URL = "https://www.aws-session-token.com"; + private static final String AWS_IMDSV2_SESSION_TOKEN = "sessiontoken"; private static final String GET_CALLER_IDENTITY_URL = "https://sts.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15"; @@ -73,6 +81,9 @@ class AwsCredentialsTest { } }; + private static final Map EMPTY_METADATA_HEADERS = Collections.emptyMap(); + private static final Map EMPTY_STRING_HEADERS = Collections.emptyMap(); + private static final AwsCredentialSource AWS_CREDENTIAL_SOURCE = new AwsCredentialSource(AWS_CREDENTIAL_SOURCE_MAP); @@ -158,6 +169,89 @@ void retrieveSubjectToken() throws IOException { assertEquals(awsCredential.getAudience(), headers.get("x-goog-cloud-target-resource")); assertTrue(headers.containsKey("x-amz-date")); assertNotNull(headers.get("Authorization")); + + List requests = transportFactory.transport.getRequests(); + assertEquals(3, requests.size()); + + // Validate region request. + ValidateRequest(requests.get(0), AWS_REGION_URL, EMPTY_STRING_HEADERS); + + // Validate role request. + ValidateRequest(requests.get(1), AWS_CREDENTIALS_URL, EMPTY_STRING_HEADERS); + + // Validate security credentials request. + ValidateRequest(requests.get(2), AWS_CREDENTIALS_URL_WITH_ROLE, EMPTY_STRING_HEADERS); + } + + @Test + void retrieveSubjectTokenWithSessionTokenUrl() throws IOException { + MockExternalAccountCredentialsTransportFactory transportFactory = + new MockExternalAccountCredentialsTransportFactory(); + + Map credentialSourceMap = new HashMap<>(); + credentialSourceMap.put("environment_id", "aws1"); + credentialSourceMap.put("region_url", transportFactory.transport.getAwsRegionUrl()); + credentialSourceMap.put("url", transportFactory.transport.getAwsCredentialsUrl()); + credentialSourceMap.put("regional_cred_verification_url", GET_CALLER_IDENTITY_URL); + credentialSourceMap.put( + "imdsv2_session_token_url", transportFactory.transport.getAwsImdsv2SessionTokenUrl()); + + AwsCredentials awsCredential = + (AwsCredentials) + AwsCredentials.newBuilder(AWS_CREDENTIAL) + .setHttpTransportFactory(transportFactory) + .setCredentialSource(new AwsCredentialSource(credentialSourceMap)) + .build(); + + String subjectToken = URLDecoder.decode(awsCredential.retrieveSubjectToken(), "UTF-8"); + + JsonParser parser = OAuth2Utils.JSON_FACTORY.createJsonParser(subjectToken); + GenericJson json = parser.parseAndClose(GenericJson.class); + + List> headersList = (List>) json.get("headers"); + Map headers = new HashMap<>(); + for (Map header : headersList) { + headers.put(header.get("key"), header.get("value")); + } + + assertEquals("POST", json.get("method")); + assertEquals(GET_CALLER_IDENTITY_URL, json.get("url")); + assertEquals(URI.create(GET_CALLER_IDENTITY_URL).getHost(), headers.get("host")); + assertEquals("token", headers.get("x-amz-security-token")); + assertEquals(awsCredential.getAudience(), headers.get("x-goog-cloud-target-resource")); + assertTrue(headers.containsKey("x-amz-date")); + assertNotNull(headers.get("Authorization")); + + List requests = transportFactory.transport.getRequests(); + assertEquals(4, requests.size()); + + // Validate the session token request + ValidateRequest( + requests.get(0), + AWS_IMDSV2_SESSION_TOKEN_URL, + new HashMap() { + { + put( + AwsCredentials.AWS_IMDSV2_SESSION_TOKEN_TTL_HEADER, + AwsCredentials.AWS_IMDSV2_SESSION_TOKEN_TTL); + } + }); + + Map sessionTokenHeader = + new HashMap() { + { + put(AwsCredentials.AWS_IMDSV2_SESSION_TOKEN_HEADER, AWS_IMDSV2_SESSION_TOKEN); + } + }; + + // Validate region request. + ValidateRequest(requests.get(1), AWS_REGION_URL, sessionTokenHeader); + + // Validate role request. + ValidateRequest(requests.get(2), AWS_CREDENTIALS_URL, sessionTokenHeader); + + // Validate security credentials request. + ValidateRequest(requests.get(3), AWS_CREDENTIALS_URL_WITH_ROLE, sessionTokenHeader); } @Test @@ -179,6 +273,12 @@ void retrieveSubjectToken_noRegion_expectThrows() { assertThrows( IOException.class, awsCredential::retrieveSubjectToken, "Exception should be thrown."); assertEquals("Failed to retrieve AWS region.", exception.getMessage()); + + List requests = transportFactory.transport.getRequests(); + assertEquals(1, requests.size()); + + // Validate region request. + ValidateRequest(requests.get(0), AWS_REGION_URL, EMPTY_STRING_HEADERS); } @Test @@ -201,6 +301,15 @@ void retrieveSubjectToken_noRole_expectThrows() { assertThrows( IOException.class, awsCredential::retrieveSubjectToken, "Exception should be thrown."); assertEquals("Failed to retrieve AWS IAM role.", exception.getMessage()); + + List requests = transportFactory.transport.getRequests(); + assertEquals(2, requests.size()); + + // Validate region request. + ValidateRequest(requests.get(0), AWS_REGION_URL, EMPTY_STRING_HEADERS); + + // Validate role request. + ValidateRequest(requests.get(1), AWS_CREDENTIALS_URL, EMPTY_STRING_HEADERS); } @Test @@ -223,6 +332,18 @@ void retrieveSubjectToken_noCredentials_expectThrows() { assertThrows( IOException.class, awsCredential::retrieveSubjectToken, "Exception should be thrown."); assertEquals("Failed to retrieve AWS credentials.", exception.getMessage()); + + List requests = transportFactory.transport.getRequests(); + assertEquals(3, requests.size()); + + // Validate region request. + ValidateRequest(requests.get(0), AWS_REGION_URL, EMPTY_STRING_HEADERS); + + // Validate role request. + ValidateRequest(requests.get(1), AWS_CREDENTIALS_URL, EMPTY_STRING_HEADERS); + + // Validate security credentials request. + ValidateRequest(requests.get(2), AWS_CREDENTIALS_URL_WITH_ROLE, EMPTY_STRING_HEADERS); } @Test @@ -248,6 +369,10 @@ void retrieveSubjectToken_noRegionUrlProvided() { "Unable to determine the AWS region. The credential source does not " + "contain the region URL.", exception.getMessage()); + + // No requests because the credential source does not contain region URL. + List requests = transportFactory.transport.getRequests(); + assertTrue(requests.isEmpty()); } @Test @@ -263,7 +388,8 @@ void getAwsSecurityCredentials_fromEnvironmentVariablesNoToken() throws IOExcept .setEnvironmentProvider(environmentProvider) .build(); - AwsSecurityCredentials credentials = testAwsCredentials.getAwsSecurityCredentials(); + AwsSecurityCredentials credentials = + testAwsCredentials.getAwsSecurityCredentials(EMPTY_METADATA_HEADERS); assertEquals("awsAccessKeyId", credentials.getAccessKeyId()); assertEquals("awsSecretAccessKey", credentials.getSecretAccessKey()); @@ -284,7 +410,8 @@ void getAwsSecurityCredentials_fromEnvironmentVariablesWithToken() throws IOExce .setEnvironmentProvider(environmentProvider) .build(); - AwsSecurityCredentials credentials = testAwsCredentials.getAwsSecurityCredentials(); + AwsSecurityCredentials credentials = + testAwsCredentials.getAwsSecurityCredentials(EMPTY_METADATA_HEADERS); assertEquals("awsAccessKeyId", credentials.getAccessKeyId()); assertEquals("awsSecretAccessKey", credentials.getSecretAccessKey()); @@ -303,11 +430,21 @@ void getAwsSecurityCredentials_fromMetadataServer() throws IOException { .setCredentialSource(buildAwsCredentialSource(transportFactory)) .build(); - AwsSecurityCredentials credentials = awsCredential.getAwsSecurityCredentials(); + AwsSecurityCredentials credentials = + awsCredential.getAwsSecurityCredentials(EMPTY_METADATA_HEADERS); assertEquals("accessKeyId", credentials.getAccessKeyId()); assertEquals("secretAccessKey", credentials.getSecretAccessKey()); assertEquals("token", credentials.getToken()); + + List requests = transportFactory.transport.getRequests(); + assertEquals(2, requests.size()); + + // Validate role request. + ValidateRequest(requests.get(0), AWS_CREDENTIALS_URL, EMPTY_STRING_HEADERS); + + // Validate security credentials request. + ValidateRequest(requests.get(1), AWS_CREDENTIALS_URL_WITH_ROLE, EMPTY_STRING_HEADERS); } @Test @@ -329,11 +466,17 @@ void getAwsSecurityCredentials_fromMetadataServer_noUrlProvided() { IOException exception = assertThrows( IOException.class, - awsCredential::getAwsSecurityCredentials, + () -> { + awsCredential.getAwsSecurityCredentials(EMPTY_METADATA_HEADERS); + }, "Exception should be thrown."); assertEquals( "Unable to determine the AWS IAM role name. The credential source does not contain the url field.", exception.getMessage()); + + // No requests because url field is not present in credential source. + List requests = transportFactory.transport.getRequests(); + assertTrue(requests.isEmpty()); } @Test @@ -352,11 +495,15 @@ void getAwsRegion_awsRegionEnvironmentVariable() throws IOException { .setEnvironmentProvider(environmentProvider) .build(); - String region = awsCredentials.getAwsRegion(); + String region = awsCredentials.getAwsRegion(EMPTY_METADATA_HEADERS); // Should attempt to retrieve the region from AWS_REGION env var first. // Metadata server would return us-east-1b. assertEquals("region", region); + + // No requests because region is obtained from environment variables. + List requests = transportFactory.transport.getRequests(); + assertTrue(requests.isEmpty()); } @Test @@ -374,11 +521,15 @@ void getAwsRegion_awsDefaultRegionEnvironmentVariable() throws IOException { .setEnvironmentProvider(environmentProvider) .build(); - String region = awsCredentials.getAwsRegion(); + String region = awsCredentials.getAwsRegion(EMPTY_METADATA_HEADERS); // Should attempt to retrieve the region from DEFAULT_AWS_REGION before calling the metadata // server. Metadata server would return us-east-1b. assertEquals("defaultRegion", region); + + // No requests because region is obtained from environment variables. + List requests = transportFactory.transport.getRequests(); + assertTrue(requests.isEmpty()); } @Test @@ -392,7 +543,7 @@ void getAwsRegion_metadataServer() throws IOException { .setCredentialSource(buildAwsCredentialSource(transportFactory)) .build(); - String region = awsCredentials.getAwsRegion(); + String region = awsCredentials.getAwsRegion(EMPTY_METADATA_HEADERS); // Should retrieve the region from the Metadata server. String expectedRegion = @@ -401,6 +552,12 @@ void getAwsRegion_metadataServer() throws IOException { .getAwsRegion() .substring(0, transportFactory.transport.getAwsRegion().length() - 1); assertEquals(expectedRegion, region); + + List requests = transportFactory.transport.getRequests(); + assertEquals(1, requests.size()); + + // Validate region request. + ValidateRequest(requests.get(0), AWS_REGION_URL, EMPTY_STRING_HEADERS); } @Test @@ -510,6 +667,19 @@ public void builder() { assertEquals(credentials.getEnvironmentProvider(), SystemEnvironmentProvider.getInstance()); } + private static void ValidateRequest( + MockLowLevelHttpRequest request, String expectedUrl, Map expectedHeaders) { + assertEquals(expectedUrl, request.getUrl()); + Map> actualHeaders = request.getHeaders(); + + for (Map.Entry expectedHeader : expectedHeaders.entrySet()) { + assertTrue(actualHeaders.containsKey(expectedHeader.getKey())); + List actualValues = actualHeaders.get(expectedHeader.getKey()); + assertEquals(1, actualValues.size()); + assertEquals(expectedHeader.getValue(), actualValues.get(0)); + } + } + private static AwsCredentialSource buildAwsCredentialSource( MockExternalAccountCredentialsTransportFactory transportFactory) { Map credentialSourceMap = new HashMap<>(); @@ -517,6 +687,7 @@ private static AwsCredentialSource buildAwsCredentialSource( credentialSourceMap.put("region_url", transportFactory.transport.getAwsRegionUrl()); credentialSourceMap.put("url", transportFactory.transport.getAwsCredentialsUrl()); credentialSourceMap.put("regional_cred_verification_url", GET_CALLER_IDENTITY_URL); + return new AwsCredentialSource(credentialSourceMap); } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java index c59560f56..fb94bb93d 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java @@ -411,7 +411,7 @@ void exchangeExternalCredentialForAccessToken() throws IOException { // Validate no internal options set. Map query = - TestUtils.parseQuery(transportFactory.transport.getRequest().getContentAsString()); + TestUtils.parseQuery(transportFactory.transport.getLastRequest().getContentAsString()); assertNull(query.get("options")); } @@ -435,7 +435,7 @@ void exchangeExternalCredentialForAccessToken_withInternalOptions() throws IOExc // Validate internal options set. Map query = - TestUtils.parseQuery(transportFactory.transport.getRequest().getContentAsString()); + TestUtils.parseQuery(transportFactory.transport.getLastRequest().getContentAsString()); assertNotNull(query.get("options")); assertEquals(internalOptions.toString(), query.get("options")); } @@ -457,7 +457,7 @@ void exchangeExternalCredentialForAccessToken_workforceCred_expectUserProjectPas // Validate internal options set. Map query = - TestUtils.parseQuery(transportFactory.transport.getRequest().getContentAsString()); + TestUtils.parseQuery(transportFactory.transport.getLastRequest().getContentAsString()); GenericJson internalOptions = new GenericJson(); internalOptions.setFactory(OAuth2Utils.JSON_FACTORY); internalOptions.put("userProject", "userProject"); @@ -485,7 +485,7 @@ void exchangeExternalCredentialForAccessToken_workforceCredWithInternalOptions_e // Validate internal options set. Map query = - TestUtils.parseQuery(transportFactory.transport.getRequest().getContentAsString()); + TestUtils.parseQuery(transportFactory.transport.getLastRequest().getContentAsString()); assertNotNull(query.get("options")); assertEquals(internalOptions.toString(), query.get("options")); } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java index 5f8dc3ca0..73a3d2f12 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/IdentityPoolCredentialsTest.java @@ -346,7 +346,7 @@ void refreshAccessToken_internalOptionsSet() throws IOException { // If the IdentityPoolCredential is initialized with a userProject, it must be passed // to STS via internal options. Map query = - TestUtils.parseQuery(transportFactory.transport.getRequest().getContentAsString()); + TestUtils.parseQuery(transportFactory.transport.getLastRequest().getContentAsString()); assertNotNull(query.get("options")); GenericJson expectedInternalOptions = new GenericJson(); diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java index 3eeb2d2db..d0da844d8 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java @@ -54,6 +54,7 @@ import com.google.auth.http.HttpTransportFactory; import com.google.auth.oauth2.GoogleCredentialsTest.MockTokenServerTransportFactory; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -66,6 +67,7 @@ import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -107,11 +109,11 @@ class ImpersonatedCredentialsTest extends BaseSerializationTest { + "CJzdWIiOiIxMDIxMDE1NTA4MzQyMDA3MDg1NjgifQ.redacted"; public static final String ACCESS_TOKEN = "1/MkSJoj1xsli0AccessToken_NKPY2"; + private static final Set IMMUTABLE_SCOPES_SET = ImmutableSet.of("scope1", "scope2"); private static final String PROJECT_ID = "project-id"; public static final String IMPERSONATED_CLIENT_EMAIL = "impersonated-account@iam.gserviceaccount.com"; - private static final List SCOPES = - Arrays.asList("https://www.googleapis.com/auth/devstorage.read_only"); + private static final List IMMUTABLE_SCOPES_LIST = ImmutableList.of("scope1", "scope2"); private static final int VALID_LIFETIME = 300; private static final int INVALID_LIFETIME = 43210; private static JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance(); @@ -156,7 +158,7 @@ private GoogleCredentials getSourceCredentials() throws IOException { .setClientEmail(SA_CLIENT_EMAIL) .setPrivateKey(privateKey) .setPrivateKeyId(SA_PRIVATE_KEY_ID) - .setScopes(SCOPES) + .setScopes(IMMUTABLE_SCOPES_LIST) .setProjectId(PROJECT_ID) .setHttpTransportFactory(transportFactory) .build(); @@ -275,7 +277,7 @@ void createScopedRequired_False() { sourceCredentials, IMPERSONATED_CLIENT_EMAIL, null, - SCOPES, + IMMUTABLE_SCOPES_LIST, VALID_LIFETIME, mockTransportFactory); assertFalse(targetCredentials.createScopedRequired()); @@ -288,13 +290,36 @@ void createScoped() { sourceCredentials, IMPERSONATED_CLIENT_EMAIL, DELEGATES, - SCOPES, + IMMUTABLE_SCOPES_LIST, VALID_LIFETIME, mockTransportFactory, QUOTA_PROJECT_ID); ImpersonatedCredentials scoped_credentials = - (ImpersonatedCredentials) targetCredentials.createScoped(Arrays.asList("scope1", "scope2")); + (ImpersonatedCredentials) targetCredentials.createScoped(IMMUTABLE_SCOPES_LIST); + assertEquals(targetCredentials.getAccount(), scoped_credentials.getAccount()); + assertEquals(targetCredentials.getDelegates(), scoped_credentials.getDelegates()); + assertEquals(targetCredentials.getLifetime(), scoped_credentials.getLifetime()); + assertEquals( + targetCredentials.getSourceCredentials(), scoped_credentials.getSourceCredentials()); + assertEquals(targetCredentials.getQuotaProjectId(), scoped_credentials.getQuotaProjectId()); + assertEquals(Arrays.asList("scope1", "scope2"), scoped_credentials.getScopes()); + } + + @Test + void createScopedWithImmutableScopes() { + ImpersonatedCredentials targetCredentials = + ImpersonatedCredentials.create( + sourceCredentials, + IMPERSONATED_CLIENT_EMAIL, + DELEGATES, + IMMUTABLE_SCOPES_LIST, + VALID_LIFETIME, + mockTransportFactory, + QUOTA_PROJECT_ID); + + ImpersonatedCredentials scoped_credentials = + (ImpersonatedCredentials) targetCredentials.createScoped(IMMUTABLE_SCOPES_SET); assertEquals(targetCredentials.getAccount(), scoped_credentials.getAccount()); assertEquals(targetCredentials.getDelegates(), scoped_credentials.getDelegates()); assertEquals(targetCredentials.getLifetime(), scoped_credentials.getLifetime()); @@ -319,7 +344,7 @@ void refreshAccessToken_unauthorized() throws IOException { sourceCredentials, IMPERSONATED_CLIENT_EMAIL, null, - SCOPES, + IMMUTABLE_SCOPES_LIST, VALID_LIFETIME, mockTransportFactory); @@ -348,7 +373,7 @@ void refreshAccessToken_malformedTarget() throws IOException { sourceCredentials, invalidTargetEmail, null, - SCOPES, + IMMUTABLE_SCOPES_LIST, VALID_LIFETIME, mockTransportFactory); @@ -365,7 +390,7 @@ void refreshAccessToken_malformedTarget() throws IOException { void credential_with_zero_lifetime() throws IllegalStateException { ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create( - sourceCredentials, IMPERSONATED_CLIENT_EMAIL, null, SCOPES, 0); + sourceCredentials, IMPERSONATED_CLIENT_EMAIL, null, IMMUTABLE_SCOPES_LIST, 0); assertEquals(3600, targetCredentials.getLifetime()); } @@ -378,7 +403,11 @@ void credential_with_invalid_lifetime() throws IOException, IllegalStateExceptio () -> { ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create( - sourceCredentials, IMPERSONATED_CLIENT_EMAIL, null, SCOPES, INVALID_LIFETIME); + sourceCredentials, + IMPERSONATED_CLIENT_EMAIL, + null, + IMMUTABLE_SCOPES_LIST, + INVALID_LIFETIME); targetCredentials.refreshAccessToken().getTokenValue(); }, String.format( @@ -415,7 +444,7 @@ void refreshAccessToken_success() throws IOException, IllegalStateException { sourceCredentials, IMPERSONATED_CLIENT_EMAIL, null, - SCOPES, + IMMUTABLE_SCOPES_LIST, VALID_LIFETIME, mockTransportFactory); @@ -433,7 +462,7 @@ void getRequestMetadata_withQuotaProjectId() throws IOException, IllegalStateExc sourceCredentials, IMPERSONATED_CLIENT_EMAIL, null, - SCOPES, + IMMUTABLE_SCOPES_LIST, VALID_LIFETIME, mockTransportFactory, QUOTA_PROJECT_ID); @@ -456,7 +485,7 @@ void getRequestMetadata_withoutQuotaProjectId() throws IOException, IllegalState sourceCredentials, IMPERSONATED_CLIENT_EMAIL, null, - SCOPES, + IMMUTABLE_SCOPES_LIST, VALID_LIFETIME, mockTransportFactory); @@ -476,7 +505,7 @@ void refreshAccessToken_delegates_success() throws IOException, IllegalStateExce sourceCredentials, IMPERSONATED_CLIENT_EMAIL, delegates, - SCOPES, + IMMUTABLE_SCOPES_LIST, VALID_LIFETIME, mockTransportFactory); @@ -495,7 +524,7 @@ void refreshAccessToken_invalidDate() throws IllegalStateException { sourceCredentials, IMPERSONATED_CLIENT_EMAIL, null, - SCOPES, + IMMUTABLE_SCOPES_LIST, VALID_LIFETIME, mockTransportFactory); @@ -517,7 +546,7 @@ void getAccount_sameAs() { sourceCredentials, IMPERSONATED_CLIENT_EMAIL, null, - SCOPES, + IMMUTABLE_SCOPES_LIST, VALID_LIFETIME, mockTransportFactory); @@ -534,7 +563,7 @@ void sign_sameAs() { sourceCredentials, IMPERSONATED_CLIENT_EMAIL, null, - SCOPES, + IMMUTABLE_SCOPES_LIST, VALID_LIFETIME, mockTransportFactory); @@ -556,7 +585,7 @@ void sign_requestIncludesDelegates() throws IOException { sourceCredentials, IMPERSONATED_CLIENT_EMAIL, ImmutableList.of("delegate@example.com"), - SCOPES, + IMMUTABLE_SCOPES_LIST, VALID_LIFETIME, mockTransportFactory); @@ -595,7 +624,7 @@ void sign_usesSourceCredentials() { sourceCredentials, IMPERSONATED_CLIENT_EMAIL, ImmutableList.of("delegate@example.com"), - SCOPES, + IMMUTABLE_SCOPES_LIST, VALID_LIFETIME, mockTransportFactory); @@ -620,7 +649,7 @@ void sign_accessDenied_throws() { sourceCredentials, IMPERSONATED_CLIENT_EMAIL, null, - SCOPES, + IMMUTABLE_SCOPES_LIST, VALID_LIFETIME, mockTransportFactory); @@ -652,7 +681,7 @@ void sign_serverError_throws() { sourceCredentials, IMPERSONATED_CLIENT_EMAIL, null, - SCOPES, + IMMUTABLE_SCOPES_LIST, VALID_LIFETIME, mockTransportFactory); @@ -685,7 +714,7 @@ void idTokenWithAudience_sameAs() throws IOException { sourceCredentials, IMPERSONATED_CLIENT_EMAIL, null, - SCOPES, + IMMUTABLE_SCOPES_LIST, VALID_LIFETIME, mockTransportFactory); @@ -716,7 +745,7 @@ void idTokenWithAudience_withEmail() throws IOException { sourceCredentials, IMPERSONATED_CLIENT_EMAIL, null, - SCOPES, + IMMUTABLE_SCOPES_LIST, VALID_LIFETIME, mockTransportFactory); @@ -746,7 +775,7 @@ void idToken_withServerError() { sourceCredentials, IMPERSONATED_CLIENT_EMAIL, null, - SCOPES, + IMMUTABLE_SCOPES_LIST, VALID_LIFETIME, mockTransportFactory); @@ -776,7 +805,7 @@ void idToken_withOtherError() { sourceCredentials, IMPERSONATED_CLIENT_EMAIL, null, - SCOPES, + IMMUTABLE_SCOPES_LIST, VALID_LIFETIME, mockTransportFactory); @@ -806,7 +835,7 @@ void hashCode_equals() throws IOException { sourceCredentials, IMPERSONATED_CLIENT_EMAIL, null, - SCOPES, + IMMUTABLE_SCOPES_LIST, VALID_LIFETIME, mockTransportFactory); @@ -815,7 +844,7 @@ void hashCode_equals() throws IOException { sourceCredentials, IMPERSONATED_CLIENT_EMAIL, null, - SCOPES, + IMMUTABLE_SCOPES_LIST, VALID_LIFETIME, mockTransportFactory); @@ -834,7 +863,7 @@ void serialize() throws IOException, ClassNotFoundException { sourceCredentials, IMPERSONATED_CLIENT_EMAIL, null, - SCOPES, + IMMUTABLE_SCOPES_LIST, VALID_LIFETIME, mockTransportFactory); GoogleCredentials deserializedCredentials = serializeAndDeserialize(targetCredentials); diff --git a/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java b/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java index 43a8dea44..74f4771ca 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java @@ -67,12 +67,14 @@ public class MockExternalAccountCredentialsTransport extends MockHttpTransport { private static final String ISSUED_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token"; private static final String AWS_CREDENTIALS_URL = "https://www.aws-credentials.com"; private static final String AWS_REGION_URL = "https://www.aws-region.com"; + private static final String AWS_IMDSV2_SESSION_TOKEN_URL = "https://www.aws-session-token.com"; private static final String METADATA_SERVER_URL = "https://www.metadata.google.com"; private static final String STS_URL = "https://sts.googleapis.com"; private static final String SUBJECT_TOKEN = "subjectToken"; private static final String TOKEN_TYPE = "Bearer"; private static final String ACCESS_TOKEN = "accessToken"; + private static final String AWS_IMDSV2_SESSION_TOKEN = "sessiontoken"; private static final String SERVICE_ACCOUNT_ACCESS_TOKEN = "serviceAccountAccessToken"; private static final String AWS_REGION = "us-east-1b"; private static final Long EXPIRES_IN = 3600L; @@ -86,7 +88,7 @@ public class MockExternalAccountCredentialsTransport extends MockHttpTransport { private Queue responseErrorSequence = new ArrayDeque<>(); private Queue refreshTokenSequence = new ArrayDeque<>(); private Queue> scopeSequence = new ArrayDeque<>(); - private MockLowLevelHttpRequest request; + private List requests = new ArrayList<>(); private String expireTime; private String metadataServerContentType; private String stsContent; @@ -109,7 +111,7 @@ public void addScopeSequence(List... scopes) { @Override public LowLevelHttpRequest buildRequest(final String method, final String url) { - this.request = + MockLowLevelHttpRequest request = new MockLowLevelHttpRequest(url) { @Override public LowLevelHttpResponse execute() throws IOException { @@ -119,6 +121,11 @@ public LowLevelHttpResponse execute() throws IOException { throw responseErrorSequence.poll(); } + if (AWS_IMDSV2_SESSION_TOKEN_URL.equals(url)) { + return new MockLowLevelHttpResponse() + .setContentType("text/html") + .setContent(AWS_IMDSV2_SESSION_TOKEN); + } if (AWS_REGION_URL.equals(url)) { return new MockLowLevelHttpResponse() .setContentType("text/html") @@ -208,15 +215,25 @@ public LowLevelHttpResponse execute() throws IOException { return null; } }; - return this.request; + + this.requests.add(request); + return request; } public String getStsContent() { return stsContent; } - public MockLowLevelHttpRequest getRequest() { - return request; + public MockLowLevelHttpRequest getLastRequest() { + if (requests.isEmpty()) { + return null; + } + + return requests.get(requests.size() - 1); + } + + public List getRequests() { + return Collections.unmodifiableList(requests); } public String getTokenType() { @@ -255,6 +272,10 @@ public String getAwsRegionUrl() { return AWS_REGION_URL; } + public String getAwsImdsv2SessionTokenUrl() { + return AWS_IMDSV2_SESSION_TOKEN_URL; + } + public String getAwsRegion() { return AWS_REGION; } diff --git a/oauth2_http/pom.xml b/oauth2_http/pom.xml index 93ac5409c..d9ab07527 100644 --- a/oauth2_http/pom.xml +++ b/oauth2_http/pom.xml @@ -5,7 +5,7 @@ com.google.auth google-auth-library-parent - 1.5.3 + 1.6.0 ../pom.xml diff --git a/pom.xml b/pom.xml index 05d687269..02bed18df 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.auth google-auth-library-parent - 1.5.3 + 1.6.0 pom Google Auth Library for Java Client libraries providing authentication and @@ -59,10 +59,10 @@ UTF-8 - 1.41.3 + 1.41.4 5.8.2 31.0.1-android - 2.0.2 + 2.0.4 3.0.2 false 1.8.2 @@ -142,7 +142,7 @@ org.sonatype.plugins nexus-staging-maven-plugin - 1.6.8 + 1.6.12 true ossrh @@ -222,7 +222,7 @@ maven-compiler-plugin - 3.9.0 + 3.10.1 1.8 1.8 diff --git a/versions.txt b/versions.txt index 1d25f3dc2..93cdd0df7 100644 --- a/versions.txt +++ b/versions.txt @@ -1,9 +1,9 @@ # Format: # module:released-version:current-version -google-auth-library:1.5.3:1.5.3 -google-auth-library-bom:1.5.3:1.5.3 -google-auth-library-parent:1.5.3:1.5.3 -google-auth-library-appengine:1.5.3:1.5.3 -google-auth-library-credentials:1.5.3:1.5.3 -google-auth-library-oauth2-http:1.5.3:1.5.3 +google-auth-library:1.6.0:1.6.0 +google-auth-library-bom:1.6.0:1.6.0 +google-auth-library-parent:1.6.0:1.6.0 +google-auth-library-appengine:1.6.0:1.6.0 +google-auth-library-credentials:1.6.0:1.6.0 +google-auth-library-oauth2-http:1.6.0:1.6.0