From bad58c35bbd859d0a4483254507140b3c29e3222 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 17 May 2022 20:00:17 +0000 Subject: [PATCH 01/10] chore(main): release 1.7.1-SNAPSHOT (#922) :robot: I have created a release *beep* *boop* --- ### Updating meta-information for bleeding-edge SNAPSHOT release. --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --- appengine/pom.xml | 2 +- bom/pom.xml | 2 +- credentials/pom.xml | 2 +- oauth2_http/pom.xml | 2 +- pom.xml | 2 +- versions.txt | 12 ++++++------ 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/appengine/pom.xml b/appengine/pom.xml index b23af89fc..f40cfdaa3 100644 --- a/appengine/pom.xml +++ b/appengine/pom.xml @@ -5,7 +5,7 @@ com.google.auth google-auth-library-parent - 1.7.0 + 1.7.1-SNAPSHOT ../pom.xml diff --git a/bom/pom.xml b/bom/pom.xml index f9f23bfcb..951ac42ee 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.auth google-auth-library-bom - 1.7.0 + 1.7.1-SNAPSHOT pom Google Auth Library for Java BOM diff --git a/credentials/pom.xml b/credentials/pom.xml index 5fed3789b..82bc206a8 100644 --- a/credentials/pom.xml +++ b/credentials/pom.xml @@ -4,7 +4,7 @@ com.google.auth google-auth-library-parent - 1.7.0 + 1.7.1-SNAPSHOT ../pom.xml diff --git a/oauth2_http/pom.xml b/oauth2_http/pom.xml index 156c3c5d2..656b86a5f 100644 --- a/oauth2_http/pom.xml +++ b/oauth2_http/pom.xml @@ -5,7 +5,7 @@ com.google.auth google-auth-library-parent - 1.7.0 + 1.7.1-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index 190f87cb4..bd67e1e6c 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.auth google-auth-library-parent - 1.7.0 + 1.7.1-SNAPSHOT pom Google Auth Library for Java Client libraries providing authentication and diff --git a/versions.txt b/versions.txt index 3b95a5022..05ee34faa 100644 --- a/versions.txt +++ b/versions.txt @@ -1,9 +1,9 @@ # Format: # module:released-version:current-version -google-auth-library:1.7.0:1.7.0 -google-auth-library-bom:1.7.0:1.7.0 -google-auth-library-parent:1.7.0:1.7.0 -google-auth-library-appengine:1.7.0:1.7.0 -google-auth-library-credentials:1.7.0:1.7.0 -google-auth-library-oauth2-http:1.7.0:1.7.0 +google-auth-library:1.7.0:1.7.1-SNAPSHOT +google-auth-library-bom:1.7.0:1.7.1-SNAPSHOT +google-auth-library-parent:1.7.0:1.7.1-SNAPSHOT +google-auth-library-appengine:1.7.0:1.7.1-SNAPSHOT +google-auth-library-credentials:1.7.0:1.7.1-SNAPSHOT +google-auth-library-oauth2-http:1.7.0:1.7.1-SNAPSHOT From bbb51ce7a9265cb991739cd90e1ccf65675d05dc Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Thu, 19 May 2022 20:54:15 +0000 Subject: [PATCH 02/10] feat: add build scripts for native image testing in Java 17 (#1440) (#923) Source-Link: https://github.com/googleapis/synthtool/commit/505ce5a7edb58bf6d9d4de10b4bb4e81000ae324 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-java:latest@sha256:2567a120ce90fadb6201999b87d649d9f67459de28815ad239bce9ebfaa18a74 --- .github/.OwlBot.lock.yaml | 4 +-- .kokoro/build.sh | 5 ++++ .kokoro/presubmit/graalvm-native-17.cfg | 33 +++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 .kokoro/presubmit/graalvm-native-17.cfg diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml index f60d77493..a79f06271 100644 --- a/.github/.OwlBot.lock.yaml +++ b/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-java:latest - digest: sha256:fc52b202aa298a50a12c64efd04fea3884d867947effe2fa85382a246c09e813 -# created: 2022-04-06T16:30:03.627422514Z + digest: sha256:2567a120ce90fadb6201999b87d649d9f67459de28815ad239bce9ebfaa18a74 +# created: 2022-05-19T15:12:45.278246753Z diff --git a/.kokoro/build.sh b/.kokoro/build.sh index 317bf8686..c483ec7bf 100755 --- a/.kokoro/build.sh +++ b/.kokoro/build.sh @@ -74,6 +74,11 @@ graalvm) mvn -B ${INTEGRATION_TEST_ARGS} -ntp -Pnative -Penable-integration-tests test RETURN_CODE=$? ;; +graalvm17) + # Run Unit and Integration Tests with Native Image + mvn -B ${INTEGRATION_TEST_ARGS} -ntp -Pnative -Penable-integration-tests test + RETURN_CODE=$? + ;; samples) SAMPLES_DIR=samples # only run ITs in snapshot/ on presubmit PRs. run ITs in all 3 samples/ subdirectories otherwise. diff --git a/.kokoro/presubmit/graalvm-native-17.cfg b/.kokoro/presubmit/graalvm-native-17.cfg new file mode 100644 index 000000000..a3f7fb9d4 --- /dev/null +++ b/.kokoro/presubmit/graalvm-native-17.cfg @@ -0,0 +1,33 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/graalvm17" +} + +env_vars: { + key: "JOB_TYPE" + value: "graalvm17" +} + +# TODO: remove this after we've migrated all tests and scripts +env_vars: { + key: "GCLOUD_PROJECT" + value: "gcloud-devel" +} + +env_vars: { + key: "GOOGLE_CLOUD_PROJECT" + value: "gcloud-devel" +} + +env_vars: { + key: "GOOGLE_APPLICATION_CREDENTIALS" + value: "secret_manager/java-it-service-account" +} + +env_vars: { + key: "SECRET_MANAGER_KEYS" + value: "java-it-service-account" +} \ No newline at end of file From c357d00432bf264af5d31fa701230adbb73c387f Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Fri, 10 Jun 2022 18:35:58 +0200 Subject: [PATCH 03/10] chore(deps): update dependency org.apache.maven.plugins:maven-failsafe-plugin to v3.0.0-m7 (#925) --- oauth2_http/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauth2_http/pom.xml b/oauth2_http/pom.xml index 656b86a5f..327128666 100644 --- a/oauth2_http/pom.xml +++ b/oauth2_http/pom.xml @@ -61,7 +61,7 @@ org.apache.maven.plugins maven-failsafe-plugin - 3.0.0-M6 + 3.0.0-M7 1200 sponge_log From ca1ada105c5bd7fb2e0d33d5e261b42d8395dd99 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Fri, 10 Jun 2022 18:38:16 +0200 Subject: [PATCH 04/10] chore(deps): update dependency com.google.http-client:google-http-client-bom to v1.42.0 (#928) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [com.google.http-client:google-http-client-bom](https://togithub.com/googleapis/google-http-java-client) | `1.41.8` -> `1.42.0` | [![age](https://badges.renovateapi.com/packages/maven/com.google.http-client:google-http-client-bom/1.42.0/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/maven/com.google.http-client:google-http-client-bom/1.42.0/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/maven/com.google.http-client:google-http-client-bom/1.42.0/compatibility-slim/1.41.8)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/maven/com.google.http-client:google-http-client-bom/1.42.0/confidence-slim/1.41.8)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
googleapis/google-http-java-client ### [`v1.42.0`](https://togithub.com/googleapis/google-http-java-client/blob/HEAD/CHANGELOG.md#​1420-httpsgithubcomgoogleapisgoogle-http-java-clientcomparev1417v1420-2022-06-09) [Compare Source](https://togithub.com/googleapis/google-http-java-client/compare/v1.41.8...v1.42.0) ##### Features - add build scripts for native image testing in Java 17 ([#​1440](https://togithub.com/googleapis/google-http-java-client/issues/1440)) ([#​1666](https://togithub.com/googleapis/google-http-java-client/issues/1666)) ([05d4019](https://togithub.com/googleapis/google-http-java-client/commit/05d40193d40097e5a793154a0951f2577fc80f04)) - next release from main branch is 1.42.0 ([#​1633](https://togithub.com/googleapis/google-http-java-client/issues/1633)) ([9acb1ab](https://togithub.com/googleapis/google-http-java-client/commit/9acb1abaa97392174dd35c5e0e68346f8f653b5b)) ##### Dependencies - update dependency com.fasterxml.jackson.core:jackson-core to v2.13.3 ([#​1665](https://togithub.com/googleapis/google-http-java-client/issues/1665)) ([e4f0959](https://togithub.com/googleapis/google-http-java-client/commit/e4f095997050047d9a6cc20f034f5ef744aefd44)) - update dependency com.google.errorprone:error_prone_annotations to v2.13.0 ([#​1630](https://togithub.com/googleapis/google-http-java-client/issues/1630)) ([bf777b3](https://togithub.com/googleapis/google-http-java-client/commit/bf777b364c8aafec09c486dc965587eae90549df)) - update dependency com.google.errorprone:error_prone_annotations to v2.13.1 ([#​1632](https://togithub.com/googleapis/google-http-java-client/issues/1632)) ([9e46cd8](https://togithub.com/googleapis/google-http-java-client/commit/9e46cd85ed1c14161f6473f926802bf281edc4ad)) - update dependency com.google.errorprone:error_prone_annotations to v2.14.0 ([#​1667](https://togithub.com/googleapis/google-http-java-client/issues/1667)) ([3516e18](https://togithub.com/googleapis/google-http-java-client/commit/3516e185b811d1935eebce31ba65da4813f7e998)) - update dependency com.google.protobuf:protobuf-java to v3.20.1 ([#​1639](https://togithub.com/googleapis/google-http-java-client/issues/1639)) ([90a99e2](https://togithub.com/googleapis/google-http-java-client/commit/90a99e27b053f5dc6078d6d8cd9bfe150237e2b4)) - update dependency com.google.protobuf:protobuf-java to v3.21.0 ([#​1668](https://togithub.com/googleapis/google-http-java-client/issues/1668)) ([babbe94](https://togithub.com/googleapis/google-http-java-client/commit/babbe94104710db7b4b428756d7db6c069674ff1)) - update dependency com.google.protobuf:protobuf-java to v3.21.1 ([#​1669](https://togithub.com/googleapis/google-http-java-client/issues/1669)) ([30ec091](https://togithub.com/googleapis/google-http-java-client/commit/30ec091faea7b5ec9f130cb3fdee396e9923a4b9)) - update dependency org.apache.felix:maven-bundle-plugin to v5.1.6 ([#​1643](https://togithub.com/googleapis/google-http-java-client/issues/1643)) ([8547f5f](https://togithub.com/googleapis/google-http-java-client/commit/8547f5fff9b27782162b0b6f0db7445c02918a45)) - update project.appengine.version to v2.0.5 ([#​1662](https://togithub.com/googleapis/google-http-java-client/issues/1662)) ([2c82c0d](https://togithub.com/googleapis/google-http-java-client/commit/2c82c0d4da1162cbc6950cdd6b2f4472b884db13)) - update project.opencensus.version to v0.31.1 ([#​1644](https://togithub.com/googleapis/google-http-java-client/issues/1644)) ([3c65a07](https://togithub.com/googleapis/google-http-java-client/commit/3c65a07c14d2bf7aa6cce25122df85670955d459)) ##### [1.41.7](https://togithub.com/googleapis/google-http-java-client/compare/v1.41.6...v1.41.7) (2022-04-11) ##### Dependencies - revert dependency com.google.protobuf:protobuf-java to v3.19.4 ([#​1626](https://togithub.com/googleapis/google-http-java-client/issues/1626)) ([076433f](https://togithub.com/googleapis/google-http-java-client/commit/076433f3c233a757f31d5fa39bb6cedbb43b8361)) ##### [1.41.6](https://togithub.com/googleapis/google-http-java-client/compare/v1.41.5...v1.41.6) (2022-04-06) ##### Bug Fixes - `Content-Encoding: gzip` along with `Transfer-Encoding: chunked` sometimes terminates early ([#​1608](https://togithub.com/googleapis/google-http-java-client/issues/1608)) ([941da8b](https://togithub.com/googleapis/google-http-java-client/commit/941da8badf64068d11a53ac57a4ba35b2ad13490)) ##### Dependencies - update dependency com.google.errorprone:error_prone_annotations to v2.12.1 ([#​1622](https://togithub.com/googleapis/google-http-java-client/issues/1622)) ([4e1101d](https://togithub.com/googleapis/google-http-java-client/commit/4e1101d7674cb5715b88a00750cdd5286a9ae077)) - update dependency com.google.protobuf:protobuf-java to v3.20.0 ([#​1621](https://togithub.com/googleapis/google-http-java-client/issues/1621)) ([640dc40](https://togithub.com/googleapis/google-http-java-client/commit/640dc4080249b65e5cabb7e1ae6cd9cd5b11bd8e)) ##### [1.41.5](https://togithub.com/googleapis/google-http-java-client/compare/v1.41.4...v1.41.5) (2022-03-21) ##### Documentation - **deps:** libraries-bom 24.4.0 release ([#​1596](https://togithub.com/googleapis/google-http-java-client/issues/1596)) ([327fe12](https://togithub.com/googleapis/google-http-java-client/commit/327fe12a122ebb4022a2da55694217233a2badaf)) ##### Dependencies - update actions/checkout action to v3 ([#​1593](https://togithub.com/googleapis/google-http-java-client/issues/1593)) ([92002c0](https://togithub.com/googleapis/google-http-java-client/commit/92002c07d60b738657383e2484f56abc1cde6920)) - update dependency com.fasterxml.jackson.core:jackson-core to v2.13.2 ([#​1598](https://togithub.com/googleapis/google-http-java-client/issues/1598)) ([41ac833](https://togithub.com/googleapis/google-http-java-client/commit/41ac833249e18cbbd304f825b12202e51bebec85)) - update project.appengine.version to v2 (major) ([#​1597](https://togithub.com/googleapis/google-http-java-client/issues/1597)) ([c06cf95](https://togithub.com/googleapis/google-http-java-client/commit/c06cf95f9b1be77e2229c3b2f78ece0789eaec15)) ##### [1.41.4](https://togithub.com/googleapis/google-http-java-client/compare/v1.41.3...v1.41.4) (2022-02-11) ##### Dependencies - update dependency com.google.code.gson:gson to v2.9.0 ([#​1582](https://togithub.com/googleapis/google-http-java-client/issues/1582)) ([8772778](https://togithub.com/googleapis/google-http-java-client/commit/877277821dad65545518b06123e6e7b9801147a1)) ##### [1.41.3](https://togithub.com/googleapis/google-http-java-client/compare/v1.41.2...v1.41.3) (2022-02-09) ##### Dependencies - update dependency com.google.protobuf:protobuf-java to v3.19.4 ([#​1568](https://togithub.com/googleapis/google-http-java-client/issues/1568)) ([416e5d7](https://togithub.com/googleapis/google-http-java-client/commit/416e5d7146ad145e3d5140110144b5119c6126df)) - update dependency com.puppycrawl.tools:checkstyle to v9.3 ([#​1569](https://togithub.com/googleapis/google-http-java-client/issues/1569)) ([9c7ade8](https://togithub.com/googleapis/google-http-java-client/commit/9c7ade85eceb2dc348e1f9aa0637d0509d634160)) - update project.opencensus.version to v0.31.0 ([#​1563](https://togithub.com/googleapis/google-http-java-client/issues/1563)) ([0f9d2b7](https://togithub.com/googleapis/google-http-java-client/commit/0f9d2b77ae23ea143b5b8caaa21af6548ca92345)) ##### [1.41.2](https://togithub.com/googleapis/google-http-java-client/compare/v1.41.1...v1.41.2) (2022-01-27) ##### Dependencies - **java:** update actions/github-script action to v5 ([#​1339](https://togithub.com/googleapis/google-http-java-client/issues/1339)) ([#​1561](https://togithub.com/googleapis/google-http-java-client/issues/1561)) ([c5dbec1](https://togithub.com/googleapis/google-http-java-client/commit/c5dbec1bbfb5f26f952cb8d80f607327594ab7a8)) - update dependency com.google.errorprone:error_prone_annotations to v2.11.0 ([#​1560](https://togithub.com/googleapis/google-http-java-client/issues/1560)) ([d9609b0](https://togithub.com/googleapis/google-http-java-client/commit/d9609b00089952d816deffa178640bfcae1f2c3a)) ##### [1.41.1](https://togithub.com/googleapis/google-http-java-client/compare/v1.41.0...v1.41.1) (2022-01-21) ##### Dependencies - update dependency com.fasterxml.jackson.core:jackson-core to v2.13.1 ([#​1527](https://togithub.com/googleapis/google-http-java-client/issues/1527)) ([7750398](https://togithub.com/googleapis/google-http-java-client/commit/7750398d6f4d6e447bfe078092f5cb146f747e50)) - update dependency com.google.protobuf:protobuf-java to v3.19.3 ([#​1549](https://togithub.com/googleapis/google-http-java-client/issues/1549)) ([50c0765](https://togithub.com/googleapis/google-http-java-client/commit/50c0765f1eadbf7aef2dccf5f78ab62e2533c6f6)) - update dependency com.puppycrawl.tools:checkstyle to v9.2.1 ([#​1532](https://togithub.com/googleapis/google-http-java-client/issues/1532)) ([e13eebd](https://togithub.com/googleapis/google-http-java-client/commit/e13eebd288afbde3aa7bdc0229c2d0db90ebbd4c)) - update dependency kr.motd.maven:os-maven-plugin to v1.7.0 ([#​1547](https://togithub.com/googleapis/google-http-java-client/issues/1547)) ([8df0dbe](https://togithub.com/googleapis/google-http-java-client/commit/8df0dbe53521e918985e8f4882392cd2e0a0a1c3)) - update dependency org.apache.felix:maven-bundle-plugin to v5 ([#​1548](https://togithub.com/googleapis/google-http-java-client/issues/1548)) ([ac10b6c](https://togithub.com/googleapis/google-http-java-client/commit/ac10b6c9fbe4986b8bf130d9f83ae77e84d74e5f)) - update project.appengine.version to v1.9.94 ([#​1557](https://togithub.com/googleapis/google-http-java-client/issues/1557)) ([05c78f4](https://togithub.com/googleapis/google-http-java-client/commit/05c78f4bee92cc501aa084ad970ed6ac9c0e0444)) - update project.opencensus.version to v0.30.0 ([#​1526](https://togithub.com/googleapis/google-http-java-client/issues/1526)) ([318e54a](https://togithub.com/googleapis/google-http-java-client/commit/318e54ae9be6bfeb4f5af0af0cb954031d95d1f9))
--- ### Configuration πŸ“… **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. β™» **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. πŸ”• **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, click this checkbox. --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/google-auth-library-java). --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index bd67e1e6c..c454f971f 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 - 1.41.8 + 1.42.0 5.8.2 31.0.1-android 2.0.5 From f2697569d60f740d4f0b9f888d0c05b464291786 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Fri, 10 Jun 2022 18:42:17 +0200 Subject: [PATCH 05/10] chore(deps): update dependency org.apache.maven.plugins:maven-surefire-plugin to v3.0.0-m7 (#926) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [org.apache.maven.plugins:maven-surefire-plugin](https://maven.apache.org/surefire/) | `3.0.0-M6` -> `3.0.0-M7` | [![age](https://badges.renovateapi.com/packages/maven/org.apache.maven.plugins:maven-surefire-plugin/3.0.0-M7/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/maven/org.apache.maven.plugins:maven-surefire-plugin/3.0.0-M7/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/maven/org.apache.maven.plugins:maven-surefire-plugin/3.0.0-M7/compatibility-slim/3.0.0-M6)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/maven/org.apache.maven.plugins:maven-surefire-plugin/3.0.0-M7/confidence-slim/3.0.0-M6)](https://docs.renovatebot.com/merge-confidence/) | --- ### Configuration πŸ“… **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. β™» **Rebasing**: Renovate will not automatically rebase this PR, because other commits have been found. πŸ”• **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, click this checkbox. ⚠ **Warning**: custom changes will be lost. --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/google-auth-library-java). --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c454f971f..f68dfb56c 100644 --- a/pom.xml +++ b/pom.xml @@ -194,7 +194,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.0.0-M6 + 3.0.0-M7 sponge_log From c3e8d169704943735c6b3df7bd0187f04fdd9aa5 Mon Sep 17 00:00:00 2001 From: Leo <39062083+lsirac@users.noreply.github.com> Date: Fri, 24 Jun 2022 15:36:01 -0700 Subject: [PATCH 06/10] feat: Adds Pluggable Auth support (WIF) (#908) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Adds Pluggable Auth support to ADC (#895) * chore(deps): update dependency com.google.http-client:google-http-client-bom to v1.41.5 (#896) [![WhiteSource Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [com.google.http-client:google-http-client-bom](https://togithub.com/googleapis/google-http-java-client) | `1.41.4` -> `1.41.5` | [![age](https://badges.renovateapi.com/packages/maven/com.google.http-client:google-http-client-bom/1.41.5/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/maven/com.google.http-client:google-http-client-bom/1.41.5/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/maven/com.google.http-client:google-http-client-bom/1.41.5/compatibility-slim/1.41.4)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/maven/com.google.http-client:google-http-client-bom/1.41.5/confidence-slim/1.41.4)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
googleapis/google-http-java-client ### [`v1.41.5`](https://togithub.com/googleapis/google-http-java-client/blob/HEAD/CHANGELOG.md#​1415-httpsgithubcomgoogleapisgoogle-http-java-clientcomparev1414v1415-2022-03-21) [Compare Source](https://togithub.com/googleapis/google-http-java-client/compare/v1.41.4...v1.41.5)
--- ### Configuration πŸ“… **Schedule**: At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. β™» **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. πŸ”• **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, click this checkbox. --- This PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/google-auth-library-java). * feat: Add ability to provide PrivateKey as Pkcs8 encoded string #883 (#889) * feat: Add ability to provide PrivateKey as Pkcs8 encoded string #883 This change adds a new method `setPrivateKeyString` in `ServiceAccountCredentials.Builder` to accept Pkcs8 encoded string representation of private keys. Co-authored-by: Timur Sadykov * chore: fix downstream check (#898) * fix: update branding in ExternalAccountCredentials (#893) These changes align the Javadoc comments with the branding that Google uses externally: + STS -> Security Token Service + GCP -> Google Cloud + Remove references to a Google-internal token type Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: - [ ] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/google-auth-library-java/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea - [ ] Ensure the tests and linter pass: Tests are failing, but I don't think that was caused by the changes in this PR - [ ] Code coverage does not decrease (if any source code was changed): n/a - [ ] Appropriate docs were updated (if necessary): n/a * feat: Adds the ExecutableHandler interface for Pluggable Auth * feat: Adds a Pluggable Auth specific exception * feat: Adds new PluggableAuthCredentials class that plug into ADC * feat: Adds unit tests for PluggableAuthCredentials and ExternalAccountCredentials * Add units tests for GoogleCredentials * fix: update javadoc/comments * fix: A concrete ExecutableOptions implementation is not needed * review: javadoc changes + constants Co-authored-by: WhiteSource Renovate Co-authored-by: Navina Ramesh Co-authored-by: Timur Sadykov Co-authored-by: Neenu Shaji Co-authored-by: Jeff Williams * feat: finalizes PluggableAuth implementation (#906) * Adds ExecutableResponse class * Adds unit tests for ExecutableResponse * Adds 3rd party executable handler * Adds unit tests for PluggableAuthHandler * Fix build issues * don't fail on javadoc errors * feat: Improve Pluggable Auth error handling (#912) * feat: improves pluggable auth error handling * cleanup * fix: consume input stream immediately for Pluggable Auth (#915) * feat: improves pluggable auth error handling * cleanup * fix: consume input stream immediately so that the spawned process will not hang if the STDOUT buffer is filled. * fix: fix merge * fix: review comments * fix: refactor to keep ImpersonatedCredentials final (#917) * fix: adds more documentation for InternalProcessBuilder and moves it to the bottom of the file * fix: keep ImpersonatedCredentials final * fix: make sure executor is shutdown Co-authored-by: WhiteSource Renovate Co-authored-by: Navina Ramesh Co-authored-by: Timur Sadykov Co-authored-by: Neenu Shaji Co-authored-by: Jeff Williams Co-authored-by: Emily Ball --- .../google/auth/oauth2/ExecutableHandler.java | 67 ++ .../auth/oauth2/ExecutableResponse.java | 206 +++++ .../oauth2/ExternalAccountCredentials.java | 56 +- .../auth/oauth2/PluggableAuthCredentials.java | 327 +++++++ .../auth/oauth2/PluggableAuthException.java | 48 ++ .../auth/oauth2/PluggableAuthHandler.java | 300 +++++++ .../auth/oauth2/ExecutableResponseTest.java | 302 +++++++ .../ExternalAccountCredentialsTest.java | 109 +++ .../auth/oauth2/GoogleCredentialsTest.java | 23 + ...ckExternalAccountCredentialsTransport.java | 5 +- .../oauth2/PluggableAuthCredentialsTest.java | 444 ++++++++++ .../oauth2/PluggableAuthExceptionTest.java | 71 ++ .../auth/oauth2/PluggableAuthHandlerTest.java | 813 ++++++++++++++++++ oauth2_http/pom.xml | 12 + pom.xml | 3 + 15 files changed, 2779 insertions(+), 7 deletions(-) create mode 100644 oauth2_http/java/com/google/auth/oauth2/ExecutableHandler.java create mode 100644 oauth2_http/java/com/google/auth/oauth2/ExecutableResponse.java create mode 100644 oauth2_http/java/com/google/auth/oauth2/PluggableAuthCredentials.java create mode 100644 oauth2_http/java/com/google/auth/oauth2/PluggableAuthException.java create mode 100644 oauth2_http/java/com/google/auth/oauth2/PluggableAuthHandler.java create mode 100644 oauth2_http/javatests/com/google/auth/oauth2/ExecutableResponseTest.java create mode 100644 oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java create mode 100644 oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthExceptionTest.java create mode 100644 oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthHandlerTest.java diff --git a/oauth2_http/java/com/google/auth/oauth2/ExecutableHandler.java b/oauth2_http/java/com/google/auth/oauth2/ExecutableHandler.java new file mode 100644 index 000000000..a052f2a5b --- /dev/null +++ b/oauth2_http/java/com/google/auth/oauth2/ExecutableHandler.java @@ -0,0 +1,67 @@ +/* + * Copyright 2022 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.auth.oauth2; + +import java.io.IOException; +import java.util.Map; +import javax.annotation.Nullable; + +/** An interface for 3rd party executable handling. */ +interface ExecutableHandler { + + /** An interface for required fields needed to call 3rd party executables. */ + interface ExecutableOptions { + + /** An absolute path to the command used to retrieve 3rd party tokens. */ + String getExecutableCommand(); + + /** A set of process-local environment variable mappings to be set for the script to execute. */ + Map getEnvironmentMap(); + + /** A timeout for waiting for the executable to finish, in milliseconds. */ + int getExecutableTimeoutMs(); + + /** + * An output file path which points to the 3rd party credentials generated by the executable. + */ + @Nullable + String getOutputFilePath(); + } + + /** + * Handles executing the 3rd party script and parsing the token from the response. + * + * @param options A set executable options for handling the executable. + * @return A 3rd party token. + */ + String retrieveTokenFromExecutable(ExecutableOptions options) throws IOException; +} diff --git a/oauth2_http/java/com/google/auth/oauth2/ExecutableResponse.java b/oauth2_http/java/com/google/auth/oauth2/ExecutableResponse.java new file mode 100644 index 000000000..5559b5442 --- /dev/null +++ b/oauth2_http/java/com/google/auth/oauth2/ExecutableResponse.java @@ -0,0 +1,206 @@ +/* + * Copyright 2022 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.auth.oauth2; + +import com.google.api.client.json.GenericJson; +import java.io.IOException; +import java.math.BigDecimal; +import java.time.Instant; +import javax.annotation.Nullable; + +/** + * Encapsulates response values for the 3rd party executable response (e.g. OIDC, SAML, error + * responses). + */ +class ExecutableResponse { + + private static final String SAML_SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:saml2"; + + private final int version; + private final boolean success; + + @Nullable private Long expirationTime; + @Nullable private String tokenType; + @Nullable private String subjectToken; + @Nullable private String errorCode; + @Nullable private String errorMessage; + + ExecutableResponse(GenericJson json) throws IOException { + if (!json.containsKey("version")) { + throw new PluggableAuthException( + "INVALID_EXECUTABLE_RESPONSE", "The executable response is missing the `version` field."); + } + + if (!json.containsKey("success")) { + throw new PluggableAuthException( + "INVALID_EXECUTABLE_RESPONSE", "The executable response is missing the `success` field."); + } + + this.version = parseIntField(json.get("version")); + this.success = (boolean) json.get("success"); + + if (success) { + if (!json.containsKey("token_type")) { + throw new PluggableAuthException( + "INVALID_EXECUTABLE_RESPONSE", + "The executable response is missing the `token_type` field."); + } + + if (!json.containsKey("expiration_time")) { + throw new PluggableAuthException( + "INVALID_EXECUTABLE_RESPONSE", + "The executable response is missing the `expiration_time` field."); + } + + this.tokenType = (String) json.get("token_type"); + this.expirationTime = parseLongField(json.get("expiration_time")); + + if (SAML_SUBJECT_TOKEN_TYPE.equals(tokenType)) { + this.subjectToken = (String) json.get("saml_response"); + } else { + this.subjectToken = (String) json.get("id_token"); + } + if (subjectToken == null || subjectToken.isEmpty()) { + throw new PluggableAuthException( + "INVALID_EXECUTABLE_RESPONSE", + "The executable response does not contain a valid token."); + } + } else { + // Error response must contain both an error code and message. + this.errorCode = (String) json.get("code"); + this.errorMessage = (String) json.get("message"); + if (errorCode == null + || errorCode.isEmpty() + || errorMessage == null + || errorMessage.isEmpty()) { + throw new PluggableAuthException( + "INVALID_EXECUTABLE_RESPONSE", + "The executable response must contain `error` and `message` fields when unsuccessful."); + } + } + } + + /** + * Returns the version of the executable output. Only version `1` is currently supported. This is + * useful for future changes to the expected output format. + * + * @return The version of the JSON output. + */ + int getVersion() { + return this.version; + } + + /** + * Returns the status of the response. + * + *

When this is true, the response will contain the 3rd party token for a sign in / refresh + * operation. When this is false, the response should contain an additional error code and + * message. + * + * @return Whether the `success` field in the executable response is true. + */ + boolean isSuccessful() { + return this.success; + } + + /** Returns true if the subject token is expired or not present, false otherwise. */ + boolean isExpired() { + return this.expirationTime == null || this.expirationTime <= Instant.now().getEpochSecond(); + } + + /** Returns whether the execution was successful and returned an unexpired token. */ + boolean isValid() { + return isSuccessful() && !isExpired(); + } + + /** Returns the subject token expiration time in seconds (Unix epoch time). */ + @Nullable + Long getExpirationTime() { + return this.expirationTime; + } + + /** + * Returns the 3rd party subject token type. + * + *

Possible valid values: + * + *

    + *
  • urn:ietf:params:oauth:token-type:id_token + *
  • urn:ietf:params:oauth:token-type:jwt + *
  • urn:ietf:params:oauth:token-type:saml2 + *
+ * + * @return The 3rd party subject token type for success responses, null otherwise. + */ + @Nullable + String getTokenType() { + return this.tokenType; + } + + /** Returns the subject token if the execution was successful, null otherwise. */ + @Nullable + String getSubjectToken() { + return this.subjectToken; + } + + /** Returns the error code if the execution was unsuccessful, null otherwise. */ + @Nullable + String getErrorCode() { + return this.errorCode; + } + + /** Returns the error message if the execution was unsuccessful, null otherwise. */ + @Nullable + String getErrorMessage() { + return this.errorMessage; + } + + private static int parseIntField(Object field) { + if (field instanceof String) { + return Integer.parseInt((String) field); + } + if (field instanceof BigDecimal) { + return ((BigDecimal) field).intValue(); + } + return (int) field; + } + + private static long parseLongField(Object field) { + if (field instanceof String) { + return Long.parseLong((String) field); + } + if (field instanceof BigDecimal) { + return ((BigDecimal) field).longValue(); + } + return (long) field; + } +} diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java index 379e2a1cf..85af46335 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java @@ -39,6 +39,7 @@ import com.google.auth.http.HttpTransportFactory; import com.google.auth.oauth2.AwsCredentials.AwsCredentialSource; import com.google.auth.oauth2.IdentityPoolCredentials.IdentityPoolCredentialSource; +import com.google.auth.oauth2.PluggableAuthCredentials.PluggableAuthCredentialSource; import com.google.common.base.MoreObjects; import java.io.IOException; import java.io.InputStream; @@ -76,6 +77,7 @@ abstract static class CredentialSource { "https://www.googleapis.com/auth/cloud-platform"; static final String EXTERNAL_ACCOUNT_FILE_TYPE = "external_account"; + static final String EXECUTABLE_SOURCE_KEY = "executable"; private final String transportFactoryClassName; private final String audience; @@ -99,6 +101,10 @@ abstract static class CredentialSource { @Nullable protected final ImpersonatedCredentials impersonatedCredentials; + // Internal override for impersonated credentials. This is done to keep + // impersonatedCredentials final. + @Nullable private ImpersonatedCredentials impersonatedCredentialsOverride; + private EnvironmentProvider environmentProvider; /** @@ -194,7 +200,7 @@ protected ExternalAccountCredentials( validateServiceAccountImpersonationInfoUrl(serviceAccountImpersonationUrl); } - this.impersonatedCredentials = initializeImpersonatedCredentials(); + this.impersonatedCredentials = buildImpersonatedCredentials(); } /** @@ -236,10 +242,10 @@ protected ExternalAccountCredentials(ExternalAccountCredentials.Builder builder) validateServiceAccountImpersonationInfoUrl(serviceAccountImpersonationUrl); } - this.impersonatedCredentials = initializeImpersonatedCredentials(); + this.impersonatedCredentials = buildImpersonatedCredentials(); } - private ImpersonatedCredentials initializeImpersonatedCredentials() { + ImpersonatedCredentials buildImpersonatedCredentials() { if (serviceAccountImpersonationUrl == null) { return null; } @@ -250,6 +256,11 @@ private ImpersonatedCredentials initializeImpersonatedCredentials() { AwsCredentials.newBuilder((AwsCredentials) this) .setServiceAccountImpersonationUrl(null) .build(); + } else if (this instanceof PluggableAuthCredentials) { + sourceCredentials = + PluggableAuthCredentials.newBuilder((PluggableAuthCredentials) this) + .setServiceAccountImpersonationUrl(null) + .build(); } else { sourceCredentials = IdentityPoolCredentials.newBuilder((IdentityPoolCredentials) this) @@ -269,6 +280,10 @@ private ImpersonatedCredentials initializeImpersonatedCredentials() { .build(); } + void overrideImpersonatedCredentials(ImpersonatedCredentials credentials) { + this.impersonatedCredentialsOverride = credentials; + } + @Override public void getRequestMetadata( URI uri, Executor executor, final RequestMetadataCallback callback) { @@ -374,8 +389,20 @@ static ExternalAccountCredentials fromJson( .setClientId(clientId) .setClientSecret(clientSecret) .build(); + } else if (isPluggableAuthCredential(credentialSourceMap)) { + return PluggableAuthCredentials.newBuilder() + .setHttpTransportFactory(transportFactory) + .setAudience(audience) + .setSubjectTokenType(subjectTokenType) + .setTokenUrl(tokenUrl) + .setTokenInfoUrl(tokenInfoUrl) + .setCredentialSource(new PluggableAuthCredentialSource(credentialSourceMap)) + .setServiceAccountImpersonationUrl(serviceAccountImpersonationUrl) + .setQuotaProjectId(quotaProjectId) + .setClientId(clientId) + .setClientSecret(clientSecret) + .build(); } - return IdentityPoolCredentials.newBuilder() .setHttpTransportFactory(transportFactory) .setAudience(audience) @@ -391,6 +418,11 @@ static ExternalAccountCredentials fromJson( .build(); } + private static boolean isPluggableAuthCredential(Map credentialSource) { + // Pluggable Auth is enabled via a nested executable field in the credential source. + return credentialSource.containsKey(EXECUTABLE_SOURCE_KEY); + } + private static boolean isAwsCredential(Map credentialSource) { return credentialSource.containsKey("environment_id") && ((String) credentialSource.get("environment_id")).startsWith("aws"); @@ -406,7 +438,10 @@ private static boolean isAwsCredential(Map credentialSource) { protected AccessToken exchangeExternalCredentialForAccessToken( StsTokenExchangeRequest stsTokenExchangeRequest) throws IOException { // Handle service account impersonation if necessary. - if (impersonatedCredentials != null) { + // Internal override takes priority. + if (impersonatedCredentialsOverride != null) { + return impersonatedCredentialsOverride.refreshAccessToken(); + } else if (impersonatedCredentials != null) { return impersonatedCredentials.refreshAccessToken(); } @@ -468,6 +503,15 @@ public String getServiceAccountImpersonationUrl() { return serviceAccountImpersonationUrl; } + /** The service account email to be impersonated, if available. */ + @Nullable + public String getServiceAccountEmail() { + if (serviceAccountImpersonationUrl == null || serviceAccountImpersonationUrl.isEmpty()) { + return null; + } + return ImpersonatedCredentials.extractTargetPrincipal(serviceAccountImpersonationUrl); + } + @Override @Nullable public String getQuotaProjectId() { @@ -499,7 +543,7 @@ EnvironmentProvider getEnvironmentProvider() { } /** - * Returns whether or not the current configuration is for Workforce Pools (which enable 3p user + * Returns whether the current configuration is for Workforce Pools (which enable 3p user * identities, rather than workloads). */ public boolean isWorkforcePoolConfiguration() { diff --git a/oauth2_http/java/com/google/auth/oauth2/PluggableAuthCredentials.java b/oauth2_http/java/com/google/auth/oauth2/PluggableAuthCredentials.java new file mode 100644 index 000000000..e3506c080 --- /dev/null +++ b/oauth2_http/java/com/google/auth/oauth2/PluggableAuthCredentials.java @@ -0,0 +1,327 @@ +/* + * Copyright 2022 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.auth.oauth2; + +import com.google.auth.oauth2.ExecutableHandler.ExecutableOptions; +import com.google.common.annotations.VisibleForTesting; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nullable; + +/** + * PluggableAuthCredentials enables the exchange of workload identity pool external credentials for + * Google access tokens by retrieving 3rd party tokens through a user supplied executable. These + * scripts/executables are completely independent of the Google Cloud Auth libraries. These + * credentials plug into ADC and will call the specified executable to retrieve the 3rd party token + * to be exchanged for a Google access token. + * + *

To use these credentials, the GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES environment variable + * must be set to '1'. This is for security reasons. + * + *

Both OIDC and SAML are supported. The executable must adhere to a specific response format + * defined below. + * + *

The executable should print out the 3rd party token to STDOUT in JSON format. This is not + * required when an output_file is specified in the credential source, with the expectation being + * that the output file will contain the JSON response instead. + * + *

+ * OIDC response sample:
+ * {
+ *   "version": 1,
+ *   "success": true,
+ *   "token_type": "urn:ietf:params:oauth:token-type:id_token",
+ *   "id_token": "HEADER.PAYLOAD.SIGNATURE",
+ *   "expiration_time": 1620433341
+ * }
+ *
+ * SAML2 response sample:
+ * {
+ *   "version": 1,
+ *   "success": true,
+ *   "token_type": "urn:ietf:params:oauth:token-type:saml2",
+ *   "saml_response": "...",
+ *   "expiration_time": 1620433341
+ * }
+ *
+ * Error response sample:
+ * {
+ *   "version": 1,
+ *   "success": false,
+ *   "code": "401",
+ *   "message": "Error message."
+ * }
+ *
+ * The auth libraries will populate certain environment variables that will be accessible by the
+ * executable, such as: GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE, GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE,
+ * GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE, GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL, and
+ * GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE.
+ *
+ * 

Please see this repositories README for a complete executable request/response specification. + *

+ */ +public class PluggableAuthCredentials extends ExternalAccountCredentials { + + /** + * Encapsulates the credential source portion of the configuration for PluggableAuthCredentials. + * + *

Command is the only required field. If timeout_millis is not specified, the library will + * default to a 30 second timeout. + * + *

+   * Sample credential source for Pluggable Auth credentials:
+   * {
+   *   ...
+   *   "credential_source": {
+   *     "executable": {
+   *       "command": "/path/to/get/credentials.sh --arg1=value1 --arg2=value2",
+   *       "timeout_millis": 5000,
+   *       "output_file": "/path/to/generated/cached/credentials"
+   *     }
+   *   }
+   * }
+   * 
+ */ + static class PluggableAuthCredentialSource extends CredentialSource { + + // The default timeout for waiting for the executable to finish (30 seconds). + private static final int DEFAULT_EXECUTABLE_TIMEOUT_MS = 30 * 1000; + // The minimum timeout for waiting for the executable to finish (5 seconds). + private static final int MINIMUM_EXECUTABLE_TIMEOUT_MS = 5 * 1000; + // The maximum timeout for waiting for the executable to finish (120 seconds). + private static final int MAXIMUM_EXECUTABLE_TIMEOUT_MS = 120 * 1000; + + private static final String COMMAND_KEY = "command"; + private static final String TIMEOUT_MILLIS_KEY = "timeout_millis"; + private static final String OUTPUT_FILE_KEY = "output_file"; + + // Required. The command used to retrieve the 3rd party token. + private final String executableCommand; + + // Optional. Set to the default timeout when not provided. + private final int executableTimeoutMs; + + // Optional. Provided when the 3rd party executable caches the response at the specified + // location. + @Nullable private final String outputFilePath; + + PluggableAuthCredentialSource(Map credentialSourceMap) { + super(credentialSourceMap); + + if (!credentialSourceMap.containsKey(EXECUTABLE_SOURCE_KEY)) { + throw new IllegalArgumentException( + "Invalid credential source for PluggableAuth credentials."); + } + + Map executable = + (Map) credentialSourceMap.get(EXECUTABLE_SOURCE_KEY); + + // Command is the only required field. + if (!executable.containsKey(COMMAND_KEY)) { + throw new IllegalArgumentException( + "The PluggableAuthCredentialSource is missing the required 'command' field."); + } + + // Parse the executable timeout. + if (executable.containsKey(TIMEOUT_MILLIS_KEY)) { + Object timeout = executable.get(TIMEOUT_MILLIS_KEY); + if (timeout instanceof BigDecimal) { + executableTimeoutMs = ((BigDecimal) timeout).intValue(); + } else if (executable.get(TIMEOUT_MILLIS_KEY) instanceof Integer) { + executableTimeoutMs = (int) timeout; + } else { + executableTimeoutMs = Integer.parseInt((String) timeout); + } + } else { + executableTimeoutMs = DEFAULT_EXECUTABLE_TIMEOUT_MS; + } + + // Provided timeout must be between 5s and 120s. + if (executableTimeoutMs < MINIMUM_EXECUTABLE_TIMEOUT_MS + || executableTimeoutMs > MAXIMUM_EXECUTABLE_TIMEOUT_MS) { + throw new IllegalArgumentException( + String.format( + "The executable timeout must be between %s and %s milliseconds.", + MINIMUM_EXECUTABLE_TIMEOUT_MS, MAXIMUM_EXECUTABLE_TIMEOUT_MS)); + } + + executableCommand = (String) executable.get(COMMAND_KEY); + outputFilePath = (String) executable.get(OUTPUT_FILE_KEY); + } + + String getCommand() { + return executableCommand; + } + + int getTimeoutMs() { + return executableTimeoutMs; + } + + @Nullable + String getOutputFilePath() { + return outputFilePath; + } + } + + private final PluggableAuthCredentialSource config; + + private final ExecutableHandler handler; + + /** Internal constructor. See {@link Builder}. */ + PluggableAuthCredentials(Builder builder) { + super(builder); + this.config = (PluggableAuthCredentialSource) builder.credentialSource; + + if (builder.handler != null) { + handler = builder.handler; + } else { + handler = new PluggableAuthHandler(getEnvironmentProvider()); + } + + // Re-initialize impersonated credentials as the handler hasn't been set yet when + // this is called in the base class. + overrideImpersonatedCredentials(buildImpersonatedCredentials()); + } + + @Override + public AccessToken refreshAccessToken() throws IOException { + String credential = retrieveSubjectToken(); + StsTokenExchangeRequest.Builder stsTokenExchangeRequest = + StsTokenExchangeRequest.newBuilder(credential, getSubjectTokenType()) + .setAudience(getAudience()); + + Collection scopes = getScopes(); + if (scopes != null && !scopes.isEmpty()) { + stsTokenExchangeRequest.setScopes(new ArrayList<>(scopes)); + } + return exchangeExternalCredentialForAccessToken(stsTokenExchangeRequest.build()); + } + + /** + * Returns the 3rd party subject token by calling the executable specified in the credential + * source. + * + * @throws IOException if an error occurs with the executable execution. + */ + @Override + public String retrieveSubjectToken() throws IOException { + String executableCommand = config.getCommand(); + String outputFilePath = config.getOutputFilePath(); + int executableTimeoutMs = config.getTimeoutMs(); + + Map envMap = new HashMap<>(); + envMap.put("GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE", getAudience()); + envMap.put("GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE", getSubjectTokenType()); + // Always set to 0 for Workload Identity Federation. + envMap.put("GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE", "0"); + if (getServiceAccountEmail() != null) { + envMap.put("GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL", getServiceAccountEmail()); + } + if (outputFilePath != null && !outputFilePath.isEmpty()) { + envMap.put("GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE", outputFilePath); + } + + ExecutableOptions options = + new ExecutableOptions() { + @Override + public String getExecutableCommand() { + return executableCommand; + } + + @Override + public Map getEnvironmentMap() { + return envMap; + } + + @Override + public int getExecutableTimeoutMs() { + return executableTimeoutMs; + } + + @Nullable + @Override + public String getOutputFilePath() { + return outputFilePath; + } + }; + + // Delegate handling of the executable to the handler. + return this.handler.retrieveTokenFromExecutable(options); + } + + /** Clones the PluggableAuthCredentials with the specified scopes. */ + @Override + public PluggableAuthCredentials createScoped(Collection newScopes) { + return new PluggableAuthCredentials( + (PluggableAuthCredentials.Builder) newBuilder(this).setScopes(newScopes)); + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static Builder newBuilder(PluggableAuthCredentials pluggableAuthCredentials) { + return new Builder(pluggableAuthCredentials); + } + + @VisibleForTesting + @Nullable + ExecutableHandler getExecutableHandler() { + return this.handler; + } + + public static class Builder extends ExternalAccountCredentials.Builder { + + private ExecutableHandler handler; + + Builder() {} + + Builder(PluggableAuthCredentials credentials) { + super(credentials); + this.handler = credentials.handler; + } + + public Builder setExecutableHandler(ExecutableHandler handler) { + this.handler = handler; + return this; + } + + @Override + public PluggableAuthCredentials build() { + return new PluggableAuthCredentials(this); + } + } +} diff --git a/oauth2_http/java/com/google/auth/oauth2/PluggableAuthException.java b/oauth2_http/java/com/google/auth/oauth2/PluggableAuthException.java new file mode 100644 index 000000000..894b324a9 --- /dev/null +++ b/oauth2_http/java/com/google/auth/oauth2/PluggableAuthException.java @@ -0,0 +1,48 @@ +/* + * Copyright 2022 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.auth.oauth2; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** Encapsulates the error response's for 3rd party executables defined by the executable spec. */ +class PluggableAuthException extends OAuthException { + + PluggableAuthException(String errorCode, String errorDescription) { + super(errorCode, checkNotNull(errorDescription), /* errorUri=*/ null); + } + + /** The message with format: Error code {errorCode}: {errorDescription}. */ + @Override + public String getMessage() { + return "Error code " + getErrorCode() + ": " + getErrorDescription(); + } +} diff --git a/oauth2_http/java/com/google/auth/oauth2/PluggableAuthHandler.java b/oauth2_http/java/com/google/auth/oauth2/PluggableAuthHandler.java new file mode 100644 index 000000000..24b0978cd --- /dev/null +++ b/oauth2_http/java/com/google/auth/oauth2/PluggableAuthHandler.java @@ -0,0 +1,300 @@ +/* + * Copyright 2022 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.auth.oauth2; + +import com.google.api.client.json.GenericJson; +import com.google.api.client.json.JsonParser; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Splitter; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; + +/** + * Internal handler for retrieving 3rd party tokens from user defined scripts/executables for + * workload identity federation. + * + *

See {@link PluggableAuthCredentials}. + */ +final class PluggableAuthHandler implements ExecutableHandler { + + // The maximum supported version for the executable response. + // The executable response always includes a version number that is used + // to detect compatibility with the response and library verions. + private static final int EXECUTABLE_SUPPORTED_MAX_VERSION = 1; + + // The GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES dictates if this feature is enabled. + // The GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES environment variable must be set to '1' for + // security reasons. + private static final String GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES = + "GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES"; + + // The exit status of the 3P script that represents a successful execution. + private static final int EXIT_CODE_SUCCESS = 0; + + private final EnvironmentProvider environmentProvider; + private InternalProcessBuilder internalProcessBuilder; + + PluggableAuthHandler(EnvironmentProvider environmentProvider) { + this.environmentProvider = environmentProvider; + } + + @VisibleForTesting + PluggableAuthHandler( + EnvironmentProvider environmentProvider, InternalProcessBuilder internalProcessBuilder) { + this.environmentProvider = environmentProvider; + this.internalProcessBuilder = internalProcessBuilder; + } + + @Override + public String retrieveTokenFromExecutable(ExecutableOptions options) throws IOException { + // Validate that executables are allowed to run. To use Pluggable Auth, + // The GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES environment variable must be set to 1 + // for security reasons. + if (!"1".equals(this.environmentProvider.getEnv(GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES))) { + throw new PluggableAuthException( + "PLUGGABLE_AUTH_DISABLED", + "Pluggable Auth executables need " + + "to be explicitly allowed to run by setting the " + + "GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES environment variable to 1."); + } + + // Users can specify an output file path in the Pluggable Auth ADC configuration. + // This is the file's absolute path. Their executable will handle writing the 3P credentials to + // this file. + // If specified, we will first check if we have valid unexpired credentials stored in this + // location to avoid running the executable until they are expired. + ExecutableResponse executableResponse = getCachedExecutableResponse(options); + + // If the output_file does not contain a valid response, call the executable. + if (executableResponse == null) { + executableResponse = getExecutableResponse(options); + } + + // The executable response includes a version. Validate that the version is compatible + // with the library. + if (executableResponse.getVersion() > EXECUTABLE_SUPPORTED_MAX_VERSION) { + throw new PluggableAuthException( + "UNSUPPORTED_VERSION", + "The version of the executable response is not supported. " + + String.format( + "The maximum version currently supported is %s.", + EXECUTABLE_SUPPORTED_MAX_VERSION)); + } + + if (!executableResponse.isSuccessful()) { + throw new PluggableAuthException( + executableResponse.getErrorCode(), executableResponse.getErrorMessage()); + } + + if (executableResponse.isExpired()) { + throw new PluggableAuthException("INVALID_RESPONSE", "The executable response is expired."); + } + + // Subject token is valid and can be returned. + return executableResponse.getSubjectToken(); + } + + @Nullable + ExecutableResponse getCachedExecutableResponse(ExecutableOptions options) + throws PluggableAuthException { + ExecutableResponse executableResponse = null; + if (options.getOutputFilePath() != null && !options.getOutputFilePath().isEmpty()) { + // Try reading cached response from output_file. + try { + File outputFile = new File(options.getOutputFilePath()); + // Check if the output file is valid and not empty. + if (outputFile.isFile() && outputFile.length() > 0) { + InputStream inputStream = new FileInputStream(options.getOutputFilePath()); + BufferedReader reader = + new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + JsonParser parser = OAuth2Utils.JSON_FACTORY.createJsonParser(reader); + ExecutableResponse cachedResponse = + new ExecutableResponse(parser.parseAndClose(GenericJson.class)); + // If the cached response is successful and unexpired, we can use it. + // Response version will be validated below. + if (cachedResponse.isValid()) { + executableResponse = cachedResponse; + } + } + } catch (Exception e) { + throw new PluggableAuthException( + "INVALID_OUTPUT_FILE", + "The output_file specified contains an invalid or malformed response." + e); + } + } + return executableResponse; + } + + ExecutableResponse getExecutableResponse(ExecutableOptions options) throws IOException { + List components = Splitter.on(" ").splitToList(options.getExecutableCommand()); + + // Create the process. + InternalProcessBuilder processBuilder = getProcessBuilder(components); + + // Inject environment variables. + Map envMap = processBuilder.environment(); + envMap.putAll(options.getEnvironmentMap()); + + // Redirect error stream. + processBuilder.redirectErrorStream(true); + + // Start the process. + Process process = processBuilder.start(); + + ExecutableResponse execResp; + String executableOutput = ""; + ExecutorService executor = Executors.newSingleThreadExecutor(); + try { + // Consume the input stream while waiting for the program to finish so that + // the process won't hang if the STDOUT buffer is filled. + Future future = + executor.submit( + () -> { + BufferedReader reader = + new BufferedReader( + new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8)); + + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line).append(System.lineSeparator()); + } + return sb.toString().trim(); + }); + + boolean success = process.waitFor(options.getExecutableTimeoutMs(), TimeUnit.MILLISECONDS); + if (!success) { + // Process has not terminated within the specified timeout. + throw new PluggableAuthException( + "TIMEOUT_EXCEEDED", "The executable failed to finish within the timeout specified."); + } + int exitCode = process.exitValue(); + if (exitCode != EXIT_CODE_SUCCESS) { + throw new PluggableAuthException( + "EXIT_CODE", String.format("The executable failed with exit code %s.", exitCode)); + } + + executableOutput = future.get(); + executor.shutdownNow(); + + JsonParser parser = OAuth2Utils.JSON_FACTORY.createJsonParser(executableOutput); + execResp = new ExecutableResponse(parser.parseAndClose(GenericJson.class)); + } catch (IOException e) { + // Destroy the process. + process.destroy(); + + // Shutdown executor if needed. + if (!executor.isShutdown()) { + executor.shutdownNow(); + } + + if (e instanceof PluggableAuthException) { + throw e; + } + // An error may have occurred in the executable and should be surfaced. + throw new PluggableAuthException( + "INVALID_RESPONSE", + String.format("The executable returned an invalid response: %s.", executableOutput)); + } catch (InterruptedException | ExecutionException e) { + // Destroy the process. + process.destroy(); + + throw new PluggableAuthException( + "INTERRUPTED", String.format("The execution was interrupted: %s.", e)); + } + + process.destroy(); + return execResp; + } + + InternalProcessBuilder getProcessBuilder(List commandComponents) { + if (internalProcessBuilder != null) { + return internalProcessBuilder; + } + return new DefaultProcessBuilder(new ProcessBuilder(commandComponents)); + } + + /** + * An interface for creating and managing a process. + * + *

ProcessBuilder is final and does not implement any interface. This class allows concrete + * implementations to be specified to test these changes. + */ + abstract static class InternalProcessBuilder { + + abstract Map environment(); + + abstract InternalProcessBuilder redirectErrorStream(boolean redirectErrorStream); + + abstract Process start() throws IOException; + } + + /** + * A default implementation for {@link InternalProcessBuilder} that wraps {@link ProcessBuilder}. + */ + static final class DefaultProcessBuilder extends InternalProcessBuilder { + ProcessBuilder processBuilder; + + DefaultProcessBuilder(ProcessBuilder processBuilder) { + this.processBuilder = processBuilder; + } + + @Override + Map environment() { + return this.processBuilder.environment(); + } + + @Override + InternalProcessBuilder redirectErrorStream(boolean redirectErrorStream) { + this.processBuilder.redirectErrorStream(redirectErrorStream); + return this; + } + + @Override + Process start() throws IOException { + return this.processBuilder.start(); + } + } +} diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ExecutableResponseTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ExecutableResponseTest.java new file mode 100644 index 000000000..b6f85684a --- /dev/null +++ b/oauth2_http/javatests/com/google/auth/oauth2/ExecutableResponseTest.java @@ -0,0 +1,302 @@ +/* + * Copyright 2022 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.auth.oauth2; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.google.api.client.json.GenericJson; +import java.io.IOException; +import java.math.BigDecimal; +import java.time.Instant; +import org.junit.jupiter.api.Test; + +/** Tests for {@link ExecutableResponse}. */ +class ExecutableResponseTest { + + private static final String TOKEN_TYPE_OIDC = "urn:ietf:params:oauth:token-type:id_token"; + private static final String TOKEN_TYPE_SAML = "urn:ietf:params:oauth:token-type:saml2"; + private static final String ID_TOKEN = "header.payload.signature"; + private static final String SAML_RESPONSE = "samlResponse"; + + private static final int EXECUTABLE_SUPPORTED_MAX_VERSION = 1; + private static final int EXPIRATION_DURATION = 3600; + + @Test + void constructor_successOidcResponse() throws IOException { + ExecutableResponse response = new ExecutableResponse(buildOidcResponse()); + + assertTrue(response.isSuccessful()); + assertTrue(response.isValid()); + assertEquals(1, response.getVersion()); + assertEquals(TOKEN_TYPE_OIDC, response.getTokenType()); + assertEquals(ID_TOKEN, response.getSubjectToken()); + assertEquals( + Instant.now().getEpochSecond() + EXPIRATION_DURATION, response.getExpirationTime()); + assertEquals(1, response.getVersion()); + } + + @Test + void constructor_successSamlResponse() throws IOException { + ExecutableResponse response = new ExecutableResponse(buildSamlResponse()); + + assertTrue(response.isSuccessful()); + assertTrue(response.isValid()); + assertEquals(EXECUTABLE_SUPPORTED_MAX_VERSION, response.getVersion()); + assertEquals(TOKEN_TYPE_SAML, response.getTokenType()); + assertEquals(SAML_RESPONSE, response.getSubjectToken()); + assertEquals( + Instant.now().getEpochSecond() + EXPIRATION_DURATION, response.getExpirationTime()); + } + + @Test + void constructor_validErrorResponse() throws IOException { + ExecutableResponse response = new ExecutableResponse(buildErrorResponse()); + + assertFalse(response.isSuccessful()); + assertFalse(response.isValid()); + assertTrue(response.isExpired()); + assertNull(response.getSubjectToken()); + assertNull(response.getTokenType()); + assertNull(response.getExpirationTime()); + assertEquals(1, response.getVersion()); + assertEquals("401", response.getErrorCode()); + assertEquals("Caller not authorized.", response.getErrorMessage()); + } + + @Test + void constructor_errorResponseMissingCode_throws() { + GenericJson jsonResponse = buildErrorResponse(); + + Object[] values = new Object[] {null, ""}; + for (Object value : values) { + jsonResponse.put("code", value); + + PluggableAuthException exception = + assertThrows( + PluggableAuthException.class, + () -> new ExecutableResponse(jsonResponse), + "Exception should be thrown."); + + assertEquals( + "Error code INVALID_EXECUTABLE_RESPONSE: The executable response must contain " + + "`error` and `message` fields when unsuccessful.", + exception.getMessage()); + } + } + + @Test + void constructor_errorResponseMissingMessage_throws() { + GenericJson jsonResponse = buildErrorResponse(); + + Object[] values = new Object[] {null, ""}; + for (Object value : values) { + jsonResponse.put("message", value); + + PluggableAuthException exception = + assertThrows( + PluggableAuthException.class, + () -> new ExecutableResponse(jsonResponse), + "Exception should be thrown."); + + assertEquals( + "Error code INVALID_EXECUTABLE_RESPONSE: The executable response must contain " + + "`error` and `message` fields when unsuccessful.", + exception.getMessage()); + } + } + + @Test + void constructor_successResponseMissingVersionField_throws() { + GenericJson jsonResponse = buildOidcResponse(); + jsonResponse.remove("version"); + + PluggableAuthException exception = + assertThrows( + PluggableAuthException.class, + () -> new ExecutableResponse(jsonResponse), + "Exception should be thrown."); + + assertEquals( + "Error code INVALID_EXECUTABLE_RESPONSE: The executable response is missing the " + + "`version` field.", + exception.getMessage()); + } + + @Test + void constructor_successResponseMissingSuccessField_throws() { + GenericJson jsonResponse = buildOidcResponse(); + jsonResponse.remove("success"); + + PluggableAuthException exception = + assertThrows( + PluggableAuthException.class, + () -> new ExecutableResponse(jsonResponse), + "Exception should be thrown."); + + assertEquals( + "Error code INVALID_EXECUTABLE_RESPONSE: The executable response is missing the " + + "`success` field.", + exception.getMessage()); + } + + @Test + void constructor_successResponseMissingTokenTypeField_throws() { + GenericJson jsonResponse = buildOidcResponse(); + jsonResponse.remove("token_type"); + + PluggableAuthException exception = + assertThrows( + PluggableAuthException.class, + () -> new ExecutableResponse(jsonResponse), + "Exception should be thrown."); + + assertEquals( + "Error code INVALID_EXECUTABLE_RESPONSE: The executable response is missing the " + + "`token_type` field.", + exception.getMessage()); + } + + @Test + void constructor_successResponseMissingExpirationTimeField_throws() { + GenericJson jsonResponse = buildOidcResponse(); + jsonResponse.remove("expiration_time"); + + PluggableAuthException exception = + assertThrows( + PluggableAuthException.class, + () -> new ExecutableResponse(jsonResponse), + "Exception should be thrown."); + + assertEquals( + "Error code INVALID_EXECUTABLE_RESPONSE: The executable response is missing the " + + "`expiration_time` field.", + exception.getMessage()); + } + + @Test + void constructor_samlResponseMissingSubjectToken_throws() { + GenericJson jsonResponse = buildSamlResponse(); + + Object[] values = new Object[] {null, ""}; + for (Object value : values) { + jsonResponse.put("saml_response", value); + + PluggableAuthException exception = + assertThrows( + PluggableAuthException.class, + () -> new ExecutableResponse(jsonResponse), + "Exception should be thrown."); + + assertEquals( + "Error code INVALID_EXECUTABLE_RESPONSE: The executable response does not " + + "contain a valid token.", + exception.getMessage()); + } + } + + @Test + void constructor_oidcResponseMissingSubjectToken_throws() { + GenericJson jsonResponse = buildOidcResponse(); + + Object[] values = new Object[] {null, ""}; + for (Object value : values) { + jsonResponse.put("id_token", value); + + PluggableAuthException exception = + assertThrows( + PluggableAuthException.class, + () -> new ExecutableResponse(jsonResponse), + "Exception should be thrown."); + + assertEquals( + "Error code INVALID_EXECUTABLE_RESPONSE: The executable response does not " + + "contain a valid token.", + exception.getMessage()); + } + } + + @Test + void isExpired() throws IOException { + GenericJson jsonResponse = buildOidcResponse(); + + BigDecimal[] values = + new BigDecimal[] { + BigDecimal.valueOf(Instant.now().getEpochSecond() - 1000), + BigDecimal.valueOf(Instant.now().getEpochSecond() + 1000) + }; + boolean[] expectedResults = new boolean[] {true, false}; + + for (int i = 0; i < values.length; i++) { + jsonResponse.put("expiration_time", values[i]); + + ExecutableResponse response = new ExecutableResponse(jsonResponse); + + assertEquals(expectedResults[i], response.isExpired()); + } + } + + private static GenericJson buildOidcResponse() { + GenericJson json = new GenericJson(); + json.setFactory(OAuth2Utils.JSON_FACTORY); + json.put("version", EXECUTABLE_SUPPORTED_MAX_VERSION); + json.put("success", true); + json.put("token_type", TOKEN_TYPE_OIDC); + json.put("id_token", ID_TOKEN); + json.put("expiration_time", Instant.now().getEpochSecond() + EXPIRATION_DURATION); + return json; + } + + private static GenericJson buildSamlResponse() { + GenericJson json = new GenericJson(); + json.setFactory(OAuth2Utils.JSON_FACTORY); + json.put("version", EXECUTABLE_SUPPORTED_MAX_VERSION); + json.put("success", true); + json.put("token_type", TOKEN_TYPE_SAML); + json.put("saml_response", "samlResponse"); + json.put("expiration_time", Instant.now().getEpochSecond() + EXPIRATION_DURATION); + return json; + } + + private static GenericJson buildErrorResponse() { + GenericJson json = new GenericJson(); + json.setFactory(OAuth2Utils.JSON_FACTORY); + json.put("version", EXECUTABLE_SUPPORTED_MAX_VERSION); + json.put("success", false); + json.put("code", "401"); + json.put("message", "Caller not authorized."); + return json; + } +} diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java index fb94bb93d..1b2b53a1c 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ExternalAccountCredentialsTest.java @@ -43,9 +43,11 @@ import com.google.auth.TestUtils; import com.google.auth.http.HttpTransportFactory; import com.google.auth.oauth2.ExternalAccountCredentialsTest.TestExternalAccountCredentials.TestCredentialSource; +import com.google.auth.oauth2.PluggableAuthCredentials.PluggableAuthCredentialSource; import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.URI; +import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; @@ -105,6 +107,16 @@ void fromStream_awsCredentials() throws IOException { assertTrue(credential instanceof AwsCredentials); } + @Test + void fromStream_pluggableAuthCredentials() throws IOException { + GenericJson json = buildJsonPluggableAuthCredential(); + + ExternalAccountCredentials credential = + ExternalAccountCredentials.fromStream(TestUtils.jsonToInputStream(json)); + + assertTrue(credential instanceof PluggableAuthCredentials); + } + @Test void fromStream_invalidStream_throws() { GenericJson json = buildJsonAwsCredential(); @@ -203,6 +215,53 @@ void fromJson_awsCredentials() { assertNotNull(credential.getCredentialSource()); } + @Test + void fromJson_pluggableAuthCredentials() { + ExternalAccountCredentials credential = + ExternalAccountCredentials.fromJson( + buildJsonPluggableAuthCredential(), OAuth2Utils.HTTP_TRANSPORT_FACTORY); + + assertTrue(credential instanceof PluggableAuthCredentials); + assertEquals("audience", credential.getAudience()); + assertEquals("subjectTokenType", credential.getSubjectTokenType()); + assertEquals(STS_URL, credential.getTokenUrl()); + assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); + assertNotNull(credential.getCredentialSource()); + + PluggableAuthCredentialSource source = + (PluggableAuthCredentialSource) credential.getCredentialSource(); + assertEquals("command", source.getCommand()); + assertEquals(30000, source.getTimeoutMs()); // Default timeout is 30s. + assertNull(source.getOutputFilePath()); + } + + @Test + void fromJson_pluggableAuthCredentials_allExecutableOptionsSet() { + GenericJson json = buildJsonPluggableAuthCredential(); + Map credentialSourceMap = (Map) json.get("credential_source"); + // Add optional params to the executable config (timeout, output file path). + Map executableConfig = + (Map) credentialSourceMap.get("executable"); + executableConfig.put("timeout_millis", 5000); + executableConfig.put("output_file", "path/to/output/file"); + + ExternalAccountCredentials credential = + ExternalAccountCredentials.fromJson(json, OAuth2Utils.HTTP_TRANSPORT_FACTORY); + + assertTrue(credential instanceof PluggableAuthCredentials); + assertEquals("audience", credential.getAudience()); + assertEquals("subjectTokenType", credential.getSubjectTokenType()); + assertEquals(STS_URL, credential.getTokenUrl()); + assertEquals("tokenInfoUrl", credential.getTokenInfoUrl()); + assertNotNull(credential.getCredentialSource()); + + PluggableAuthCredentialSource source = + (PluggableAuthCredentialSource) credential.getCredentialSource(); + assertEquals("command", source.getCommand()); + assertEquals("path/to/output/file", source.getOutputFilePath()); + assertEquals(5000, source.getTimeoutMs()); + } + @Test void fromJson_nullJson_throws() { assertThrows( @@ -513,6 +572,38 @@ void exchangeExternalCredentialForAccessToken_withServiceAccountImpersonation() transportFactory.transport.getServiceAccountAccessToken(), returnedToken.getTokenValue()); } + @Test + void exchangeExternalCredentialForAccessToken_withServiceAccountImpersonationOverride() + throws IOException { + transportFactory.transport.setExpireTime(TestUtils.getDefaultExpireTime()); + + String serviceAccountEmail = "different@different.iam.gserviceaccount.com"; + ExternalAccountCredentials credential = + ExternalAccountCredentials.fromStream( + IdentityPoolCredentialsTest.writeIdentityPoolCredentialsStream( + transportFactory.transport.getStsUrl(), + transportFactory.transport.getMetadataUrl(), + transportFactory.transport.getServiceAccountImpersonationUrl()), + transportFactory); + + // Override impersonated credentials. + ExternalAccountCredentials sourceCredentials = + IdentityPoolCredentials.newBuilder((IdentityPoolCredentials) credential) + .setServiceAccountImpersonationUrl(null) + .build(); + credential.overrideImpersonatedCredentials( + new ImpersonatedCredentials.Builder(sourceCredentials, serviceAccountEmail) + .setScopes(new ArrayList<>(sourceCredentials.getScopes())) + .setHttpTransportFactory(transportFactory) + .build()); + + credential.exchangeExternalCredentialForAccessToken( + StsTokenExchangeRequest.newBuilder("credential", "subjectTokenType").build()); + + assertTrue( + transportFactory.transport.getRequests().get(2).getUrl().contains(serviceAccountEmail)); + } + @Test void exchangeExternalCredentialForAccessToken_throws() throws IOException { ExternalAccountCredentials credential = @@ -704,6 +795,24 @@ private GenericJson buildJsonAwsCredential() { return json; } + private GenericJson buildJsonPluggableAuthCredential() { + GenericJson json = new GenericJson(); + json.put("audience", "audience"); + json.put("subject_token_type", "subjectTokenType"); + json.put("token_url", STS_URL); + json.put("token_info_url", "tokenInfoUrl"); + + Map> credentialSource = new HashMap<>(); + + Map executableConfig = new HashMap<>(); + executableConfig.put("command", "command"); + + credentialSource.put("executable", executableConfig); + json.put("credential_source", credentialSource); + + return json; + } + static class TestExternalAccountCredentials extends ExternalAccountCredentials { static class TestCredentialSource extends IdentityPoolCredentials.IdentityPoolCredentialSource { protected TestCredentialSource(Map credentialSourceMap) { diff --git a/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java index 8294749b2..4505445ce 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java @@ -260,6 +260,29 @@ void fromStream_awsCredentials_providesToken() throws IOException { TestUtils.assertContainsBearerToken(metadata, transportFactory.transport.getAccessToken()); } + @Test + void fromStream_pluggableAuthCredentials_providesToken() throws IOException { + MockExternalAccountCredentialsTransportFactory transportFactory = + new MockExternalAccountCredentialsTransportFactory(); + + InputStream stream = + PluggableAuthCredentialsTest.writeCredentialsStream(transportFactory.transport.getStsUrl()); + + GoogleCredentials credentials = GoogleCredentials.fromStream(stream, transportFactory); + + assertNotNull(credentials); + + // Create copy with mock executable handler. + PluggableAuthCredentials copy = + PluggableAuthCredentials.newBuilder((PluggableAuthCredentials) credentials) + .setExecutableHandler(options -> "pluggableAuthToken") + .build(); + + copy = copy.createScoped(SCOPES); + Map> metadata = copy.getRequestMetadata(CALL_URI); + TestUtils.assertContainsBearerToken(metadata, transportFactory.transport.getAccessToken()); + } + @Test void fromStream_Impersonation_providesToken_WithQuotaProject() throws IOException { MockTokenServerTransportFactory transportFactoryForSource = diff --git a/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java b/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java index 74f4771ca..1199ac1f7 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/MockExternalAccountCredentialsTransport.java @@ -84,6 +84,8 @@ public class MockExternalAccountCredentialsTransport extends MockHttpTransport { static final String SERVICE_ACCOUNT_IMPERSONATION_URL = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/testn@test.iam.gserviceaccount.com:generateAccessToken"; + static final String IAM_ENDPOINT = "https://iamcredentials.googleapis.com"; + private Queue responseSequence = new ArrayDeque<>(); private Queue responseErrorSequence = new ArrayDeque<>(); private Queue refreshTokenSequence = new ArrayDeque<>(); @@ -193,7 +195,8 @@ public LowLevelHttpResponse execute() throws IOException { .setContentType(Json.MEDIA_TYPE) .setContent(response.toPrettyString()); } - if (SERVICE_ACCOUNT_IMPERSONATION_URL.equals(url)) { + + if (url.contains(IAM_ENDPOINT)) { GenericJson query = OAuth2Utils.JSON_FACTORY .createJsonParser(getContentAsString()) diff --git a/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java new file mode 100644 index 000000000..01185bdb0 --- /dev/null +++ b/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthCredentialsTest.java @@ -0,0 +1,444 @@ +/* + * Copyright 2022 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.auth.oauth2; + +import static com.google.auth.oauth2.MockExternalAccountCredentialsTransport.SERVICE_ACCOUNT_IMPERSONATION_URL; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.google.api.client.http.HttpTransport; +import com.google.api.client.json.GenericJson; +import com.google.auth.TestUtils; +import com.google.auth.http.HttpTransportFactory; +import com.google.auth.oauth2.ExecutableHandler.ExecutableOptions; +import com.google.auth.oauth2.ExternalAccountCredentials.CredentialSource; +import com.google.auth.oauth2.PluggableAuthCredentials.PluggableAuthCredentialSource; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; +import org.junit.jupiter.api.Test; + +/** Tests for {@link PluggableAuthCredentials}. */ +class PluggableAuthCredentialsTest { + // The default timeout for waiting for the executable to finish (30 seconds). + private static final int DEFAULT_EXECUTABLE_TIMEOUT_MS = 30 * 1000; + // The minimum timeout for waiting for the executable to finish (5 seconds). + private static final int MINIMUM_EXECUTABLE_TIMEOUT_MS = 5 * 1000; + // The maximum timeout for waiting for the executable to finish (120 seconds). + private static final int MAXIMUM_EXECUTABLE_TIMEOUT_MS = 120 * 1000; + private static final String STS_URL = "https://sts.googleapis.com"; + + private static final PluggableAuthCredentials CREDENTIAL = + (PluggableAuthCredentials) + PluggableAuthCredentials.newBuilder() + .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) + .setAudience( + "//iam.googleapis.com/projects/123/locations/global/workloadIdentityPools/pool/providers/provider") + .setSubjectTokenType("subjectTokenType") + .setTokenUrl(STS_URL) + .setTokenInfoUrl("tokenInfoUrl") + .setCredentialSource(buildCredentialSource()) + .build(); + + static class MockExternalAccountCredentialsTransportFactory implements HttpTransportFactory { + + MockExternalAccountCredentialsTransport transport = + new MockExternalAccountCredentialsTransport(); + + @Override + public HttpTransport create() { + return transport; + } + } + + @Test + void retrieveSubjectToken_shouldDelegateToHandler() throws IOException { + PluggableAuthCredentials credential = + PluggableAuthCredentials.newBuilder(CREDENTIAL) + .setExecutableHandler(options -> "pluggableAuthToken") + .build(); + String subjectToken = credential.retrieveSubjectToken(); + assertEquals(subjectToken, "pluggableAuthToken"); + } + + @Test + void retrieveSubjectToken_shouldPassAllOptionsToHandler() throws IOException { + String command = "/path/to/executable"; + String timeout = "5000"; + String outputFile = "/path/to/output/file"; + + final ExecutableOptions[] providedOptions = {null}; + ExecutableHandler executableHandler = + options -> { + providedOptions[0] = options; + return "pluggableAuthToken"; + }; + + PluggableAuthCredentials credential = + (PluggableAuthCredentials) + PluggableAuthCredentials.newBuilder(CREDENTIAL) + .setExecutableHandler(executableHandler) + .setCredentialSource(buildCredentialSource(command, timeout, outputFile)) + .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) + .build(); + + String subjectToken = credential.retrieveSubjectToken(); + + assertEquals(subjectToken, "pluggableAuthToken"); + + // Validate that the correct options were passed to the executable handler. + ExecutableOptions options = providedOptions[0]; + assertEquals(options.getExecutableCommand(), command); + assertEquals(options.getExecutableTimeoutMs(), Integer.parseInt(timeout)); + assertEquals(options.getOutputFilePath(), outputFile); + + Map envMap = options.getEnvironmentMap(); + assertEquals(envMap.size(), 5); + assertEquals(envMap.get("GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE"), credential.getAudience()); + assertEquals( + envMap.get("GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE"), credential.getSubjectTokenType()); + assertEquals(envMap.get("GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE"), "0"); + assertEquals( + envMap.get("GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL"), + credential.getServiceAccountEmail()); + assertEquals(envMap.get("GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE"), outputFile); + } + + @Test + void retrieveSubjectToken_shouldPassMinimalOptionsToHandler() throws IOException { + String command = "/path/to/executable"; + + final ExecutableOptions[] providedOptions = {null}; + ExecutableHandler executableHandler = + options -> { + providedOptions[0] = options; + return "pluggableAuthToken"; + }; + + PluggableAuthCredentials credential = + (PluggableAuthCredentials) + PluggableAuthCredentials.newBuilder(CREDENTIAL) + .setExecutableHandler(executableHandler) + .setCredentialSource( + buildCredentialSource(command, /* timeoutMs= */ null, /* outputFile= */ null)) + .build(); + + String subjectToken = credential.retrieveSubjectToken(); + + assertEquals(subjectToken, "pluggableAuthToken"); + + // Validate that the correct options were passed to the executable handler. + ExecutableOptions options = providedOptions[0]; + assertEquals(options.getExecutableCommand(), command); + assertEquals(options.getExecutableTimeoutMs(), DEFAULT_EXECUTABLE_TIMEOUT_MS); + assertNull(options.getOutputFilePath()); + + Map envMap = options.getEnvironmentMap(); + assertEquals(envMap.size(), 3); + assertEquals(envMap.get("GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE"), credential.getAudience()); + assertEquals( + envMap.get("GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE"), credential.getSubjectTokenType()); + assertEquals(envMap.get("GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE"), "0"); + assertNull(envMap.get("GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL")); + assertNull(envMap.get("GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE")); + } + + @Test + void refreshAccessToken_withoutServiceAccountImpersonation() throws IOException { + MockExternalAccountCredentialsTransportFactory transportFactory = + new MockExternalAccountCredentialsTransportFactory(); + + transportFactory.transport.setExpireTime(TestUtils.getDefaultExpireTime()); + + PluggableAuthCredentials credential = + (PluggableAuthCredentials) + PluggableAuthCredentials.newBuilder(CREDENTIAL) + .setExecutableHandler(options -> "pluggableAuthToken") + .setTokenUrl(transportFactory.transport.getStsUrl()) + .setHttpTransportFactory(transportFactory) + .build(); + + AccessToken accessToken = credential.refreshAccessToken(); + + assertEquals(transportFactory.transport.getAccessToken(), accessToken.getTokenValue()); + + // Validate that the correct subject token was passed to STS. + Map query = + TestUtils.parseQuery(transportFactory.transport.getRequests().get(0).getContentAsString()); + assertEquals(query.get("subject_token"), "pluggableAuthToken"); + } + + @Test + void refreshAccessToken_withServiceAccountImpersonation() throws IOException { + MockExternalAccountCredentialsTransportFactory transportFactory = + new MockExternalAccountCredentialsTransportFactory(); + + transportFactory.transport.setExpireTime(TestUtils.getDefaultExpireTime()); + + PluggableAuthCredentials credential = + (PluggableAuthCredentials) + PluggableAuthCredentials.newBuilder(CREDENTIAL) + .setExecutableHandler(options -> "pluggableAuthToken") + .setTokenUrl(transportFactory.transport.getStsUrl()) + .setServiceAccountImpersonationUrl( + transportFactory.transport.getServiceAccountImpersonationUrl()) + .setHttpTransportFactory(transportFactory) + .build(); + + AccessToken accessToken = credential.refreshAccessToken(); + + assertEquals( + transportFactory.transport.getServiceAccountAccessToken(), accessToken.getTokenValue()); + + // Validate that the correct subject token was passed to STS. + Map query = + TestUtils.parseQuery(transportFactory.transport.getRequests().get(0).getContentAsString()); + assertEquals(query.get("subject_token"), "pluggableAuthToken"); + } + + @Test + void pluggableAuthCredentialSource_allFields() { + Map source = new HashMap<>(); + Map executable = new HashMap<>(); + source.put("executable", executable); + executable.put("command", "/path/to/executable"); + executable.put("timeout_millis", "10000"); + executable.put("output_file", "/path/to/output/file"); + + PluggableAuthCredentialSource credentialSource = new PluggableAuthCredentialSource(source); + + assertEquals(credentialSource.getCommand(), "/path/to/executable"); + assertEquals(credentialSource.getTimeoutMs(), 10000); + assertEquals(credentialSource.getOutputFilePath(), "/path/to/output/file"); + } + + @Test + void pluggableAuthCredentialSource_noTimeoutProvided_setToDefault() { + Map source = new HashMap<>(); + Map executable = new HashMap<>(); + source.put("executable", executable); + executable.put("command", "command"); + PluggableAuthCredentialSource credentialSource = new PluggableAuthCredentialSource(source); + + assertEquals(credentialSource.getCommand(), "command"); + assertEquals(credentialSource.getTimeoutMs(), DEFAULT_EXECUTABLE_TIMEOUT_MS); + assertNull(credentialSource.getOutputFilePath()); + } + + @Test + void pluggableAuthCredentialSource_timeoutProvidedOutOfRange_throws() { + Map source = new HashMap<>(); + Map executable = new HashMap<>(); + source.put("executable", executable); + + executable.put("command", "command"); + + int[] possibleOutOfRangeValues = new int[] {0, 4 * 1000, 121 * 1000}; + + for (int value : possibleOutOfRangeValues) { + executable.put("timeout_millis", value); + + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> { + new PluggableAuthCredentialSource(source); + }, + "Exception should be thrown."); + assertEquals( + String.format( + "The executable timeout must be between %s and %s milliseconds.", + MINIMUM_EXECUTABLE_TIMEOUT_MS, MAXIMUM_EXECUTABLE_TIMEOUT_MS), + exception.getMessage()); + } + } + + @Test + void pluggableAuthCredentialSource_validTimeoutProvided() { + Map source = new HashMap<>(); + Map executable = new HashMap<>(); + source.put("executable", executable); + + executable.put("command", "command"); + + Object[] possibleValues = new Object[] {"10000", 10000, BigDecimal.valueOf(10000L)}; + + for (Object value : possibleValues) { + executable.put("timeout_millis", value); + PluggableAuthCredentialSource credentialSource = new PluggableAuthCredentialSource(source); + + assertEquals(credentialSource.getCommand(), "command"); + assertEquals(credentialSource.getTimeoutMs(), 10000); + assertNull(credentialSource.getOutputFilePath()); + } + } + + @Test + void pluggableAuthCredentialSource_missingExecutableField_throws() { + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> new PluggableAuthCredentialSource(new HashMap<>()), + "Exception should be thrown."); + assertEquals( + "Invalid credential source for PluggableAuth credentials.", exception.getMessage()); + } + + @Test + void pluggableAuthCredentialSource_missingExecutableCommandField_throws() { + Map source = new HashMap<>(); + Map executable = new HashMap<>(); + source.put("executable", executable); + + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> new PluggableAuthCredentialSource(source), + "Exception should be thrown."); + assertEquals( + "The PluggableAuthCredentialSource is missing the required 'command' field.", + exception.getMessage()); + } + + @Test + void builder_allFields() { + List scopes = Arrays.asList("scope1", "scope2"); + + CredentialSource source = buildCredentialSource(); + ExecutableHandler handler = options -> "Token"; + + PluggableAuthCredentials credentials = + (PluggableAuthCredentials) + PluggableAuthCredentials.newBuilder() + .setExecutableHandler(handler) + .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY) + .setAudience("audience") + .setSubjectTokenType("subjectTokenType") + .setTokenUrl(STS_URL) + .setTokenInfoUrl("tokenInfoUrl") + .setCredentialSource(source) + .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) + .setQuotaProjectId("quotaProjectId") + .setClientId("clientId") + .setClientSecret("clientSecret") + .setScopes(scopes) + .build(); + + assertEquals(credentials.getExecutableHandler(), handler); + assertEquals("audience", credentials.getAudience()); + assertEquals("subjectTokenType", credentials.getSubjectTokenType()); + assertEquals(credentials.getTokenUrl(), STS_URL); + assertEquals(credentials.getTokenInfoUrl(), "tokenInfoUrl"); + assertEquals( + credentials.getServiceAccountImpersonationUrl(), SERVICE_ACCOUNT_IMPERSONATION_URL); + assertEquals(credentials.getCredentialSource(), source); + assertEquals(credentials.getQuotaProjectId(), "quotaProjectId"); + assertEquals(credentials.getClientId(), "clientId"); + assertEquals(credentials.getClientSecret(), "clientSecret"); + assertEquals(credentials.getScopes(), scopes); + assertEquals(credentials.getEnvironmentProvider(), SystemEnvironmentProvider.getInstance()); + } + + @Test + void createdScoped_clonedCredentialWithAddedScopes() { + PluggableAuthCredentials credentials = + (PluggableAuthCredentials) + PluggableAuthCredentials.newBuilder(CREDENTIAL) + .setExecutableHandler(options -> "pluggableAuthToken") + .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL) + .setQuotaProjectId("quotaProjectId") + .setClientId("clientId") + .setClientSecret("clientSecret") + .build(); + + List newScopes = Arrays.asList("scope1", "scope2"); + + PluggableAuthCredentials newCredentials = credentials.createScoped(newScopes); + + assertEquals(credentials.getAudience(), newCredentials.getAudience()); + assertEquals(credentials.getSubjectTokenType(), newCredentials.getSubjectTokenType()); + assertEquals(credentials.getTokenUrl(), newCredentials.getTokenUrl()); + assertEquals(credentials.getTokenInfoUrl(), newCredentials.getTokenInfoUrl()); + assertEquals( + credentials.getServiceAccountImpersonationUrl(), + newCredentials.getServiceAccountImpersonationUrl()); + assertEquals(credentials.getCredentialSource(), newCredentials.getCredentialSource()); + assertEquals(newScopes, newCredentials.getScopes()); + assertEquals(credentials.getQuotaProjectId(), newCredentials.getQuotaProjectId()); + assertEquals(credentials.getClientId(), newCredentials.getClientId()); + assertEquals(credentials.getClientSecret(), newCredentials.getClientSecret()); + assertEquals(credentials.getExecutableHandler(), newCredentials.getExecutableHandler()); + } + + private static CredentialSource buildCredentialSource() { + return buildCredentialSource("command", null, null); + } + + private static CredentialSource buildCredentialSource( + String command, @Nullable String timeoutMs, @Nullable String outputFile) { + Map source = new HashMap<>(); + Map executable = new HashMap<>(); + source.put("executable", executable); + executable.put("command", command); + if (timeoutMs != null) { + executable.put("timeout_millis", timeoutMs); + } + if (outputFile != null) { + executable.put("output_file", outputFile); + } + + return new PluggableAuthCredentialSource(source); + } + + static InputStream writeCredentialsStream(String tokenUrl) throws IOException { + GenericJson json = new GenericJson(); + json.put("audience", "audience"); + json.put("subject_token_type", "subjectTokenType"); + json.put("token_url", tokenUrl); + json.put("token_info_url", "tokenInfoUrl"); + json.put("type", ExternalAccountCredentials.EXTERNAL_ACCOUNT_FILE_TYPE); + + GenericJson credentialSource = new GenericJson(); + GenericJson executable = new GenericJson(); + executable.put("command", "/path/to/executable"); + credentialSource.put("executable", executable); + + json.put("credential_source", credentialSource); + return TestUtils.jsonToInputStream(json); + } +} diff --git a/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthExceptionTest.java b/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthExceptionTest.java new file mode 100644 index 000000000..f924d4137 --- /dev/null +++ b/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthExceptionTest.java @@ -0,0 +1,71 @@ +/* + * Copyright 2022 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.auth.oauth2; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +/** Tests for {@link PluggableAuthException}. */ +class PluggableAuthExceptionTest { + + private static final String MESSAGE_FORMAT = "Error code %s: %s"; + + @Test + void constructor() { + PluggableAuthException e = new PluggableAuthException("errorCode", "errorDescription"); + assertEquals("errorCode", e.getErrorCode()); + assertEquals("errorDescription", e.getErrorDescription()); + } + + @Test + void constructor_nullErrorCode_throws() { + assertThrows( + NullPointerException.class, + () -> new PluggableAuthException(/* errorCode= */ null, "errorDescription")); + } + + @Test + void constructor_nullErrorDescription_throws() { + assertThrows( + NullPointerException.class, + () -> new PluggableAuthException("errorCode", /* errorDescription= */ null)); + } + + @Test + void getMessage() { + PluggableAuthException e = new PluggableAuthException("errorCode", "errorDescription"); + String expectedMessage = String.format("Error code %s: %s", "errorCode", "errorDescription"); + assertEquals(expectedMessage, e.getMessage()); + } +} diff --git a/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthHandlerTest.java b/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthHandlerTest.java new file mode 100644 index 000000000..4e630d49c --- /dev/null +++ b/oauth2_http/javatests/com/google/auth/oauth2/PluggableAuthHandlerTest.java @@ -0,0 +1,813 @@ +/* + * Copyright 2022 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.auth.oauth2; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.api.client.json.GenericJson; +import com.google.auth.oauth2.ExecutableHandler.ExecutableOptions; +import com.google.auth.oauth2.PluggableAuthHandler.InternalProcessBuilder; +import com.google.common.collect.ImmutableMap; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; +import org.junit.jupiter.api.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +/** Tests for {@link PluggableAuthHandler}. */ +@RunWith(MockitoJUnitRunner.class) +class PluggableAuthHandlerTest { + private static final String TOKEN_TYPE_OIDC = "urn:ietf:params:oauth:token-type:id_token"; + private static final String TOKEN_TYPE_SAML = "urn:ietf:params:oauth:token-type:saml2"; + private static final String ID_TOKEN = "header.payload.signature"; + private static final String SAML_RESPONSE = "samlResponse"; + + private static final int EXECUTABLE_SUPPORTED_MAX_VERSION = 1; + private static final int EXPIRATION_DURATION = 3600; + private static final int EXIT_CODE_SUCCESS = 0; + private static final int EXIT_CODE_FAIL = 1; + + private static final ExecutableOptions DEFAULT_OPTIONS = + new ExecutableOptions() { + @Override + public String getExecutableCommand() { + return "/path/to/executable"; + } + + @Override + public Map getEnvironmentMap() { + return ImmutableMap.of("optionKey1", "optionValue1", "optionValue2", "optionValue2"); + } + + @Override + public int getExecutableTimeoutMs() { + return 30000; + } + + @Nullable + @Override + public String getOutputFilePath() { + return null; + } + }; + + @Test + void retrieveTokenFromExecutable_oidcResponse() throws IOException, InterruptedException { + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + environmentProvider.setEnv("GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES", "1"); + + Map currentEnv = new HashMap<>(); + currentEnv.put("currentEnvKey1", "currentEnvValue1"); + currentEnv.put("currentEnvKey2", "currentEnvValue2"); + + // Expected environment mappings. + HashMap expectedMap = new HashMap<>(); + expectedMap.putAll(DEFAULT_OPTIONS.getEnvironmentMap()); + expectedMap.putAll(currentEnv); + + // Mock executable handling. + Process mockProcess = Mockito.mock(Process.class); + when(mockProcess.waitFor(anyLong(), any(TimeUnit.class))).thenReturn(true); + when(mockProcess.exitValue()).thenReturn(EXIT_CODE_SUCCESS); + + when(mockProcess.getInputStream()) + .thenReturn( + new ByteArrayInputStream( + buildOidcResponse().toString().getBytes(StandardCharsets.UTF_8))); + + InternalProcessBuilder processBuilder = + buildInternalProcessBuilder( + currentEnv, mockProcess, DEFAULT_OPTIONS.getExecutableCommand()); + + PluggableAuthHandler handler = new PluggableAuthHandler(environmentProvider, processBuilder); + + // Call retrieveTokenFromExecutable(). + String token = handler.retrieveTokenFromExecutable(DEFAULT_OPTIONS); + + verify(mockProcess, times(1)).destroy(); + verify(mockProcess, times(1)) + .waitFor( + eq(Long.valueOf(DEFAULT_OPTIONS.getExecutableTimeoutMs())), eq(TimeUnit.MILLISECONDS)); + assertEquals(ID_TOKEN, token); + + // Current env map should include the mappings from options. + assertEquals(4, currentEnv.size()); + assertEquals(expectedMap, currentEnv); + } + + @Test + void retrieveTokenFromExecutable_samlResponse() throws IOException, InterruptedException { + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + environmentProvider.setEnv("GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES", "1"); + + Map currentEnv = new HashMap<>(); + currentEnv.put("currentEnvKey1", "currentEnvValue1"); + currentEnv.put("currentEnvKey2", "currentEnvValue2"); + + // Expected environment mappings. + HashMap expectedMap = new HashMap<>(); + expectedMap.putAll(DEFAULT_OPTIONS.getEnvironmentMap()); + expectedMap.putAll(currentEnv); + + // Mock executable handling. + Process mockProcess = Mockito.mock(Process.class); + when(mockProcess.waitFor(anyLong(), any(TimeUnit.class))).thenReturn(true); + when(mockProcess.exitValue()).thenReturn(EXIT_CODE_SUCCESS); + + // SAML response. + when(mockProcess.getInputStream()) + .thenReturn( + new ByteArrayInputStream( + buildSamlResponse().toString().getBytes(StandardCharsets.UTF_8))); + + InternalProcessBuilder processBuilder = + buildInternalProcessBuilder( + currentEnv, mockProcess, DEFAULT_OPTIONS.getExecutableCommand()); + + PluggableAuthHandler handler = new PluggableAuthHandler(environmentProvider, processBuilder); + + // Call retrieveTokenFromExecutable(). + String token = handler.retrieveTokenFromExecutable(DEFAULT_OPTIONS); + + verify(mockProcess, times(1)).destroy(); + verify(mockProcess, times(1)) + .waitFor( + eq(Long.valueOf(DEFAULT_OPTIONS.getExecutableTimeoutMs())), eq(TimeUnit.MILLISECONDS)); + assertEquals(SAML_RESPONSE, token); + + // Current env map should include the mappings from options. + assertEquals(4, currentEnv.size()); + assertEquals(expectedMap, currentEnv); + } + + @Test + void retrieveTokenFromExecutable_errorResponse_throws() throws InterruptedException { + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + environmentProvider.setEnv("GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES", "1"); + + // Mock executable handling. + Process mockProcess = Mockito.mock(Process.class); + when(mockProcess.waitFor(anyLong(), any(TimeUnit.class))).thenReturn(true); + when(mockProcess.exitValue()).thenReturn(EXIT_CODE_SUCCESS); + + // Error response. + when(mockProcess.getInputStream()) + .thenReturn( + new ByteArrayInputStream( + buildErrorResponse().toString().getBytes(StandardCharsets.UTF_8))); + + InternalProcessBuilder processBuilder = + buildInternalProcessBuilder( + new HashMap<>(), mockProcess, DEFAULT_OPTIONS.getExecutableCommand()); + + PluggableAuthHandler handler = new PluggableAuthHandler(environmentProvider, processBuilder); + + // Call retrieveTokenFromExecutable(). + PluggableAuthException e = + assertThrows( + PluggableAuthException.class, + () -> handler.retrieveTokenFromExecutable(DEFAULT_OPTIONS)); + + assertEquals("401", e.getErrorCode()); + assertEquals("Caller not authorized.", e.getErrorDescription()); + } + + @Test + void retrieveTokenFromExecutable_withOutputFile_usesCachedResponse() + throws IOException, InterruptedException { + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + environmentProvider.setEnv("GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES", "1"); + + // Build output_file. + File file = File.createTempFile("output_file", /* suffix= */ null, /* directory= */ null); + file.deleteOnExit(); + + OAuth2Utils.writeInputStreamToFile( + new ByteArrayInputStream(buildOidcResponse().toString().getBytes(StandardCharsets.UTF_8)), + file.getAbsolutePath()); + + // Options with output file specified. + ExecutableOptions options = + new ExecutableOptions() { + @Override + public String getExecutableCommand() { + return "/path/to/executable"; + } + + @Override + public Map getEnvironmentMap() { + return ImmutableMap.of(); + } + + @Override + public int getExecutableTimeoutMs() { + return 30000; + } + + @Override + public String getOutputFilePath() { + return file.getAbsolutePath(); + } + }; + + // Mock executable handling that does nothing since we are using the output file. + Process mockProcess = Mockito.mock(Process.class); + InternalProcessBuilder processBuilder = + buildInternalProcessBuilder(new HashMap<>(), mockProcess, options.getExecutableCommand()); + + PluggableAuthHandler handler = new PluggableAuthHandler(environmentProvider, processBuilder); + + // Call retrieveTokenFromExecutable(). + String token = handler.retrieveTokenFromExecutable(options); + + // Validate executable not invoked. + verify(mockProcess, times(0)).destroyForcibly(); + verify(mockProcess, times(0)) + .waitFor(eq(Long.valueOf(options.getExecutableTimeoutMs())), eq(TimeUnit.MILLISECONDS)); + + assertEquals(ID_TOKEN, token); + } + + @Test + void retrieveTokenFromExecutable_withInvalidOutputFile_throws() + throws IOException, InterruptedException { + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + environmentProvider.setEnv("GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES", "1"); + + // Build output_file. + File file = File.createTempFile("output_file", /* suffix= */ null, /* directory= */ null); + file.deleteOnExit(); + + OAuth2Utils.writeInputStreamToFile( + new ByteArrayInputStream("Bad response.".getBytes(StandardCharsets.UTF_8)), + file.getAbsolutePath()); + + // Options with output file specified. + ExecutableOptions options = + new ExecutableOptions() { + @Override + public String getExecutableCommand() { + return "/path/to/executable"; + } + + @Override + public Map getEnvironmentMap() { + return ImmutableMap.of(); + } + + @Override + public int getExecutableTimeoutMs() { + return 30000; + } + + @Override + public String getOutputFilePath() { + return file.getAbsolutePath(); + } + }; + + // Mock executable handling that does nothing since we are using the output file. + Process mockProcess = Mockito.mock(Process.class); + InternalProcessBuilder processBuilder = + buildInternalProcessBuilder(new HashMap<>(), mockProcess, options.getExecutableCommand()); + + PluggableAuthHandler handler = new PluggableAuthHandler(environmentProvider, processBuilder); + + // Call retrieveTokenFromExecutable(). + PluggableAuthException e = + assertThrows( + PluggableAuthException.class, () -> handler.retrieveTokenFromExecutable(options)); + + assertEquals("INVALID_OUTPUT_FILE", e.getErrorCode()); + } + + @Test + void retrieveTokenFromExecutable_expiredOutputFileResponse_callsExecutable() + throws IOException, InterruptedException { + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + environmentProvider.setEnv("GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES", "1"); + + // Build output_file. + File file = File.createTempFile("output_file", /* suffix= */ null, /* directory= */ null); + file.deleteOnExit(); + + // Create an expired response. + GenericJson json = buildOidcResponse(); + json.put("expiration_time", Instant.now().getEpochSecond() - 1); + + OAuth2Utils.writeInputStreamToFile( + new ByteArrayInputStream(json.toString().getBytes(StandardCharsets.UTF_8)), + file.getAbsolutePath()); + + // Options with output file specified. + ExecutableOptions options = + new ExecutableOptions() { + @Override + public String getExecutableCommand() { + return "/path/to/executable"; + } + + @Override + public Map getEnvironmentMap() { + return ImmutableMap.of(); + } + + @Override + public int getExecutableTimeoutMs() { + return 30000; + } + + @Override + public String getOutputFilePath() { + return file.getAbsolutePath(); + } + }; + + // Mock executable handling. + Process mockProcess = Mockito.mock(Process.class); + when(mockProcess.waitFor(anyLong(), any(TimeUnit.class))).thenReturn(true); + when(mockProcess.exitValue()).thenReturn(EXIT_CODE_SUCCESS); + when(mockProcess.getInputStream()) + .thenReturn( + new ByteArrayInputStream( + buildOidcResponse().toString().getBytes(StandardCharsets.UTF_8))); + + InternalProcessBuilder processBuilder = + buildInternalProcessBuilder(new HashMap<>(), mockProcess, options.getExecutableCommand()); + + PluggableAuthHandler handler = new PluggableAuthHandler(environmentProvider, processBuilder); + + // Call retrieveTokenFromExecutable(). + String token = handler.retrieveTokenFromExecutable(options); + + // Validate that the executable was called. + verify(mockProcess, times(1)).destroy(); + verify(mockProcess, times(1)) + .waitFor(eq(Long.valueOf(options.getExecutableTimeoutMs())), eq(TimeUnit.MILLISECONDS)); + + assertEquals(ID_TOKEN, token); + } + + @Test + void retrieveTokenFromExecutable_expiredResponse_throws() throws InterruptedException { + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + environmentProvider.setEnv("GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES", "1"); + + // Create expired response. + GenericJson json = buildOidcResponse(); + json.put("expiration_time", Instant.now().getEpochSecond() - 1); + + // Mock executable handling. + Process mockProcess = Mockito.mock(Process.class); + when(mockProcess.waitFor(anyLong(), any(TimeUnit.class))).thenReturn(true); + when(mockProcess.exitValue()).thenReturn(EXIT_CODE_SUCCESS); + when(mockProcess.getInputStream()) + .thenReturn(new ByteArrayInputStream(json.toString().getBytes(StandardCharsets.UTF_8))); + + InternalProcessBuilder processBuilder = + buildInternalProcessBuilder( + new HashMap<>(), mockProcess, DEFAULT_OPTIONS.getExecutableCommand()); + + PluggableAuthHandler handler = new PluggableAuthHandler(environmentProvider, processBuilder); + + // Call retrieveTokenFromExecutable(). + PluggableAuthException e = + assertThrows( + PluggableAuthException.class, + () -> handler.retrieveTokenFromExecutable(DEFAULT_OPTIONS)); + + assertEquals("INVALID_RESPONSE", e.getErrorCode()); + assertEquals("The executable response is expired.", e.getErrorDescription()); + } + + @Test + void retrieveTokenFromExecutable_invalidVersion_throws() throws InterruptedException { + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + environmentProvider.setEnv("GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES", "1"); + + // Mock executable handling. + Process mockProcess = Mockito.mock(Process.class); + when(mockProcess.waitFor(anyLong(), any(TimeUnit.class))).thenReturn(true); + when(mockProcess.exitValue()).thenReturn(EXIT_CODE_SUCCESS); + + // SAML response. + GenericJson json = buildSamlResponse(); + // Only version `1` is supported. + json.put("version", 2); + when(mockProcess.getInputStream()) + .thenReturn(new ByteArrayInputStream(json.toString().getBytes(StandardCharsets.UTF_8))); + + InternalProcessBuilder processBuilder = + buildInternalProcessBuilder( + new HashMap<>(), mockProcess, DEFAULT_OPTIONS.getExecutableCommand()); + + PluggableAuthHandler handler = new PluggableAuthHandler(environmentProvider, processBuilder); + + // Call retrieveTokenFromExecutable(). + PluggableAuthException e = + assertThrows( + PluggableAuthException.class, + () -> handler.retrieveTokenFromExecutable(DEFAULT_OPTIONS)); + + assertEquals("UNSUPPORTED_VERSION", e.getErrorCode()); + assertEquals( + "The version of the executable response is not supported. " + + String.format( + "The maximum version currently supported is %s.", EXECUTABLE_SUPPORTED_MAX_VERSION), + e.getErrorDescription()); + } + + @Test + void retrieveTokenFromExecutable_allowExecutablesDisabled_throws() { + // In order to use Pluggable Auth, GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES must be set to 1. + // If set to 0, a runtime exception should be thrown. + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + environmentProvider.setEnv("GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES", "0"); + + PluggableAuthHandler handler = new PluggableAuthHandler(environmentProvider); + + PluggableAuthException e = + assertThrows( + PluggableAuthException.class, + () -> handler.retrieveTokenFromExecutable(DEFAULT_OPTIONS)); + + assertEquals("PLUGGABLE_AUTH_DISABLED", e.getErrorCode()); + assertEquals( + "Pluggable Auth executables need to be explicitly allowed to run by " + + "setting the GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES environment variable to 1.", + e.getErrorDescription()); + } + + @Test + void getExecutableResponse_oidcResponse() throws IOException, InterruptedException { + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + environmentProvider.setEnv("GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES", "1"); + + Map currentEnv = new HashMap<>(); + currentEnv.put("currentEnvKey1", "currentEnvValue1"); + currentEnv.put("currentEnvKey2", "currentEnvValue2"); + + // Expected environment mappings. + HashMap expectedMap = new HashMap<>(); + expectedMap.putAll(DEFAULT_OPTIONS.getEnvironmentMap()); + expectedMap.putAll(currentEnv); + + // Mock executable handling. + Process mockProcess = Mockito.mock(Process.class); + when(mockProcess.waitFor(anyLong(), any(TimeUnit.class))).thenReturn(true); + when(mockProcess.exitValue()).thenReturn(EXIT_CODE_SUCCESS); + + // OIDC response. + when(mockProcess.getInputStream()) + .thenReturn( + new ByteArrayInputStream( + buildOidcResponse().toString().getBytes(StandardCharsets.UTF_8))); + + InternalProcessBuilder processBuilder = + buildInternalProcessBuilder( + currentEnv, mockProcess, DEFAULT_OPTIONS.getExecutableCommand()); + + PluggableAuthHandler handler = new PluggableAuthHandler(environmentProvider, processBuilder); + + ExecutableResponse response = handler.getExecutableResponse(DEFAULT_OPTIONS); + + verify(mockProcess, times(1)).destroy(); + verify(mockProcess, times(1)) + .waitFor( + eq(Long.valueOf(DEFAULT_OPTIONS.getExecutableTimeoutMs())), eq(TimeUnit.MILLISECONDS)); + assertEquals(EXECUTABLE_SUPPORTED_MAX_VERSION, response.getVersion()); + assertTrue(response.isSuccessful()); + assertEquals(TOKEN_TYPE_OIDC, response.getTokenType()); + assertEquals(ID_TOKEN, response.getSubjectToken()); + assertEquals( + Instant.now().getEpochSecond() + EXPIRATION_DURATION, response.getExpirationTime()); + // Current env map should include the mappings from options. + assertEquals(4, currentEnv.size()); + assertEquals(expectedMap, currentEnv); + } + + @Test + void getExecutableResponse_samlResponse() throws IOException, InterruptedException { + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + environmentProvider.setEnv("GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES", "1"); + + Map currentEnv = new HashMap<>(); + currentEnv.put("currentEnvKey1", "currentEnvValue1"); + currentEnv.put("currentEnvKey2", "currentEnvValue2"); + + // Expected environment mappings. + HashMap expectedMap = new HashMap<>(); + expectedMap.putAll(DEFAULT_OPTIONS.getEnvironmentMap()); + expectedMap.putAll(currentEnv); + + // Mock executable handling. + Process mockProcess = Mockito.mock(Process.class); + when(mockProcess.waitFor(anyLong(), any(TimeUnit.class))).thenReturn(true); + when(mockProcess.exitValue()).thenReturn(EXIT_CODE_SUCCESS); + + // SAML response. + when(mockProcess.getInputStream()) + .thenReturn( + new ByteArrayInputStream( + buildSamlResponse().toString().getBytes(StandardCharsets.UTF_8))); + + InternalProcessBuilder processBuilder = + buildInternalProcessBuilder( + currentEnv, mockProcess, DEFAULT_OPTIONS.getExecutableCommand()); + + PluggableAuthHandler handler = new PluggableAuthHandler(environmentProvider, processBuilder); + ExecutableResponse response = handler.getExecutableResponse(DEFAULT_OPTIONS); + + verify(mockProcess, times(1)).destroy(); + verify(mockProcess, times(1)) + .waitFor( + eq(Long.valueOf(DEFAULT_OPTIONS.getExecutableTimeoutMs())), eq(TimeUnit.MILLISECONDS)); + assertEquals(EXECUTABLE_SUPPORTED_MAX_VERSION, response.getVersion()); + assertTrue(response.isSuccessful()); + assertEquals(TOKEN_TYPE_SAML, response.getTokenType()); + assertEquals(SAML_RESPONSE, response.getSubjectToken()); + assertEquals( + Instant.now().getEpochSecond() + EXPIRATION_DURATION, response.getExpirationTime()); + + // Current env map should include the mappings from options. + assertEquals(4, currentEnv.size()); + assertEquals(expectedMap, currentEnv); + + verify(mockProcess, times(1)).destroy(); + } + + @Test + void getExecutableResponse_errorResponse() throws IOException, InterruptedException { + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + environmentProvider.setEnv("GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES", "1"); + + Map currentEnv = new HashMap<>(); + currentEnv.put("currentEnvKey1", "currentEnvValue1"); + currentEnv.put("currentEnvKey2", "currentEnvValue2"); + + // Expected environment mappings. + HashMap expectedMap = new HashMap<>(); + expectedMap.putAll(DEFAULT_OPTIONS.getEnvironmentMap()); + expectedMap.putAll(currentEnv); + + // Mock executable handling. + Process mockProcess = Mockito.mock(Process.class); + + when(mockProcess.waitFor(anyLong(), any(TimeUnit.class))).thenReturn(true); + when(mockProcess.exitValue()).thenReturn(EXIT_CODE_SUCCESS); + + // Error response. + when(mockProcess.getInputStream()) + .thenReturn( + new ByteArrayInputStream( + buildErrorResponse().toString().getBytes(StandardCharsets.UTF_8))); + + InternalProcessBuilder processBuilder = + buildInternalProcessBuilder( + currentEnv, mockProcess, DEFAULT_OPTIONS.getExecutableCommand()); + PluggableAuthHandler handler = new PluggableAuthHandler(environmentProvider, processBuilder); + + // Call getExecutableResponse(). + ExecutableResponse response = handler.getExecutableResponse(DEFAULT_OPTIONS); + + verify(mockProcess, times(1)).destroy(); + verify(mockProcess, times(1)) + .waitFor( + eq(Long.valueOf(DEFAULT_OPTIONS.getExecutableTimeoutMs())), eq(TimeUnit.MILLISECONDS)); + assertEquals(EXECUTABLE_SUPPORTED_MAX_VERSION, response.getVersion()); + assertFalse(response.isSuccessful()); + assertEquals("401", response.getErrorCode()); + assertEquals("Caller not authorized.", response.getErrorMessage()); + + // Current env map should include the mappings from options. + assertEquals(4, currentEnv.size()); + assertEquals(expectedMap, currentEnv); + } + + @Test + void getExecutableResponse_timeoutExceeded_throws() throws InterruptedException { + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + environmentProvider.setEnv("GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES", "1"); + + // Mock executable handling. + Process mockProcess = Mockito.mock(Process.class); + when(mockProcess.waitFor(anyLong(), any(TimeUnit.class))).thenReturn(false); + + InternalProcessBuilder processBuilder = + buildInternalProcessBuilder( + new HashMap<>(), mockProcess, DEFAULT_OPTIONS.getExecutableCommand()); + PluggableAuthHandler handler = new PluggableAuthHandler(environmentProvider, processBuilder); + + // Call getExecutableResponse(). + PluggableAuthException e = + assertThrows( + PluggableAuthException.class, () -> handler.getExecutableResponse(DEFAULT_OPTIONS)); + + assertEquals("TIMEOUT_EXCEEDED", e.getErrorCode()); + assertEquals( + "The executable failed to finish within the timeout specified.", e.getErrorDescription()); + verify(mockProcess, times(1)) + .waitFor( + eq(Long.valueOf(DEFAULT_OPTIONS.getExecutableTimeoutMs())), eq(TimeUnit.MILLISECONDS)); + verify(mockProcess, times(1)).destroy(); + } + + @Test + void getExecutableResponse_nonZeroExitCode_throws() throws InterruptedException { + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + environmentProvider.setEnv("GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES", "1"); + + // Mock executable handling. + Process mockProcess = Mockito.mock(Process.class); + when(mockProcess.waitFor(anyLong(), any(TimeUnit.class))).thenReturn(true); + when(mockProcess.exitValue()).thenReturn(EXIT_CODE_FAIL); + + InternalProcessBuilder processBuilder = + buildInternalProcessBuilder( + new HashMap<>(), mockProcess, DEFAULT_OPTIONS.getExecutableCommand()); + + PluggableAuthHandler handler = new PluggableAuthHandler(environmentProvider, processBuilder); + + // Call getExecutableResponse(). + PluggableAuthException e = + assertThrows( + PluggableAuthException.class, () -> handler.getExecutableResponse(DEFAULT_OPTIONS)); + + assertEquals("EXIT_CODE", e.getErrorCode()); + assertEquals( + String.format("The executable failed with exit code %s.", EXIT_CODE_FAIL), + e.getErrorDescription()); + + verify(mockProcess, times(1)) + .waitFor( + eq(Long.valueOf(DEFAULT_OPTIONS.getExecutableTimeoutMs())), eq(TimeUnit.MILLISECONDS)); + verify(mockProcess, times(1)).destroy(); + } + + @Test + void getExecutableResponse_processInterrupted_throws() throws InterruptedException { + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + environmentProvider.setEnv("GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES", "1"); + + // Mock executable handling. + Process mockProcess = Mockito.mock(Process.class); + when(mockProcess.waitFor(anyLong(), any(TimeUnit.class))).thenThrow(new InterruptedException()); + + InternalProcessBuilder processBuilder = + buildInternalProcessBuilder( + new HashMap<>(), mockProcess, DEFAULT_OPTIONS.getExecutableCommand()); + + PluggableAuthHandler handler = new PluggableAuthHandler(environmentProvider, processBuilder); + + // Call getExecutableResponse(). + PluggableAuthException e = + assertThrows( + PluggableAuthException.class, () -> handler.getExecutableResponse(DEFAULT_OPTIONS)); + + assertEquals("INTERRUPTED", e.getErrorCode()); + assertEquals( + String.format("The execution was interrupted: %s.", new InterruptedException()), + e.getErrorDescription()); + + verify(mockProcess, times(1)) + .waitFor( + eq(Long.valueOf(DEFAULT_OPTIONS.getExecutableTimeoutMs())), eq(TimeUnit.MILLISECONDS)); + verify(mockProcess, times(1)).destroy(); + } + + @Test + void getExecutableResponse_invalidResponse_throws() throws InterruptedException { + TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); + environmentProvider.setEnv("GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES", "1"); + + // Mock executable handling. + Process mockProcess = Mockito.mock(Process.class); + when(mockProcess.waitFor(anyLong(), any(TimeUnit.class))).thenReturn(true); + when(mockProcess.exitValue()).thenReturn(EXIT_CODE_SUCCESS); + + // Mock bad executable response. + String badResponse = "badResponse"; + when(mockProcess.getInputStream()) + .thenReturn(new ByteArrayInputStream(badResponse.getBytes(StandardCharsets.UTF_8))); + + InternalProcessBuilder processBuilder = + buildInternalProcessBuilder( + new HashMap<>(), mockProcess, DEFAULT_OPTIONS.getExecutableCommand()); + + PluggableAuthHandler handler = new PluggableAuthHandler(environmentProvider, processBuilder); + + // Call getExecutableResponse(). + PluggableAuthException e = + assertThrows( + PluggableAuthException.class, () -> handler.getExecutableResponse(DEFAULT_OPTIONS)); + + assertEquals("INVALID_RESPONSE", e.getErrorCode()); + assertEquals( + String.format("The executable returned an invalid response: %s.", badResponse), + e.getErrorDescription()); + + verify(mockProcess, times(1)) + .waitFor( + eq(Long.valueOf(DEFAULT_OPTIONS.getExecutableTimeoutMs())), eq(TimeUnit.MILLISECONDS)); + verify(mockProcess, times(1)).destroy(); + } + + private static GenericJson buildOidcResponse() { + GenericJson json = new GenericJson(); + json.setFactory(OAuth2Utils.JSON_FACTORY); + json.put("version", EXECUTABLE_SUPPORTED_MAX_VERSION); + json.put("success", true); + json.put("token_type", TOKEN_TYPE_OIDC); + json.put("id_token", ID_TOKEN); + json.put("expiration_time", Instant.now().getEpochSecond() + EXPIRATION_DURATION); + return json; + } + + private static GenericJson buildSamlResponse() { + GenericJson json = new GenericJson(); + json.setFactory(OAuth2Utils.JSON_FACTORY); + json.put("version", EXECUTABLE_SUPPORTED_MAX_VERSION); + json.put("success", true); + json.put("token_type", TOKEN_TYPE_SAML); + json.put("saml_response", SAML_RESPONSE); + json.put("expiration_time", Instant.now().getEpochSecond() + EXPIRATION_DURATION); + return json; + } + + private static GenericJson buildErrorResponse() { + GenericJson json = new GenericJson(); + json.setFactory(OAuth2Utils.JSON_FACTORY); + json.put("version", EXECUTABLE_SUPPORTED_MAX_VERSION); + json.put("success", false); + json.put("code", "401"); + json.put("message", "Caller not authorized."); + return json; + } + + private static InternalProcessBuilder buildInternalProcessBuilder( + Map currentEnv, Process process, String command) { + return new InternalProcessBuilder() { + + @Override + Map environment() { + return currentEnv; + } + + @Override + InternalProcessBuilder redirectErrorStream(boolean redirectErrorStream) { + return this; + } + + @Override + Process start() { + return process; + } + }; + } +} diff --git a/oauth2_http/pom.xml b/oauth2_http/pom.xml index 327128666..fea8123fc 100644 --- a/oauth2_http/pom.xml +++ b/oauth2_http/pom.xml @@ -134,5 +134,17 @@ 1.3 test + + junit + junit + 4.13.2 + test + + + org.mockito + mockito-core + 2.23.4 + test + diff --git a/pom.xml b/pom.xml index f68dfb56c..6182ed492 100644 --- a/pom.xml +++ b/pom.xml @@ -169,6 +169,7 @@ 3.4.0 7 + false @@ -329,6 +330,7 @@ + false none 7 ${project.build.directory}/javadoc @@ -504,6 +506,7 @@ ${sourceFileExclude} + false From 23716b82fb3000f5210bb5604127aad7ef52cb76 Mon Sep 17 00:00:00 2001 From: Leo <39062083+lsirac@users.noreply.github.com> Date: Fri, 24 Jun 2022 15:53:14 -0700 Subject: [PATCH 07/10] docs: updates README for Pluggable Auth (#921) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Adds Pluggable Auth support to ADC (#895) * chore(deps): update dependency com.google.http-client:google-http-client-bom to v1.41.5 (#896) [![WhiteSource Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [com.google.http-client:google-http-client-bom](https://togithub.com/googleapis/google-http-java-client) | `1.41.4` -> `1.41.5` | [![age](https://badges.renovateapi.com/packages/maven/com.google.http-client:google-http-client-bom/1.41.5/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/maven/com.google.http-client:google-http-client-bom/1.41.5/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/maven/com.google.http-client:google-http-client-bom/1.41.5/compatibility-slim/1.41.4)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/maven/com.google.http-client:google-http-client-bom/1.41.5/confidence-slim/1.41.4)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes

googleapis/google-http-java-client ### [`v1.41.5`](https://togithub.com/googleapis/google-http-java-client/blob/HEAD/CHANGELOG.md#​1415-httpsgithubcomgoogleapisgoogle-http-java-clientcomparev1414v1415-2022-03-21) [Compare Source](https://togithub.com/googleapis/google-http-java-client/compare/v1.41.4...v1.41.5)
--- ### Configuration πŸ“… **Schedule**: At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. β™» **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. πŸ”• **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, click this checkbox. --- This PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/google-auth-library-java). * feat: Add ability to provide PrivateKey as Pkcs8 encoded string #883 (#889) * feat: Add ability to provide PrivateKey as Pkcs8 encoded string #883 This change adds a new method `setPrivateKeyString` in `ServiceAccountCredentials.Builder` to accept Pkcs8 encoded string representation of private keys. Co-authored-by: Timur Sadykov * chore: fix downstream check (#898) * fix: update branding in ExternalAccountCredentials (#893) These changes align the Javadoc comments with the branding that Google uses externally: + STS -> Security Token Service + GCP -> Google Cloud + Remove references to a Google-internal token type Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: - [ ] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/google-auth-library-java/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea - [ ] Ensure the tests and linter pass: Tests are failing, but I don't think that was caused by the changes in this PR - [ ] Code coverage does not decrease (if any source code was changed): n/a - [ ] Appropriate docs were updated (if necessary): n/a * feat: Adds the ExecutableHandler interface for Pluggable Auth * feat: Adds a Pluggable Auth specific exception * feat: Adds new PluggableAuthCredentials class that plug into ADC * feat: Adds unit tests for PluggableAuthCredentials and ExternalAccountCredentials * Add units tests for GoogleCredentials * fix: update javadoc/comments * fix: A concrete ExecutableOptions implementation is not needed * review: javadoc changes + constants Co-authored-by: WhiteSource Renovate Co-authored-by: Navina Ramesh Co-authored-by: Timur Sadykov Co-authored-by: Neenu Shaji Co-authored-by: Jeff Williams * feat: finalizes PluggableAuth implementation (#906) * Adds ExecutableResponse class * Adds unit tests for ExecutableResponse * Adds 3rd party executable handler * Adds unit tests for PluggableAuthHandler * Fix build issues * don't fail on javadoc errors * feat: Improve Pluggable Auth error handling (#912) * feat: improves pluggable auth error handling * cleanup * fix: consume input stream immediately for Pluggable Auth (#915) * feat: improves pluggable auth error handling * cleanup * fix: consume input stream immediately so that the spawned process will not hang if the STDOUT buffer is filled. * fix: fix merge * fix: review comments * fix: refactor to keep ImpersonatedCredentials final (#917) * fix: adds more documentation for InternalProcessBuilder and moves it to the bottom of the file * fix: keep ImpersonatedCredentials final * feat: documents pluggable auth in README * fix: provider * fix: update table of contents * fix: update Co-authored-by: WhiteSource Renovate Co-authored-by: Navina Ramesh Co-authored-by: Timur Sadykov Co-authored-by: Neenu Shaji Co-authored-by: Jeff Williams Co-authored-by: Emily Ball --- README.md | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/README.md b/README.md index 9370e67e8..b03d5aeb4 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,10 @@ credentials as well as utility methods to create them and to get Application Def * [Application Default Credentials](#application-default-credentials) * [ImpersonatedCredentials](#impersonatedcredentials) * [Workload Identity Federation](#workload-identity-federation) + * [Accessing resources from AWS](#accessing-resources-from-aws) + * [Accessing resources from Azure](#access-resources-from-microsoft-azure) + * [Accessing resources from an OIDC identity provider](#accessing-resources-from-an-oidc-identity-provider) + * [Accessing resources using Executable-sourced credentials](#using-executable-sourced-credentials-with-oidc-and-saml) * [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) @@ -323,6 +327,131 @@ request to `$URL_TO_GET_OIDC_TOKEN`, e.g. `Metadata-Flavor=Google`. You can now [use the Auth library](#using-external-identities) to call Google Cloud resources from an OIDC provider. +#### Using Executable-sourced credentials with OIDC and SAML + +**Executable-sourced credentials** +For executable-sourced credentials, a local executable is used to retrieve the 3rd party token. +The executable must handle providing a valid, unexpired OIDC ID token or SAML assertion in JSON format +to stdout. + +To use executable-sourced credentials, the `GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES` +environment variable must be set to `1`. + +To generate an executable-sourced workload identity configuration, run the following command: + +```bash +# Generate a configuration file for executable-sourced credentials. +gcloud iam workload-identity-pools create-cred-config \ + projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$POOL_ID/providers/$PROVIDER_ID \ + --service-account=$SERVICE_ACCOUNT_EMAIL \ + --subject-token-type=$SUBJECT_TOKEN_TYPE \ + # The absolute path for the program, including arguments. + # e.g. --executable-command="/path/to/command --foo=bar" + --executable-command=$EXECUTABLE_COMMAND \ + # Optional argument for the executable timeout. Defaults to 30s. + # --executable-timeout-millis=$EXECUTABLE_TIMEOUT \ + # Optional argument for the absolute path to the executable output file. + # See below on how this argument impacts the library behaviour. + # --executable-output-file=$EXECUTABLE_OUTPUT_FILE \ + --output-file /path/to/generated/config.json +``` +Where the following variables need to be substituted: +- `$PROJECT_NUMBER`: The Google Cloud project number. +- `$POOL_ID`: The workload identity pool ID. +- `$PROVIDER_ID`: The OIDC or SAML provider ID. +- `$SERVICE_ACCOUNT_EMAIL`: The email of the service account to impersonate. +- `$SUBJECT_TOKEN_TYPE`: The subject token type. +- `$EXECUTABLE_COMMAND`: The full command to run, including arguments. Must be an absolute path to the program. + +The `--executable-timeout-millis` flag is optional. This is the duration for which +the auth library will wait for the executable to finish, in milliseconds. +Defaults to 30 seconds when not provided. The maximum allowed value is 2 minutes. +The minimum is 5 seconds. + +The `--executable-output-file` flag is optional. If provided, the file path must +point to the 3PI credential response generated by the executable. This is useful +for caching the credentials. By specifying this path, the Auth libraries will first +check for its existence before running the executable. By caching the executable JSON +response to this file, it improves performance as it avoids the need to run the executable +until the cached credentials in the output file are expired. The executable must +handle writing to this file - the auth libraries will only attempt to read from +this location. The format of contents in the file should match the JSON format +expected by the executable shown below. + +To retrieve the 3rd party token, the library will call the executable +using the command specified. The executable's output must adhere to the response format +specified below. It must output the response to stdout. + +A sample successful executable OIDC response: +```json +{ + "version": 1, + "success": true, + "token_type": "urn:ietf:params:oauth:token-type:id_token", + "id_token": "HEADER.PAYLOAD.SIGNATURE", + "expiration_time": 1620499962 +} +``` + +A sample successful executable SAML response: +```json +{ + "version": 1, + "success": true, + "token_type": "urn:ietf:params:oauth:token-type:saml2", + "saml_response": "...", + "expiration_time": 1620499962 +} +``` +A sample executable error response: +```json +{ + "version": 1, + "success": false, + "code": "401", + "message": "Caller not authorized." +} +``` +These are all required fields for an error response. The code and message +fields will be used by the library as part of the thrown exception. + +Response format fields summary: + * `version`: The version of the JSON output. Currently only version 1 is supported. + * `success`: The status of the response. When true, the response must contain the 3rd party token, + token type, and expiration. The executable must also exit with exit code 0. + When false, the response must contain the error code and message fields and exit with a non-zero value. + * `token_type`: The 3rd party subject token type. Must be *urn:ietf:params:oauth:token-type:jwt*, + *urn:ietf:params:oauth:token-type:id_token*, or *urn:ietf:params:oauth:token-type:saml2*. + * `id_token`: The 3rd party OIDC token. + * `saml_response`: The 3rd party SAML response. + * `expiration_time`: The 3rd party subject token expiration time in seconds (unix epoch time). + * `code`: The error code string. + * `message`: The error message. + +All response types must include both the `version` and `success` fields. + * Successful responses must include the `token_type`, `expiration_time`, and one of + `id_token` or `saml_response`. + * Error responses must include both the `code` and `message` fields. + +The library will populate the following environment variables when the executable is run: + * `GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE`: The audience field from the credential configuration. Always present. + * `GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL`: The service account email. Only present when service account impersonation is used. + * `GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE`: The output file location from the credential configuration. Only present when specified in the credential configuration. + +These environment variables can be used by the executable to avoid hard-coding these values. + +##### Security considerations +The following security practices are highly recommended: + * Access to the script should be restricted as it will be displaying credentials to stdout. This ensures that rogue processes do not gain access to the script. + * The configuration file should not be modifiable. Write access should be restricted to avoid processes modifying the executable command portion. + +Given the complexity of using executable-sourced credentials, it is recommended to use +the existing supported mechanisms (file-sourced/URL-sourced) for providing 3rd party +credentials unless they do not meet your specific requirements. + +You can now [use the Auth library](#using-external-identities) to call Google Cloud +resources from an OIDC or SAML provider. + #### Using External Identities External identities (AWS, Azure, and OIDC-based providers) can be used with From f1058b06396c8c5d65b94f564694ff2cbb1932e3 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Mon, 27 Jun 2022 21:06:15 +0200 Subject: [PATCH 08/10] chore(deps): update dependency org.mockito:mockito-core to v4 (#935) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [org.mockito:mockito-core](https://togithub.com/mockito/mockito) | `2.23.4` -> `4.6.1` | [![age](https://badges.renovateapi.com/packages/maven/org.mockito:mockito-core/4.6.1/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/maven/org.mockito:mockito-core/4.6.1/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/maven/org.mockito:mockito-core/4.6.1/compatibility-slim/2.23.4)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/maven/org.mockito:mockito-core/4.6.1/confidence-slim/2.23.4)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
mockito/mockito ### [`v4.6.1`](https://togithub.com/mockito/mockito/releases/tag/v4.6.1) [Compare Source](https://togithub.com/mockito/mockito/compare/v4.6.0...v4.6.1) *Changelog generated by [Shipkit Changelog Gradle Plugin](https://togithub.com/shipkit/shipkit-changelog)* ##### 4.6.1 - 2022-06-02 - [6 commit(s)](https://togithub.com/mockito/mockito/compare/v4.6.0...v4.6.1) by Andy Coates, Chen Ni, dependabot\[bot] - Bump material from 1.6.0 to 1.6.1 [(#​2662)](https://togithub.com/mockito/mockito/pull/2662) - Bump core-ktx from 1.7.0 to 1.8.0 [(#​2661)](https://togithub.com/mockito/mockito/pull/2661) - Bump groovy from 3.0.10 to 3.0.11 [(#​2660)](https://togithub.com/mockito/mockito/pull/2660) - Fix for Issue2656 [(#​2659)](https://togithub.com/mockito/mockito/pull/2659) - Bump assertj-core from 3.22.0 to 3.23.1 [(#​2658)](https://togithub.com/mockito/mockito/pull/2658) - Regression? Strictness set in `@MockitoSettings` ignored after upgrade from 4.5.1 to 4.6.0 [(#​2656)](https://togithub.com/mockito/mockito/issues/2656) - Fix typo [(#​2655)](https://togithub.com/mockito/mockito/pull/2655) ### [`v4.6.0`](https://togithub.com/mockito/mockito/releases/tag/v4.6.0) [Compare Source](https://togithub.com/mockito/mockito/compare/v4.5.1...v4.6.0) *Changelog generated by [Shipkit Changelog Gradle Plugin](https://togithub.com/shipkit/shipkit-changelog)* ##### 4.6.0 - 2022-05-27 - [14 commit(s)](https://togithub.com/mockito/mockito/compare/v4.5.1...v4.6.0) by HervΓ© Boutemy, K. Siva Prasad Reddy, Rafael Winterhalter, dependabot\[bot] - Bump shipkit-changelog from 1.1.15 to 1.2.0 [(#​2654)](https://togithub.com/mockito/mockito/pull/2654) - Bump versions.errorprone from 2.13.1 to 2.14.0 [(#​2653)](https://togithub.com/mockito/mockito/pull/2653) - Bump shipkit-auto-version from 1.1.20 to 1.2.0 [(#​2651)](https://togithub.com/mockito/mockito/pull/2651) - Fixes [#​2648](https://togithub.com/mockito/mockito/issues/2648) : Add support for customising strictness via [@​Mock](https://togithub.com/Mock) annotation and MockSettings [(#​2650)](https://togithub.com/mockito/mockito/pull/2650) - Any way to enable Strict Stubbing when using Mockito.mock() without using [@​Mock](https://togithub.com/Mock)? [(#​2648)](https://togithub.com/mockito/mockito/issues/2648) - Reintroduce inheriting type annotations from interfaces if only one interface is mocked, including additional interfaces. [(#​2645)](https://togithub.com/mockito/mockito/pull/2645) - Bump com.diffplug.spotless from 6.6.0 to 6.6.1 [(#​2643)](https://togithub.com/mockito/mockito/pull/2643) - fix Reproducible Build issue [(#​2642)](https://togithub.com/mockito/mockito/pull/2642) - Bump com.diffplug.spotless from 6.5.2 to 6.6.0 [(#​2641)](https://togithub.com/mockito/mockito/pull/2641) - Mockito mock of interfaces lost annotation information [(#​2640)](https://togithub.com/mockito/mockito/issues/2640) - Bump material from 1.5.0 to 1.6.0 [(#​2637)](https://togithub.com/mockito/mockito/pull/2637) - Bump com.diffplug.spotless from 6.5.1 to 6.5.2 [(#​2636)](https://togithub.com/mockito/mockito/pull/2636) - Bump versions.bytebuddy from 1.12.9 to 1.12.10 [(#​2635)](https://togithub.com/mockito/mockito/pull/2635) - Bump com.diffplug.spotless from 6.5.0 to 6.5.1 [(#​2632)](https://togithub.com/mockito/mockito/pull/2632) - Bump com.diffplug.spotless from 6.4.2 to 6.5.0 [(#​2631)](https://togithub.com/mockito/mockito/pull/2631) ### [`v4.5.1`](https://togithub.com/mockito/mockito/releases/tag/v4.5.1) [Compare Source](https://togithub.com/mockito/mockito/compare/v4.5.0...v4.5.1) *Changelog generated by [Shipkit Changelog Gradle Plugin](https://togithub.com/shipkit/shipkit-changelog)* ##### 4.5.1 - 2022-04-21 - [2 commit(s)](https://togithub.com/mockito/mockito/compare/v4.5.0...v4.5.1) by Jeremy Landis, dependabot\[bot] - Fixes [#​2623](https://togithub.com/mockito/mockito/issues/2623): Use zulu distribution and java 11 for release GHA job [(#​2624)](https://togithub.com/mockito/mockito/pull/2624) - Missing errorprone module for 4.5.0 in central as release was done with jdk 8 [(#​2623)](https://togithub.com/mockito/mockito/issues/2623) - Bump kotlinVersion from 1.6.20 to 1.6.21 [(#​2622)](https://togithub.com/mockito/mockito/pull/2622) ##### Missing `net.bytebuddy.utility.GraalImageCode` exception If you encounter any issues with missing ByteBuddy classes, make sure you are using ByteBuddy 1.12 or higher. ### [`v4.5.0`](https://togithub.com/mockito/mockito/releases/tag/v4.5.0) [Compare Source](https://togithub.com/mockito/mockito/compare/v4.4.0...v4.5.0) *Changelog generated by [Shipkit Changelog Gradle Plugin](https://togithub.com/shipkit/shipkit-changelog)* ##### 4.5.0 - 2022-04-19 - [15 commit(s)](https://togithub.com/mockito/mockito/compare/v4.4.0...v4.5.0) by Andrei Silviu Dragnea, Rafael Winterhalter, Rick Ossendrijver, dependabot\[bot] - Bump versions.errorprone from 2.13.0 to 2.13.1 [(#​2621)](https://togithub.com/mockito/mockito/pull/2621) - Bump versions.errorprone from 2.12.1 to 2.13.0 [(#​2619)](https://togithub.com/mockito/mockito/pull/2619) - Groovy inline [(#​2618)](https://togithub.com/mockito/mockito/pull/2618) - Bump actions/setup-java from 2 to 3 [(#​2615)](https://togithub.com/mockito/mockito/pull/2615) - Bump versions.bytebuddy from 1.12.8 to 1.12.9 [(#​2614)](https://togithub.com/mockito/mockito/pull/2614) - Support subclass mocks on Graal VM. [(#​2613)](https://togithub.com/mockito/mockito/pull/2613) - Bump com.diffplug.spotless from 6.4.1 to 6.4.2 [(#​2611)](https://togithub.com/mockito/mockito/pull/2611) - Bump kotlinx-coroutines-core from 1.6.0-native-mt to 1.6.1-native-mt [(#​2609)](https://togithub.com/mockito/mockito/pull/2609) - Bump versions.errorprone from 2.10.0 to 2.12.1 [(#​2608)](https://togithub.com/mockito/mockito/pull/2608) - Bump kotlinVersion from 1.6.10 to 1.6.20 [(#​2607)](https://togithub.com/mockito/mockito/pull/2607) - Bump com.diffplug.spotless from 6.4.0 to 6.4.1 [(#​2606)](https://togithub.com/mockito/mockito/pull/2606) - Bump com.diffplug.spotless from 6.3.0 to 6.4.0 [(#​2605)](https://togithub.com/mockito/mockito/pull/2605) - Bump org.eclipse.osgi from 3.17.100 to 3.17.200 [(#​2597)](https://togithub.com/mockito/mockito/pull/2597) - Deprecate ListUtil and Fields classes [(#​2593)](https://togithub.com/mockito/mockito/pull/2593) - mockito-errorprone seems not compatible with ErrorProne 2.11.0 [(#​2554)](https://togithub.com/mockito/mockito/issues/2554) - NullPointerException from Groovy metaclass methods when using mockito-inline (but not mockito-core) [(#​2522)](https://togithub.com/mockito/mockito/issues/2522) ### [`v4.4.0`](https://togithub.com/mockito/mockito/releases/tag/v4.4.0) [Compare Source](https://togithub.com/mockito/mockito/compare/v4.3.1...v4.4.0) *Changelog generated by [Shipkit Changelog Gradle Plugin](https://togithub.com/shipkit/shipkit-changelog)* ##### 4.4.0 - 2022-03-08 - [16 commit(s)](https://togithub.com/mockito/mockito/compare/v4.3.1...v4.4.0) by Andrew Kozel, Brice Dutheil, Jean-Baptiste Mille, Mirko Alicastro, dependabot\[bot] - Bump groovy from 3.0.9 to 3.0.10 [(#​2586)](https://togithub.com/mockito/mockito/pull/2586) - Bump google-java-format from 1.14.0 to 1.15.0 [(#​2585)](https://togithub.com/mockito/mockito/pull/2585) - Bump actions/checkout from 2.4.0 to 3 [(#​2582)](https://togithub.com/mockito/mockito/pull/2582) - Bump shipkit-auto-version from 1.1.19 to 1.1.20 [(#​2580)](https://togithub.com/mockito/mockito/pull/2580) - Bump biz.aQute.bnd.builder from 6.1.0 to 6.2.0 [(#​2579)](https://togithub.com/mockito/mockito/pull/2579) - Bump biz.aQute.bnd.gradle from 6.1.0 to 6.2.0 [(#​2578)](https://togithub.com/mockito/mockito/pull/2578) - Adds a Google Java Format for JDK17 [(#​2572)](https://togithub.com/mockito/mockito/pull/2572) - Clean up JUnit3 references [(#​2570)](https://togithub.com/mockito/mockito/pull/2570) - Bump com.diffplug.spotless from 6.2.2 to 6.3.0 [(#​2567)](https://togithub.com/mockito/mockito/pull/2567) - Bump google-java-format from 1.13.0 to 1.14.0 [(#​2565)](https://togithub.com/mockito/mockito/pull/2565) - Bump versions.bytebuddy from 1.12.7 to 1.12.8 [(#​2564)](https://togithub.com/mockito/mockito/pull/2564) - Bump com.diffplug.spotless from 6.2.1 to 6.2.2 [(#​2562)](https://togithub.com/mockito/mockito/pull/2562) - Bump com.github.ben-manes.versions from 0.41.0 to 0.42.0 [(#​2559)](https://togithub.com/mockito/mockito/pull/2559) - Bump com.diffplug.spotless from 6.2.0 to 6.2.1 [(#​2556)](https://togithub.com/mockito/mockito/pull/2556) - Fixes [#​2548](https://togithub.com/mockito/mockito/issues/2548) : Makes InOrder able to verify static methods [(#​2549)](https://togithub.com/mockito/mockito/pull/2549) - \[PR open] Add feature to verify static methods calls in order [(#​2548)](https://togithub.com/mockito/mockito/issues/2548) - Fixes [#​2201](https://togithub.com/mockito/mockito/issues/2201) : Fixed checking of declared exceptions. [(#​2547)](https://togithub.com/mockito/mockito/pull/2547) - Calling getExceptionTypes() on concrete object that is used as interface doesn't return exception types from interface [(#​2201)](https://togithub.com/mockito/mockito/issues/2201) ### [`v4.3.1`](https://togithub.com/mockito/mockito/releases/tag/v4.3.1) [Compare Source](https://togithub.com/mockito/mockito/compare/v4.3.0...v4.3.1) *Changelog generated by [Shipkit Changelog Gradle Plugin](https://togithub.com/shipkit/shipkit-changelog)* ##### 4.3.1 - 2022-01-25 - [1 commit(s)](https://togithub.com/mockito/mockito/compare/v4.3.0...v4.3.1) by Stefano Cordio - Add `mockito-core` to the BOM [(#​2550)](https://togithub.com/mockito/mockito/pull/2550) ### [`v4.3.0`](https://togithub.com/mockito/mockito/releases/tag/v4.3.0) [Compare Source](https://togithub.com/mockito/mockito/compare/v4.2.0...v4.3.0) *Changelog generated by [Shipkit Changelog Gradle Plugin](https://togithub.com/shipkit/shipkit-changelog)* ##### 4.3.0 - 2022-01-24 - [20 commit(s)](https://togithub.com/mockito/mockito/compare/v4.2.0...v4.3.0) by Andrew Kozel, John Pyeatt, Liam Miller-Cushon, Thomas Keller, Tim van der Lippe, dependabot\[bot], temp-droid - Fixes [#​2489](https://togithub.com/mockito/mockito/issues/2489) : Fixed issue related to exceptions thrown from the nested spies [(#​2546)](https://togithub.com/mockito/mockito/pull/2546) - Issue 2544 [(#​2545)](https://togithub.com/mockito/mockito/pull/2545) - Bump versions.bytebuddy from 1.12.6 to 1.12.7 [(#​2543)](https://togithub.com/mockito/mockito/pull/2543) - Bump com.diffplug.spotless from 6.1.2 to 6.2.0 [(#​2542)](https://togithub.com/mockito/mockito/pull/2542) - Bump material from 1.4.0 to 1.5.0 [(#​2541)](https://togithub.com/mockito/mockito/pull/2541) - Bump appcompat from 1.4.0 to 1.4.1 [(#​2539)](https://togithub.com/mockito/mockito/pull/2539) - Bump com.diffplug.spotless from 6.1.1 to 6.1.2 [(#​2536)](https://togithub.com/mockito/mockito/pull/2536) - Remove an `@link` [(#​2535)](https://togithub.com/mockito/mockito/pull/2535) - Bump com.diffplug.spotless from 6.1.0 to 6.1.1 [(#​2534)](https://togithub.com/mockito/mockito/pull/2534) - Bump com.github.ben-manes.versions from 0.40.0 to 0.41.0 [(#​2533)](https://togithub.com/mockito/mockito/pull/2533) - Bump assertj-core from 3.21.0 to 3.22.0 [(#​2531)](https://togithub.com/mockito/mockito/pull/2531) - Bump com.github.ben-manes.versions from 0.39.0 to 0.40.0 [(#​2529)](https://togithub.com/mockito/mockito/pull/2529) - Bump com.diffplug.spotless from 6.0.5 to 6.1.0 [(#​2527)](https://togithub.com/mockito/mockito/pull/2527) - Bump kotlinx-coroutines-core from 1.5.2-native-mt to 1.6.0-native-mt [(#​2526)](https://togithub.com/mockito/mockito/pull/2526) - Bump versions.bytebuddy from 1.12.5 to 1.12.6 [(#​2524)](https://togithub.com/mockito/mockito/pull/2524) - Bump com.diffplug.spotless from 6.0.4 to 6.0.5 [(#​2520)](https://togithub.com/mockito/mockito/pull/2520) - Bump versions.bytebuddy from 1.12.4 to 1.12.5 [(#​2519)](https://togithub.com/mockito/mockito/pull/2519) - Fixes [#​2510](https://togithub.com/mockito/mockito/issues/2510): Remove ExpectedException from internal test suite [(#​2518)](https://togithub.com/mockito/mockito/pull/2518) - Fix JavaDoc warnings and enforce zero errors in Gradle [(#​2513)](https://togithub.com/mockito/mockito/pull/2513) - Remove `ExpectedException` from internal test suite [(#​2510)](https://togithub.com/mockito/mockito/issues/2510) - Incomplete stack trace returned from spy inside another spy [(#​2489)](https://togithub.com/mockito/mockito/issues/2489) - Introduce a BOM for Mockito's artifacts (closes [#​2321](https://togithub.com/mockito/mockito/issues/2321)) [(#​2323)](https://togithub.com/mockito/mockito/pull/2323) - Provide a bill of materials (BOM) [(#​2321)](https://togithub.com/mockito/mockito/issues/2321) ### [`v4.2.0`](https://togithub.com/mockito/mockito/releases/tag/v4.2.0) [Compare Source](https://togithub.com/mockito/mockito/compare/v4.1.0...v4.2.0) *Changelog generated by [Shipkit Changelog Gradle Plugin](https://togithub.com/shipkit/shipkit-changelog)* ##### 4.2.0 - 2021-12-16 - [21 commit(s)](https://togithub.com/mockito/mockito/compare/v4.1.0...v4.2.0) by Liam Miller-Cushon, Rafael Winterhalter, Tim van der Lippe, dependabot\[bot], temp-droid - Update ByteBuddy to 1.12.4 [(#​2515)](https://togithub.com/mockito/mockito/pull/2515) - Bump kotlinVersion from 1.6.0 to 1.6.10 [(#​2514)](https://togithub.com/mockito/mockito/pull/2514) - Add DoNotMock mention to main JavaDoc [(#​2512)](https://togithub.com/mockito/mockito/pull/2512) - Replace ExpectedException in MissingInvocationInOrderCheckerTest [(#​2511)](https://togithub.com/mockito/mockito/pull/2511) - Update Gradle to version 7.3.1 [(#​2509)](https://togithub.com/mockito/mockito/pull/2509) - Fixes [#​2497](https://togithub.com/mockito/mockito/issues/2497): Throw exception on invalid matchers for `mockStatic` [(#​2506)](https://togithub.com/mockito/mockito/pull/2506) - Make sure interface types are initialized before inline mocking to avoid blocking code of static initializers. [(#​2505)](https://togithub.com/mockito/mockito/pull/2505) - Bump org.eclipse.osgi from 3.17.0 to 3.17.100 [(#​2504)](https://togithub.com/mockito/mockito/pull/2504) - Bump com.diffplug.spotless from 6.0.2 to 6.0.4 [(#​2501)](https://togithub.com/mockito/mockito/pull/2501) - Bump com.diffplug.spotless from 6.0.1 to 6.0.2 [(#​2498)](https://togithub.com/mockito/mockito/pull/2498) - ArgumentMatchers not working for Mockito.mockStatic [(#​2497)](https://togithub.com/mockito/mockito/issues/2497) - Bump versions.bytebuddy from 1.12.2 to 1.12.3 [(#​2496)](https://togithub.com/mockito/mockito/pull/2496) - Bump com.diffplug.spotless from 6.0.0 to 6.0.1 [(#​2495)](https://togithub.com/mockito/mockito/pull/2495) - Remove the recommendation to import ArgumentMatchers methods using Mockito [(#​2494)](https://togithub.com/mockito/mockito/pull/2494) - Bump versions.junitJupiter from 5.8.1 to 5.8.2 [(#​2491)](https://togithub.com/mockito/mockito/pull/2491) - Bump junit-platform-launcher from 1.8.1 to 1.8.2 [(#​2490)](https://togithub.com/mockito/mockito/pull/2490) - Fix typo 'DoNoMock' should be 'DoNotMock' [(#​2487)](https://togithub.com/mockito/mockito/pull/2487) - Bump biz.aQute.bnd.gradle from 6.0.0 to 6.1.0 [(#​2486)](https://togithub.com/mockito/mockito/pull/2486) - Bump biz.aQute.bnd.builder from 6.0.0 to 6.1.0 [(#​2485)](https://togithub.com/mockito/mockito/pull/2485) - Bump versions.bytebuddy from 1.12.1 to 1.12.2 [(#​2484)](https://togithub.com/mockito/mockito/pull/2484) - Bump google-java-format from 1.12.0 to 1.13.0 [(#​2483)](https://togithub.com/mockito/mockito/pull/2483) - Add annotation to mark a type as DoNotMock [(#​1833)](https://togithub.com/mockito/mockito/pull/1833) ### [`v4.1.0`](https://togithub.com/mockito/mockito/releases/tag/v4.1.0) [Compare Source](https://togithub.com/mockito/mockito/compare/v4.0.0...v4.1.0) ### Major new feature: `@DoNotMock` You can now mark classes/interfaces with `@org.mockito.DoNotMock` to disallow mocking with Mockito. For more information, see our documentation: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/DoNotMock.html *Changelog generated by [Shipkit Changelog Gradle Plugin](https://togithub.com/shipkit/shipkit-changelog)* ##### 4.1.0 - 2021-11-19 - [20 commit(s)](https://togithub.com/mockito/mockito/compare/v4.0.0...v4.1.0) by Lars Vogel, MikaΓ«l Francoeur, S.YAMAMOTO, Tim van der Lippe, dependabot\[bot] - Disable memory test [(#​2480)](https://togithub.com/mockito/mockito/pull/2480) - Bump appcompat from 1.3.1 to 1.4.0 [(#​2477)](https://togithub.com/mockito/mockito/pull/2477) - Bump kotlinVersion from 1.5.31 to 1.6.0 [(#​2474)](https://togithub.com/mockito/mockito/pull/2474) - Bump versions.bytebuddy from 1.12.0 to 1.12.1 [(#​2472)](https://togithub.com/mockito/mockito/pull/2472) - Bump com.diffplug.gradle.spotless from 4.5.1 to 6.0.0 [(#​2471)](https://togithub.com/mockito/mockito/pull/2471) - Bump versions.bytebuddy from 1.11.22 to 1.12.0 [(#​2469)](https://togithub.com/mockito/mockito/pull/2469) - Bump versions.errorprone from 2.9.0 to 2.10.0 [(#​2466)](https://togithub.com/mockito/mockito/pull/2466) - Bump auto-service from 1.0 to 1.0.1 [(#​2463)](https://togithub.com/mockito/mockito/pull/2463) - Bump actions/checkout from 2.3.5 to 2.4.0 [(#​2462)](https://togithub.com/mockito/mockito/pull/2462) - Fixes [#​2460](https://togithub.com/mockito/mockito/issues/2460): Remove a sentence commits to a particular version [(#​2461)](https://togithub.com/mockito/mockito/pull/2461) - Clarify Javadoc of RETURNS_SMART_NULLS, default answer in Mockito 4.0.0? [(#​2460)](https://togithub.com/mockito/mockito/issues/2460) - Bump versions.bytebuddy from 1.11.21 to 1.11.22 [(#​2458)](https://togithub.com/mockito/mockito/pull/2458) - Updated readme with the latest Mockito version [(#​2456)](https://togithub.com/mockito/mockito/pull/2456) - Bump core-ktx from 1.6.0 to 1.7.0 [(#​2454)](https://togithub.com/mockito/mockito/pull/2454) - Bump google-java-format from 1.11.0 to 1.12.0 [(#​2450)](https://togithub.com/mockito/mockito/pull/2450) - Bump versions.bytebuddy from 1.11.20 to 1.11.21 [(#​2448)](https://togithub.com/mockito/mockito/pull/2448) - Use new CodeCov uploader [(#​2447)](https://togithub.com/mockito/mockito/pull/2447) - Bump actions/checkout from 2.3.4 to 2.3.5 [(#​2445)](https://togithub.com/mockito/mockito/pull/2445) - Fixes [#​2389](https://togithub.com/mockito/mockito/issues/2389) : Parallel use of mocks with deep stubbing may lead to ConcurrentModificationException [(#​2444)](https://togithub.com/mockito/mockito/pull/2444) - Bump versions.bytebuddy from 1.11.19 to 1.11.20 [(#​2443)](https://togithub.com/mockito/mockito/pull/2443) - Parallel use of mocks with deep stubbing may lead to ConcurrentModificationException [(#​2389)](https://togithub.com/mockito/mockito/issues/2389) - Add annotation to mark a type as DoNotMock [(#​1833)](https://togithub.com/mockito/mockito/pull/1833) - Cannot mock this class: class java.io.InputStream with Java 13 [(#​1827)](https://togithub.com/mockito/mockito/issues/1827) - Cannot mock wrapper types, String.class or Class.class [(#​1734)](https://togithub.com/mockito/mockito/issues/1734) ### [`v4.0.0`](https://togithub.com/mockito/mockito/releases/tag/v4.0.0) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.12.4...v4.0.0) ### Mockito 4: Removing deprecated APIs. All of these APIs have been marked as deprecated and have been present in Mockito for quite a while. An overview of now-deleted classes/methods: - `org.mockito.Matchers` which was an alias for `org.mockito.ArgumentMatchers` - `org.mockito.ArgumentMatchers#{anyObject,anyVararg}` both which were aliases for `org.mockito.ArgumentMatchers#any` - `org.mockito.ArgumentMatchers#any*Of`, which were aliases for the same method name without the Of and the generic parameters (which were ignored) - `org.mockito.ArgumentMatchers#{is}{Not}Null(Class)` which took a class which was ignored. Aliases for the same methods without the parameter - `org.mockito.MockedStatic#verify` which had the parameter types reversed - `org.mockito.Mockito#verifyZeroInteractions` an alias of `verifyNoMoreInteractions` - `org.mockito.Mockito#debug` framework integration API that we later refactored - `org.mockito.configuration.AnnotationEngine` which was leaking internal APIs and instead users should use `org.mockito.plugins.AnnotationEngine` - `org.mockito.exceptions.verification.TooLittleActualInvocations` fixed the grammar from "Little" to "Few" - Numerous internal APIs that we never officially supported and can now remove - `org.mockito.plugins.InstantiatorProvider` which was leaking internal APIs and instead users should use InstantiatorProvider2 (we should probably rename back to remove the number in a future major release) - `org.mockito.runners` a package that hosted several old JUnit runners which were no longer supported. Users should instead use `org.mockito.junit.MockitoJUnitRunner` which is our official JUnit4 runner. ### [`v3.12.4`](https://togithub.com/mockito/mockito/releases/tag/v3.12.4) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.12.3...v3.12.4) *Changelog generated by [Shipkit Changelog Gradle Plugin](https://togithub.com/shipkit/shipkit-changelog)* ##### 3.12.4 - 2021-08-25 - [1 commit(s)](https://togithub.com/mockito/mockito/compare/v3.12.3...v3.12.4) by Rafael Winterhalter - No notable improvements. No pull requests (issues) were referenced from commits. ### [`v3.12.3`](https://togithub.com/mockito/mockito/releases/tag/v3.12.3) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.12.2...v3.12.3) *Changelog generated by [Shipkit Changelog Gradle Plugin](https://togithub.com/shipkit/shipkit-changelog)* ##### 3.12.3 - 2021-08-24 - [9 commit(s)](https://togithub.com/mockito/mockito/compare/v3.12.2...v3.12.3) by Rafael Winterhalter - Fix implementation of proxy mock maker for toString and add additional unit tests. [(#​2405)](https://togithub.com/mockito/mockito/pull/2405) - Avoid cache breakage [(#​2402)](https://togithub.com/mockito/mockito/pull/2402) - Add a limited mock maker that is based only on the java.lang.reflect.Proxy utility [(#​2397)](https://togithub.com/mockito/mockito/pull/2397) ### [`v3.12.2`](https://togithub.com/mockito/mockito/releases/tag/v3.12.2) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.12.1...v3.12.2) *Changelog generated by [Shipkit Changelog Gradle Plugin](https://togithub.com/shipkit/shipkit-changelog)* ##### 3.12.2 - 2021-08-24 - [2 commit(s)](https://togithub.com/mockito/mockito/compare/v3.12.1...v3.12.2) by Dmitry Vyazelenko, dependabot\[bot] - Fixes [#​2399](https://togithub.com/mockito/mockito/issues/2399) : Adds defaultAnswer to the MockitoMockKey to distinguish the mock types, i.e. to separate mocks from spies otherwise spy type is reused for a mock or vice versa. [(#​2400)](https://togithub.com/mockito/mockito/pull/2400) - Sporadic mock verification failures related to hashCode/equals on 3.12.1 [(#​2399)](https://togithub.com/mockito/mockito/issues/2399) - Bump versions.errorprone from 2.8.1 to 2.9.0 [(#​2396)](https://togithub.com/mockito/mockito/pull/2396) ### [`v3.12.1`](https://togithub.com/mockito/mockito/releases/tag/v3.12.1) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.12.0...v3.12.1) *Changelog generated by [Shipkit Changelog Gradle Plugin](https://togithub.com/shipkit/shipkit-changelog)* ##### 3.12.1 - 2021-08-20 - [2 commit(s)](https://togithub.com/mockito/mockito/compare/v3.12.0...v3.12.1) by Tim van der Lippe, dependabot\[bot] - Fix verifyNoMoreInteractions inOrder invocations for spies [(#​2395)](https://togithub.com/mockito/mockito/pull/2395) - Regression with InOrder verification after [#​2369](https://togithub.com/mockito/mockito/issues/2369) [(#​2394)](https://togithub.com/mockito/mockito/issues/2394) - Bump versions.bytebuddy from 1.11.12 to 1.11.13 [(#​2393)](https://togithub.com/mockito/mockito/pull/2393) ### [`v3.12.0`](https://togithub.com/mockito/mockito/releases/tag/v3.12.0) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.11.2...v3.12.0) *Changelog generated by [Shipkit Changelog Gradle Plugin](https://togithub.com/shipkit/shipkit-changelog)* ##### 3.12.0 - 2021-08-19 - [31 commit(s)](https://togithub.com/mockito/mockito/compare/v3.11.2...v3.12.0) by EugeneLesnov, Lars Vogel, Logan Rosen, Rafael Winterhalter, Rob Pridham, Tim van der Lippe, dependabot\[bot], saurabh7248 - Add checks for sealed types [(#​2392)](https://togithub.com/mockito/mockito/pull/2392) - Bump versions.bytebuddy from 1.11.10 to 1.11.12 [(#​2388)](https://togithub.com/mockito/mockito/pull/2388) - Bump versions.bytebuddy from 1.11.9 to 1.11.10 [(#​2387)](https://togithub.com/mockito/mockito/pull/2387) - Bump versions.errorprone from 2.8.0 to 2.8.1 [(#​2386)](https://togithub.com/mockito/mockito/pull/2386) - Update StaticMockTest to use unified verify method [(#​2385)](https://togithub.com/mockito/mockito/pull/2385) - Reorder InjectMock Javadoc to fit the order of injection [(#​2383)](https://togithub.com/mockito/mockito/pull/2383) - Bump core-ktx from 1.5.0 to 1.6.0 [(#​2382)](https://togithub.com/mockito/mockito/pull/2382) - Bump google-java-format from 1.10.0 to 1.11.0 [(#​2381)](https://togithub.com/mockito/mockito/pull/2381) - Downgrade Android gradle plugin [(#​2380)](https://togithub.com/mockito/mockito/pull/2380) - Applied [@​CheckReturnValue](https://togithub.com/CheckReturnValue) to some classes [(#​2379)](https://togithub.com/mockito/mockito/pull/2379) - how to solve gradle sync failed after 'Add basic Android instrumented and unit tests' [(#​2378)](https://togithub.com/mockito/mockito/issues/2378) - Bump junit from 1.1.2 to 1.1.3 [(#​2377)](https://togithub.com/mockito/mockito/pull/2377) - Bump appcompat from 1.3.0 to 1.3.1 [(#​2376)](https://togithub.com/mockito/mockito/pull/2376) - Bump kotlin-gradle-plugin from 1.5.20 to 1.5.21 [(#​2374)](https://togithub.com/mockito/mockito/pull/2374) - Bump material from 1.3.0 to 1.4.0 [(#​2373)](https://togithub.com/mockito/mockito/pull/2373) - Bump espresso-core from 3.3.0 to 3.4.0 [(#​2372)](https://togithub.com/mockito/mockito/pull/2372) - Fixes [#​2331](https://togithub.com/mockito/mockito/issues/2331) [(#​2369)](https://togithub.com/mockito/mockito/pull/2369) - Fix typo in exception [(#​2368)](https://togithub.com/mockito/mockito/pull/2368) - Bump versions.bytebuddy from 1.11.8 to 1.11.9 [(#​2367)](https://togithub.com/mockito/mockito/pull/2367) - Bump versions.errorprone from 2.7.1 to 2.8.0 [(#​2365)](https://togithub.com/mockito/mockito/pull/2365) - Bump versions.bytebuddy from 1.11.7 to 1.11.8 [(#​2361)](https://togithub.com/mockito/mockito/pull/2361) - Basic Android instrumented and unit tests (closes [#​2341](https://togithub.com/mockito/mockito/issues/2341)) [(#​2360)](https://togithub.com/mockito/mockito/pull/2360) - Bump versions.bytebuddy from 1.11.6 to 1.11.7 [(#​2359)](https://togithub.com/mockito/mockito/pull/2359) - Bump kotlin-stdlib from 1.5.20 to 1.5.21 [(#​2356)](https://togithub.com/mockito/mockito/pull/2356) - Bump kotlinx-coroutines-core from 1.5.1 to 1.5.1-native-mt [(#​2354)](https://togithub.com/mockito/mockito/pull/2354) - Bump kotlinx-coroutines-core from 1.5.0-native-mt to 1.5.1 [(#​2353)](https://togithub.com/mockito/mockito/pull/2353) - Bump versions.bytebuddy from 1.11.5 to 1.11.6 [(#​2351)](https://togithub.com/mockito/mockito/pull/2351) - Bump gradle-errorprone-plugin from 2.0.1 to 2.0.2 [(#​2347)](https://togithub.com/mockito/mockito/pull/2347) - Bump kotlin-stdlib from 1.5.10 to 1.5.20 [(#​2343)](https://togithub.com/mockito/mockito/pull/2343) - Bump versions.bytebuddy from 1.11.3 to 1.11.5 [(#​2337)](https://togithub.com/mockito/mockito/pull/2337) - Bump assertj-core from 3.20.1 to 3.20.2 [(#​2336)](https://togithub.com/mockito/mockito/pull/2336) - Spy doesn't forward hashcode/equals to actual object [(#​2331)](https://togithub.com/mockito/mockito/issues/2331) - Fixes [#​2311](https://togithub.com/mockito/mockito/issues/2311) [(#​2320)](https://togithub.com/mockito/mockito/pull/2320) ### [`v3.11.2`](https://togithub.com/mockito/mockito/releases/tag/v3.11.2) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.11.1...v3.11.2) *Changelog generated by [Shipkit Changelog Gradle Plugin](https://togithub.com/shipkit/shipkit-changelog)* ##### 3.11.2 - 2021-06-21 - [5 commit(s)](https://togithub.com/mockito/mockito/compare/v3.11.1...v3.11.2) by dependabot\[bot] - Bump versions.bytebuddy from 1.11.2 to 1.11.3 [(#​2333)](https://togithub.com/mockito/mockito/pull/2333) - Bump assertj-core from 3.20.0 to 3.20.1 [(#​2332)](https://togithub.com/mockito/mockito/pull/2332) - Bump org.eclipse.osgi from 3.16.200 to 3.16.300 [(#​2330)](https://togithub.com/mockito/mockito/pull/2330) - Bump assertj-core from 3.19.0 to 3.20.0 [(#​2329)](https://togithub.com/mockito/mockito/pull/2329) - Bump shipkit-auto-version from 1.1.17 to 1.1.19 [(#​2328)](https://togithub.com/mockito/mockito/pull/2328) ### [`v3.11.1`](https://togithub.com/mockito/mockito/releases/tag/v3.11.1) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.11.0...v3.11.1) *Changelog generated by [Shipkit Changelog Gradle Plugin](https://togithub.com/shipkit/shipkit-changelog)* ##### 3.11.1 - 2021-06-11 - [3 commit(s)](https://togithub.com/mockito/mockito/compare/v3.11.0...v3.11.1) by Charles Munger, dependabot\[bot] - Bump versions.bytebuddy from 1.11.1 to 1.11.2 [(#​2322)](https://togithub.com/mockito/mockito/pull/2322) - Check package-privacy of method params [(#​2318)](https://togithub.com/mockito/mockito/pull/2318) - Bump shipkit-auto-version from 1.1.16 to 1.1.17 [(#​2317)](https://togithub.com/mockito/mockito/pull/2317) ### [`v3.11.0`](https://togithub.com/mockito/mockito/releases/tag/v3.11.0) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.10.0...v3.11.0) *Changelog generated by [Shipkit Changelog Gradle Plugin](https://togithub.com/shipkit/shipkit-changelog)* ##### 3.11.0 - 2021-06-03 - [18 commit(s)](https://togithub.com/mockito/mockito/compare/v3.10.0...v3.11.0) by Charles Munger, Szczepan Faber, dependabot\[bot] - Bump versions.bytebuddy from 1.11.0 to 1.11.1 [(#​2313)](https://togithub.com/mockito/mockito/pull/2313) - Undo parent for MultipleParentsClassLoader [(#​2312)](https://togithub.com/mockito/mockito/pull/2312) - Bump shipkit-auto-version from 1.1.14 to 1.1.16 [(#​2310)](https://togithub.com/mockito/mockito/pull/2310) - Bump gradle/wrapper-validation-action from 1.0.3 to 1.0.4 [(#​2309)](https://togithub.com/mockito/mockito/pull/2309) - Bump com.github.ben-manes.versions from 0.38.0 to 0.39.0 [(#​2308)](https://togithub.com/mockito/mockito/pull/2308) - Bump shipkit-auto-version from 1.1.11 to 1.1.14 [(#​2307)](https://togithub.com/mockito/mockito/pull/2307) - Use the parent classloader if the context classloader is a child of it. [(#​2306)](https://togithub.com/mockito/mockito/pull/2306) - Bump kotlin-stdlib from 1.5.0 to 1.5.10 [(#​2305)](https://togithub.com/mockito/mockito/pull/2305) - "The type is not public and its mock class is loaded by a different class loader" with a context classloader that delegates [(#​2303)](https://togithub.com/mockito/mockito/issues/2303) - Enabled automated changelog [(#​2301)](https://togithub.com/mockito/mockito/pull/2301) - Bump kotlinx-coroutines-core from 1.4.3-native-mt to 1.5.0-native-mt [(#​2299)](https://togithub.com/mockito/mockito/pull/2299) - Bump versions.errorprone from 2.6.0 to 2.7.1 [(#​2298)](https://togithub.com/mockito/mockito/pull/2298) - Bump junit-platform-launcher from 1.7.1 to 1.7.2 [(#​2297)](https://togithub.com/mockito/mockito/pull/2297) - Bump versions.junitJupiter from 5.7.1 to 5.7.2 [(#​2296)](https://togithub.com/mockito/mockito/pull/2296) - Renamed main dev branch [(#​2295)](https://togithub.com/mockito/mockito/pull/2295) - Bump gradle/wrapper-validation-action from 1 to 1.0.3 [(#​2294)](https://togithub.com/mockito/mockito/pull/2294) - Bump actions/checkout from 2 to 2.3.4 [(#​2293)](https://togithub.com/mockito/mockito/pull/2293) - 'this' is not available - when enabling mock-maker-inline [(#​2082)](https://togithub.com/mockito/mockito/issues/2082) ### [`v3.10.0`](https://togithub.com/mockito/mockito/releases/tag/v3.10.0) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.8.0...v3.10.0) *Changelog generated by [Shipkit Changelog Gradle Plugin](https://togithub.com/shipkit/shipkit-changelog)* ##### 3.10.0 - 2021-05-12 - [0 commit(s)](https://togithub.com/mockito/mockito/compare/HEAD...v3.10.0) by - No notable improvements. No pull requests (issues) were referenced from commits. ### [`v3.8.0`](https://togithub.com/mockito/mockito/releases/tag/v3.8.0) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.7.7...v3.8.0) *Changelog generated by [Shipkit Changelog Gradle Plugin](https://togithub.com/shipkit/shipkit-changelog)* ##### 3.8.0 - 2021-02-22 - [1 commit(s)](https://togithub.com/mockito/mockito/compare/v3.7.18...v3.8.0) by Tim van der Lippe - Publish new minor version to Maven central [(#​2213)](https://togithub.com/mockito/mockito/pull/2213) ### [`v3.7.7`](https://togithub.com/mockito/mockito/releases/tag/v3.7.7) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.7.0...v3.7.7) *Changelog generated by [Shipkit Changelog Gradle Plugin](https://togithub.com/shipkit/shipkit-changelog)* ##### 3.7.7 - 2021-01-16 - [1 commit(s)](https://togithub.com/mockito/mockito/compare/v3.7.6...v3.7.7) by Stefan Bohn - Fix reversed order of verify parameters [(#​2179)](https://togithub.com/mockito/mockito/pull/2179) - Feature request: Unify order of parameters [(#​2173)](https://togithub.com/mockito/mockito/issues/2173) ### [`v3.7.0`](https://togithub.com/mockito/mockito/releases/tag/v3.7.0) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.6.28...v3.7.0) *Changelog generated by [Shipkit Changelog Gradle Plugin](https://togithub.com/shipkit/shipkit-changelog)* ##### 3.7.0 - 2021-01-04 - [2 commit(s)](https://togithub.com/mockito/mockito/compare/v3.6.54...v3.7.0) by Szczepan Faber, Tim van der Lippe - Publish new minor version to Maven central [(#​2165)](https://togithub.com/mockito/mockito/pull/2165) ### [`v3.6.28`](https://togithub.com/mockito/mockito/releases/tag/v3.6.28) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.6.0...v3.6.28) *Changelog generated by [Shipkit Changelog Gradle Plugin](https://togithub.com/shipkit/shipkit-changelog)* ##### 3.6.28 - 2020-11-25 - [1 commit(s)](https://togithub.com/mockito/mockito/compare/v3.6.27...v3.6.28) by Szczepan Faber - No notable improvements. No pull requests (issues) were referenced from commits. ### [`v3.6.0`](https://togithub.com/mockito/mockito/releases/tag/v3.6.0) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.5.15...v3.6.0) *Release notes were automatically generated by [Shipkit](http://shipkit.org/)* ##### 3.6.0 - 2020-10-27 - [7 commits](https://togithub.com/mockito/mockito/compare/v3.5.15...v3.6.0) by [Szczepan Faber](https://togithub.com/mockitoguy) (4), [shipkit-org](https://togithub.com/shipkit-org) (2), [Tim van der Lippe](https://togithub.com/TimvdLippe) (1) - published to [![Bintray](https://img.shields.io/badge/Bintray-3.6.0-green.svg)](https://bintray.com/mockito/maven/mockito/3.6.0) - Retry the release [(#​2078)](https://togithub.com/mockito/mockito/pull/2078) - Retry 3.6.0 release [(#​2077)](https://togithub.com/mockito/mockito/pull/2077) ### [`v3.5.15`](https://togithub.com/mockito/mockito/releases/tag/v3.5.15) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.5.13...v3.5.15) *Release notes were automatically generated by [Shipkit](http://shipkit.org/)* ##### 3.5.15 - 2020-10-19 - [4 commits](https://togithub.com/mockito/mockito/compare/v3.5.14...v3.5.15) by [Rafael Winterhalter](https://togithub.com/raphw) - published to [![Bintray](https://img.shields.io/badge/Bintray-3.5.15-green.svg)](https://bintray.com/mockito/maven/mockito/3.5.15) - Mock resolver plugin [(#​2042)](https://togithub.com/mockito/mockito/pull/2042) ### [`v3.5.13`](https://togithub.com/mockito/mockito/releases/tag/v3.5.13) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.5.11...v3.5.13) *Release notes were automatically generated by [Shipkit](http://shipkit.org/)* ##### 3.5.13 - 2020-09-24 - [1 commit](https://togithub.com/mockito/mockito/compare/v3.5.12...v3.5.13) by [Sinan Kozak](https://togithub.com/kozaxinan) - published to [![Bintray](https://img.shields.io/badge/Bintray-3.5.13-green.svg)](https://bintray.com/mockito/maven/mockito/3.5.13) - Use single version for strictly in mockito-android [(#​2053)](https://togithub.com/mockito/mockito/pull/2053) ### [`v3.5.11`](https://togithub.com/mockito/mockito/releases/tag/v3.5.11) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.5.10...v3.5.11) *Release notes were automatically generated by [Shipkit](http://shipkit.org/)* ##### 3.5.11 - 2020-09-17 - [2 commits](https://togithub.com/mockito/mockito/compare/v3.5.10...v3.5.11) by [Rafael Winterhalter](https://togithub.com/raphw) - published to [![Bintray](https://img.shields.io/badge/Bintray-3.5.11-green.svg)](https://bintray.com/mockito/maven/mockito/3.5.11) - Do not exclude synthetic constructors from instrumentation. Fixes [#​2040](https://togithub.com/mockito/mockito/issues/2040). [(#​2046)](https://togithub.com/mockito/mockito/pull/2046) - Mockito.spy(Activity).getBaseContext() returns null on Robolectric 4.4 and Java8 [(#​2040)](https://togithub.com/mockito/mockito/issues/2040) ### [`v3.5.10`](https://togithub.com/mockito/mockito/releases/tag/v3.5.10) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.5.9...v3.5.10) *Release notes were automatically generated by [Shipkit](http://shipkit.org/)* ##### 3.5.10 - 2020-09-03 - [2 commits](https://togithub.com/mockito/mockito/compare/v3.5.9...v3.5.10) by [Rafael Winterhalter](https://togithub.com/raphw) - published to [![Bintray](https://img.shields.io/badge/Bintray-3.5.10-green.svg)](https://bintray.com/mockito/maven/mockito/3.5.10) - Escape mock during method dispatch on mock to avoid premature garbage collection. [(#​2034)](https://togithub.com/mockito/mockito/pull/2034) - Exception "The mock object was garbage collected." [(#​1802)](https://togithub.com/mockito/mockito/issues/1802) ### [`v3.5.9`](https://togithub.com/mockito/mockito/releases/tag/v3.5.9) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.5.7...v3.5.9) *Release notes were automatically generated by [Shipkit](http://shipkit.org/)* ##### 3.5.9 - 2020-09-01 - [1 commit](https://togithub.com/mockito/mockito/compare/v3.5.8...v3.5.9) by [Sinan Kozak](https://togithub.com/kozaxinan) - published to [![Bintray](https://img.shields.io/badge/Bintray-3.5.9-green.svg)](https://bintray.com/mockito/maven/mockito/3.5.9) - Fixes [#​2007](https://togithub.com/mockito/mockito/issues/2007) : Downgrade objenesis version for mockito-android [(#​2024)](https://togithub.com/mockito/mockito/pull/2024) - Android instrumentation test packaging fails for mockito-android 3.5.0 with minSdk < 26 [(#​2007)](https://togithub.com/mockito/mockito/issues/2007) ### [`v3.5.7`](https://togithub.com/mockito/mockito/releases/tag/v3.5.7) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.5.6...v3.5.7) *Release notes were automatically generated by [Shipkit](http://shipkit.org/)* ##### 3.5.7 - 2020-08-25 - [2 commits](https://togithub.com/mockito/mockito/compare/v3.5.6...v3.5.7) by [Rafael Winterhalter](https://togithub.com/raphw) - published to [![Bintray](https://img.shields.io/badge/Bintray-3.5.7-green.svg)](https://bintray.com/mockito/maven/mockito/3.5.7) - Initializes classes prior to instrumentation to avoid uncontrolled code execution. [(#​2023)](https://togithub.com/mockito/mockito/pull/2023) - Stackoverflow error when upgrading to v3.5.2 [(#​2011)](https://togithub.com/mockito/mockito/issues/2011) ### [`v3.5.6`](https://togithub.com/mockito/mockito/releases/tag/v3.5.6) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.5.5...v3.5.6) *Release notes were automatically generated by [Shipkit](http://shipkit.org/)* ##### 3.5.6 - 2020-08-24 - [5 commits](https://togithub.com/mockito/mockito/compare/v3.5.5...v3.5.6) by [Rafael Winterhalter](https://togithub.com/raphw) - published to [![Bintray](https://img.shields.io/badge/Bintray-3.5.6-green.svg)](https://bintray.com/mockito/maven/mockito/3.5.6) - Only apply argument on illegal module access for inline tests if Java version is at least 9. [(#​2022)](https://togithub.com/mockito/mockito/pull/2022) - Constructor dispatch [(#​2021)](https://togithub.com/mockito/mockito/pull/2021) ### [`v3.5.5`](https://togithub.com/mockito/mockito/releases/tag/v3.5.5) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.5.2...v3.5.5) *Release notes were automatically generated by [Shipkit](http://shipkit.org/)* ##### 3.5.5 - 2020-08-22 - [3 commits](https://togithub.com/mockito/mockito/compare/v3.5.4...v3.5.5) by [Rafael Winterhalter](https://togithub.com/raphw) - published to [![Bintray](https://img.shields.io/badge/Bintray-3.5.5-green.svg)](https://bintray.com/mockito/maven/mockito/3.5.5) - Constructor dispatch [(#​2020)](https://togithub.com/mockito/mockito/pull/2020) ### [`v3.5.2`](https://togithub.com/mockito/mockito/releases/tag/v3.5.2) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.5.0...v3.5.2) *Release notes were automatically generated by [Shipkit](http://shipkit.org/)* ##### 3.5.2 - 2020-08-18 - [1 commit](https://togithub.com/mockito/mockito/compare/v3.5.1...v3.5.2) by [Tim van der Lippe](https://togithub.com/TimvdLippe) - published to [![Bintray](https://img.shields.io/badge/Bintray-3.5.2-green.svg)](https://bintray.com/mockito/maven/mockito/3.5.2) - No pull requests referenced in commit messages. ### [`v3.5.0`](https://togithub.com/mockito/mockito/releases/tag/v3.5.0) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.4.6...v3.5.0) *Release notes were automatically generated by [Shipkit](http://shipkit.org/)* ##### 3.5.0 - 2020-08-15 - [9 commits](https://togithub.com/mockito/mockito/compare/v3.4.8...v3.5.0) by [Rafael Winterhalter](https://togithub.com/raphw) - published to [![Bintray](https://img.shields.io/badge/Bintray-3.5.0-green.svg)](https://bintray.com/mockito/maven/mockito/3.5.0) - Pre release 3.5.0 [(#​2004)](https://togithub.com/mockito/mockito/pull/2004) ### [`v3.4.6`](https://togithub.com/mockito/mockito/releases/tag/v3.4.6) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.4.4...v3.4.6) *Release notes were automatically generated by [Shipkit](http://shipkit.org/)* ##### 3.4.6 - 2020-07-29 - [3 commits](https://togithub.com/mockito/mockito/compare/v3.4.5...v3.4.6) by [Rafael Winterhalter](https://togithub.com/raphw) (2), [Valery Yatsynovich](https://togithub.com/valfirst) (1) - published to [![Bintray](https://img.shields.io/badge/Bintray-3.4.6-green.svg)](https://bintray.com/mockito/maven/mockito/3.4.6) - \[Bugfixes] Do not pass static mocks to regular listener callback. [(#​1989)](https://togithub.com/mockito/mockito/pull/1989) - MockitoJUnitRunner causes NPE when using [@​Mock](https://togithub.com/Mock) on MockedStatic fields [(#​1988)](https://togithub.com/mockito/mockito/issues/1988) - Fixes [#​1985](https://togithub.com/mockito/mockito/issues/1985) : Update README to refer the latest documentation [(#​1986)](https://togithub.com/mockito/mockito/pull/1986) - README should refer the latest available documentation [(#​1985)](https://togithub.com/mockito/mockito/issues/1985) ### [`v3.4.4`](https://togithub.com/mockito/mockito/releases/tag/v3.4.4) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.4.3...v3.4.4) *Release notes were automatically generated by [Shipkit](http://shipkit.org/)* ##### 3.4.4 - 2020-07-18 - [2 commits](https://togithub.com/mockito/mockito/compare/v3.4.3...v3.4.4) by [Rafael Winterhalter](https://togithub.com/raphw) - published to [![Bintray](https://img.shields.io/badge/Bintray-3.4.4-green.svg)](https://bintray.com/mockito/maven/mockito/3.4.4) - Fixes [#​1855](https://togithub.com/mockito/mockito/issues/1855) and [#​939](https://togithub.com/mockito/mockito/issues/939): improve error message when the inline mock maker cannot be used. [(#​1974)](https://togithub.com/mockito/mockito/pull/1974) - javax.tools.ToolProvider could not be found in InlineByteBuddyMockMaker [(#​1855)](https://togithub.com/mockito/mockito/issues/1855) ### [`v3.4.3`](https://togithub.com/mockito/mockito/releases/tag/v3.4.3) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.4.2...v3.4.3) *Release notes were automatically generated by [Shipkit](http://shipkit.org/)* ##### 3.4.3 - 2020-07-17 - [1 commit](https://togithub.com/mockito/mockito/compare/v3.4.2...v3.4.3) by [Robert Chmielowiec](https://togithub.com/chmielowiec) - published to [![Bintray](https://img.shields.io/badge/Bintray-3.4.3-green.svg)](https://bintray.com/mockito/maven/mockito/3.4.3) - Fix Javadoc invalid syntax [(#​1978)](https://togithub.com/mockito/mockito/pull/1978) - Broken documentation [(#​1977)](https://togithub.com/mockito/mockito/issues/1977) ### [`v3.4.2`](https://togithub.com/mockito/mockito/releases/tag/v3.4.2) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.4.0...v3.4.2) *Release notes were automatically generated by [Shipkit](http://shipkit.org/)* ##### 3.4.2 - 2020-07-16 - [2 commits](https://togithub.com/mockito/mockito/compare/v3.4.1...v3.4.2) by [Rafael Winterhalter](https://togithub.com/raphw) - published to [![Bintray](https://img.shields.io/badge/Bintray-3.4.2-green.svg)](https://bintray.com/mockito/maven/mockito/3.4.2) - Fixes [#​1967](https://togithub.com/mockito/mockito/issues/1967): Correctly handle mocks with limited life-cycle in listeners. [(#​1968)](https://togithub.com/mockito/mockito/pull/1968) - Static method mocks incompatible with MockitoExtension (NotAMockException) [(#​1967)](https://togithub.com/mockito/mockito/issues/1967) ### [`v3.4.0`](https://togithub.com/mockito/mockito/releases/tag/v3.4.0) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.3.3...v3.4.0) *Release notes were automatically generated by [Shipkit](http://shipkit.org/)* ##### 3.4.0 - 2020-07-10 - [19 commits](https://togithub.com/mockito/mockito/compare/v3.3.12...v3.4.0) by 9 authors - published to [![Bintray](https://img.shields.io/badge/Bintray-3.4.0-green.svg)](https://bintray.com/mockito/maven/mockito/3.4.0) - Commits: [Tim van der Lippe](https://togithub.com/TimvdLippe) (5), [Erhard Pointl](https://togithub.com/epeee) (4), [Rafael Winterhalter](https://togithub.com/raphw) (3), [Eitan Adler](https://togithub.com/grimreaper) (2), adrianriley (1), akluball (1), [Artem Prigoda](https://togithub.com/arteam) (1), [Jamie Tanna](https://togithub.com/jamietanna) (1), [Naoki Takezoe](https://togithub.com/takezoe) (1) - \[Android support] Enable mocking static methods in Mockito [(#​1013)](https://togithub.com/mockito/mockito/issues/1013) - Document using `@Mock` with method parameters [(#​1961)](https://togithub.com/mockito/mockito/pull/1961) - Documentation: `@Mock` on method parameters [(#​1960)](https://togithub.com/mockito/mockito/issues/1960) - Update errorprone gradle plugin to v1.2.1 [(#​1958)](https://togithub.com/mockito/mockito/pull/1958) - Update spotless Travis job name to be more descriptive [(#​1957)](https://togithub.com/mockito/mockito/pull/1957) - Fix a confusing typo in subclassing error message [(#​1953)](https://togithub.com/mockito/mockito/pull/1953) - Update bnd gradle plugin to v5.1.1 [(#​1952)](https://togithub.com/mockito/mockito/pull/1952) - Use errorprone 2.4.0 [(#​1951)](https://togithub.com/mockito/mockito/pull/1951) - Use jacoco v0.8.5 [(#​1950)](https://togithub.com/mockito/mockito/pull/1950) - Fixes [#​1712](https://togithub.com/mockito/mockito/issues/1712) : prepend description to AssertionError thrown in verification [(#​1949)](https://togithub.com/mockito/mockito/pull/1949) - Update gradle 6 [(#​1948)](https://togithub.com/mockito/mockito/pull/1948) - Move spotless check to separate build task [(#​1946)](https://togithub.com/mockito/mockito/pull/1946) - \[Travis] Replace JDK 9/10 with 14 [(#​1945)](https://togithub.com/mockito/mockito/pull/1945) - Fixes [#​1898](https://togithub.com/mockito/mockito/issues/1898) : Return mock name from toString method for deep stub mocks [(#​1942)](https://togithub.com/mockito/mockito/pull/1942) - \[checkstyle] switch to new DTD [(#​1940)](https://togithub.com/mockito/mockito/pull/1940) - Use google-java-format in spotless [(#​1934)](https://togithub.com/mockito/mockito/pull/1934) - Update report message to use any() instead of anyObject() [(#​1931)](https://togithub.com/mockito/mockito/pull/1931) - \[build] bump gradle to latest 5.x release [(#​1923)](https://togithub.com/mockito/mockito/pull/1923) - \[build] update gradle-errorprone-plugin to 1.1.0 [(#​1908)](https://togithub.com/mockito/mockito/pull/1908) - RETURNS_DEEP_STUBS override a mock's toString to `null` [(#​1898)](https://togithub.com/mockito/mockito/issues/1898) - "description" not printing when verify args don't match [(#​1712)](https://togithub.com/mockito/mockito/issues/1712) ### [`v3.3.3`](https://togithub.com/mockito/mockito/releases/tag/v3.3.3) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.3.0...v3.3.3) *Release notes were automatically generated by [Shipkit](http://shipkit.org/)* ##### 3.3.3 - 2020-03-13 - [1 commit](https://togithub.com/mockito/mockito/compare/v3.3.2...v3.3.3) by [Tim van der Lippe](https://togithub.com/TimvdLippe) - published to [![Bintray](https://img.shields.io/badge/Bintray-3.3.3-green.svg)](https://bintray.com/mockito/maven/mockito/3.3.3) - No pull requests referenced in commit messages. ### [`v3.3.0`](https://togithub.com/mockito/mockito/releases/tag/v3.3.0) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.2.4...v3.3.0) *Release notes were automatically generated by [Shipkit](http://shipkit.org/)* ##### 3.3.0 - 2020-02-21 - [1 commit](https://togithub.com/mockito/mockito/compare/v3.2.11...v3.3.0) by [Tim van der Lippe](https://togithub.com/TimvdLippe) - published to [![Bintray](https://img.shields.io/badge/Bintray-3.3.0-green.svg)](https://bintray.com/mockito/maven/mockito/3.3.0) - No pull requests referenced in commit messages. ### [`v3.2.4`](https://togithub.com/mockito/mockito/releases/tag/v3.2.4) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.2.0...v3.2.4) *Release notes were automatically generated by [Shipkit](http://shipkit.org/)* ##### 3.2.4 - 2019-12-16 - [1 commit](https://togithub.com/mockito/mockito/compare/v3.2.3...v3.2.4) by [Tim van der Lippe](https://togithub.com/TimvdLippe) - published to [![Bintray](https://img.shields.io/badge/Bintray-3.2.4-green.svg)](https://bintray.com/mockito/maven/mockito/3.2.4) - No pull requests referenced in commit messages. ### [`v3.2.0`](https://togithub.com/mockito/mockito/releases/tag/v3.2.0) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.1.0...v3.2.0) *Release notes were automatically generated by [Shipkit](http://shipkit.org/)* ##### 3.2.0 - 2019-11-29 - [1 commit](https://togithub.com/mockito/mockito/compare/v3.1.13...v3.2.0) by [Tim van der Lippe](https://togithub.com/TimvdLippe) - published to [![Bintray](https://img.shields.io/badge/Bintray-3.2.0-green.svg)](https://bintray.com/mockito/maven/mockito/3.2.0) - No pull requests referenced in commit messages. ### [`v3.1.0`](https://togithub.com/mockito/mockito/releases/tag/v3.1.0) [Compare Source](https://togithub.com/mockito/mockito/compare/v3.0.0...v3.1.0) *Release notes were automatically generated by [Shipkit](http://shipkit.org/)* ##### 3.1.0 - 2019-10-01 - [1 commit](https://togithub.com/mockito/mockito/compare/v3.0.12...v3.1.0) by [Tim van der Lippe](https://togithub.com/TimvdLippe) - published to [![Bintray](https://img.shields.io/badge/Bintray-3.1.0-green.svg)](https://bintray.com/mockito/maven/mockito/3.1.0) - No pull requests referenced in commit messages. ### [`v3.0.0`](https://togithub.com/mockito/mockito/releases/tag/v3.0.0) [Compare Source](https://togithub.com/mockito/mockito/compare/v2.28.2...v3.0.0) Mockito 3.0.0 is equivalent to Mockito 2, but now requires Java 8. No API changes were made. ### [`v2.28.2`](https://togithub.com/mockito/mockito/releases/tag/v2.28.2) [Compare Source](https://togithub.com/mockito/mockito/compare/v2.28.1...v2.28.2) *Release notes were automatically generated by [Shipkit](http://shipkit.org/)* ##### 2.28.2 - 2019-05-29 - [1 commit](https://togithub.com/mockito/mockito/compare/v2.28.1...v2.28.2) by [Tim van der Lippe](https://togithub.com/TimvdLippe) - published to [![Bintray](https://img.shields.io/badge/Bintray-2.28.2-green.svg)](https://bintray.com/mockito/maven/mockito/2.28.2) - No pull requests referenced in commit messages. ### [`v2.28.1`](https://togithub.com/mockito/mockito/releases/tag/v2.28.1) [Compare Source](https://togithub.com/mockito/mockito/compare/v2.27.0...v2.28.1) *Release notes were automatically generated by [Shipkit](http://shipkit.org/)* ##### 2.28.1 - 2019-05-28 - [1 commit](https://togithub.com/mockito/mockito/compare/v2.28.0...v2.28.1) by [Tim van der Lippe](https://togithub.com/TimvdLippe) - published to [![Bintray](https://img.shields.io/badge/Bintray-2.28.1-green.svg)](https://bintray.com/mockito/maven/mockito/2.28.1) - No pull requests referenced in commit messages. ### [`v2.27.0`](https://togithub.com/mockito/mockito/releases/tag/v2.27.0) [Compare Source](https://togithub.com/mockito/mockito/compare/v2.26.0...v2.27.0) *Release notes were automatically generated by [Shipkit](http://shipkit.org/)* ##### 2.27.0 - 2019-04-10 - [1 commit](https://togithub.com/mockito/mockito/compare/v2.26.2...v2.27.0) by [Tim van der Lippe](https://togithub.com/TimvdLippe) - published to [![Bintray](https://img.shields.io/badge/Bintray-2.27.0-green.svg)](https://bintray.com/mockito/maven/mockito/2.27.0) - No pull requests referenced in commit messages. ### [`v2.26.0`](https://togithub.com/mockito/mockito/releases/tag/v2.26.0) [Compare Source](https://togithub.com/mockito/mockito/compare/v2.25.1...v2.26.0) *Release notes were automatically generated by [Shipkit](http://shipkit.org/)* ##### 2.26.0 - 2019-04-04 - [1 commit](https://togithub.com/mockito/mockito/compare/v2.25.7...v2.26.0) by [Tim van der Lippe](https://togithub.com/TimvdLippe) - published to [![Bintray](https://img.shields.io/badge/Bintray-2.26.0-green.svg)]
--- ### Configuration πŸ“… **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. β™» **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. πŸ”• **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, click this checkbox. --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/google-auth-library-java). --- oauth2_http/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauth2_http/pom.xml b/oauth2_http/pom.xml index fea8123fc..9a34e630f 100644 --- a/oauth2_http/pom.xml +++ b/oauth2_http/pom.xml @@ -143,7 +143,7 @@ org.mockito mockito-core - 2.23.4 + 4.6.1 test From a80b898ac98b7d3f732d37898edb6c4d02c4f0c0 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Mon, 27 Jun 2022 19:32:19 +0000 Subject: [PATCH 09/10] chore: update dependencies for regapic (#1467) (#936) * chore: update dependencies for regapic * add more dependencies and trigger comment * update goldens * fix indentation * remove duplicate gax-httpjson dependency * remove duplicated dependencies Source-Link: https://github.com/googleapis/synthtool/commit/fa54eb2a78c6ee48613fd33152e2130e949dcbd9 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-java:latest@sha256:1ec28a46062b19135b11178ceee60231e5f5a92dab454e23ae0aab72cd875906 --- .github/.OwlBot.lock.yaml | 4 ++-- .kokoro/common.sh | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml index a79f06271..f0625e4d9 100644 --- a/.github/.OwlBot.lock.yaml +++ b/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-java:latest - digest: sha256:2567a120ce90fadb6201999b87d649d9f67459de28815ad239bce9ebfaa18a74 -# created: 2022-05-19T15:12:45.278246753Z + digest: sha256:1ec28a46062b19135b11178ceee60231e5f5a92dab454e23ae0aab72cd875906 +# created: 2022-06-27T15:01:06.405564326Z diff --git a/.kokoro/common.sh b/.kokoro/common.sh index ace89f45a..f8f957af1 100644 --- a/.kokoro/common.sh +++ b/.kokoro/common.sh @@ -55,4 +55,6 @@ function retry_with_backoff { ## Helper functionss function now() { date +"%Y-%m-%d %H:%M:%S" | tr -d '\n'; } function msg() { println "$*" >&2; } -function println() { printf '%s\n' "$(now) $*"; } \ No newline at end of file +function println() { printf '%s\n' "$(now) $*"; } + +## Helper comment to trigger updated repo dependency release \ No newline at end of file From 5662cdc1c04bf31568a0fbabee6450c2d7760c36 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 27 Jun 2022 13:50:10 -0700 Subject: [PATCH 10/10] chore(main): release 1.8.0 (#924) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Timur Sadykov --- CHANGELOG.md | 13 +++++++++++++ appengine/pom.xml | 2 +- bom/pom.xml | 2 +- credentials/pom.xml | 2 +- oauth2_http/pom.xml | 2 +- pom.xml | 2 +- versions.txt | 12 ++++++------ 7 files changed, 24 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 069f22b80..6a2dba2a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## [1.8.0](https://github.com/googleapis/google-auth-library-java/compare/v1.7.0...v1.8.0) (2022-06-27) + + +### Features + +* add build scripts for native image testing in Java 17 ([#1440](https://github.com/googleapis/google-auth-library-java/issues/1440)) ([#923](https://github.com/googleapis/google-auth-library-java/issues/923)) ([bbb51ce](https://github.com/googleapis/google-auth-library-java/commit/bbb51ce7a9265cb991739cd90e1ccf65675d05dc)) +* Adds Pluggable Auth support (WIF) ([#908](https://github.com/googleapis/google-auth-library-java/issues/908)) ([c3e8d16](https://github.com/googleapis/google-auth-library-java/commit/c3e8d169704943735c6b3df7bd0187f04fdd9aa5)) + + +### Documentation + +* updates README for Pluggable Auth ([#921](https://github.com/googleapis/google-auth-library-java/issues/921)) ([23716b8](https://github.com/googleapis/google-auth-library-java/commit/23716b82fb3000f5210bb5604127aad7ef52cb76)) + ## [1.7.0](https://github.com/googleapis/google-auth-library-java/compare/v1.6.0...v1.7.0) (2022-05-12) diff --git a/appengine/pom.xml b/appengine/pom.xml index f40cfdaa3..c3048869b 100644 --- a/appengine/pom.xml +++ b/appengine/pom.xml @@ -5,7 +5,7 @@ com.google.auth google-auth-library-parent - 1.7.1-SNAPSHOT + 1.8.0 ../pom.xml diff --git a/bom/pom.xml b/bom/pom.xml index 951ac42ee..5301c3955 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.auth google-auth-library-bom - 1.7.1-SNAPSHOT + 1.8.0 pom Google Auth Library for Java BOM diff --git a/credentials/pom.xml b/credentials/pom.xml index 82bc206a8..a06f9dcbe 100644 --- a/credentials/pom.xml +++ b/credentials/pom.xml @@ -4,7 +4,7 @@ com.google.auth google-auth-library-parent - 1.7.1-SNAPSHOT + 1.8.0 ../pom.xml diff --git a/oauth2_http/pom.xml b/oauth2_http/pom.xml index 9a34e630f..599fd91c2 100644 --- a/oauth2_http/pom.xml +++ b/oauth2_http/pom.xml @@ -5,7 +5,7 @@ com.google.auth google-auth-library-parent - 1.7.1-SNAPSHOT + 1.8.0 ../pom.xml diff --git a/pom.xml b/pom.xml index 6182ed492..3db8436cb 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.auth google-auth-library-parent - 1.7.1-SNAPSHOT + 1.8.0 pom Google Auth Library for Java Client libraries providing authentication and diff --git a/versions.txt b/versions.txt index 05ee34faa..fc9c5d398 100644 --- a/versions.txt +++ b/versions.txt @@ -1,9 +1,9 @@ # Format: # module:released-version:current-version -google-auth-library:1.7.0:1.7.1-SNAPSHOT -google-auth-library-bom:1.7.0:1.7.1-SNAPSHOT -google-auth-library-parent:1.7.0:1.7.1-SNAPSHOT -google-auth-library-appengine:1.7.0:1.7.1-SNAPSHOT -google-auth-library-credentials:1.7.0:1.7.1-SNAPSHOT -google-auth-library-oauth2-http:1.7.0:1.7.1-SNAPSHOT +google-auth-library:1.8.0:1.8.0 +google-auth-library-bom:1.8.0:1.8.0 +google-auth-library-parent:1.8.0:1.8.0 +google-auth-library-appengine:1.8.0:1.8.0 +google-auth-library-credentials:1.8.0:1.8.0 +google-auth-library-oauth2-http:1.8.0:1.8.0