From ab3c4c12312a347e2fcad8875796ad0a60889d08 Mon Sep 17 00:00:00 2001 From: Vince Grassia <593223+vgrassia@users.noreply.github.com> Date: Thu, 9 Nov 2023 10:22:29 -0500 Subject: [PATCH 001/157] All workflows - Update 'master' to 'main' for any actions in the gh-actions repository (#328) --- .github/workflows/publish-rust-crates.yml | 2 +- .github/workflows/release-cli.yml | 8 ++++---- .github/workflows/release-napi.yml | 12 ++++++------ .github/workflows/version-bump.yml | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/publish-rust-crates.yml b/.github/workflows/publish-rust-crates.yml index 39926d18b..468570cd8 100644 --- a/.github/workflows/publish-rust-crates.yml +++ b/.github/workflows/publish-rust-crates.yml @@ -109,7 +109,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@ba6a77541fa0732735408d10f3ca1e122221201d + uses: bitwarden/gh-actions/get-keyvault-secrets@main with: keyvault: "bitwarden-ci" secrets: "cratesio-api-token" diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index bf348a291..4c52529e8 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -58,7 +58,7 @@ jobs: - name: Download all Release artifacts if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@ba6a77541fa0732735408d10f3ca1e122221201d + uses: bitwarden/gh-actions/download-artifacts@main with: workflow: build-cli.yml path: packages @@ -67,7 +67,7 @@ jobs: - name: Dry Run - Download all artifacts if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@ba6a77541fa0732735408d10f3ca1e122221201d + uses: bitwarden/gh-actions/download-artifacts@main with: workflow: build-cli.yml path: packages @@ -75,7 +75,7 @@ jobs: branch: master - name: Get checksum files - uses: bitwarden/gh-actions/get-checksum@ba6a77541fa0732735408d10f3ca1e122221201d + uses: bitwarden/gh-actions/get-checksum@main with: packages_dir: "packages" file_path: "packages/bws-sha256-checksums-${{ steps.version.outputs.version }}.txt" @@ -134,7 +134,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@ba6a77541fa0732735408d10f3ca1e122221201d + uses: bitwarden/gh-actions/get-keyvault-secrets@main with: keyvault: "bitwarden-ci" secrets: "cratesio-api-token" diff --git a/.github/workflows/release-napi.yml b/.github/workflows/release-napi.yml index ec88803a2..daa450c0b 100644 --- a/.github/workflows/release-napi.yml +++ b/.github/workflows/release-napi.yml @@ -47,7 +47,7 @@ jobs: - name: Check Release Version id: version - uses: bitwarden/gh-actions/release-version-check@ba6a77541fa0732735408d10f3ca1e122221201d + uses: bitwarden/gh-actions/release-version-check@main with: release-type: ${{ github.event.inputs.release_type }} project-type: ts @@ -101,7 +101,7 @@ jobs: - name: Download schemas if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@ba6a77541fa0732735408d10f3ca1e122221201d + uses: bitwarden/gh-actions/download-artifacts@main with: workflow: build-napi.yml artifacts: schemas.ts @@ -111,7 +111,7 @@ jobs: - name: Dry Run - Download schemas if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@ba6a77541fa0732735408d10f3ca1e122221201d + uses: bitwarden/gh-actions/download-artifacts@main with: workflow: build-napi.yml artifacts: schemas.ts @@ -132,14 +132,14 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@ba6a77541fa0732735408d10f3ca1e122221201d + uses: bitwarden/gh-actions/get-keyvault-secrets@main with: keyvault: "bitwarden-ci" secrets: "npm-api-key" - name: Download artifacts if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@ba6a77541fa0732735408d10f3ca1e122221201d + uses: bitwarden/gh-actions/download-artifacts@main with: workflow: build-napi.yml path: ${{ github.workspace }}/crates/bitwarden-napi/artifacts @@ -148,7 +148,7 @@ jobs: - name: Dry Run - Download artifacts if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@ba6a77541fa0732735408d10f3ca1e122221201d + uses: bitwarden/gh-actions/download-artifacts@main with: workflow: build-napi.yml path: ${{ github.workspace }}/crates/bitwarden-napi/artifacts diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index e875fc663..d902c5700 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -49,7 +49,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@ba6a77541fa0732735408d10f3ca1e122221201d + uses: bitwarden/gh-actions/get-keyvault-secrets@main with: keyvault: "bitwarden-ci" secrets: "github-gpg-private-key, github-gpg-private-key-passphrase" From 5268e78b0f15ae1c40488b3b32b1e443d40abd60 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 13:27:35 +0100 Subject: [PATCH 002/157] Update npm minor (#323) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate logo banner](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [@napi-rs/cli](https://togithub.com/napi-rs/napi-rs) | [`2.16.3` -> `2.16.5`](https://renovatebot.com/diffs/npm/@napi-rs%2fcli/2.16.3/2.16.5) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@napi-rs%2fcli/2.16.5?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@napi-rs%2fcli/2.16.5?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@napi-rs%2fcli/2.16.3/2.16.5?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@napi-rs%2fcli/2.16.3/2.16.5?slim=true)](https://docs.renovatebot.com/merge-confidence/) | | [prettier](https://prettier.io) ([source](https://togithub.com/prettier/prettier)) | [`3.0.3` -> `3.1.0`](https://renovatebot.com/diffs/npm/prettier/3.0.3/3.1.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/prettier/3.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/prettier/3.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/prettier/3.0.3/3.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/prettier/3.0.3/3.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | | [quicktype-core](https://togithub.com/quicktype/quicktype) | [`23.0.76` -> `23.0.77`](https://renovatebot.com/diffs/npm/quicktype-core/23.0.76/23.0.77) | [![age](https://developer.mend.io/api/mc/badges/age/npm/quicktype-core/23.0.77?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/quicktype-core/23.0.77?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/quicktype-core/23.0.76/23.0.77?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/quicktype-core/23.0.76/23.0.77?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
napi-rs/napi-rs (@​napi-rs/cli) ### [`v2.16.5`](https://togithub.com/napi-rs/napi-rs/releases/tag/%40napi-rs/cli%402.16.5) [Compare Source](https://togithub.com/napi-rs/napi-rs/compare/@napi-rs/cli@2.16.4...@napi-rs/cli@2.16.5) #### What's Changed - fix(cli): switch name parsing to allow periods in name by [@​RaphaelDarley](https://togithub.com/RaphaelDarley) [https://github.com/napi-rs/napi-rs/pull/1716](https://togithub.com/napi-rs/napi-rs/pull/1716) #### New Contributors - [@​RaphaelDarley](https://togithub.com/RaphaelDarley) made their first contribution in [https://github.com/napi-rs/napi-rs/pull/1716](https://togithub.com/napi-rs/napi-rs/pull/1716) **Full Changelog**: https://github.com/napi-rs/napi-rs/compare/[@​napi-rs/cli](https://togithub.com/napi-rs/cli)[@​2](https://togithub.com/2).16.4...[@​napi-rs/cli](https://togithub.com/napi-rs/cli)[@​2](https://togithub.com/2).16.5 ### [`v2.16.4`](https://togithub.com/napi-rs/napi-rs/releases/tag/%40napi-rs/cli%402.16.4) [Compare Source](https://togithub.com/napi-rs/napi-rs/compare/@napi-rs/cli@2.16.3...@napi-rs/cli@2.16.4) #### What's Changed - chore(cli): update ci template by [@​Brooooooklyn](https://togithub.com/Brooooooklyn) in [https://github.com/napi-rs/napi-rs/pull/1770](https://togithub.com/napi-rs/napi-rs/pull/1770) **Full Changelog**: https://github.com/napi-rs/napi-rs/compare/[@​napi-rs/cli](https://togithub.com/napi-rs/cli)[@​2](https://togithub.com/2).16.3...[@​napi-rs/cli](https://togithub.com/napi-rs/cli)[@​2](https://togithub.com/2).16.4
prettier/prettier (prettier) ### [`v3.1.0`](https://togithub.com/prettier/prettier/blob/HEAD/CHANGELOG.md#310) [Compare Source](https://togithub.com/prettier/prettier/compare/3.0.3...3.1.0) [diff](https://togithub.com/prettier/prettier/compare/3.0.3...3.1.0) πŸ”— [Release Notes](https://prettier.io/blog/2023/11/13/3.1.0.html)
quicktype/quicktype (quicktype-core) ### [`v23.0.77`](https://togithub.com/quicktype/quicktype/compare/fb0ee714b89e65f288789ce2f2e3e33b41107405...55dc6a7912715b7db45419958bf89d00637cd4cd) [Compare Source](https://togithub.com/quicktype/quicktype/compare/fb0ee714b89e65f288789ce2f2e3e33b41107405...55dc6a7912715b7db45419958bf89d00637cd4cd)
--- ### Configuration πŸ“… **Schedule**: Branch creation - "every weekend" (UTC), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. β™» **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. πŸ‘» **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://togithub.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/bitwarden/sdk). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bitwarden-napi/package-lock.json | 6 +++--- package-lock.json | 16 ++++++++-------- package.json | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/crates/bitwarden-napi/package-lock.json b/crates/bitwarden-napi/package-lock.json index 4a85cb304..65266d1e4 100644 --- a/crates/bitwarden-napi/package-lock.json +++ b/crates/bitwarden-napi/package-lock.json @@ -55,9 +55,9 @@ } }, "node_modules/@napi-rs/cli": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.16.3.tgz", - "integrity": "sha512-3mLNPlbbOhpbIUKicLrJtIearlHXUuXL3UeueYyRRplpVMNkdn8xCyzY6PcYZi3JXR8bmCOiWgkVmLnrSL7DKw==", + "version": "2.16.5", + "resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.16.5.tgz", + "integrity": "sha512-mFEzwrg4IOLngGd2/P6yeqIWgwQNn59Z08n1rndu6kLDq1gg954NH9cM1O9Da0RJuybt46p43lqgSsnAY2mxqA==", "dev": true, "bin": { "napi": "scripts/index.js" diff --git a/package-lock.json b/package-lock.json index 2ee17dd0a..7791b3b69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,8 +11,8 @@ "devDependencies": { "@openapitools/openapi-generator-cli": "2.7.0", "handlebars": "^4.7.8", - "prettier": "3.0.3", - "quicktype-core": "23.0.76", + "prettier": "3.1.0", + "quicktype-core": "23.0.77", "rimraf": "5.0.5", "ts-node": "10.9.1", "typescript": "5.2.2" @@ -1470,9 +1470,9 @@ } }, "node_modules/prettier": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", - "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz", + "integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -1494,9 +1494,9 @@ } }, "node_modules/quicktype-core": { - "version": "23.0.76", - "resolved": "https://registry.npmjs.org/quicktype-core/-/quicktype-core-23.0.76.tgz", - "integrity": "sha512-QinZRNovSTQcFuhRKxeHb22eFmyucbG96EPaQDSbz9qvIPxUhs1BZviNc8HAkHWYFqTSET/xZcEoHpm1DeDbRg==", + "version": "23.0.77", + "resolved": "https://registry.npmjs.org/quicktype-core/-/quicktype-core-23.0.77.tgz", + "integrity": "sha512-QABFgMHVsyW7CAcLGWvIQXgjZ7ehPnebfV/3x9c5HtuJVXgYLrEG/+zyQd1O+OPWpQrhbwU97doJTZRw+acA6A==", "dev": true, "dependencies": { "@glideapps/ts-necessities": "2.1.3", diff --git a/package.json b/package.json index fdeb8fe58..ad38f3a10 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,8 @@ "devDependencies": { "@openapitools/openapi-generator-cli": "2.7.0", "handlebars": "^4.7.8", - "prettier": "3.0.3", - "quicktype-core": "23.0.76", + "prettier": "3.1.0", + "quicktype-core": "23.0.77", "rimraf": "5.0.5", "ts-node": "10.9.1", "typescript": "5.2.2" From ee33be94e55463b2c0417a8887b7636e01c75eb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Mon, 13 Nov 2023 13:48:20 +0100 Subject: [PATCH 003/157] Update uniffi to solve Send conflict (#326) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Update uniffi to solve the conflict between our `Send` and Rust's `Send` --- Cargo.lock | 20 ++++++++++---------- Cargo.toml | 10 +++++----- crates/bitwarden-uniffi/src/vault/sends.rs | 16 ++++++++-------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9672d26e9..184066772 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3451,7 +3451,7 @@ checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "uniffi" version = "0.25.0" -source = "git+https://github.com/mozilla/uniffi-rs?rev=eb97592f8c48a7f5cf02a94662b8b7861a6544f3#eb97592f8c48a7f5cf02a94662b8b7861a6544f3" +source = "git+https://github.com/mozilla/uniffi-rs?rev=b369e7c15b1b7ebca34de9028209db11b7ff353d#b369e7c15b1b7ebca34de9028209db11b7ff353d" dependencies = [ "anyhow", "camino", @@ -3472,7 +3472,7 @@ dependencies = [ [[package]] name = "uniffi_bindgen" version = "0.25.0" -source = "git+https://github.com/mozilla/uniffi-rs?rev=eb97592f8c48a7f5cf02a94662b8b7861a6544f3#eb97592f8c48a7f5cf02a94662b8b7861a6544f3" +source = "git+https://github.com/mozilla/uniffi-rs?rev=b369e7c15b1b7ebca34de9028209db11b7ff353d#b369e7c15b1b7ebca34de9028209db11b7ff353d" dependencies = [ "anyhow", "askama", @@ -3495,7 +3495,7 @@ dependencies = [ [[package]] name = "uniffi_build" version = "0.25.0" -source = "git+https://github.com/mozilla/uniffi-rs?rev=eb97592f8c48a7f5cf02a94662b8b7861a6544f3#eb97592f8c48a7f5cf02a94662b8b7861a6544f3" +source = "git+https://github.com/mozilla/uniffi-rs?rev=b369e7c15b1b7ebca34de9028209db11b7ff353d#b369e7c15b1b7ebca34de9028209db11b7ff353d" dependencies = [ "anyhow", "camino", @@ -3505,7 +3505,7 @@ dependencies = [ [[package]] name = "uniffi_checksum_derive" version = "0.25.0" -source = "git+https://github.com/mozilla/uniffi-rs?rev=eb97592f8c48a7f5cf02a94662b8b7861a6544f3#eb97592f8c48a7f5cf02a94662b8b7861a6544f3" +source = "git+https://github.com/mozilla/uniffi-rs?rev=b369e7c15b1b7ebca34de9028209db11b7ff353d#b369e7c15b1b7ebca34de9028209db11b7ff353d" dependencies = [ "quote", "syn 2.0.38", @@ -3514,7 +3514,7 @@ dependencies = [ [[package]] name = "uniffi_core" version = "0.25.0" -source = "git+https://github.com/mozilla/uniffi-rs?rev=eb97592f8c48a7f5cf02a94662b8b7861a6544f3#eb97592f8c48a7f5cf02a94662b8b7861a6544f3" +source = "git+https://github.com/mozilla/uniffi-rs?rev=b369e7c15b1b7ebca34de9028209db11b7ff353d#b369e7c15b1b7ebca34de9028209db11b7ff353d" dependencies = [ "anyhow", "bytes", @@ -3529,7 +3529,7 @@ dependencies = [ [[package]] name = "uniffi_macros" version = "0.25.0" -source = "git+https://github.com/mozilla/uniffi-rs?rev=eb97592f8c48a7f5cf02a94662b8b7861a6544f3#eb97592f8c48a7f5cf02a94662b8b7861a6544f3" +source = "git+https://github.com/mozilla/uniffi-rs?rev=b369e7c15b1b7ebca34de9028209db11b7ff353d#b369e7c15b1b7ebca34de9028209db11b7ff353d" dependencies = [ "bincode", "camino", @@ -3547,7 +3547,7 @@ dependencies = [ [[package]] name = "uniffi_meta" version = "0.25.0" -source = "git+https://github.com/mozilla/uniffi-rs?rev=eb97592f8c48a7f5cf02a94662b8b7861a6544f3#eb97592f8c48a7f5cf02a94662b8b7861a6544f3" +source = "git+https://github.com/mozilla/uniffi-rs?rev=b369e7c15b1b7ebca34de9028209db11b7ff353d#b369e7c15b1b7ebca34de9028209db11b7ff353d" dependencies = [ "anyhow", "bytes", @@ -3558,7 +3558,7 @@ dependencies = [ [[package]] name = "uniffi_testing" version = "0.25.0" -source = "git+https://github.com/mozilla/uniffi-rs?rev=eb97592f8c48a7f5cf02a94662b8b7861a6544f3#eb97592f8c48a7f5cf02a94662b8b7861a6544f3" +source = "git+https://github.com/mozilla/uniffi-rs?rev=b369e7c15b1b7ebca34de9028209db11b7ff353d#b369e7c15b1b7ebca34de9028209db11b7ff353d" dependencies = [ "anyhow", "camino", @@ -3570,7 +3570,7 @@ dependencies = [ [[package]] name = "uniffi_udl" version = "0.25.0" -source = "git+https://github.com/mozilla/uniffi-rs?rev=eb97592f8c48a7f5cf02a94662b8b7861a6544f3#eb97592f8c48a7f5cf02a94662b8b7861a6544f3" +source = "git+https://github.com/mozilla/uniffi-rs?rev=b369e7c15b1b7ebca34de9028209db11b7ff353d#b369e7c15b1b7ebca34de9028209db11b7ff353d" dependencies = [ "anyhow", "uniffi_meta", @@ -3777,7 +3777,7 @@ dependencies = [ [[package]] name = "weedle2" version = "4.0.0" -source = "git+https://github.com/mozilla/uniffi-rs?rev=eb97592f8c48a7f5cf02a94662b8b7861a6544f3#eb97592f8c48a7f5cf02a94662b8b7861a6544f3" +source = "git+https://github.com/mozilla/uniffi-rs?rev=b369e7c15b1b7ebca34de9028209db11b7ff353d#b369e7c15b1b7ebca34de9028209db11b7ff353d" dependencies = [ "nom", ] diff --git a/Cargo.toml b/Cargo.toml index a192c14e5..da8805342 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,8 +24,8 @@ codegen-units = 1 # Using master until 0.25.1 is released to fix https://github.com/mozilla/uniffi-rs/issues/1798 [patch.crates-io] -uniffi = { git = "https://github.com/mozilla/uniffi-rs", rev = "eb97592f8c48a7f5cf02a94662b8b7861a6544f3" } -uniffi_build = { git = "https://github.com/mozilla/uniffi-rs", rev = "eb97592f8c48a7f5cf02a94662b8b7861a6544f3" } -uniffi_bindgen = { git = "https://github.com/mozilla/uniffi-rs", rev = "eb97592f8c48a7f5cf02a94662b8b7861a6544f3" } -uniffi_core = { git = "https://github.com/mozilla/uniffi-rs", rev = "eb97592f8c48a7f5cf02a94662b8b7861a6544f3" } -uniffi_macros = { git = "https://github.com/mozilla/uniffi-rs", rev = "eb97592f8c48a7f5cf02a94662b8b7861a6544f3" } +uniffi = { git = "https://github.com/mozilla/uniffi-rs", rev = "b369e7c15b1b7ebca34de9028209db11b7ff353d" } +uniffi_build = { git = "https://github.com/mozilla/uniffi-rs", rev = "b369e7c15b1b7ebca34de9028209db11b7ff353d" } +uniffi_bindgen = { git = "https://github.com/mozilla/uniffi-rs", rev = "b369e7c15b1b7ebca34de9028209db11b7ff353d" } +uniffi_core = { git = "https://github.com/mozilla/uniffi-rs", rev = "b369e7c15b1b7ebca34de9028209db11b7ff353d" } +uniffi_macros = { git = "https://github.com/mozilla/uniffi-rs", rev = "b369e7c15b1b7ebca34de9028209db11b7ff353d" } diff --git a/crates/bitwarden-uniffi/src/vault/sends.rs b/crates/bitwarden-uniffi/src/vault/sends.rs index 696c4a861..6e2f1b879 100644 --- a/crates/bitwarden-uniffi/src/vault/sends.rs +++ b/crates/bitwarden-uniffi/src/vault/sends.rs @@ -1,6 +1,6 @@ use std::{path::Path, sync::Arc}; -use bitwarden::vault::{self, SendListView, SendView}; +use bitwarden::vault::{Send, SendListView, SendView}; use crate::{Client, Result}; @@ -10,12 +10,12 @@ pub struct ClientSends(pub Arc); #[uniffi::export] impl ClientSends { /// Encrypt send - pub async fn encrypt(&self, send: SendView) -> Result { + pub async fn encrypt(&self, send: SendView) -> Result { Ok(self.0 .0.read().await.vault().sends().encrypt(send).await?) } /// Encrypt a send file in memory - pub async fn encrypt_buffer(&self, send: vault::Send, buffer: Vec) -> Result> { + pub async fn encrypt_buffer(&self, send: Send, buffer: Vec) -> Result> { Ok(self .0 .0 @@ -30,7 +30,7 @@ impl ClientSends { /// Encrypt a send file located in the file system pub async fn encrypt_file( &self, - send: vault::Send, + send: Send, decrypted_file_path: String, encrypted_file_path: String, ) -> Result<()> { @@ -50,12 +50,12 @@ impl ClientSends { } /// Decrypt send - pub async fn decrypt(&self, send: vault::Send) -> Result { + pub async fn decrypt(&self, send: Send) -> Result { Ok(self.0 .0.read().await.vault().sends().decrypt(send).await?) } /// Decrypt send list - pub async fn decrypt_list(&self, sends: Vec) -> Result> { + pub async fn decrypt_list(&self, sends: Vec) -> Result> { Ok(self .0 .0 @@ -68,7 +68,7 @@ impl ClientSends { } /// Decrypt a send file in memory - pub async fn decrypt_buffer(&self, send: vault::Send, buffer: Vec) -> Result> { + pub async fn decrypt_buffer(&self, send: Send, buffer: Vec) -> Result> { Ok(self .0 .0 @@ -83,7 +83,7 @@ impl ClientSends { /// Decrypt a send file located in the file system pub async fn decrypt_file( &self, - send: vault::Send, + send: Send, encrypted_file_path: String, decrypted_file_path: String, ) -> Result<()> { From 39891e0adb9049dd5852028d110ff8f2be0b6855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Mon, 13 Nov 2023 14:09:14 +0100 Subject: [PATCH 004/157] Update public modules to get correct documentation (#325) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective All the functions in `Client` that return `Client*` were inside private modules, which make them accessible for use, but `cargo doc` will not document them. By making them public, the cargo docs generate the correct documentation. Also made `aes_ops` private as it's not used outside the crypto module anymore. --- crates/bitwarden/src/crypto/aes_ops.rs | 1 + crates/bitwarden/src/crypto/mod.rs | 2 +- crates/bitwarden/src/mobile/mod.rs | 7 +++++-- crates/bitwarden/src/mobile/vault/mod.rs | 7 +++++++ crates/bitwarden/src/secrets_manager/mod.rs | 3 +++ crates/bitwarden/src/tool/exporters/mod.rs | 1 + crates/bitwarden/src/tool/generators/mod.rs | 1 + crates/bitwarden/src/tool/mod.rs | 4 ++-- 8 files changed, 21 insertions(+), 5 deletions(-) diff --git a/crates/bitwarden/src/crypto/aes_ops.rs b/crates/bitwarden/src/crypto/aes_ops.rs index 87cb96029..132718349 100644 --- a/crates/bitwarden/src/crypto/aes_ops.rs +++ b/crates/bitwarden/src/crypto/aes_ops.rs @@ -62,6 +62,7 @@ pub fn decrypt_aes256_hmac( /// ## Returns /// /// A AesCbc256_B64 EncString +#[allow(unused)] pub fn encrypt_aes256(data_dec: &[u8], key: GenericArray) -> Result { let (iv, data) = encrypt_aes256_internal(data_dec, key); diff --git a/crates/bitwarden/src/crypto/mod.rs b/crates/bitwarden/src/crypto/mod.rs index d8a1f0557..b030a5107 100644 --- a/crates/bitwarden/src/crypto/mod.rs +++ b/crates/bitwarden/src/crypto/mod.rs @@ -33,7 +33,7 @@ pub use encryptable::{Decryptable, Encryptable, LocateKey}; mod key_encryptable; pub use key_encryptable::{KeyDecryptable, KeyEncryptable}; mod aes_ops; -pub use aes_ops::{decrypt_aes256, decrypt_aes256_hmac, encrypt_aes256, encrypt_aes256_hmac}; +use aes_ops::{decrypt_aes256_hmac, encrypt_aes256_hmac}; mod symmetric_crypto_key; pub use symmetric_crypto_key::SymmetricCryptoKey; mod shareable_key; diff --git a/crates/bitwarden/src/mobile/mod.rs b/crates/bitwarden/src/mobile/mod.rs index ad2d794af..fe3083aa8 100644 --- a/crates/bitwarden/src/mobile/mod.rs +++ b/crates/bitwarden/src/mobile/mod.rs @@ -3,8 +3,11 @@ pub mod crypto; pub mod kdf; pub mod vault; -pub(crate) mod client_crypto; -pub(crate) mod client_kdf; +mod client_crypto; +mod client_kdf; + +pub use client_crypto::ClientCrypto; +pub use client_kdf::ClientKdf; // Usually we wouldn't want to expose EncStrings in the API or the schemas, // but we need them in the mobile API, so define it here to limit the scope diff --git a/crates/bitwarden/src/mobile/vault/mod.rs b/crates/bitwarden/src/mobile/vault/mod.rs index f22f004ce..97f9556af 100644 --- a/crates/bitwarden/src/mobile/vault/mod.rs +++ b/crates/bitwarden/src/mobile/vault/mod.rs @@ -5,3 +5,10 @@ mod client_password_history; mod client_sends; mod client_totp; mod client_vault; + +pub use client_ciphers::ClientCiphers; +pub use client_collection::ClientCollections; +pub use client_folders::ClientFolders; +pub use client_password_history::ClientPasswordHistory; +pub use client_sends::ClientSends; +pub use client_vault::ClientVault; diff --git a/crates/bitwarden/src/secrets_manager/mod.rs b/crates/bitwarden/src/secrets_manager/mod.rs index 0afbfe38c..27b84121e 100644 --- a/crates/bitwarden/src/secrets_manager/mod.rs +++ b/crates/bitwarden/src/secrets_manager/mod.rs @@ -3,3 +3,6 @@ pub mod secrets; mod client_projects; mod client_secrets; + +pub use client_projects::ClientProjects; +pub use client_secrets::ClientSecrets; diff --git a/crates/bitwarden/src/tool/exporters/mod.rs b/crates/bitwarden/src/tool/exporters/mod.rs index 508aae8fb..1170dbd38 100644 --- a/crates/bitwarden/src/tool/exporters/mod.rs +++ b/crates/bitwarden/src/tool/exporters/mod.rs @@ -6,6 +6,7 @@ use crate::{ }; mod client_exporter; +pub use client_exporter::ClientExporters; #[derive(JsonSchema)] #[cfg_attr(feature = "mobile", derive(uniffi::Enum))] diff --git a/crates/bitwarden/src/tool/generators/mod.rs b/crates/bitwarden/src/tool/generators/mod.rs index bdc0fb260..4d991b765 100644 --- a/crates/bitwarden/src/tool/generators/mod.rs +++ b/crates/bitwarden/src/tool/generators/mod.rs @@ -1,4 +1,5 @@ mod client_generator; mod password; +pub use client_generator::ClientGenerator; pub use password::{PassphraseGeneratorRequest, PasswordGeneratorRequest}; diff --git a/crates/bitwarden/src/tool/mod.rs b/crates/bitwarden/src/tool/mod.rs index 2130a6b0c..212ce4bf7 100644 --- a/crates/bitwarden/src/tool/mod.rs +++ b/crates/bitwarden/src/tool/mod.rs @@ -1,5 +1,5 @@ mod exporters; mod generators; -pub use exporters::ExportFormat; -pub use generators::{PassphraseGeneratorRequest, PasswordGeneratorRequest}; +pub use exporters::{ClientExporters, ExportFormat}; +pub use generators::{ClientGenerator, PassphraseGeneratorRequest, PasswordGeneratorRequest}; From a55c2d7733be6ac49b259950301b4b423f3cb4c2 Mon Sep 17 00:00:00 2001 From: tangowithfoxtrot <5676771+tangowithfoxtrot@users.noreply.github.com> Date: Thu, 16 Nov 2023 12:25:30 -0800 Subject: [PATCH 005/157] [BEEEP] SM-1005 - Add env output option (#320) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Type of change - [ ] Bug fix - [x] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ## Objective Add an env output option to `bws`. This allows for easier command-line usage in scripts, particularly where a JSON or YAML parser is not desirable or available. Basic usage examples in Bash: - `source <(bws secret get ec9e0489-244e-4b3f-8782-b0a800fe562f -o env)` - `bws secret list -o env > .env` ## Code changes - **`crates/bws/src/render.rs`:** Output secrets in `key="value"` format ## Screenshots ![image](https://github.com/bitwarden/sdk/assets/5676771/72201357-dc4c-471e-b1f0-011297dbb978) ## Before you submit - Please add **unit tests** where it makes sense to do so (encouraged but not required) --------- Co-authored-by: Daniel GarcΓ­a --- Cargo.lock | 1 + crates/bws/Cargo.toml | 1 + crates/bws/src/render.rs | 26 ++++++++++++++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 184066772..6a02ad594 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -539,6 +539,7 @@ dependencies = [ "env_logger", "log", "openssl", + "regex", "serde", "serde_json", "serde_yaml", diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 00ec40da7..9ccd5c546 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -37,6 +37,7 @@ thiserror = "1.0.40" tokio = { version = "1.28.2", features = ["rt-multi-thread", "macros"] } toml = "0.8.0" uuid = { version = "^1.3.3", features = ["serde"] } +regex = { version = "1.10.2", features=["std", "perf"], default-features=false } bitwarden = { path = "../bitwarden", version = "0.3.1", features = ["secrets"] } diff --git a/crates/bws/src/render.rs b/crates/bws/src/render.rs index 4f2a32e9a..83c5cd532 100644 --- a/crates/bws/src/render.rs +++ b/crates/bws/src/render.rs @@ -9,6 +9,7 @@ use serde::Serialize; pub(crate) enum Output { JSON, YAML, + Env, Table, TSV, None, @@ -49,6 +50,31 @@ pub(crate) fn serialize_response, const N: usiz let text = serde_yaml::to_string(&data).unwrap(); pretty_print("yaml", &text, color); } + Output::Env => { + let valid_key_regex = regex::Regex::new("^[a-zA-Z_][a-zA-Z0-9_]*$").unwrap(); + + let mut commented_out = false; + let mut text: Vec = data + .get_values() + .into_iter() + .map(|row| { + if valid_key_regex.is_match(&row[1]) { + format!("{}=\"{}\"", row[1], row[2]) + } else { + commented_out = true; + format!("# {}=\"{}\"", row[1], row[2].replace('\n', "\n# ")) + } + }) + .collect(); + + if commented_out { + text.push(String::from( + "\n# one or more secrets have been commented-out due to a problematic key name", + )); + } + + pretty_print("sh", &format!("{}\n", text.join("\n")), color); + } Output::Table => { let mut table = Table::new(); table From c4b041197652a67e52fafc9dbf772f043e5b393f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Nov 2023 10:30:53 +0100 Subject: [PATCH 006/157] Update npm minor (#336) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate logo banner](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [quicktype-core](https://togithub.com/quicktype/quicktype) | [`23.0.77` -> `23.0.79`](https://renovatebot.com/diffs/npm/quicktype-core/23.0.77/23.0.79) | [![age](https://developer.mend.io/api/mc/badges/age/npm/quicktype-core/23.0.79?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/quicktype-core/23.0.79?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/quicktype-core/23.0.77/23.0.79?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/quicktype-core/23.0.77/23.0.79?slim=true)](https://docs.renovatebot.com/merge-confidence/) | | [ts-loader](https://togithub.com/TypeStrong/ts-loader) | [`9.5.0` -> `9.5.1`](https://renovatebot.com/diffs/npm/ts-loader/9.5.0/9.5.1) | [![age](https://developer.mend.io/api/mc/badges/age/npm/ts-loader/9.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/ts-loader/9.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/ts-loader/9.5.0/9.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/ts-loader/9.5.0/9.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
quicktype/quicktype (quicktype-core) ### [`v23.0.79`](https://togithub.com/quicktype/quicktype/compare/317deefa6a0c8ba0201b9b2b50d00c7e93c41d78...958d199d74b507175d9a49871c5d3770eaaf7873) [Compare Source](https://togithub.com/quicktype/quicktype/compare/317deefa6a0c8ba0201b9b2b50d00c7e93c41d78...958d199d74b507175d9a49871c5d3770eaaf7873) ### [`v23.0.78`](https://togithub.com/quicktype/quicktype/compare/55dc6a7912715b7db45419958bf89d00637cd4cd...317deefa6a0c8ba0201b9b2b50d00c7e93c41d78) [Compare Source](https://togithub.com/quicktype/quicktype/compare/55dc6a7912715b7db45419958bf89d00637cd4cd...317deefa6a0c8ba0201b9b2b50d00c7e93c41d78)
TypeStrong/ts-loader (ts-loader) ### [`v9.5.1`](https://togithub.com/TypeStrong/ts-loader/blob/HEAD/CHANGELOG.md#951) [Compare Source](https://togithub.com/TypeStrong/ts-loader/compare/v9.5.0...v9.5.1) - [fix: inputSourceMap can be null](https://togithub.com/TypeStrong/ts-loader/pull/1639) \[[#​1638](https://togithub.com/TypeStrong/ts-loader/issues/1638)] - thanks [@​johnnyreilly](https://togithub.com/johnnyreilly) and [@​michaeltford](https://togithub.com/michaeltford)
--- ### Configuration πŸ“… **Schedule**: Branch creation - "every weekend" (UTC), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. β™» **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. πŸ‘» **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://togithub.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/bitwarden/sdk). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- languages/js_webassembly/package-lock.json | 8 ++++---- languages/js_webassembly/package.json | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/languages/js_webassembly/package-lock.json b/languages/js_webassembly/package-lock.json index 066f862be..e3e20e9aa 100644 --- a/languages/js_webassembly/package-lock.json +++ b/languages/js_webassembly/package-lock.json @@ -7,7 +7,7 @@ "devDependencies": { "html-webpack-plugin": "5.5.3", "text-encoding": "0.7.0", - "ts-loader": "9.5.0", + "ts-loader": "9.5.1", "wasm-pack": "0.12.1", "webpack": "5.89.0", "webpack-cli": "5.1.4", @@ -3613,9 +3613,9 @@ } }, "node_modules/ts-loader": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.0.tgz", - "integrity": "sha512-LLlB/pkB4q9mW2yLdFMnK3dEHbrBjeZTYguaaIfusyojBgAGf5kF+O6KcWqiGzWqHk0LBsoolrp4VftEURhybg==", + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", + "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", "dev": true, "dependencies": { "chalk": "^4.1.0", diff --git a/languages/js_webassembly/package.json b/languages/js_webassembly/package.json index 95d7002a5..d5d7342df 100644 --- a/languages/js_webassembly/package.json +++ b/languages/js_webassembly/package.json @@ -6,7 +6,7 @@ "devDependencies": { "html-webpack-plugin": "5.5.3", "text-encoding": "0.7.0", - "ts-loader": "9.5.0", + "ts-loader": "9.5.1", "wasm-pack": "0.12.1", "webpack": "5.89.0", "webpack-cli": "5.1.4", diff --git a/package-lock.json b/package-lock.json index 7791b3b69..ac70b185d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@openapitools/openapi-generator-cli": "2.7.0", "handlebars": "^4.7.8", "prettier": "3.1.0", - "quicktype-core": "23.0.77", + "quicktype-core": "23.0.79", "rimraf": "5.0.5", "ts-node": "10.9.1", "typescript": "5.2.2" @@ -1494,9 +1494,9 @@ } }, "node_modules/quicktype-core": { - "version": "23.0.77", - "resolved": "https://registry.npmjs.org/quicktype-core/-/quicktype-core-23.0.77.tgz", - "integrity": "sha512-QABFgMHVsyW7CAcLGWvIQXgjZ7ehPnebfV/3x9c5HtuJVXgYLrEG/+zyQd1O+OPWpQrhbwU97doJTZRw+acA6A==", + "version": "23.0.79", + "resolved": "https://registry.npmjs.org/quicktype-core/-/quicktype-core-23.0.79.tgz", + "integrity": "sha512-Auzy8AhorFt6YGeB53/dzUSINmKKassAyCsr2wpNgG9XoC3i6oUoLuySNUzYIkyCFCGmKdBRBQeyAqPOVteoYw==", "dev": true, "dependencies": { "@glideapps/ts-necessities": "2.1.3", diff --git a/package.json b/package.json index ad38f3a10..97f08c571 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "@openapitools/openapi-generator-cli": "2.7.0", "handlebars": "^4.7.8", "prettier": "3.1.0", - "quicktype-core": "23.0.77", + "quicktype-core": "23.0.79", "rimraf": "5.0.5", "ts-node": "10.9.1", "typescript": "5.2.2" From 9fab1fa4ddbdcae7b597bbb674da4e2fd11fd287 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Nov 2023 10:58:19 +0100 Subject: [PATCH 007/157] Update Rust crate itertools to 0.12.0 (#338) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate logo banner](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [itertools](https://togithub.com/rust-itertools/itertools) | dependencies | minor | `0.11.0` -> `0.12.0` | --- ### Release Notes
rust-itertools/itertools (itertools) ### [`v0.12.0`](https://togithub.com/rust-itertools/itertools/blob/HEAD/CHANGELOG.md#0120) [Compare Source](https://togithub.com/rust-itertools/itertools/compare/v0.11.0...v0.12.0) ##### Breaking - Made `take_while_inclusive` consume iterator by value ([#​709](https://togithub.com/rust-itertools/itertools/issues/709)) - Added `Clone` bound to `Unique` ([#​777](https://togithub.com/rust-itertools/itertools/issues/777)) ##### Added - Added `Itertools::try_len` ([#​723](https://togithub.com/rust-itertools/itertools/issues/723)) - Added free function `sort_unstable` ([#​796](https://togithub.com/rust-itertools/itertools/issues/796)) - Added `GroupMap::fold_with` ([#​778](https://togithub.com/rust-itertools/itertools/issues/778), [#​785](https://togithub.com/rust-itertools/itertools/issues/785)) - Added `PeekNth::{peek_mut, peek_nth_mut}` ([#​716](https://togithub.com/rust-itertools/itertools/issues/716)) - Added `PeekNth::{next_if, next_if_eq}` ([#​734](https://togithub.com/rust-itertools/itertools/issues/734)) - Added conversion into `(Option,Option)` to `EitherOrBoth` ([#​713](https://togithub.com/rust-itertools/itertools/issues/713)) - Added conversion from `Either` to `EitherOrBoth` ([#​715](https://togithub.com/rust-itertools/itertools/issues/715)) - Implemented `ExactSizeIterator` for `Tuples` ([#​761](https://togithub.com/rust-itertools/itertools/issues/761)) - Implemented `ExactSizeIterator` for `(Circular)TupleWindows` ([#​752](https://togithub.com/rust-itertools/itertools/issues/752)) - Made `EitherOrBoth` a shorthand for `EitherOrBoth` ([#​719](https://togithub.com/rust-itertools/itertools/issues/719)) ##### Changed - Added missing `#[must_use]` annotations on iterator adaptors ([#​794](https://togithub.com/rust-itertools/itertools/issues/794)) - Made `Combinations` lazy ([#​795](https://togithub.com/rust-itertools/itertools/issues/795)) - Made `Intersperse(With)` lazy ([#​797](https://togithub.com/rust-itertools/itertools/issues/797)) - Made `Permutations` lazy ([#​793](https://togithub.com/rust-itertools/itertools/issues/793)) - Made `Product` lazy ([#​800](https://togithub.com/rust-itertools/itertools/issues/800)) - Made `TupleWindows` lazy ([#​602](https://togithub.com/rust-itertools/itertools/issues/602)) - Specialized `Combinations::{count, size_hint}` ([#​729](https://togithub.com/rust-itertools/itertools/issues/729)) - Specialized `CombinationsWithReplacement::{count, size_hint}` ([#​737](https://togithub.com/rust-itertools/itertools/issues/737)) - Specialized `Powerset::fold` ([#​765](https://togithub.com/rust-itertools/itertools/issues/765)) - Specialized `Powerset::count` ([#​735](https://togithub.com/rust-itertools/itertools/issues/735)) - Specialized `TupleCombinations::{count, size_hint}` ([#​763](https://togithub.com/rust-itertools/itertools/issues/763)) - Specialized `TupleCombinations::fold` ([#​775](https://togithub.com/rust-itertools/itertools/issues/775)) - Specialized `WhileSome::fold` ([#​780](https://togithub.com/rust-itertools/itertools/issues/780)) - Specialized `WithPosition::fold` ([#​772](https://togithub.com/rust-itertools/itertools/issues/772)) - Specialized `ZipLongest::fold` ([#​774](https://togithub.com/rust-itertools/itertools/issues/774)) - Changed `{min, max}_set*` operations require `alloc` feature, instead of `std` ([#​760](https://togithub.com/rust-itertools/itertools/issues/760)) - Improved documentation of `tree_fold1` ([#​787](https://togithub.com/rust-itertools/itertools/issues/787)) - Improved documentation of `permutations` ([#​724](https://togithub.com/rust-itertools/itertools/issues/724)) - Fixed typo in documentation of `multiunzip` ([#​770](https://togithub.com/rust-itertools/itertools/issues/770)) ##### Notable Internal Changes - Improved specialization tests ([#​799](https://togithub.com/rust-itertools/itertools/issues/799), [#​786](https://togithub.com/rust-itertools/itertools/issues/786), [#​782](https://togithub.com/rust-itertools/itertools/issues/782)) - Simplified implementation of `Permutations` ([#​739](https://togithub.com/rust-itertools/itertools/issues/739), [#​748](https://togithub.com/rust-itertools/itertools/issues/748), [#​790](https://togithub.com/rust-itertools/itertools/issues/790)) - Combined `Merge`/`MergeBy`/`MergeJoinBy` implementations ([#​736](https://togithub.com/rust-itertools/itertools/issues/736)) - Simplified `Permutations::size_hint` ([#​739](https://togithub.com/rust-itertools/itertools/issues/739)) - Fix wrapping arithmetic in benchmarks ([#​770](https://togithub.com/rust-itertools/itertools/issues/770)) - Enforced `rustfmt` in CI ([#​751](https://togithub.com/rust-itertools/itertools/issues/751)) - Disallowed compile warnings in CI ([#​720](https://togithub.com/rust-itertools/itertools/issues/720)) - Used `cargo hack` to check MSRV ([#​754](https://togithub.com/rust-itertools/itertools/issues/754))
--- ### Configuration πŸ“… **Schedule**: Branch creation - "every weekend" (UTC), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. β™» **Rebasing**: Whenever PR is behind base branch, 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, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/bitwarden/sdk). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- crates/sdk-schemas/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6a02ad594..7ca15b625 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1647,9 +1647,9 @@ checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" [[package]] name = "itertools" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" dependencies = [ "either", ] diff --git a/crates/sdk-schemas/Cargo.toml b/crates/sdk-schemas/Cargo.toml index c6de9eaca..64ce34274 100644 --- a/crates/sdk-schemas/Cargo.toml +++ b/crates/sdk-schemas/Cargo.toml @@ -15,7 +15,7 @@ internal = [ schemars = { version = "0.8.12", features = ["preserve_order"] } serde_json = "1.0.96" anyhow = "1.0.71" -itertools = "0.11.0" +itertools = "0.12.0" bitwarden = { path = "../bitwarden" } bitwarden-json = { path = "../bitwarden-json" } From 8788f184750fef3045d0fc38666ddf82627d34b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Mon, 20 Nov 2023 11:03:45 +0100 Subject: [PATCH 008/157] Split initializeCrypto (#329) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Split initializeCrypto into separate user and organization methods, so clients won't need to hold on to the master password until after sync is done to initialize the organization keys. --- crates/bitwarden-uniffi/README.md | 1 + crates/bitwarden-uniffi/src/crypto.rs | 35 +++++++ crates/bitwarden-uniffi/src/docs.rs | 5 +- crates/bitwarden-uniffi/src/lib.rs | 22 +---- .../src/client/encryption_settings.rs | 4 + crates/bitwarden/src/mobile/client_crypto.rs | 13 ++- crates/bitwarden/src/mobile/crypto.rs | 57 +++++++---- crates/bitwarden/tests/register.rs | 16 ++- .../bitwarden/myapplication/MainActivity.kt | 21 ++-- languages/kotlin/doc.md | 99 +++++++++++++++---- languages/swift/iOS/App/ContentView.swift | 14 ++- 11 files changed, 212 insertions(+), 75 deletions(-) create mode 100644 crates/bitwarden-uniffi/src/crypto.rs diff --git a/crates/bitwarden-uniffi/README.md b/crates/bitwarden-uniffi/README.md index 7dff0b291..1be7706bb 100644 --- a/crates/bitwarden-uniffi/README.md +++ b/crates/bitwarden-uniffi/README.md @@ -5,6 +5,7 @@ ```bash cargo +nightly rustdoc -p bitwarden -- -Zunstable-options --output-format json cargo +nightly rustdoc -p bitwarden-uniffi -- -Zunstable-options --output-format json +npm run schemas npx ts-node ./support/docs/docs.ts > languages/kotlin/doc.md ``` diff --git a/crates/bitwarden-uniffi/src/crypto.rs b/crates/bitwarden-uniffi/src/crypto.rs new file mode 100644 index 000000000..1792a5bbc --- /dev/null +++ b/crates/bitwarden-uniffi/src/crypto.rs @@ -0,0 +1,35 @@ +use std::sync::Arc; + +use bitwarden::mobile::crypto::{InitOrgCryptoRequest, InitUserCryptoRequest}; + +use crate::{error::Result, Client}; + +#[derive(uniffi::Object)] +pub struct ClientCrypto(pub(crate) Arc); + +#[uniffi::export] +impl ClientCrypto { + /// Initialization method for the user crypto. Needs to be called before any other crypto operations. + pub async fn initialize_user_crypto(&self, req: InitUserCryptoRequest) -> Result<()> { + Ok(self + .0 + .0 + .write() + .await + .crypto() + .initialize_user_crypto(req) + .await?) + } + + /// Initialization method for the organization crypto. Needs to be called after `initialize_user_crypto` but before any other crypto operations. + pub async fn initialize_org_crypto(&self, req: InitOrgCryptoRequest) -> Result<()> { + Ok(self + .0 + .0 + .write() + .await + .crypto() + .initialize_org_crypto(req) + .await?) + } +} diff --git a/crates/bitwarden-uniffi/src/docs.rs b/crates/bitwarden-uniffi/src/docs.rs index bc47a19c3..45fabb0b7 100644 --- a/crates/bitwarden-uniffi/src/docs.rs +++ b/crates/bitwarden-uniffi/src/docs.rs @@ -1,7 +1,7 @@ use bitwarden::{ auth::password::MasterPasswordPolicyOptions, client::kdf::Kdf, - mobile::crypto::InitCryptoRequest, + mobile::crypto::{InitOrgCryptoRequest, InitUserCryptoRequest}, tool::{ExportFormat, PassphraseGeneratorRequest, PasswordGeneratorRequest}, vault::{ Cipher, CipherView, Collection, Folder, FolderView, Send, SendListView, SendView, @@ -24,7 +24,8 @@ pub enum DocRef { SendListView(SendListView), // Crypto - InitCryptoRequest(InitCryptoRequest), + InitUserCryptoRequest(InitUserCryptoRequest), + InitOrgCryptoRequest(InitOrgCryptoRequest), // Generators PasswordGeneratorRequest(PasswordGeneratorRequest), diff --git a/crates/bitwarden-uniffi/src/lib.rs b/crates/bitwarden-uniffi/src/lib.rs index 6e5415d38..5035f22b9 100644 --- a/crates/bitwarden-uniffi/src/lib.rs +++ b/crates/bitwarden-uniffi/src/lib.rs @@ -4,9 +4,10 @@ use std::sync::Arc; use async_lock::RwLock; use auth::ClientAuth; -use bitwarden::{client::client_settings::ClientSettings, mobile::crypto::InitCryptoRequest}; +use bitwarden::client::client_settings::ClientSettings; pub mod auth; +pub mod crypto; mod error; pub mod tool; mod uniffi_support; @@ -15,6 +16,7 @@ pub mod vault; #[cfg(feature = "docs")] pub mod docs; +use crypto::ClientCrypto; use error::Result; use tool::ClientGenerators; use vault::ClientVault; @@ -22,9 +24,6 @@ use vault::ClientVault; #[derive(uniffi::Object)] pub struct Client(RwLock); -#[derive(uniffi::Object)] -pub struct ClientCrypto(Arc); - #[uniffi::export] impl Client { /// Initialize a new instance of the SDK client @@ -58,18 +57,3 @@ impl Client { msg } } - -#[uniffi::export] -impl ClientCrypto { - /// Initialization method for the crypto. Needs to be called before any other crypto operations. - pub async fn initialize_crypto(&self, req: InitCryptoRequest) -> Result<()> { - Ok(self - .0 - .0 - .write() - .await - .crypto() - .initialize_crypto(req) - .await?) - } -} diff --git a/crates/bitwarden/src/client/encryption_settings.rs b/crates/bitwarden/src/client/encryption_settings.rs index da5398a12..6533c7d2e 100644 --- a/crates/bitwarden/src/client/encryption_settings.rs +++ b/crates/bitwarden/src/client/encryption_settings.rs @@ -80,6 +80,10 @@ impl EncryptionSettings { let private_key = self.private_key.as_ref().ok_or(Error::VaultLocked)?; + // Make sure we only keep the keys given in the arguments and not any of the previous + // ones, which might be from organizations that the user is no longer a part of anymore + self.org_keys.clear(); + // Decrypt the org keys with the private key for (org_id, org_enc_key) in org_enc_keys { let data = match org_enc_key { diff --git a/crates/bitwarden/src/mobile/client_crypto.rs b/crates/bitwarden/src/mobile/client_crypto.rs index 67f69d8f1..c0edb1982 100644 --- a/crates/bitwarden/src/mobile/client_crypto.rs +++ b/crates/bitwarden/src/mobile/client_crypto.rs @@ -2,7 +2,9 @@ use crate::Client; #[cfg(feature = "internal")] use crate::{ error::Result, - mobile::crypto::{initialize_crypto, InitCryptoRequest}, + mobile::crypto::{ + initialize_org_crypto, initialize_user_crypto, InitOrgCryptoRequest, InitUserCryptoRequest, + }, }; pub struct ClientCrypto<'a> { @@ -11,8 +13,13 @@ pub struct ClientCrypto<'a> { impl<'a> ClientCrypto<'a> { #[cfg(feature = "internal")] - pub async fn initialize_crypto(&mut self, req: InitCryptoRequest) -> Result<()> { - initialize_crypto(self.client, req).await + pub async fn initialize_user_crypto(&mut self, req: InitUserCryptoRequest) -> Result<()> { + initialize_user_crypto(self.client, req).await + } + + #[cfg(feature = "internal")] + pub async fn initialize_org_crypto(&mut self, req: InitOrgCryptoRequest) -> Result<()> { + initialize_org_crypto(self.client, req).await } } diff --git a/crates/bitwarden/src/mobile/crypto.rs b/crates/bitwarden/src/mobile/crypto.rs index f4d26dc6c..2cf850279 100644 --- a/crates/bitwarden/src/mobile/crypto.rs +++ b/crates/bitwarden/src/mobile/crypto.rs @@ -9,23 +9,32 @@ use crate::{client::kdf::Kdf, crypto::EncString, error::Result, Client}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] -pub struct InitCryptoRequest { +pub struct InitUserCryptoRequest { /// The user's KDF parameters, as received from the prelogin request pub kdf_params: Kdf, /// The user's email address pub email: String, - /// The user's master password - pub password: String, - /// The user's encrypted symmetric crypto key - pub user_key: String, - /// The user's encryptred private key + /// The user's encrypted private key pub private_key: String, - /// The encryption keys for all the organizations the user is a part of - pub organization_keys: HashMap, + /// The initialization method to use + pub method: InitUserCryptoMethod, } #[cfg(feature = "internal")] -pub async fn initialize_crypto(client: &mut Client, req: InitCryptoRequest) -> Result<()> { +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "mobile", derive(uniffi::Enum))] +pub enum InitUserCryptoMethod { + Password { + /// The user's master password + password: String, + /// The user's encrypted symmetric crypto key + user_key: String, + }, +} + +#[cfg(feature = "internal")] +pub async fn initialize_user_crypto(client: &mut Client, req: InitUserCryptoRequest) -> Result<()> { let login_method = crate::client::LoginMethod::User(crate::client::UserLoginMethod::Username { client_id: "".to_string(), email: req.email, @@ -33,18 +42,30 @@ pub async fn initialize_crypto(client: &mut Client, req: InitCryptoRequest) -> R }); client.set_login_method(login_method); - let user_key = req.user_key.parse::()?; - let private_key = req.private_key.parse::()?; + let private_key: EncString = req.private_key.parse()?; - client.initialize_user_crypto(&req.password, user_key, private_key)?; + match req.method { + InitUserCryptoMethod::Password { password, user_key } => { + let user_key: EncString = user_key.parse()?; + client.initialize_user_crypto(&password, user_key, private_key)?; + } + } - let organization_keys = req - .organization_keys - .into_iter() - .map(|(k, v)| Ok((k, v.parse::()?))) - .collect::>>()?; + Ok(()) +} - client.initialize_org_crypto(organization_keys)?; +#[cfg(feature = "internal")] +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "mobile", derive(uniffi::Record))] +pub struct InitOrgCryptoRequest { + /// The encryption keys for all the organizations the user is a part of + pub organization_keys: HashMap, +} +#[cfg(feature = "internal")] +pub async fn initialize_org_crypto(client: &mut Client, req: InitOrgCryptoRequest) -> Result<()> { + let organization_keys = req.organization_keys.into_iter().collect(); + client.initialize_org_crypto(organization_keys)?; Ok(()) } diff --git a/crates/bitwarden/tests/register.rs b/crates/bitwarden/tests/register.rs index d4e632e55..956fe86ce 100644 --- a/crates/bitwarden/tests/register.rs +++ b/crates/bitwarden/tests/register.rs @@ -4,7 +4,11 @@ async fn test_register_initialize_crypto() { use std::num::NonZeroU32; - use bitwarden::{client::kdf::Kdf, mobile::crypto::InitCryptoRequest, Client}; + use bitwarden::{ + client::kdf::Kdf, + mobile::crypto::{InitUserCryptoMethod, InitUserCryptoRequest}, + Client, + }; let mut client = Client::new(None); @@ -22,13 +26,15 @@ async fn test_register_initialize_crypto() { // Ensure we can initialize the crypto with the new keys client .crypto() - .initialize_crypto(InitCryptoRequest { + .initialize_user_crypto(InitUserCryptoRequest { kdf_params: kdf, email: email.to_owned(), - password: password.to_owned(), - user_key: register_response.encrypted_user_key, private_key: register_response.keys.private.to_string(), - organization_keys: Default::default(), + + method: InitUserCryptoMethod::Password { + password: password.to_owned(), + user_key: register_response.encrypted_user_key, + }, }) .await .unwrap(); diff --git a/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt b/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt index 2d204e075..13dec7d0e 100644 --- a/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt +++ b/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt @@ -12,7 +12,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import com.bitwarden.core.DateTime import com.bitwarden.core.Folder -import com.bitwarden.core.InitCryptoRequest +import com.bitwarden.core.InitOrgCryptoRequest +import com.bitwarden.core.InitUserCryptoMethod +import com.bitwarden.core.InitUserCryptoRequest import com.bitwarden.core.Kdf import com.bitwarden.core.Uuid import com.bitwarden.myapplication.ui.theme.MyApplicationTheme @@ -117,7 +119,7 @@ class MainActivity : ComponentActivity() { }.body() val folders = (syncBody["folders"] as JsonArray).map { - val o = it as JsonObject; + val o = it as JsonObject Folder( (o["id"] as JsonPrimitive).content, (o["name"] as JsonPrimitive).content, @@ -136,13 +138,20 @@ class MainActivity : ComponentActivity() { orgKeys[(o["id"] as JsonPrimitive).content] = (o["key"] as JsonPrimitive).content } - client.crypto().initializeCrypto( - InitCryptoRequest( + client.crypto().initializeUserCrypto( + InitUserCryptoRequest( kdfParams = kdf, email = EMAIL, - password = PASSWORD, - userKey = loginBody.Key, privateKey = loginBody.PrivateKey, + method = InitUserCryptoMethod.Password( + password = PASSWORD, + userKey = loginBody.Key + ) + ) + ) + + client.crypto().initializeOrgCrypto( + InitOrgCryptoRequest( organizationKeys = orgKeys ) ) diff --git a/languages/kotlin/doc.md b/languages/kotlin/doc.md index 6174df750..70446bd8f 100644 --- a/languages/kotlin/doc.md +++ b/languages/kotlin/doc.md @@ -182,14 +182,26 @@ Decrypt collection list ## ClientCrypto -### `initialize_crypto` +### `initialize_user_crypto` -Initialization method for the crypto. Needs to be called before any other crypto operations. +Initialization method for the user crypto. Needs to be called before any other crypto operations. **Arguments**: - self: -- req: [InitCryptoRequest](#initcryptorequest) +- req: [InitUserCryptoRequest](#initusercryptorequest) + +**Output**: std::result::Result<,BitwardenError> + +### `initialize_org_crypto` + +Initialization method for the organization crypto. Needs to be called after +`initialize_user_crypto` but before any other crypto operations. + +**Arguments**: + +- self: +- req: [InitOrgCryptoRequest](#initorgcryptorequest) **Output**: std::result::Result<,BitwardenError> @@ -492,6 +504,11 @@ implementations. array + + key + + More recent ciphers uses individual encryption keys to encrypt the other fields of the Cipher. + name @@ -617,6 +634,11 @@ implementations. array + + key + + + name string @@ -835,7 +857,7 @@ implementations. -## `InitCryptoRequest` +## `InitOrgCryptoRequest` @@ -844,34 +866,75 @@ implementations. - - - + + + +
Description
kdfParamsThe user's KDF parameters, as received from the prelogin requestorganizationKeysobjectThe encryption keys for all the organizations the user is a part of
+ +## `InitUserCryptoMethod` + + - - - + + + - - + + + + + + +
emailstringThe user's email addressKeyTypeDescription
passwordstringThe user's master passwordobject
+ + + + + + + + + + + + + + + + +
KeyTypeDescription
passwordstringThe user's master password
user_keystringThe user's encrypted symmetric crypto key
+
+ +## `InitUserCryptoRequest` + + + + + + + + + + + - + - + - + - - - + + +
KeyTypeDescription
kdfParamsThe user's KDF parameters, as received from the prelogin request
userKeyemail stringThe user's encrypted symmetric crypto keyThe user's email address
privateKey stringThe user's encryptred private keyThe user's encrypted private key
organizationKeysobjectThe encryption keys for all the organizations the user is a part ofmethodThe initialization method to use
diff --git a/languages/swift/iOS/App/ContentView.swift b/languages/swift/iOS/App/ContentView.swift index 4b3555403..484caf14f 100644 --- a/languages/swift/iOS/App/ContentView.swift +++ b/languages/swift/iOS/App/ContentView.swift @@ -136,13 +136,19 @@ struct ContentView: View { ///////////////////////////// Initialize crypto ///////////////////////////// - try await client.crypto().initializeCrypto( - req: InitCryptoRequest( + try await client.crypto().initializeUserCrypto( + req: InitUserCryptoRequest( kdfParams: kdf, email: EMAIL, - password: PASSWORD, - userKey: loginData.Key, privateKey: loginData.PrivateKey, + method: InitUserCryptoMethod.password( + password: PASSWORD, + userKey: loginData.Key + ) + )) + + try await client.crypto().initializeOrgCrypto( + req: InitOrgCryptoRequest( organizationKeys: Dictionary.init( uniqueKeysWithValues: syncData.profile.organizations.map { ($0.id, $0.key) } ) From 4c68256820e65572313d209c5c09e07f0f2d29e1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Nov 2023 11:38:19 +0100 Subject: [PATCH 009/157] Update pyo3 non-major (#289) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate logo banner](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [pyo3](https://togithub.com/pyo3/pyo3) | dependencies | minor | `0.19.1` -> `0.20.0` | | [pyo3-asyncio](https://togithub.com/awestlake87/pyo3-asyncio) | dependencies | minor | `0.19.0` -> `0.20.0` | | [pyo3-build-config](https://togithub.com/pyo3/pyo3) | build-dependencies | minor | `0.19.1` -> `0.20.0` | | [pyo3-log](https://togithub.com/vorner/pyo3-log) | dependencies | minor | `0.8.3` -> `0.9.0` | --- ### Release Notes
pyo3/pyo3 (pyo3) ### [`v0.20.0`](https://togithub.com/pyo3/pyo3/blob/HEAD/CHANGELOG.md#0200---2023-10-11) [Compare Source](https://togithub.com/pyo3/pyo3/compare/v0.19.2...v0.20.0) ##### Packaging - Dual-license PyO3 under either the Apache 2.0 OR the MIT license. This makes the project GPLv2 compatible. [#​3108](https://togithub.com/PyO3/pyo3/pull/3108) - Update MSRV to Rust 1.56. [#​3208](https://togithub.com/PyO3/pyo3/pull/3208) - Bump `indoc` dependency to 2.0 and `unindent` dependency to 0.2. [#​3237](https://togithub.com/PyO3/pyo3/pull/3237) - Bump `syn` dependency to 2.0. [#​3239](https://togithub.com/PyO3/pyo3/pull/3239) - Drop support for debug builds of Python 3.7. [#​3387](https://togithub.com/PyO3/pyo3/pull/3387) - Bump `chrono` optional dependency to require 0.4.25 or newer. [#​3427](https://togithub.com/PyO3/pyo3/pull/3427) - Support Python 3.12. [#​3488](https://togithub.com/PyO3/pyo3/pull/3488) ##### Added - Support `__lt__`, `__le__`, `__eq__`, `__ne__`, `__gt__` and `__ge__` in `#[pymethods]`. [#​3203](https://togithub.com/PyO3/pyo3/pull/3203) - Add FFI definition `Py_GETENV`. [#​3336](https://togithub.com/PyO3/pyo3/pull/3336) - Add `as_ptr` and `into_ptr` inherent methods for `Py`, `PyAny`, `PyRef`, and `PyRefMut`. [#​3359](https://togithub.com/PyO3/pyo3/pull/3359) - Implement `DoubleEndedIterator` for `PyTupleIterator` and `PyListIterator`. [#​3366](https://togithub.com/PyO3/pyo3/pull/3366) - Add `#[pyclass(rename_all = "...")]` option: this allows renaming all getters and setters of a struct, or all variants of an enum. Available renaming rules are: `"camelCase"`, `"kebab-case"`, `"lowercase"`, `"PascalCase"`, `"SCREAMING-KEBAB-CASE"`, `"SCREAMING_SNAKE_CASE"`, `"snake_case"`, `"UPPERCASE"`. [#​3384](https://togithub.com/PyO3/pyo3/pull/3384) - Add FFI definitions `PyObject_GC_IsTracked` and `PyObject_GC_IsFinalized` on Python 3.9 and up (PyPy 3.10 and up). [#​3403](https://togithub.com/PyO3/pyo3/pull/3403) - Add types for `None`, `Ellipsis`, and `NotImplemented`. [#​3408](https://togithub.com/PyO3/pyo3/pull/3408) - Add FFI definitions for the `Py_mod_multiple_interpreters` constant and its possible values. [#​3494](https://togithub.com/PyO3/pyo3/pull/3494) - Add FFI definitions for `PyInterpreterConfig` struct, its constants and `Py_NewInterpreterFromConfig`. [#​3502](https://togithub.com/PyO3/pyo3/pull/3502) ##### Changed - Change `PySet::discard` to return `PyResult` (previously returned nothing). [#​3281](https://togithub.com/PyO3/pyo3/pull/3281) - Optimize implmentation of `IntoPy` for Rust tuples to Python tuples. [#​3321](https://togithub.com/PyO3/pyo3/pull/3321) - Change `PyDict::get_item` to no longer suppress arbitrary exceptions (the return type is now `PyResult>` instead of `Option<&PyAny>`), and deprecate `PyDict::get_item_with_error`. [#​3330](https://togithub.com/PyO3/pyo3/pull/3330) - Deprecate FFI definitions which are deprecated in Python 3.12. [#​3336](https://togithub.com/PyO3/pyo3/pull/3336) - `AsPyPointer` is now an `unsafe trait`. [#​3358](https://togithub.com/PyO3/pyo3/pull/3358) - Accept all `os.PathLike` values in implementation of `FromPyObject` for `PathBuf`. [#​3374](https://togithub.com/PyO3/pyo3/pull/3374) - Add `__builtins__` to globals in `py.run()` and `py.eval()` if they're missing. [#​3378](https://togithub.com/PyO3/pyo3/pull/3378) - Optimize implementation of `FromPyObject` for `BigInt` and `BigUint`. [#​3379](https://togithub.com/PyO3/pyo3/pull/3379) - `PyIterator::from_object` and `PyByteArray::from` now take a single argument of type `&PyAny` (previously took two arguments `Python` and `AsPyPointer`). [#​3389](https://togithub.com/PyO3/pyo3/pull/3389) - Replace `AsPyPointer` with `AsRef` as a bound in the blanket implementation of `From<&T> for PyObject`. [#​3391](https://togithub.com/PyO3/pyo3/pull/3391) - Replace blanket `impl IntoPy for &T where T: AsPyPointer` with implementations of `impl IntoPy` for `&PyAny`, `&T where T: AsRef`, and `&Py`. [#​3393](https://togithub.com/PyO3/pyo3/pull/3393) - Preserve `std::io::Error` kind in implementation of `From` for `PyErr` [#​3396](https://togithub.com/PyO3/pyo3/pull/3396) - Try to select a relevant `ErrorKind` in implementation of `From` for `OSError` subclass. [#​3397](https://togithub.com/PyO3/pyo3/pull/3397) - Retrieve the original `PyErr` in implementation of `From` for `PyErr` if the `std::io::Error` has been built using a Python exception (previously would create a new exception wrapping the `std::io::Error`). [#​3402](https://togithub.com/PyO3/pyo3/pull/3402) - `#[pymodule]` will now return the same module object on repeated import by the same Python interpreter, on Python 3.9 and up. [#​3446](https://togithub.com/PyO3/pyo3/pull/3446) - Truncate leap-seconds and warn when converting `chrono` types to Python `datetime` types (`datetime` cannot represent leap-seconds). [#​3458](https://togithub.com/PyO3/pyo3/pull/3458) - `Err` returned from `#[pyfunction]` will now have a non-None `__context__` if called from inside a `catch` block. [#​3455](https://togithub.com/PyO3/pyo3/pull/3455) - Deprecate undocumented `#[__new__]` form of `#[new]` attribute. [#​3505](https://togithub.com/PyO3/pyo3/pull/3505) ##### Removed - Remove all functionality deprecated in PyO3 0.18, including `#[args]` attribute for `#[pymethods]`. [#​3232](https://togithub.com/PyO3/pyo3/pull/3232) - Remove `IntoPyPointer` trait in favour of `into_ptr` inherent methods. [#​3385](https://togithub.com/PyO3/pyo3/pull/3385) ##### Fixed - Handle exceptions properly in `PySet::discard`. [#​3281](https://togithub.com/PyO3/pyo3/pull/3281) - The `PyTupleIterator` type returned by `PyTuple::iter` is now public and hence can be named by downstream crates. [#​3366](https://togithub.com/PyO3/pyo3/pull/3366) - Linking of `PyOS_FSPath` on PyPy. [#​3374](https://togithub.com/PyO3/pyo3/pull/3374) - Fix memory leak in `PyTypeBuilder::build`. [#​3401](https://togithub.com/PyO3/pyo3/pull/3401) - Disable removed FFI definitions `_Py_GetAllocatedBlocks`, `_PyObject_GC_Malloc`, and `_PyObject_GC_Calloc` on Python 3.11 and up. [#​3403](https://togithub.com/PyO3/pyo3/pull/3403) - Fix `ResourceWarning` and crashes related to GC when running with debug builds of CPython. [#​3404](https://togithub.com/PyO3/pyo3/pull/3404) - Some-wrapping of `Option` default arguments will no longer re-wrap `Some(T)` or expressions evaluating to `None`. [#​3461](https://togithub.com/PyO3/pyo3/pull/3461) - Fix `IterNextOutput::Return` not returning a value on PyPy. [#​3471](https://togithub.com/PyO3/pyo3/pull/3471) - Emit compile errors instead of ignoring macro invocations inside `#[pymethods]` blocks. [#​3491](https://togithub.com/PyO3/pyo3/pull/3491) - Emit error on invalid arguments to `#[new]`, `#[classmethod]`, `#[staticmethod]`, and `#[classattr]`. [#​3484](https://togithub.com/PyO3/pyo3/pull/3484) - Disable `PyMarshal_WriteObjectToString` from `PyMarshal_ReadObjectFromString` with the `abi3` feature. [#​3490](https://togithub.com/PyO3/pyo3/pull/3490) - Fix FFI definitions for `_PyFrameEvalFunction` on Python 3.11 and up (it now receives a `_PyInterpreterFrame` opaque struct). [#​3500](https://togithub.com/PyO3/pyo3/pull/3500) ### [`v0.19.2`](https://togithub.com/pyo3/pyo3/blob/HEAD/CHANGELOG.md#0192---2023-08-01) [Compare Source](https://togithub.com/pyo3/pyo3/compare/v0.19.1...v0.19.2) ##### Added - Add FFI definitions `PyState_AddModule`, `PyState_RemoveModule` and `PyState_FindModule` for PyPy 3.9 and up. [#​3295](https://togithub.com/PyO3/pyo3/pull/3295) - Add FFI definitions `_PyObject_CallFunction_SizeT` and `_PyObject_CallMethod_SizeT`. [#​3297](https://togithub.com/PyO3/pyo3/pull/3297) - Add a "performance" section to the guide collecting performance-related tricks and problems. [#​3304](https://togithub.com/PyO3/pyo3/pull/3304) - Add `PyErr::Display` for all Python versions, and FFI symbol `PyErr_DisplayException` for Python 3.12. [#​3334](https://togithub.com/PyO3/pyo3/pull/3334) - Add FFI definition `PyType_GetDict()` for Python 3.12. [#​3339](https://togithub.com/PyO3/pyo3/pull/3339) - Add `PyAny::downcast_exact`. [#​3346](https://togithub.com/PyO3/pyo3/pull/3346) - Add `PySlice::full()` to construct a full slice (`::`). [#​3353](https://togithub.com/PyO3/pyo3/pull/3353) ##### Changed - Update `PyErr` for 3.12 betas to avoid deprecated ffi methods. [#​3306](https://togithub.com/PyO3/pyo3/pull/3306) - Update FFI definitions of `object.h` for Python 3.12.0b4. [#​3335](https://togithub.com/PyO3/pyo3/pull/3335) - Update `pyo3::ffi` struct definitions to be compatible with 3.12.0b4. [#​3342](https://togithub.com/PyO3/pyo3/pull/3342) - Optimize conversion of `float` to `f64` (and `PyFloat::value`) on non-abi3 builds. [#​3345](https://togithub.com/PyO3/pyo3/pull/3345) ##### Fixed - Fix timezone conversion bug for FixedOffset datetimes that were being incorrectly converted to and from UTC. [#​3269](https://togithub.com/PyO3/pyo3/pull/3269) - Fix `SystemError` raised in `PyUnicodeDecodeError_Create` on PyPy 3.10. [#​3297](https://togithub.com/PyO3/pyo3/pull/3297) - Correct FFI definition `Py_EnterRecursiveCall` to return `c_int` (was incorrectly returning `()`). [#​3300](https://togithub.com/PyO3/pyo3/pull/3300) - Fix case where `PyErr::matches` and `PyErr::is_instance` returned results inconsistent with `PyErr::get_type`. [#​3313](https://togithub.com/PyO3/pyo3/pull/3313) - Fix loss of panic message in `PanicException` when unwinding after the exception was "normalized". [#​3326](https://togithub.com/PyO3/pyo3/pull/3326) - Fix `PyErr::from_value` and `PyErr::into_value` losing traceback on conversion. [#​3328](https://togithub.com/PyO3/pyo3/pull/3328) - Fix reference counting of immortal objects on Python 3.12.0b4. [#​3335](https://togithub.com/PyO3/pyo3/pull/3335)
vorner/pyo3-log (pyo3-log) ### [`v0.9.0`](https://togithub.com/vorner/pyo3-log/blob/HEAD/CHANGELOG.md#090) [Compare Source](https://togithub.com/vorner/pyo3-log/compare/v0.8.4...v0.9.0) - Bump lowest allowed version of pyo3 to 0.15 β€’ this prevents linking multiple pyo3 versions together. ### [`v0.8.4`](https://togithub.com/vorner/pyo3-log/blob/HEAD/CHANGELOG.md#084) [Compare Source](https://togithub.com/vorner/pyo3-log/compare/v0.8.3...v0.8.4) - Allow pyo3 0.20.
--- ### Configuration πŸ“… **Schedule**: Branch creation - "every weekend" (UTC), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. β™» **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. πŸ‘» **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://togithub.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/bitwarden/sdk). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 45 +++++++++++++++++----------------- crates/bitwarden-py/Cargo.toml | 8 +++--- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7ca15b625..e4ad7e61e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1577,9 +1577,9 @@ dependencies = [ [[package]] name = "indoc" -version = "1.0.9" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" +checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" [[package]] name = "infer" @@ -2274,9 +2274,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.19.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e681a6cfdc4adcc93b4d3cf993749a4552018ee0a9b65fc0ccfad74352c72a38" +checksum = "04e8453b658fe480c3e70c8ed4e3d3ec33eb74988bd186561b0cc66b85c3bc4b" dependencies = [ "cfg-if", "indoc", @@ -2291,9 +2291,9 @@ dependencies = [ [[package]] name = "pyo3-asyncio" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2cc34c1f907ca090d7add03dc523acdd91f3a4dab12286604951e2f5152edad" +checksum = "6ea6b68e93db3622f3bb3bf363246cf948ed5375afe7abff98ccbdd50b184995" dependencies = [ "futures", "once_cell", @@ -2305,9 +2305,9 @@ dependencies = [ [[package]] name = "pyo3-asyncio-macros" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4045f06429547179e4596f5c0b13c82efc8b04296016780133653ed69ce26b3" +checksum = "56c467178e1da6252c95c29ecf898b133f742e9181dca5def15dc24e19d45a39" dependencies = [ "proc-macro2", "quote", @@ -2316,9 +2316,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.19.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076c73d0bc438f7a4ef6fdd0c3bb4732149136abd952b110ac93e4edb13a6ba5" +checksum = "a96fe70b176a89cff78f2fa7b3c930081e163d5379b4dcdf993e3ae29ca662e5" dependencies = [ "once_cell", "target-lexicon", @@ -2326,9 +2326,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.19.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53cee42e77ebe256066ba8aa77eff722b3bb91f3419177cf4cd0f304d3284d9" +checksum = "214929900fd25e6604661ed9cf349727c8920d47deff196c4e28165a6ef2a96b" dependencies = [ "libc", "pyo3-build-config", @@ -2336,9 +2336,9 @@ dependencies = [ [[package]] name = "pyo3-log" -version = "0.8.4" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c09c2b349b6538d8a73d436ca606dab6ce0aaab4dad9e6b7bdd57a4f556c3bc3" +checksum = "4c10808ee7250403bedb24bc30c32493e93875fef7ba3e4292226fe924f398bd" dependencies = [ "arc-swap", "log", @@ -2347,25 +2347,26 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.19.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfeb4c99597e136528c6dd7d5e3de5434d1ceaf487436a3f03b2d56b6fc9efd1" +checksum = "dac53072f717aa1bfa4db832b39de8c875b7c7af4f4a6fe93cdbf9264cf8383b" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 1.0.109", + "syn 2.0.38", ] [[package]] name = "pyo3-macros-backend" -version = "0.19.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "947dc12175c254889edc0c02e399476c2f652b4b9ebd123aa655c224de259536" +checksum = "7774b5a8282bd4f25f803b1f0d945120be959a36c72e08e7cd031c792fdfd424" dependencies = [ + "heck", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.38", ] [[package]] @@ -3581,9 +3582,9 @@ dependencies = [ [[package]] name = "unindent" -version = "0.1.11" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" [[package]] name = "unsafe-libyaml" diff --git a/crates/bitwarden-py/Cargo.toml b/crates/bitwarden-py/Cargo.toml index 5d8d95575..613203c84 100644 --- a/crates/bitwarden-py/Cargo.toml +++ b/crates/bitwarden-py/Cargo.toml @@ -10,17 +10,17 @@ name = "bitwarden_py" crate-type = ["cdylib"] [dependencies] -pyo3 = { version = "0.19.1", features = ["extension-module"] } -pyo3-log = "0.8.3" +pyo3 = { version = "0.20.0", features = ["extension-module"] } +pyo3-log = "0.9.0" bitwarden-json = { path = "../bitwarden-json", features = ["secrets"] } [build-dependencies] -pyo3-build-config = { version = "0.19.1" } +pyo3-build-config = { version = "0.20.0" } [target.'cfg(not(target_arch="wasm32"))'.dependencies] tokio = { version = "1.28.2", features = ["rt-multi-thread", "macros"] } -pyo3-asyncio = { version = "0.19.0", features = [ +pyo3-asyncio = { version = "0.20.0", features = [ "attributes", "tokio-runtime", ] } From d64782e7bf4937eb50500556057a70b686ac57ff Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Nov 2023 15:32:10 +0100 Subject: [PATCH 010/157] Lock file maintenance (#324) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate logo banner](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Update | Change | |---|---| | lockFileMaintenance | All locks refreshed | πŸ”§ This Pull Request updates lock files to use the latest dependency versions. --- ### Configuration πŸ“… **Schedule**: Branch creation - "before 4am on Monday" (UTC), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. β™» **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. πŸ‘» **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://togithub.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/bitwarden/sdk). --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Daniel García --- Cargo.lock | 283 +++++++++++---------- crates/bitwarden-napi/package-lock.json | 6 +- crates/bitwarden-napi/src/client.rs | 1 - crates/bitwarden-wasm/Cargo.toml | 2 +- languages/js_webassembly/package-lock.json | 168 ++++++------ package-lock.json | 30 +-- 6 files changed, 247 insertions(+), 243 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e4ad7e61e..f9fbba7e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -156,7 +156,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -203,11 +203,11 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.0.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e900cdcd39bb94a14487d3f7ef92ca222162e6c7c3fe7cb3550ea75fb486ed" +checksum = "655b9c7fe787d3b25cc0f804a1a8401790f0c5bc395beb5a64dc77d8de079105" dependencies = [ - "event-listener 3.0.1", + "event-listener 3.1.0", "event-listener-strategy", "pin-project-lite", ] @@ -220,7 +220,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -332,7 +332,7 @@ dependencies = [ "bitwarden-api-identity", "cbc", "chrono", - "getrandom 0.2.10", + "getrandom 0.2.11", "hkdf", "hmac", "lazy_static", @@ -495,9 +495,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c79ad7fb2dd38f3dabd76b09c6a5a20c038fc0213ef1e9afd30eb777f120f019" +checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c" dependencies = [ "memchr", "serde", @@ -547,7 +547,7 @@ dependencies = [ "tempfile", "thiserror", "tokio", - "toml 0.8.6", + "toml 0.8.8", "uuid", ] @@ -586,9 +586,9 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12024c4645c97566567129c204f65d5815a8c9aecf30fcbe682b2fe034996d36" +checksum = "e34637b3140142bdf929fb439e8aa4ebad7651ebf7b1080b3930aa16ac1459ff" dependencies = [ "serde", ] @@ -656,9 +656,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.7" +version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b" +checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" dependencies = [ "clap_builder", "clap_derive", @@ -666,9 +666,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.7" +version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663" +checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" dependencies = [ "anstream", "anstyle", @@ -694,7 +694,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -732,9 +732,9 @@ dependencies = [ [[package]] name = "color-spantrace" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce" +checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" dependencies = [ "once_cell", "owo-colors", @@ -925,7 +925,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37e366bff8cd32dd8754b0991fb66b279dc48f598c3a18914852a6673deef583" dependencies = [ "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -1002,9 +1002,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d2f3407d9a573d666de4b5bdf10569d73ca9478087346697dcbae6244bfbcd" +checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" [[package]] name = "either" @@ -1029,9 +1029,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" dependencies = [ "humantime", "is-terminal", @@ -1048,9 +1048,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" dependencies = [ "libc", "windows-sys 0.48.0", @@ -1064,9 +1064,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" -version = "3.0.1" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cec0252c2afff729ee6f00e903d479fba81784c8e2bd77447673471fdfaea1" +checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" dependencies = [ "concurrent-queue", "parking", @@ -1079,15 +1079,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d96b852f1345da36d551b9473fa1e2b1eb5c5195585c6c018118bc92a8d91160" dependencies = [ - "event-listener 3.0.1", + "event-listener 3.1.0", "pin-project-lite", ] [[package]] name = "eyre" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +checksum = "80f656be11ddf91bd709454d15d5bd896fbaf4cc3314e69349e4d1569f5b46cd" dependencies = [ "indenter", "once_cell", @@ -1150,9 +1150,12 @@ dependencies = [ [[package]] name = "fs-err" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0845fa252299212f0389d64ba26f34fa32cfe41588355f21ed507c59a0f64541" +checksum = "fb5fd9bcbe8b1087cbd395b51498c01bc997cef73e778a80b77a811af5e2d29f" +dependencies = [ + "autocfg", +] [[package]] name = "futures" @@ -1225,7 +1228,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -1300,9 +1303,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "js-sys", @@ -1349,9 +1352,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.21" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" dependencies = [ "bytes", "fnv", @@ -1359,7 +1362,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 1.9.3", + "indexmap 2.1.0", "slab", "tokio", "tokio-util", @@ -1419,9 +1422,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" dependencies = [ "bytes", "fnv", @@ -1567,9 +1570,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", "hashbrown 0.14.2", @@ -1680,18 +1683,18 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.149" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libloading" -version = "0.7.4" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" dependencies = [ "cfg-if", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -1700,6 +1703,17 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.1", + "libc", + "redox_syscall", +] + [[package]] name = "line-wrap" version = "0.1.1" @@ -1711,9 +1725,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" [[package]] name = "lock_api" @@ -1814,9 +1828,9 @@ dependencies = [ [[package]] name = "napi" -version = "2.13.3" +version = "2.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd063c93b900149304e3ba96ce5bf210cd4f81ef5eb80ded0d100df3e85a3ac0" +checksum = "1133249c46e92da921bafc8aba4912bf84d6c475f7625183772ed2d0844dc3a7" dependencies = [ "bitflags 2.4.1", "ctor", @@ -1828,15 +1842,15 @@ dependencies = [ [[package]] name = "napi-build" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882a73d9ef23e8dc2ebbffb6a6ae2ef467c0f18ac10711e4cc59c5485d41df0e" +checksum = "d4b4532cf86bfef556348ac65e561e3123879f0e7566cca6d43a6ff5326f13df" [[package]] name = "napi-derive" -version = "2.13.0" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da1c6a8fa84d549aa8708fcd062372bf8ec6e849de39016ab921067d21bde367" +checksum = "a0cca5738c6e81eb5ffd2c8ff2b4f05ece9c4c60c7e2b36cec6524492cf7f330" dependencies = [ "cfg-if", "convert_case", @@ -1848,9 +1862,9 @@ dependencies = [ [[package]] name = "napi-derive-backend" -version = "1.0.52" +version = "1.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20bbc7c69168d06a848f925ec5f0e0997f98e8c8d4f2cc30157f0da51c009e17" +checksum = "35960e5f33228192a9b661447d0dfe8f5a3790ff5b4058c4d67680ded4f65b91" dependencies = [ "convert_case", "once_cell", @@ -1863,9 +1877,9 @@ dependencies = [ [[package]] name = "napi-sys" -version = "2.2.3" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "166b5ef52a3ab5575047a9fe8d4a030cdd0f63c96f071cd6907674453b07bae3" +checksum = "2503fa6af34dc83fb74888df8b22afe933b58d37daf7d80424b1c60c68196b8b" dependencies = [ "libloading", ] @@ -2043,9 +2057,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.57" +version = "0.10.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" +checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33" dependencies = [ "bitflags 2.4.1", "cfg-if", @@ -2064,7 +2078,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -2084,9 +2098,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.93" +version = "0.9.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" +checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9" dependencies = [ "cc", "libc", @@ -2137,7 +2151,7 @@ checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.4.1", + "redox_syscall", "smallvec", "windows-targets 0.48.5", ] @@ -2239,12 +2253,12 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "plist" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a4a0cfc5fb21a09dc6af4bf834cf10d4a32fccd9e2ea468c4b1751a097487aa" +checksum = "e5699cc8a63d1aa2b1ee8e12b9ad70ac790d65788cd36101fa37f87ea46c4cef" dependencies = [ "base64 0.21.5", - "indexmap 1.9.3", + "indexmap 2.1.0", "line-wrap", "quick-xml", "serde", @@ -2354,7 +2368,7 @@ dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -2366,14 +2380,14 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] name = "quick-xml" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" dependencies = [ "memchr", ] @@ -2446,7 +2460,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.10", + "getrandom 0.2.11", ] [[package]] @@ -2458,15 +2472,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.4.1" @@ -2478,12 +2483,12 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ - "getrandom 0.2.10", - "redox_syscall 0.2.16", + "getrandom 0.2.11", + "libredox", "thiserror", ] @@ -2619,9 +2624,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.21" +version = "0.38.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" dependencies = [ "bitflags 2.4.1", "errno", @@ -2668,9 +2673,9 @@ dependencies = [ [[package]] name = "schemars" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c" +checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" dependencies = [ "chrono", "dyn-clone", @@ -2683,9 +2688,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c" +checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" dependencies = [ "proc-macro2", "quote", @@ -2722,7 +2727,7 @@ checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -2772,22 +2777,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.190" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" +checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.190" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" +checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -2842,7 +2847,7 @@ checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -2872,7 +2877,7 @@ version = "0.9.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c" dependencies = [ - "indexmap 2.0.2", + "indexmap 2.1.0", "itoa", "ryu", "serde", @@ -2942,9 +2947,9 @@ dependencies = [ [[package]] name = "signature" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", "rand_core 0.6.4", @@ -2967,9 +2972,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "socket2" @@ -3041,7 +3046,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3073,9 +3078,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.38" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -3136,16 +3141,16 @@ checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ "cfg-if", "fastrand 2.0.1", - "redox_syscall 0.4.1", + "redox_syscall", "rustix", "windows-sys 0.48.0", ] [[package]] name = "termcolor" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" +checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" dependencies = [ "winapi-util", ] @@ -3167,7 +3172,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3226,9 +3231,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.33.0" +version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" dependencies = [ "backtrace", "bytes", @@ -3243,13 +3248,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3287,9 +3292,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ff9e3abce27ee2c9a37f9ad37238c1bdd4e789c84ba37df76aa4d528f5072cc" +checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" dependencies = [ "serde", "serde_spanned", @@ -3308,11 +3313,11 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.20.7" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" dependencies = [ - "indexmap 2.0.2", + "indexmap 2.1.0", "serde", "serde_spanned", "toml_datetime", @@ -3344,7 +3349,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3369,20 +3374,20 @@ dependencies = [ [[package]] name = "tracing-log" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ - "lazy_static", "log", + "once_cell", "tracing-core", ] [[package]] name = "tracing-subscriber" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", "nu-ansi-term 0.46.0", @@ -3510,7 +3515,7 @@ version = "0.25.0" source = "git+https://github.com/mozilla/uniffi-rs?rev=b369e7c15b1b7ebca34de9028209db11b7ff353d#b369e7c15b1b7ebca34de9028209db11b7ff353d" dependencies = [ "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3540,7 +3545,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.38", + "syn 2.0.39", "toml 0.5.11", "uniffi_build", "uniffi_meta", @@ -3612,9 +3617,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" +checksum = "c58fe91d841bc04822c9801002db4ea904b9e4b8e6bbad25127b46eff8dc516b" dependencies = [ "serde", ] @@ -3697,7 +3702,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", "wasm-bindgen-shared", ] @@ -3731,7 +3736,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3967,9 +3972,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" -version = "0.5.17" +version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3b801d0e0a6726477cc207f60162da452f3a95adb368399bef20a946e06f65c" +checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" dependencies = [ "memchr", ] @@ -3986,9 +3991,9 @@ dependencies = [ [[package]] name = "wiremock" -version = "0.5.19" +version = "0.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f71803d3a1c80377a06221e0530be02035d5b3e854af56c6ece7ac20ac441d" +checksum = "079aee011e8a8e625d16df9e785de30a6b77f80a6126092d76a57375f96448da" dependencies = [ "assert-json-diff", "async-trait", @@ -4008,6 +4013,6 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/crates/bitwarden-napi/package-lock.json b/crates/bitwarden-napi/package-lock.json index 65266d1e4..3ccf3a7cb 100644 --- a/crates/bitwarden-napi/package-lock.json +++ b/crates/bitwarden-napi/package-lock.json @@ -95,9 +95,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.8.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.9.tgz", - "integrity": "sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==", + "version": "20.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.2.tgz", + "integrity": "sha512-WHZXKFCEyIUJzAwh3NyyTHYSR35SevJ6mZ1nWwJafKtiQbqRTIKSRcw3Ma3acqgsent3RRDqeVwpHntMk+9irg==", "dev": true, "peer": true, "dependencies": { diff --git a/crates/bitwarden-napi/src/client.rs b/crates/bitwarden-napi/src/client.rs index 840fd2533..d794b18f4 100644 --- a/crates/bitwarden-napi/src/client.rs +++ b/crates/bitwarden-napi/src/client.rs @@ -1,7 +1,6 @@ extern crate log; use bitwarden_json::client::Client as JsonClient; -use napi::bindgen_prelude::*; use napi_derive::napi; #[napi] diff --git a/crates/bitwarden-wasm/Cargo.toml b/crates/bitwarden-wasm/Cargo.toml index 6055388fb..add03f0f3 100644 --- a/crates/bitwarden-wasm/Cargo.toml +++ b/crates/bitwarden-wasm/Cargo.toml @@ -11,7 +11,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] js-sys = "0.3.63" serde = {version = "1.0.163", features = ["derive"] } -wasm-bindgen = { version = "0.2.86", features = ["serde-serialize"] } +wasm-bindgen = { version = "=0.2.87", features = ["serde-serialize"] } wasm-bindgen-futures = "0.4.36" console_error_panic_hook = "0.1.7" console_log = { version = "1.0.0", features = ["color"] } diff --git a/languages/js_webassembly/package-lock.json b/languages/js_webassembly/package-lock.json index e3e20e9aa..d5345c00a 100644 --- a/languages/js_webassembly/package-lock.json +++ b/languages/js_webassembly/package-lock.json @@ -88,9 +88,9 @@ "dev": true }, "node_modules/@types/body-parser": { - "version": "1.19.4", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.4.tgz", - "integrity": "sha512-N7UDG0/xiPQa2D/XrVJXjkWbpqHCd2sBaB32ggRF2l83RhPfamgKGF8gwwqyksS95qUS5ZYF9aF+lLPRlwI2UA==", + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", "dev": true, "dependencies": { "@types/connect": "*", @@ -98,27 +98,27 @@ } }, "node_modules/@types/bonjour": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.12.tgz", - "integrity": "sha512-ky0kWSqXVxSqgqJvPIkgFkcn4C8MnRog308Ou8xBBIVo39OmUFy+jqNe0nPwLCDFxUpmT9EvT91YzOJgkDRcFg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/connect": { - "version": "3.4.37", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.37.tgz", - "integrity": "sha512-zBUSRqkfZ59OcwXon4HVxhx5oWCJmc0OtBTK05M+p0dYjgN6iTwIL2T/WbsQZrEsdnwaF9cWQ+azOnpPvIqY3Q==", + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/connect-history-api-fallback": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.2.tgz", - "integrity": "sha512-gX2j9x+NzSh4zOhnRPSdPPmTepS4DfxES0AvIFv3jGv5QyeAJf6u6dY5/BAoAJU9Qq1uTvwOku8SSC2GnCRl6Q==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.3.tgz", + "integrity": "sha512-6mfQ6iNvhSKCZJoY6sIG3m0pKkdUcweVNOLuBBKvoWGzl2yRxOJcYOTRyLKt3nxXvBLJWa6QkW//tgbIwJehmA==", "dev": true, "dependencies": { "@types/express-serve-static-core": "*", @@ -126,9 +126,9 @@ } }, "node_modules/@types/eslint": { - "version": "8.44.6", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.6.tgz", - "integrity": "sha512-P6bY56TVmX8y9J87jHNgQh43h6VVU+6H7oN7hgvivV81K2XY8qJZ5vqPy/HdUoVIelii2kChYVzQanlswPWVFw==", + "version": "8.44.7", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.7.tgz", + "integrity": "sha512-f5ORu2hcBbKei97U73mf+l9t4zTGl74IqZ0GQk4oVea/VS8tQZYkUveSYojk+frraAVYId0V2WC9O4PTNru2FQ==", "dev": true, "dependencies": { "@types/estree": "*", @@ -136,9 +136,9 @@ } }, "node_modules/@types/eslint-scope": { - "version": "3.7.6", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.6.tgz", - "integrity": "sha512-zfM4ipmxVKWdxtDaJ3MP3pBurDXOCoyjvlpE3u6Qzrmw4BPbfm4/ambIeTk/r/J0iq/+2/xp0Fmt+gFvXJY2PQ==", + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "dev": true, "dependencies": { "@types/eslint": "*", @@ -146,15 +146,15 @@ } }, "node_modules/@types/estree": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.3.tgz", - "integrity": "sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, "node_modules/@types/express": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.20.tgz", - "integrity": "sha512-rOaqlkgEvOW495xErXMsmyX3WKBInbhG5eqojXYi3cGUaLoRDlXa5d52fkfWZT963AZ3v2eZ4MbKE6WpDAGVsw==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", "dev": true, "dependencies": { "@types/body-parser": "*", @@ -164,9 +164,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.39", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.39.tgz", - "integrity": "sha512-BiEUfAiGCOllomsRAZOiMFP7LAnrifHpt56pc4Z7l9K6ACyN06Ns1JLMBxwkfLOjJRlSf06NwWsT7yzfpaVpyQ==", + "version": "4.17.41", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", + "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", "dev": true, "dependencies": { "@types/node": "*", @@ -182,60 +182,60 @@ "dev": true }, "node_modules/@types/http-errors": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.3.tgz", - "integrity": "sha512-pP0P/9BnCj1OVvQR2lF41EkDG/lWWnDyA203b/4Fmi2eTyORnBtcDoKDwjWQthELrBvWkMOrvSOnZ8OVlW6tXA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", "dev": true }, "node_modules/@types/http-proxy": { - "version": "1.17.13", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.13.tgz", - "integrity": "sha512-GkhdWcMNiR5QSQRYnJ+/oXzu0+7JJEPC8vkWXK351BkhjraZF+1W13CUYARUvX9+NqIU2n6YHA4iwywsc/M6Sw==", + "version": "1.17.14", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", + "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/json-schema": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", - "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "node_modules/@types/mime": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.4.tgz", - "integrity": "sha512-1Gjee59G25MrQGk8bsNvC6fxNiRgUlGn2wlhGf95a59DrprnnHk80FIMMFG9XHMdrfsuA119ht06QPDXA1Z7tw==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "dev": true }, "node_modules/@types/node": { - "version": "20.8.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.9.tgz", - "integrity": "sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==", + "version": "20.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.2.tgz", + "integrity": "sha512-WHZXKFCEyIUJzAwh3NyyTHYSR35SevJ6mZ1nWwJafKtiQbqRTIKSRcw3Ma3acqgsent3RRDqeVwpHntMk+9irg==", "dev": true, "dependencies": { "undici-types": "~5.26.4" } }, "node_modules/@types/node-forge": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.8.tgz", - "integrity": "sha512-vGXshY9vim9CJjrpcS5raqSjEfKlJcWy2HNdgUasR66fAnVEYarrf1ULV4nfvpC1nZq/moA9qyqBcu83x+Jlrg==", + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.9.tgz", + "integrity": "sha512-meK88cx/sTalPSLSoCzkiUB4VPIFHmxtXm5FaaqRDqBX2i/Sy8bJ4odsan0b20RBjPh06dAQ+OTTdnyQyhJZyQ==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/qs": { - "version": "6.9.9", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.9.tgz", - "integrity": "sha512-wYLxw35euwqGvTDx6zfY1vokBFnsK0HNrzc6xNHchxfO2hpuRg74GbkEW7e3sSmPvj0TjCDT1VCa6OtHXnubsg==", + "version": "6.9.10", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.10.tgz", + "integrity": "sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw==", "dev": true }, "node_modules/@types/range-parser": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.6.tgz", - "integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "dev": true }, "node_modules/@types/retry": { @@ -245,9 +245,9 @@ "dev": true }, "node_modules/@types/send": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.3.tgz", - "integrity": "sha512-/7fKxvKUoETxjFUsuFlPB9YndePpxxRAOfGC/yJdc9kTjTeP5kRCTzfnE8kPUKCeyiyIZu0YQ76s50hCedI1ug==", + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", "dev": true, "dependencies": { "@types/mime": "^1", @@ -255,18 +255,18 @@ } }, "node_modules/@types/serve-index": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.3.tgz", - "integrity": "sha512-4KG+yMEuvDPRrYq5fyVm/I2uqAJSAwZK9VSa+Zf+zUq9/oxSSvy3kkIqyL+jjStv6UCVi8/Aho0NHtB1Fwosrg==", + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", "dev": true, "dependencies": { "@types/express": "*" } }, "node_modules/@types/serve-static": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.4.tgz", - "integrity": "sha512-aqqNfs1XTF0HDrFdlY//+SGUxmdSUbjeRXb5iaZc3x0/vMbYmdw9qvOgHWOyyLFxSSRnUuP5+724zBgfw8/WAw==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", + "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", "dev": true, "dependencies": { "@types/http-errors": "*", @@ -275,18 +275,18 @@ } }, "node_modules/@types/sockjs": { - "version": "0.3.35", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.35.tgz", - "integrity": "sha512-tIF57KB+ZvOBpAQwSaACfEu7htponHXaFzP7RfKYgsOS0NoYnn+9+jzp7bbq4fWerizI3dTB4NfAZoyeQKWJLw==", + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/ws": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.8.tgz", - "integrity": "sha512-flUksGIQCnJd6sZ1l5dqCEG/ksaoAg/eUwiLAGTJQcfgvZJKF++Ta4bJA6A5aPSJmsr+xlseHn4KLgVlNnvPTg==", + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.9.tgz", + "integrity": "sha512-jbdrY0a8lxfdTp/+r7Z4CkycbOFN8WX+IOchLJr3juT/xzbJ8URyTVSJ/hvNdadTgM1mnedb47n+Y31GsFnQlg==", "dev": true, "dependencies": { "@types/node": "*" @@ -836,9 +836,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001558", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001558.tgz", - "integrity": "sha512-/Et7DwLqpjS47JPEcz6VnxU9PwcIdVi0ciLXRWBQdj1XFye68pSQYpV0QtPTfUKWuOaEig+/Vez2l74eDc1tPQ==", + "version": "1.0.30001563", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001563.tgz", + "integrity": "sha512-na2WUmOxnwIZtwnFI2CZ/3er0wdNzU7hN+cPYz/z2ajHThnkWjNBOpEPP4n+4r2WPM847JaMotaJE3bnfzjyKw==", "dev": true, "funding": [ { @@ -1278,9 +1278,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.569", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.569.tgz", - "integrity": "sha512-LsrJjZ0IbVy12ApW3gpYpcmHS3iRxH4bkKOW98y1/D+3cvDUWGcbzbsFinfUS8knpcZk/PG/2p/RnkMCYN7PVg==", + "version": "1.4.588", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.588.tgz", + "integrity": "sha512-soytjxwbgcCu7nh5Pf4S2/4wa6UIu+A3p03U2yVr53qGxi1/VTR3ENI+p50v+UxqqZAfl48j3z55ud7VHIOr9w==", "dev": true }, "node_modules/encodeurl": { @@ -1315,9 +1315,9 @@ } }, "node_modules/envinfo": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.10.0.tgz", - "integrity": "sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz", + "integrity": "sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg==", "dev": true, "bin": { "envinfo": "dist/cli.js" @@ -1327,9 +1327,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz", - "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", + "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", "dev": true }, "node_modules/escalade": { @@ -2845,9 +2845,9 @@ } }, "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "engines": { "node": ">=6" @@ -3521,9 +3521,9 @@ } }, "node_modules/terser": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.23.0.tgz", - "integrity": "sha512-Iyy83LN0uX9ZZLCX4Qbu5JiHiWjOCTwrmM9InWOzVeM++KNWEsqV4YgN9U9E8AlohQ6Gs42ztczlWOG/lwDAMA==", + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.24.0.tgz", + "integrity": "sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", diff --git a/package-lock.json b/package-lock.json index ac70b185d..6ee8982ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -363,9 +363,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.8.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.9.tgz", - "integrity": "sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==", + "version": "20.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.2.tgz", + "integrity": "sha512-WHZXKFCEyIUJzAwh3NyyTHYSR35SevJ6mZ1nWwJafKtiQbqRTIKSRcw3Ma3acqgsent3RRDqeVwpHntMk+9irg==", "dev": true, "peer": true, "dependencies": { @@ -373,9 +373,9 @@ } }, "node_modules/@types/urijs": { - "version": "1.19.22", - "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.22.tgz", - "integrity": "sha512-qnYBwfN7O/+i6E1Kr8JaCKsrCLpRCiQ1XxkSxNIYuJ/5Aagt0+HlMX78DJMUrNzDULMz0eu2gcprlxJaDtACOw==", + "version": "1.19.23", + "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.23.tgz", + "integrity": "sha512-3Zbk6RzmIpvKTNEHO2RcPOGHM++BQEITMqBRR1Ju32WbruhV/pygYgxiP3xA0b1B88zjzs0Izzjxsbj768+IjA==", "dev": true }, "node_modules/abort-controller": { @@ -1252,9 +1252,9 @@ } }, "node_modules/lru-cache": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", - "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.3.tgz", + "integrity": "sha512-B7gr+F6MkqB3uzINHXNctGieGsRTMwIBgxkp0yq/5BwcuDzD4A8wQpHQW6vDAm1uKSLQghmRdD9sKqf2vJ1cEg==", "dev": true, "engines": { "node": "14 || >=16.14" @@ -2007,9 +2007,9 @@ "dev": true }, "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "engines": { "node": ">= 10.0.0" @@ -2130,9 +2130,9 @@ } }, "node_modules/yaml": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.3.tgz", - "integrity": "sha512-zw0VAJxgeZ6+++/su5AFoqBbZbrEakwu+X0M5HmcwUiBL7AzcuPKjj5we4xfQLp78LkEMpD0cOnUhmgOVy3KdQ==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", "dev": true, "engines": { "node": ">= 14" From 27045bf2c33c1906685742e230841e6fe33e9cb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Mon, 20 Nov 2023 15:32:38 +0100 Subject: [PATCH 011/157] Generate immutable records in uniffi bindings (#332) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Update the uniffi code generation to output immutable structs. I've pointed the dependencies to my fork until this is included in the main uniffi repo, but it should be enough to let the mobile devs test if the solution is appropiate for them. --- Cargo.lock | 38 ++++++++++++++--------------- Cargo.toml | 12 ++++----- crates/bitwarden-uniffi/Cargo.toml | 4 +-- crates/bitwarden-uniffi/uniffi.toml | 2 ++ crates/bitwarden/Cargo.toml | 2 +- crates/bitwarden/uniffi.toml | 2 ++ crates/uniffi-bindgen/Cargo.toml | 2 +- 7 files changed, 33 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f9fbba7e7..c040eb279 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3457,8 +3457,8 @@ checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "uniffi" -version = "0.25.0" -source = "git+https://github.com/mozilla/uniffi-rs?rev=b369e7c15b1b7ebca34de9028209db11b7ff353d#b369e7c15b1b7ebca34de9028209db11b7ff353d" +version = "0.25.1" +source = "git+https://github.com/mozilla/uniffi-rs?rev=0a03b713306d6ce3de033157fc2ce92a238c2e24#0a03b713306d6ce3de033157fc2ce92a238c2e24" dependencies = [ "anyhow", "camino", @@ -3478,8 +3478,8 @@ dependencies = [ [[package]] name = "uniffi_bindgen" -version = "0.25.0" -source = "git+https://github.com/mozilla/uniffi-rs?rev=b369e7c15b1b7ebca34de9028209db11b7ff353d#b369e7c15b1b7ebca34de9028209db11b7ff353d" +version = "0.25.1" +source = "git+https://github.com/mozilla/uniffi-rs?rev=0a03b713306d6ce3de033157fc2ce92a238c2e24#0a03b713306d6ce3de033157fc2ce92a238c2e24" dependencies = [ "anyhow", "askama", @@ -3501,8 +3501,8 @@ dependencies = [ [[package]] name = "uniffi_build" -version = "0.25.0" -source = "git+https://github.com/mozilla/uniffi-rs?rev=b369e7c15b1b7ebca34de9028209db11b7ff353d#b369e7c15b1b7ebca34de9028209db11b7ff353d" +version = "0.25.1" +source = "git+https://github.com/mozilla/uniffi-rs?rev=0a03b713306d6ce3de033157fc2ce92a238c2e24#0a03b713306d6ce3de033157fc2ce92a238c2e24" dependencies = [ "anyhow", "camino", @@ -3511,8 +3511,8 @@ dependencies = [ [[package]] name = "uniffi_checksum_derive" -version = "0.25.0" -source = "git+https://github.com/mozilla/uniffi-rs?rev=b369e7c15b1b7ebca34de9028209db11b7ff353d#b369e7c15b1b7ebca34de9028209db11b7ff353d" +version = "0.25.1" +source = "git+https://github.com/mozilla/uniffi-rs?rev=0a03b713306d6ce3de033157fc2ce92a238c2e24#0a03b713306d6ce3de033157fc2ce92a238c2e24" dependencies = [ "quote", "syn 2.0.39", @@ -3520,8 +3520,8 @@ dependencies = [ [[package]] name = "uniffi_core" -version = "0.25.0" -source = "git+https://github.com/mozilla/uniffi-rs?rev=b369e7c15b1b7ebca34de9028209db11b7ff353d#b369e7c15b1b7ebca34de9028209db11b7ff353d" +version = "0.25.1" +source = "git+https://github.com/mozilla/uniffi-rs?rev=0a03b713306d6ce3de033157fc2ce92a238c2e24#0a03b713306d6ce3de033157fc2ce92a238c2e24" dependencies = [ "anyhow", "bytes", @@ -3535,8 +3535,8 @@ dependencies = [ [[package]] name = "uniffi_macros" -version = "0.25.0" -source = "git+https://github.com/mozilla/uniffi-rs?rev=b369e7c15b1b7ebca34de9028209db11b7ff353d#b369e7c15b1b7ebca34de9028209db11b7ff353d" +version = "0.25.1" +source = "git+https://github.com/mozilla/uniffi-rs?rev=0a03b713306d6ce3de033157fc2ce92a238c2e24#0a03b713306d6ce3de033157fc2ce92a238c2e24" dependencies = [ "bincode", "camino", @@ -3553,8 +3553,8 @@ dependencies = [ [[package]] name = "uniffi_meta" -version = "0.25.0" -source = "git+https://github.com/mozilla/uniffi-rs?rev=b369e7c15b1b7ebca34de9028209db11b7ff353d#b369e7c15b1b7ebca34de9028209db11b7ff353d" +version = "0.25.1" +source = "git+https://github.com/mozilla/uniffi-rs?rev=0a03b713306d6ce3de033157fc2ce92a238c2e24#0a03b713306d6ce3de033157fc2ce92a238c2e24" dependencies = [ "anyhow", "bytes", @@ -3564,8 +3564,8 @@ dependencies = [ [[package]] name = "uniffi_testing" -version = "0.25.0" -source = "git+https://github.com/mozilla/uniffi-rs?rev=b369e7c15b1b7ebca34de9028209db11b7ff353d#b369e7c15b1b7ebca34de9028209db11b7ff353d" +version = "0.25.1" +source = "git+https://github.com/mozilla/uniffi-rs?rev=0a03b713306d6ce3de033157fc2ce92a238c2e24#0a03b713306d6ce3de033157fc2ce92a238c2e24" dependencies = [ "anyhow", "camino", @@ -3576,8 +3576,8 @@ dependencies = [ [[package]] name = "uniffi_udl" -version = "0.25.0" -source = "git+https://github.com/mozilla/uniffi-rs?rev=b369e7c15b1b7ebca34de9028209db11b7ff353d#b369e7c15b1b7ebca34de9028209db11b7ff353d" +version = "0.25.1" +source = "git+https://github.com/mozilla/uniffi-rs?rev=0a03b713306d6ce3de033157fc2ce92a238c2e24#0a03b713306d6ce3de033157fc2ce92a238c2e24" dependencies = [ "anyhow", "uniffi_meta", @@ -3784,7 +3784,7 @@ dependencies = [ [[package]] name = "weedle2" version = "4.0.0" -source = "git+https://github.com/mozilla/uniffi-rs?rev=b369e7c15b1b7ebca34de9028209db11b7ff353d#b369e7c15b1b7ebca34de9028209db11b7ff353d" +source = "git+https://github.com/mozilla/uniffi-rs?rev=0a03b713306d6ce3de033157fc2ce92a238c2e24#0a03b713306d6ce3de033157fc2ce92a238c2e24" dependencies = [ "nom", ] diff --git a/Cargo.toml b/Cargo.toml index da8805342..c7c2906dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,10 +22,10 @@ codegen-units = 1 # This is fine as long as we don't have any unhandled panics, but let's keep it disabled for now # strip = true -# Using master until 0.25.1 is released to fix https://github.com/mozilla/uniffi-rs/issues/1798 +# Using git dependency temporarily to add support for immutable records in generated code [patch.crates-io] -uniffi = { git = "https://github.com/mozilla/uniffi-rs", rev = "b369e7c15b1b7ebca34de9028209db11b7ff353d" } -uniffi_build = { git = "https://github.com/mozilla/uniffi-rs", rev = "b369e7c15b1b7ebca34de9028209db11b7ff353d" } -uniffi_bindgen = { git = "https://github.com/mozilla/uniffi-rs", rev = "b369e7c15b1b7ebca34de9028209db11b7ff353d" } -uniffi_core = { git = "https://github.com/mozilla/uniffi-rs", rev = "b369e7c15b1b7ebca34de9028209db11b7ff353d" } -uniffi_macros = { git = "https://github.com/mozilla/uniffi-rs", rev = "b369e7c15b1b7ebca34de9028209db11b7ff353d" } +uniffi = { git = "https://github.com/mozilla/uniffi-rs", rev = "0a03b713306d6ce3de033157fc2ce92a238c2e24" } +uniffi_build = { git = "https://github.com/mozilla/uniffi-rs", rev = "0a03b713306d6ce3de033157fc2ce92a238c2e24" } +uniffi_bindgen = { git = "https://github.com/mozilla/uniffi-rs", rev = "0a03b713306d6ce3de033157fc2ce92a238c2e24" } +uniffi_core = { git = "https://github.com/mozilla/uniffi-rs", rev = "0a03b713306d6ce3de033157fc2ce92a238c2e24" } +uniffi_macros = { git = "https://github.com/mozilla/uniffi-rs", rev = "0a03b713306d6ce3de033157fc2ce92a238c2e24" } diff --git a/crates/bitwarden-uniffi/Cargo.toml b/crates/bitwarden-uniffi/Cargo.toml index a7b774c3c..53da0eb8d 100644 --- a/crates/bitwarden-uniffi/Cargo.toml +++ b/crates/bitwarden-uniffi/Cargo.toml @@ -18,13 +18,13 @@ chrono = { version = ">=0.4.26, <0.5", features = [ "std", ], default-features = false } env_logger = "0.10.0" -uniffi = "=0.25.0" +uniffi = "=0.25.1" schemars = { version = ">=0.8, <0.9", optional = true } bitwarden = { path = "../bitwarden", features = ["mobile", "internal"] } [build-dependencies] -uniffi = { version = "=0.25.0", features = ["build"] } +uniffi = { version = "=0.25.1", features = ["build"] } [target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies] openssl = { version = "0.10", features = ["vendored"] } diff --git a/crates/bitwarden-uniffi/uniffi.toml b/crates/bitwarden-uniffi/uniffi.toml index 456a3676e..c9cb0899c 100644 --- a/crates/bitwarden-uniffi/uniffi.toml +++ b/crates/bitwarden-uniffi/uniffi.toml @@ -1,7 +1,9 @@ [bindings.kotlin] package_name = "com.bitwarden.sdk" cdylib_name = "bitwarden_uniffi" +generate_immutable_records = true [bindings.swift] ffi_module_name = "BitwardenFFI" module_name = "BitwardenSDK" +generate_immutable_records = true diff --git a/crates/bitwarden/Cargo.toml b/crates/bitwarden/Cargo.toml index 65db9db7d..fea9a2fd7 100644 --- a/crates/bitwarden/Cargo.toml +++ b/crates/bitwarden/Cargo.toml @@ -50,7 +50,7 @@ chrono = { version = ">=0.4.26, <0.5", features = [ "serde", "std", ], default-features = false } -uniffi = { version = "=0.25.0", optional = true } +uniffi = { version = "=0.25.1", optional = true } # We don't use this directly (it's used by rand), but we need it here to enable WASM support getrandom = { version = ">=0.2.9", features = ["js"] } diff --git a/crates/bitwarden/uniffi.toml b/crates/bitwarden/uniffi.toml index 5c66e5382..bc4a9d9ec 100644 --- a/crates/bitwarden/uniffi.toml +++ b/crates/bitwarden/uniffi.toml @@ -1,6 +1,8 @@ [bindings.kotlin] package_name = "com.bitwarden.core" +generate_immutable_records = true [bindings.swift] ffi_module_name = "BitwardenCoreFFI" module_name = "BitwardenCore" +generate_immutable_records = true diff --git a/crates/uniffi-bindgen/Cargo.toml b/crates/uniffi-bindgen/Cargo.toml index 980434e89..825f923e3 100644 --- a/crates/uniffi-bindgen/Cargo.toml +++ b/crates/uniffi-bindgen/Cargo.toml @@ -10,4 +10,4 @@ name = "uniffi-bindgen" path = "uniffi-bindgen.rs" [dependencies] -uniffi = { version = "=0.25.0", features = ["cli"] } +uniffi = { version = "=0.25.1", features = ["cli"] } From bebf731307d4a93ef4dba8c80373b3de22be60ca Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 21 Nov 2023 14:03:25 +0100 Subject: [PATCH 012/157] AES tests (#340) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel GarcΓ­a --- crates/bitwarden/src/crypto/aes_ops.rs | 100 +++++++++++++++++++--- crates/bitwarden/src/crypto/enc_string.rs | 12 ++- crates/bitwarden/src/crypto/master_key.rs | 11 ++- crates/bitwarden/src/crypto/rsa.rs | 4 +- 4 files changed, 110 insertions(+), 17 deletions(-) diff --git a/crates/bitwarden/src/crypto/aes_ops.rs b/crates/bitwarden/src/crypto/aes_ops.rs index 132718349..3deedabd8 100644 --- a/crates/bitwarden/src/crypto/aes_ops.rs +++ b/crates/bitwarden/src/crypto/aes_ops.rs @@ -13,10 +13,9 @@ use aes::cipher::{ BlockEncryptMut, KeyIvInit, }; use hmac::Mac; -use rand::RngCore; use crate::{ - crypto::{EncString, PbkdfSha256Hmac, PBKDF_SHA256_HMAC_OUT_SIZE}, + crypto::{PbkdfSha256Hmac, PBKDF_SHA256_HMAC_OUT_SIZE}, error::{CryptoError, Result}, }; @@ -63,10 +62,11 @@ pub fn decrypt_aes256_hmac( /// /// A AesCbc256_B64 EncString #[allow(unused)] -pub fn encrypt_aes256(data_dec: &[u8], key: GenericArray) -> Result { - let (iv, data) = encrypt_aes256_internal(data_dec, key); +pub fn encrypt_aes256(data_dec: &[u8], key: GenericArray) -> ([u8; 16], Vec) { + let rng = rand::thread_rng(); + let (iv, data) = encrypt_aes256_internal(rng, data_dec, key); - Ok(EncString::AesCbc256_B64 { iv, data }) + (iv, data) } /// Encrypt using AES-256 in CBC mode with MAC. @@ -80,11 +80,12 @@ pub fn encrypt_aes256_hmac( data_dec: &[u8], mac_key: GenericArray, key: GenericArray, -) -> Result { - let (iv, data) = encrypt_aes256_internal(data_dec, key); +) -> Result<([u8; 16], [u8; 32], Vec)> { + let rng = rand::thread_rng(); + let (iv, data) = encrypt_aes256_internal(rng, data_dec, key); let mac = validate_mac(&mac_key, &iv, &data)?; - Ok(EncString::AesCbc256_HmacSha256_B64 { iv, mac, data }) + Ok((iv, mac, data)) } /// Encrypt using AES-256 in CBC mode. @@ -92,9 +93,13 @@ pub fn encrypt_aes256_hmac( /// Used internally by: /// - [encrypt_aes256] /// - [encrypt_aes256_hmac] -fn encrypt_aes256_internal(data_dec: &[u8], key: GenericArray) -> ([u8; 16], Vec) { +fn encrypt_aes256_internal( + mut rng: impl rand::RngCore, + data_dec: &[u8], + key: GenericArray, +) -> ([u8; 16], Vec) { let mut iv = [0u8; 16]; - rand::thread_rng().fill_bytes(&mut iv); + rng.fill_bytes(&mut iv); let data = cbc::Encryptor::::new(&key, &iv.into()) .encrypt_padded_vec_mut::(data_dec); @@ -112,3 +117,78 @@ fn validate_mac(mac_key: &[u8], iv: &[u8], data: &[u8]) -> Result<[u8; 32]> { Ok(mac) } + +#[cfg(test)] +mod tests { + use aes::cipher::generic_array::sequence::GenericSequence; + use base64::Engine; + use rand::SeedableRng; + + use crate::util::BASE64_ENGINE; + + use super::*; + + /// Helper function for generating a `GenericArray` of size 32 with each element being + /// a multiple of a given increment, starting from a given offset. + fn generate_generic_array(offset: u8, increment: u8) -> GenericArray { + GenericArray::generate(|i| offset + i as u8 * increment) + } + + /// Helper function for generating a vector of a given size with each element being + /// a multiple of a given increment, starting from a given offset. + fn generate_vec(length: usize, offset: u8, increment: u8) -> Vec { + (0..length).map(|i| offset + i as u8 * increment).collect() + } + + #[test] + fn test_encrypt_aes256_internal() { + let key = generate_generic_array(0, 1); + + let rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + let result = encrypt_aes256_internal(rng, "EncryptMe!".as_bytes(), key); + assert_eq!( + result, + ( + [62, 0, 239, 47, 137, 95, 64, 214, 127, 91, 184, 232, 31, 9, 165, 161], + vec![214, 76, 187, 97, 58, 146, 212, 140, 95, 164, 177, 204, 179, 133, 172, 148] + ) + ); + } + + #[test] + fn test_validate_mac() { + let mac_key = generate_vec(16, 0, 16); + + let iv = generate_vec(16, 0, 16); + let data = generate_vec(16, 0, 16); + + let result = validate_mac(&mac_key, &iv, &data); + + assert!(result.is_ok()); + let mac = result.unwrap(); + assert_eq!(mac.len(), 32); + } + + #[test] + fn test_decrypt_aes256() { + let iv = generate_vec(16, 0, 1); + let iv: &[u8; 16] = iv.as_slice().try_into().unwrap(); + let key = generate_generic_array(0, 1); + let data = BASE64_ENGINE.decode("ByUF8vhyX4ddU9gcooznwA==").unwrap(); + + let decrypted = decrypt_aes256(iv, data, key).unwrap(); + + assert_eq!(String::from_utf8(decrypted).unwrap(), "EncryptMe!"); + } + + #[test] + fn test_encrypt_decrypt_aes256() { + let key = generate_generic_array(0, 1); + let data = "EncryptMe!"; + + let (iv, encrypted) = encrypt_aes256(data.as_bytes(), key); + let decrypted = decrypt_aes256(&iv, encrypted, key).unwrap(); + + assert_eq!(String::from_utf8(decrypted).unwrap(), "EncryptMe!"); + } +} diff --git a/crates/bitwarden/src/crypto/enc_string.rs b/crates/bitwarden/src/crypto/enc_string.rs index ac7f3fc6b..09f877c50 100644 --- a/crates/bitwarden/src/crypto/enc_string.rs +++ b/crates/bitwarden/src/crypto/enc_string.rs @@ -1,5 +1,6 @@ use std::{fmt::Display, str::FromStr}; +use aes::cipher::{generic_array::GenericArray, typenum::U32}; use base64::Engine; use serde::{de::Visitor, Deserialize}; @@ -331,6 +332,15 @@ impl serde::Serialize for EncString { } impl EncString { + pub(crate) fn encrypt_aes256_hmac( + data_dec: &[u8], + mac_key: GenericArray, + key: GenericArray, + ) -> Result { + let (iv, mac, data) = super::encrypt_aes256_hmac(data_dec, mac_key, key)?; + Ok(EncString::AesCbc256_HmacSha256_B64 { iv, mac, data }) + } + /// The numerical representation of the encryption type of the [EncString]. const fn enc_type(&self) -> u8 { match self { @@ -357,7 +367,7 @@ fn invalid_len_error(expected: usize) -> impl Fn(Vec) -> EncStringParseError impl LocateKey for EncString {} impl KeyEncryptable for &[u8] { fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { - super::encrypt_aes256_hmac(self, key.mac_key.ok_or(CryptoError::InvalidMac)?, key.key) + EncString::encrypt_aes256_hmac(self, key.mac_key.ok_or(CryptoError::InvalidMac)?, key.key) } } diff --git a/crates/bitwarden/src/crypto/master_key.rs b/crates/bitwarden/src/crypto/master_key.rs index b2c8e6e5f..fdcf93dde 100644 --- a/crates/bitwarden/src/crypto/master_key.rs +++ b/crates/bitwarden/src/crypto/master_key.rs @@ -4,8 +4,8 @@ use rand::Rng; use sha2::Digest; use super::{ - encrypt_aes256_hmac, hkdf_expand, EncString, KeyDecryptable, PbkdfSha256Hmac, - SymmetricCryptoKey, UserKey, PBKDF_SHA256_HMAC_OUT_SIZE, + hkdf_expand, EncString, KeyDecryptable, PbkdfSha256Hmac, SymmetricCryptoKey, UserKey, + PBKDF_SHA256_HMAC_OUT_SIZE, }; use crate::{client::kdf::Kdf, error::Result, util::BASE64_ENGINE}; @@ -61,8 +61,11 @@ fn make_user_key( rng.fill(&mut user_key); let stretched_key = stretch_master_key(master_key)?; - let protected = - encrypt_aes256_hmac(&user_key, stretched_key.mac_key.unwrap(), stretched_key.key)?; + let protected = EncString::encrypt_aes256_hmac( + &user_key, + stretched_key.mac_key.unwrap(), + stretched_key.key, + )?; let u: &[u8] = &user_key; Ok((UserKey::new(SymmetricCryptoKey::try_from(u)?), protected)) diff --git a/crates/bitwarden/src/crypto/rsa.rs b/crates/bitwarden/src/crypto/rsa.rs index 0d2d135b9..f105dbfe2 100644 --- a/crates/bitwarden/src/crypto/rsa.rs +++ b/crates/bitwarden/src/crypto/rsa.rs @@ -5,7 +5,7 @@ use rsa::{ }; use crate::{ - crypto::{encrypt_aes256_hmac, EncString, SymmetricCryptoKey}, + crypto::{EncString, SymmetricCryptoKey}, error::{Error, Result}, util::BASE64_ENGINE, }; @@ -33,7 +33,7 @@ pub(super) fn make_key_pair(key: &SymmetricCryptoKey) -> Result { .to_pkcs8_der() .map_err(|_| Error::Internal("unable to create private key"))?; - let protected = encrypt_aes256_hmac(pkcs.as_bytes(), key.mac_key.unwrap(), key.key)?; + let protected = EncString::encrypt_aes256_hmac(pkcs.as_bytes(), key.mac_key.unwrap(), key.key)?; Ok(RsaKeyPair { public: b64, From 24f0dfdbb09dbc5ff0ca879b89948d7be7d7ae1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Thu, 23 Nov 2023 12:49:46 +0100 Subject: [PATCH 013/157] [PM-4695] Implement unlock with user key and update examples to use biometrics (#330) ``` - [ ] Bug fix - [x] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Enable the SDK to export the decrypted user key and expose a way to initialize the SDK with it. The Android and iOS examples have been revamped to allow choosing how to unlock the client. Some small notes: - The iOS example is directly storing the user key in a biometric protected keychain, while that doesn't seem possible in Android. Instead, we generate a key in the secure keystore and use it to encrypt/decrypt the user key. - The iOS example biometrics don't seem to work on the simulator and require a real device for testing. This depends on the refactor done on #329 --- crates/bitwarden-uniffi/src/crypto.rs | 13 + crates/bitwarden/src/client/client.rs | 17 + .../src/client/encryption_settings.rs | 38 +- crates/bitwarden/src/mobile/client_crypto.rs | 8 +- crates/bitwarden/src/mobile/crypto.rs | 24 +- languages/kotlin/app/build.gradle | 1 + .../com/bitwarden/myapplication/Biometrics.kt | 132 ++++++ .../bitwarden/myapplication/MainActivity.kt | 369 +++++++++++----- languages/kotlin/doc.md | 32 ++ .../swift/iOS/App.xcodeproj/project.pbxproj | 12 +- languages/swift/iOS/App/Biometrics.swift | 92 ++++ languages/swift/iOS/App/ContentView.swift | 405 +++++++++++------- 12 files changed, 848 insertions(+), 295 deletions(-) create mode 100644 languages/kotlin/app/src/main/java/com/bitwarden/myapplication/Biometrics.kt create mode 100644 languages/swift/iOS/App/Biometrics.swift diff --git a/crates/bitwarden-uniffi/src/crypto.rs b/crates/bitwarden-uniffi/src/crypto.rs index 1792a5bbc..804202e2f 100644 --- a/crates/bitwarden-uniffi/src/crypto.rs +++ b/crates/bitwarden-uniffi/src/crypto.rs @@ -32,4 +32,17 @@ impl ClientCrypto { .initialize_org_crypto(req) .await?) } + + /// Get the uses's decrypted encryption key. Note: It's very important + /// to keep this key safe, as it can be used to decrypt all of the user's data + pub async fn get_user_encryption_key(&self) -> Result { + Ok(self + .0 + .0 + .write() + .await + .crypto() + .get_user_encryption_key() + .await?) + } } diff --git a/crates/bitwarden/src/client/client.rs b/crates/bitwarden/src/client/client.rs index 213a12fb2..960ca9bda 100644 --- a/crates/bitwarden/src/client/client.rs +++ b/crates/bitwarden/src/client/client.rs @@ -222,6 +222,23 @@ impl Client { Ok(self.encryption_settings.as_ref().unwrap()) } + #[cfg(feature = "mobile")] + pub(crate) fn initialize_user_crypto_decrypted_key( + &mut self, + decrypted_user_key: &str, + private_key: EncString, + ) -> Result<&EncryptionSettings> { + let user_key = decrypted_user_key.parse::()?; + self.encryption_settings = Some(EncryptionSettings::new_decrypted_key( + user_key, + private_key, + )?); + Ok(self + .encryption_settings + .as_ref() + .expect("It was initialized on the previous line")) + } + pub(crate) fn initialize_crypto_single_key( &mut self, key: SymmetricCryptoKey, diff --git a/crates/bitwarden/src/client/encryption_settings.rs b/crates/bitwarden/src/client/encryption_settings.rs index 6533c7d2e..7c16725e7 100644 --- a/crates/bitwarden/src/client/encryption_settings.rs +++ b/crates/bitwarden/src/client/encryption_settings.rs @@ -27,6 +27,7 @@ impl std::fmt::Debug for EncryptionSettings { } impl EncryptionSettings { + /// Initialize the encryption settings with the user password and their encrypted keys #[cfg(feature = "internal")] pub(crate) fn new( login_method: &UserLoginMethod, @@ -45,24 +46,33 @@ impl EncryptionSettings { // Decrypt the user key let user_key = master_key.decrypt_user_key(user_key)?; - // Decrypt the private key with the user key - let private_key = { - let dec: Vec = private_key.decrypt_with_key(&user_key)?; - Some( - rsa::RsaPrivateKey::from_pkcs8_der(&dec) - .map_err(|_| CryptoError::InvalidKey)?, - ) - }; - - Ok(EncryptionSettings { - user_key, - private_key, - org_keys: HashMap::new(), - }) + Self::new_decrypted_key(user_key, private_key) } } } + /// Initialize the encryption settings with the decrypted user key and the encrypted user private key + /// This should only be used when unlocking the vault via biometrics or when the vault is set to lock: "never" + /// Otherwise handling the decrypted user key is dangerous and discouraged + #[cfg(feature = "internal")] + pub(crate) fn new_decrypted_key( + user_key: SymmetricCryptoKey, + private_key: EncString, + ) -> Result { + let private_key = { + let dec: Vec = private_key.decrypt_with_key(&user_key)?; + Some(rsa::RsaPrivateKey::from_pkcs8_der(&dec).map_err(|_| CryptoError::InvalidKey)?) + }; + + Ok(EncryptionSettings { + user_key, + private_key, + org_keys: HashMap::new(), + }) + } + + /// Initialize the encryption settings with only a single decrypted key. + /// This is used only for logging in Secrets Manager with an access token pub(crate) fn new_single_key(key: SymmetricCryptoKey) -> Self { EncryptionSettings { user_key: key, diff --git a/crates/bitwarden/src/mobile/client_crypto.rs b/crates/bitwarden/src/mobile/client_crypto.rs index c0edb1982..118e02726 100644 --- a/crates/bitwarden/src/mobile/client_crypto.rs +++ b/crates/bitwarden/src/mobile/client_crypto.rs @@ -3,7 +3,8 @@ use crate::Client; use crate::{ error::Result, mobile::crypto::{ - initialize_org_crypto, initialize_user_crypto, InitOrgCryptoRequest, InitUserCryptoRequest, + get_user_encryption_key, initialize_org_crypto, initialize_user_crypto, + InitOrgCryptoRequest, InitUserCryptoRequest, }, }; @@ -21,6 +22,11 @@ impl<'a> ClientCrypto<'a> { pub async fn initialize_org_crypto(&mut self, req: InitOrgCryptoRequest) -> Result<()> { initialize_org_crypto(self.client, req).await } + + #[cfg(feature = "internal")] + pub async fn get_user_encryption_key(&mut self) -> Result { + get_user_encryption_key(self.client).await + } } impl<'a> Client { diff --git a/crates/bitwarden/src/mobile/crypto.rs b/crates/bitwarden/src/mobile/crypto.rs index 2cf850279..9c2c8478f 100644 --- a/crates/bitwarden/src/mobile/crypto.rs +++ b/crates/bitwarden/src/mobile/crypto.rs @@ -3,7 +3,12 @@ use std::collections::HashMap; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::{client::kdf::Kdf, crypto::EncString, error::Result, Client}; +use crate::{ + client::kdf::Kdf, + crypto::EncString, + error::{Error, Result}, + Client, +}; #[cfg(feature = "internal")] #[derive(Serialize, Deserialize, Debug, JsonSchema)] @@ -31,6 +36,10 @@ pub enum InitUserCryptoMethod { /// The user's encrypted symmetric crypto key user_key: String, }, + DecryptedKey { + /// The user's decrypted encryption key, obtained using `get_user_encryption_key` + decrypted_user_key: String, + }, } #[cfg(feature = "internal")] @@ -49,6 +58,9 @@ pub async fn initialize_user_crypto(client: &mut Client, req: InitUserCryptoRequ let user_key: EncString = user_key.parse()?; client.initialize_user_crypto(&password, user_key, private_key)?; } + InitUserCryptoMethod::DecryptedKey { decrypted_user_key } => { + client.initialize_user_crypto_decrypted_key(&decrypted_user_key, private_key)?; + } } Ok(()) @@ -69,3 +81,13 @@ pub async fn initialize_org_crypto(client: &mut Client, req: InitOrgCryptoReques client.initialize_org_crypto(organization_keys)?; Ok(()) } + +#[cfg(feature = "internal")] +pub async fn get_user_encryption_key(client: &mut Client) -> Result { + let user_key = client + .get_encryption_settings()? + .get_key(&None) + .ok_or(Error::VaultLocked)?; + + Ok(user_key.to_base64()) +} diff --git a/languages/kotlin/app/build.gradle b/languages/kotlin/app/build.gradle index 709e32889..ef42d43bb 100644 --- a/languages/kotlin/app/build.gradle +++ b/languages/kotlin/app/build.gradle @@ -60,6 +60,7 @@ dependencies { implementation 'androidx.compose.ui:ui-graphics' implementation 'androidx.compose.ui:ui-tooling-preview' implementation 'androidx.compose.material3:material3' + implementation "androidx.biometric:biometric:1.1.0" implementation "io.ktor:ktor-client-core:2.3.3" implementation "io.ktor:ktor-client-cio:2.3.3" implementation "io.ktor:ktor-client-content-negotiation:2.3.3" diff --git a/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/Biometrics.kt b/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/Biometrics.kt new file mode 100644 index 000000000..8f8115e8d --- /dev/null +++ b/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/Biometrics.kt @@ -0,0 +1,132 @@ +package com.bitwarden.myapplication + +import android.os.Build +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties +import android.util.Log +import androidx.biometric.BiometricManager +import androidx.biometric.BiometricManager.Authenticators +import androidx.biometric.BiometricPrompt +import androidx.biometric.BiometricPrompt.CryptoObject +import androidx.core.content.ContextCompat +import androidx.fragment.app.FragmentActivity +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import java.security.KeyStore +import java.util.Base64 +import javax.crypto.Cipher +import javax.crypto.KeyGenerator +import javax.crypto.SecretKey +import javax.crypto.spec.GCMParameterSpec + +/** + * IMPORTANT: This file is provided only for the purpose of demostrating the use of the biometric unlock functionality. + * It hasn't gone through a throrough security review and should not be considered production ready. It also doesn't + * handle a lot of errors and edge cases that a production application would need to deal with. + * Developers are encouraged to review and improve the code as needed to meet their security requirements. + * Additionally, we recommend to consult with security experts and conduct thorough testing before using the code in production. + */ + +class Biometric(private var activity: FragmentActivity) { + private var promptInfo: BiometricPrompt.PromptInfo = + BiometricPrompt.PromptInfo.Builder().setTitle("Unlock") + .setSubtitle("Bitwarden biometric unlock") + .setDescription("Confirm biometric to continue").setConfirmationRequired(true) + .setNegativeButtonText("Use account password").build() + + suspend fun encryptString( + keyName: String, plaintext: String, callback: (String, String) -> Unit + ) { + if (canAuthenticate()) { + val cipher = getCipher() + cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(keyName)) + + val bio = createBiometricPrompt { + val ciphertext = it.cipher!!.doFinal(plaintext.toByteArray()) + callback( + String(Base64.getEncoder().encode(ciphertext)), + String(Base64.getEncoder().encode(cipher.iv)) + ) + } + CoroutineScope(Dispatchers.Main).async { + bio.authenticate(promptInfo, CryptoObject(cipher)) + }.await() + } + } + + suspend fun decryptString( + keyName: String, encrypted: String, initializationVector: String, callback: (String) -> Unit + ) { + if (canAuthenticate()) { + val enc = Base64.getDecoder().decode(encrypted) + val iv = Base64.getDecoder().decode(initializationVector) + + val cipher = getCipher() + cipher.init(Cipher.DECRYPT_MODE, getSecretKey(keyName), GCMParameterSpec(128, iv)) + + val bio = createBiometricPrompt { + callback(String(it.cipher!!.doFinal(enc))) + } + + CoroutineScope(Dispatchers.Main).async { + bio.authenticate(promptInfo, CryptoObject(cipher)) + }.await() + } + } + + private fun canAuthenticate() = BiometricManager.from(activity) + .canAuthenticate(Authenticators.BIOMETRIC_STRONG) == BiometricManager.BIOMETRIC_SUCCESS + + private fun createBiometricPrompt(processData: (CryptoObject) -> Unit): BiometricPrompt { + return BiometricPrompt(activity, + ContextCompat.getMainExecutor(activity), + object : BiometricPrompt.AuthenticationCallback() { + override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { + super.onAuthenticationError(errorCode, errString) + Log.e("Biometric", "$errorCode :: $errString") + } + + override fun onAuthenticationFailed() { + super.onAuthenticationFailed() + Log.e("Biometric", "Authentication failed for an unknown reason") + } + + override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { + super.onAuthenticationSucceeded(result) + processData(result.cryptoObject!!) + } + }) + } + + private fun getCipher(): Cipher { + val transform = + "${KeyProperties.KEY_ALGORITHM_AES}/${KeyProperties.BLOCK_MODE_GCM}/${KeyProperties.ENCRYPTION_PADDING_NONE}" + return Cipher.getInstance(transform) + } + + private fun getSecretKey(keyName: String): SecretKey { + // If the SecretKey exists, return it + val keyStore = KeyStore.getInstance("AndroidKeyStore") + keyStore.load(null) + keyStore.getKey(keyName, null)?.let { return it as SecretKey } + + // Otherwise, we generate a new one + val keyGenParams = KeyGenParameterSpec.Builder( + keyName, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT + ).apply { + setBlockModes(KeyProperties.BLOCK_MODE_GCM) + setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + setKeySize(256) + setUserAuthenticationRequired(true) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG) + } + }.build() + + val keyGenerator = + KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore") + keyGenerator.init(keyGenParams) + return keyGenerator.generateKey() + } +} diff --git a/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt b/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt index 13dec7d0e..3c03c6bca 100644 --- a/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt +++ b/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt @@ -1,15 +1,27 @@ package com.bitwarden.myapplication +import android.content.Context import android.os.Bundle -import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.Checkbox +import androidx.compose.material3.Divider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.fragment.app.FragmentActivity import com.bitwarden.core.DateTime import com.bitwarden.core.Folder import com.bitwarden.core.InitOrgCryptoRequest @@ -44,151 +56,272 @@ import java.security.cert.X509Certificate import java.util.Base64 import javax.net.ssl.X509TrustManager -class MainActivity : ComponentActivity() { +/** + * IMPORTANT: This file is provided only for the purpose of demostrating the use of the SDK functionality. + * It hasn't gone through a throrough security review and should not be considered production ready. It also doesn't + * handle a lot of errors and edge cases that a production application would need to deal with. + * Developers are encouraged to review and improve the code as needed to meet their security requirements. + * Additionally, we recommend to consult with security experts and conduct thorough testing before using the code in production. + */ + +const val SERVER_URL = "https://10.0.2.2:8080/" +const val API_URL = SERVER_URL + "api/" +const val IDENTITY_URL = SERVER_URL + "identity/" + +const val EMAIL = "test@bitwarden.com" +const val PASSWORD = "asdfasdfasdf" + +// We should separate keys for each user by appending the user_id +const val BIOMETRIC_KEY = "biometric_key" + +class MainActivity : FragmentActivity() { + private lateinit var biometric: Biometric + private lateinit var client: Client + private lateinit var http: HttpClient + + private var accessToken = "" + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + biometric = Biometric(this) + client = Client(null) + http = httpClient() + + setContent { + MyApplicationTheme { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { - val SERVER_URL = "https://10.0.2.2:8080/" - val API_URL = SERVER_URL + "api/" - val IDENTITY_URL = SERVER_URL + "identity/" + val setupBiometrics = remember { mutableStateOf(true) } + val outputText = remember { mutableStateOf("") } - val EMAIL = "test@bitwarden.com" - val PASSWORD = "asdfasdfasdf" + Row { + Checkbox(checked = setupBiometrics.value, + onCheckedChange = { isChecked -> + setupBiometrics.value = isChecked + }) + Text( + "Setup biometric unlock after login", + modifier = Modifier.align(CenterVertically) + ) + } - GlobalScope.launch { - var client = Client(null) - val http = httpClient() + Button({ + GlobalScope.launch { + clientExamplePassword( + client, http, outputText, setupBiometrics.value + ) + } + }) { + Text("Login with username + password") + } - ///////////////////////////// Get master password hash ///////////////////////////// - @Serializable - data class PreloginRequest(val email: String) + Divider( + color = Color.Black, + thickness = 1.dp, + modifier = Modifier.padding(30.dp) + ) - @Serializable - data class PreloginResponse( - val kdf: UInt, - val kdfIterations: UInt, - val kdfMemory: UInt?, - val kdfParallelism: UInt? - ) + Button({ + GlobalScope.launch { + clientExampleBiometrics(client, http, outputText) + } + }) { + Text("Unlock with biometrics") + } - val prelogin_body = http.post(IDENTITY_URL + "accounts/prelogin") { - contentType(ContentType.Application.Json) - setBody(PreloginRequest(EMAIL)) - }.body() - val kdf = if (prelogin_body.kdf == 0u) { - Kdf.Pbkdf2(prelogin_body.kdfIterations) - } else { - Kdf.Argon2id( - prelogin_body.kdfIterations, - prelogin_body.kdfMemory!!, - prelogin_body.kdfParallelism!! - ) + Button({ + GlobalScope.launch { + client.destroy() + client = Client(null) + outputText.value = "OK" + } + }) { + Text("Lock & reset client") + } + + Text( + "Output: " + outputText.value, + modifier = Modifier.padding(vertical = 10.dp) + ) + } + } } - val masterPasswordHash = client.auth().hashPassword(EMAIL, PASSWORD, kdf) + } + } + + private suspend fun clientExamplePassword( + client: Client, http: HttpClient, outputText: MutableState, setupBiometrics: Boolean + ) { + println("### Logging in with username and password ###") + ///////////////////////////// Get master password hash ///////////////////////////// + @Serializable + data class PreloginRequest(val email: String) - ///////////////////////////// Login ///////////////////////////// + @Serializable + data class PreloginResponse( + val kdf: UInt, val kdfIterations: UInt, val kdfMemory: UInt?, val kdfParallelism: UInt? + ) - @Serializable - data class LoginResponse( - val Key: String, - val PrivateKey: String, - val access_token: String, - val refresh_token: String, + val prelogin_body = http.post(IDENTITY_URL + "accounts/prelogin") { + contentType(ContentType.Application.Json) + setBody(PreloginRequest(EMAIL)) + }.body() + val kdf = if (prelogin_body.kdf == 0u) { + Kdf.Pbkdf2(prelogin_body.kdfIterations) + } else { + Kdf.Argon2id( + prelogin_body.kdfIterations, + prelogin_body.kdfMemory!!, + prelogin_body.kdfParallelism!! ) + } + val masterPasswordHash = client.auth().hashPassword(EMAIL, PASSWORD, kdf) - val loginBody = http.post(IDENTITY_URL + "connect/token") { - contentType(ContentType.Application.Json) - header("Auth-Email", Base64.getEncoder().encodeToString(EMAIL.toByteArray())) - setBody(FormDataContent(Parameters.build { - append("scope", "api offline_access") - append("client_id", "web") - append("deviceType", "12") - append("deviceIdentifier", "0745d426-8dab-484a-9816-4959721d77c7") - append("deviceName", "edge") - - append("grant_type", "password") - append("username", EMAIL) - append("password", masterPasswordHash) - })) - }.body() - - ///////////////////////////// Sync ///////////////////////////// - - val syncBody = http.get(API_URL + "sync?excludeDomains=true") { - bearerAuth(loginBody.access_token) - }.body() - - val folders = (syncBody["folders"] as JsonArray).map { - val o = it as JsonObject - Folder( - (o["id"] as JsonPrimitive).content, - (o["name"] as JsonPrimitive).content, - DateTime.parse( - (o["revisionDate"] as JsonPrimitive).content - ) - ) - } + ///////////////////////////// Login ///////////////////////////// - ///////////////////////////// Initialize crypto ///////////////////////////// - val orgs = ((syncBody["profile"] as JsonObject)["organizations"]) as JsonArray - val orgKeys = HashMap() + @Serializable + data class LoginResponse( + val Key: String, + val PrivateKey: String, + val access_token: String, + val refresh_token: String, + ) - for (org in orgs) { - val o = org as JsonObject - orgKeys[(o["id"] as JsonPrimitive).content] = (o["key"] as JsonPrimitive).content - } + val loginBody = http.post(IDENTITY_URL + "connect/token") { + contentType(ContentType.Application.Json) + header("Auth-Email", Base64.getEncoder().encodeToString(EMAIL.toByteArray())) + setBody(FormDataContent(Parameters.build { + append("scope", "api offline_access") + append("client_id", "web") + append("deviceType", "12") + append("deviceIdentifier", "0745d426-8dab-484a-9816-4959721d77c7") + append("deviceName", "edge") - client.crypto().initializeUserCrypto( - InitUserCryptoRequest( - kdfParams = kdf, - email = EMAIL, - privateKey = loginBody.PrivateKey, - method = InitUserCryptoMethod.Password( - password = PASSWORD, - userKey = loginBody.Key - ) - ) - ) + append("grant_type", "password") + append("username", EMAIL) + append("password", masterPasswordHash) + })) + }.body() - client.crypto().initializeOrgCrypto( - InitOrgCryptoRequest( - organizationKeys = orgKeys + client.crypto().initializeUserCrypto( + InitUserCryptoRequest( + kdfParams = kdf, + email = EMAIL, + privateKey = loginBody.PrivateKey, + method = InitUserCryptoMethod.Password( + password = PASSWORD, userKey = loginBody.Key ) ) + ) - ///////////////////////////// Decrypt some folders ///////////////////////////// + accessToken = loginBody.access_token - val decryptedFolders = client.vault().folders().decryptList(folders) + decryptVault(client, http, outputText) - println(decryptedFolders) + if (setupBiometrics) { + // Save values for future logins + val sharedPref = getPreferences(Context.MODE_PRIVATE) + with(sharedPref.edit()) { + putString("accessToken", accessToken) + putString("privateKey", loginBody.PrivateKey) - } + putInt("kdfType", prelogin_body.kdf.toInt()) + putInt("kdfIterations", prelogin_body.kdfIterations.toInt()) + putInt("kdfMemory", (prelogin_body.kdfMemory ?: 0u).toInt()) + putInt("kdfParallelism", (prelogin_body.kdfParallelism ?: 0u).toInt()) - setContent { - MyApplicationTheme { - // A surface container using the 'background' color from the theme - Surface( - modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background - ) { - Greeting("Hey") + // TODO: This should be protected by Android's secure KeyStore + val decryptedKey = client.crypto().getUserEncryptionKey() + + biometric.encryptString(BIOMETRIC_KEY, decryptedKey) { key, iv -> + putString("encryptedUserKey", key) + putString("encryptedUserKeyIv", iv) + apply() } } } } -} -@Composable -fun Greeting(name: String, modifier: Modifier = Modifier) { - Text( - text = "Hello $name!", modifier = modifier - ) -} + private suspend fun clientExampleBiometrics( + client: Client, http: HttpClient, outputText: MutableState + ) { + println("### Unlocking with Biometrics ###") + + val pref = getPreferences(Context.MODE_PRIVATE) + accessToken = pref.getString("accessToken", "").orEmpty() + val privateKey = pref.getString("privateKey", "") + + val kdf = if (pref.getInt("kdfType", 0) == 0) { + Kdf.Pbkdf2(pref.getInt("kdfIterations", 0).toUInt()) + } else { + Kdf.Argon2id( + pref.getInt("kdfIterations", 0).toUInt(), + pref.getInt("kdfMemory", 0).toUInt(), + pref.getInt("kdfParallelism", 0).toUInt() + ) + } + + val encryptedUserKey = pref.getString("encryptedUserKey", "")!! + val encryptedUserKeyIv = pref.getString("encryptedUserKeyIv", "")!! + + biometric.decryptString( + BIOMETRIC_KEY, encryptedUserKey, encryptedUserKeyIv + ) { key -> + GlobalScope.launch { + client.crypto().initializeUserCrypto( + InitUserCryptoRequest( + kdfParams = kdf, + email = EMAIL, + privateKey = privateKey!!, + method = InitUserCryptoMethod.DecryptedKey(decryptedUserKey = key) + ) + ) + + decryptVault(client, http, outputText) + } + } + } + + suspend fun decryptVault(client: Client, http: HttpClient, outputText: MutableState) { + ///////////////////////////// Sync ///////////////////////////// + + val syncBody = http.get(API_URL + "sync?excludeDomains=true") { + bearerAuth(accessToken) + }.body() + + val folders = (syncBody["folders"] as JsonArray).map { + val o = it as JsonObject + Folder( + (o["id"] as JsonPrimitive).content, + (o["name"] as JsonPrimitive).content, + DateTime.parse( + (o["revisionDate"] as JsonPrimitive).content + ) + ) + } + + ///////////////////////////// Initialize org crypto ///////////////////////////// + val orgs = ((syncBody["profile"] as JsonObject)["organizations"]) as JsonArray + val orgKeys = HashMap() + + for (org in orgs) { + val o = org as JsonObject + orgKeys[(o["id"] as JsonPrimitive).content] = (o["key"] as JsonPrimitive).content + } + + client.crypto().initializeOrgCrypto(InitOrgCryptoRequest(organizationKeys = orgKeys)) + + ///////////////////////////// Decrypt some folders ///////////////////////////// -@Preview(showBackground = true) -@Composable -fun GreetingPreview() { - MyApplicationTheme { - Greeting("Sdk") + val decryptedFolders = client.vault().folders().decryptList(folders) + outputText.value = decryptedFolders.toString() + println(decryptedFolders) } } diff --git a/languages/kotlin/doc.md b/languages/kotlin/doc.md index 70446bd8f..fd65c7b71 100644 --- a/languages/kotlin/doc.md +++ b/languages/kotlin/doc.md @@ -205,6 +205,17 @@ Initialization method for the organization crypto. Needs to be called after **Output**: std::result::Result<,BitwardenError> +### `get_user_encryption_key` + +Get the uses's decrypted encryption key. Note: It's very important to keep this key safe, +as it can be used to decrypt all of the user's data + +**Arguments**: + +- self: + +**Output**: std::result::Result + ## ClientExporters ### `export_vault` @@ -906,6 +917,27 @@ implementations. + + decryptedKey + object + + + + + + + + + + + + + + + +
KeyTypeDescription
decrypted_user_keystringThe user's decrypted encryption key, obtained using `get_user_encryption_key`
+ + ## `InitUserCryptoRequest` diff --git a/languages/swift/iOS/App.xcodeproj/project.pbxproj b/languages/swift/iOS/App.xcodeproj/project.pbxproj index 003c09c0d..e8b3519fd 100644 --- a/languages/swift/iOS/App.xcodeproj/project.pbxproj +++ b/languages/swift/iOS/App.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 33F2B0502B0511C700E1E91C /* Biometrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33F2B04F2B0511C700E1E91C /* Biometrics.swift */; }; 55B6153E2A8678B300BE93F4 /* testApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B6153D2A8678B300BE93F4 /* testApp.swift */; }; 55B615402A8678B300BE93F4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B6153F2A8678B300BE93F4 /* ContentView.swift */; }; 55B615422A8678B400BE93F4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 55B615412A8678B400BE93F4 /* Assets.xcassets */; }; @@ -15,6 +16,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 33F2B04F2B0511C700E1E91C /* Biometrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Biometrics.swift; sourceTree = ""; }; 55B6153A2A8678B300BE93F4 /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; }; 55B6153D2A8678B300BE93F4 /* testApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = testApp.swift; sourceTree = ""; }; 55B6153F2A8678B300BE93F4 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -60,6 +62,7 @@ 55B6153F2A8678B300BE93F4 /* ContentView.swift */, 55B615412A8678B400BE93F4 /* Assets.xcassets */, 55B615432A8678B400BE93F4 /* Preview Content */, + 33F2B04F2B0511C700E1E91C /* Biometrics.swift */, ); path = App; sourceTree = ""; @@ -118,7 +121,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1420; - LastUpgradeCheck = 1420; + LastUpgradeCheck = 1500; TargetAttributes = { 55B615392A8678B300BE93F4 = { CreatedOnToolsVersion = 14.2; @@ -160,6 +163,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 33F2B0502B0511C700E1E91C /* Biometrics.swift in Sources */, 55B615402A8678B300BE93F4 /* ContentView.swift in Sources */, 55B6153E2A8678B300BE93F4 /* testApp.swift in Sources */, ); @@ -172,6 +176,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -204,6 +209,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -232,6 +238,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -264,6 +271,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -293,6 +301,7 @@ DEVELOPMENT_TEAM = LTZ2PFU5D6; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSFaceIDUsageDescription = "Unlock vault with biometrics"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -323,6 +332,7 @@ DEVELOPMENT_TEAM = LTZ2PFU5D6; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSFaceIDUsageDescription = "Unlock vault with biometrics"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; diff --git a/languages/swift/iOS/App/Biometrics.swift b/languages/swift/iOS/App/Biometrics.swift new file mode 100644 index 000000000..2fc5ef2a3 --- /dev/null +++ b/languages/swift/iOS/App/Biometrics.swift @@ -0,0 +1,92 @@ +// +// Biometrics.swift +// App +// +// Created by Dani on 15/11/23. +// + +/** + * IMPORTANT: This file is provided only for the purpose of demostrating the use of the biometric unlock functionality. + * It hasn't gone through a throrough security review and should not be considered production ready. It also doesn't + * handle a lot of errors and edge cases that a production application would need to deal with. + * Developers are encouraged to review and improve the code as needed to meet their security requirements. + * Additionally, we recommend to consult with security experts and conduct thorough testing before using the code in production. + */ + +import Foundation +import LocalAuthentication + +let SERVICE: String = "com.example.app" + +// We should separate keys for each user by appending the user_id +let KEY: String = "biometric_key" + + +func biometricStoreValue(value: String) { + var error: Unmanaged? + let accessControl = SecAccessControlCreateWithFlags( + nil, + kSecAttrAccessibleWhenUnlockedThisDeviceOnly, + .biometryCurrentSet, + &error) + + guard accessControl != nil && error == nil else { + fatalError("SecAccessControlCreateWithFlags failed") + } + + let query = [ + kSecClass: kSecClassGenericPassword, + kSecAttrService: SERVICE, + kSecAttrAccount: KEY, + kSecValueData: value.data(using: .utf8)!, + kSecAttrAccessControl: accessControl as Any + ] as CFDictionary + + // Try to delete the previous secret, if it exists + // Otherwise we get `errSecDuplicateItem` + SecItemDelete(query) + + let status = SecItemAdd(query, nil) + guard status == errSecSuccess else { + fatalError("Unable to store the secret: " + errToString(status: status)) + } +} + +private func errToString(status: OSStatus) -> String { + if let err = SecCopyErrorMessageString(status, nil) as String? { + err + } else { + "Unknown error" + } +} + +func biometricRetrieveValue() -> String? { + let searchQuery = [ + kSecClass: kSecClassGenericPassword, + kSecAttrService: SERVICE, + kSecAttrAccount: KEY, + kSecMatchLimit: kSecMatchLimitOne, + kSecReturnData: true, + kSecReturnAttributes: true, + ] as CFDictionary + + var item: AnyObject? + let status = SecItemCopyMatching(searchQuery, &item) + + // If the item is not found, we just return nil + if status == errSecItemNotFound { + return nil + } + + // TODO: We probably want to handle these errors better + guard status == noErr else { + fatalError("Unable to retrieve the secret: " + errToString(status: status)) + } + + if let resultDictionary = item as? [String: Any], + let data = resultDictionary[kSecValueData as String] as? Data { + return String(decoding: data, as: UTF8.self) + } + + return nil +} diff --git a/languages/swift/iOS/App/ContentView.swift b/languages/swift/iOS/App/ContentView.swift index 484caf14f..5df7dc3fd 100644 --- a/languages/swift/iOS/App/ContentView.swift +++ b/languages/swift/iOS/App/ContentView.swift @@ -8,176 +8,261 @@ import BitwardenSdk import SwiftUI -struct ContentView: View { - - @State private var msg: String - - init() { - let client = Client(settings: nil) +/** + * IMPORTANT: This file is provided only for the purpose of demostrating the use of the SDK functionality. + * It hasn't gone through a throrough security review and should not be considered production ready. It also doesn't + * handle a lot of errors and edge cases that a production application would need to deal with. + * Developers are encouraged to review and improve the code as needed to meet their security requirements. + * Additionally, we recommend to consult with security experts and conduct thorough testing before using the code in production. + */ - _msg = State(initialValue: client.echo(msg: "Sdk")) +let SERVER_URL = "https://localhost:8080/" +let API_URL = SERVER_URL + "api/" +let IDENTITY_URL = SERVER_URL + "identity/" - let SERVER_URL = "https://localhost:8080/" - let API_URL = SERVER_URL + "api/" - let IDENTITY_URL = SERVER_URL + "identity/" - - let EMAIL = "test@bitwarden.com" - let PASSWORD = "asdfasdfasdf" +let EMAIL = "test@bitwarden.com" +let PASSWORD = "asdfasdfasdf" +struct ContentView: View { + private var http: URLSession + + @State private var client: Client + + @State private var accessToken: String = "" + + init() { // Disable SSL Cert validation. Don't do this in production - let ignoreHttpsDelegate = IgnoreHttpsDelegate() - let http = URLSession( - configuration: URLSessionConfiguration.default, delegate: ignoreHttpsDelegate, + http = URLSession( + configuration: URLSessionConfiguration.default, delegate: IgnoreHttpsDelegate(), delegateQueue: nil) - - Task { - - ////////////////////////////// Get master password hash ////////////////////////////// - - struct PreloginRequest: Codable { let email: String } - struct PreloginResponse: Codable { - let kdf: UInt32 - let kdfIterations: UInt32 - let kdfMemory: UInt32? - let kdfParallelism: UInt32? - - } - - let (preloginDataJson, _) = try await http.data( - for: request( - method: "POST", url: IDENTITY_URL + "accounts/prelogin", - fn: { r in - r.setValue("application/json", forHTTPHeaderField: "Content-Type") - r.httpBody = try JSONEncoder().encode(PreloginRequest(email: EMAIL)) - })) - let preloginData = try JSONDecoder().decode( - PreloginResponse.self, from: preloginDataJson) - - let kdf: Kdf - if preloginData.kdf == 0 { - kdf = Kdf.pbkdf2(iterations: preloginData.kdfIterations) - } else { - kdf = Kdf.argon2id( - iterations: preloginData.kdfIterations, memory: preloginData.kdfMemory!, - parallelism: preloginData.kdfParallelism!) - } - - let passwordHash = try await client.auth().hashPassword( - email: EMAIL, password: PASSWORD, kdfParams: kdf) - - ///////////////////////////// Login ///////////////////////////// - - struct LoginResponse: Codable { - let Key: String - let PrivateKey: String - let access_token: String - let refresh_token: String - } - - let (loginDataJson, _) = try await http.data( - for: request( - method: "POST", url: IDENTITY_URL + "connect/token", - fn: { r in - r.setValue( - EMAIL.data(using: .utf8)?.base64EncodedString(), - forHTTPHeaderField: "Auth-Email") - r.setValue( - "application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") - - var comp = URLComponents() - comp.queryItems = [ - URLQueryItem(name: "scope", value: "api offline_access"), - URLQueryItem(name: "client_id", value: "web"), - URLQueryItem(name: "deviceType", value: "12"), - URLQueryItem( - name: "deviceIdentifier", - value: "0745d426-8dab-484a-9816-4959721d77c7"), - URLQueryItem(name: "deviceName", value: "edge"), - URLQueryItem(name: "grant_type", value: "password"), - URLQueryItem(name: "username", value: EMAIL), - URLQueryItem(name: "password", value: passwordHash), - ] - r.httpBody = comp.percentEncodedQuery! - .replacingOccurrences(of: "@", with: "%40") - .replacingOccurrences(of: "+", with: "%2B") - .data(using: .utf8) - })) - let loginData = try JSONDecoder().decode(LoginResponse.self, from: loginDataJson) - - ///////////////////////////// Sync ///////////////////////////// - - struct SyncOrganization: Codable { - let id: String - let key: String - } - struct SyncProfile: Codable { - let organizations: [SyncOrganization] - - } - struct SyncFolder: Codable { - let id: String - let name: String - let revisionDate: String - } - struct SyncResponse: Codable { - let profile: SyncProfile - let folders: [SyncFolder] - } - - let (syncDataJson, _) = try await http.data( - for: request( - method: "GET", url: API_URL + "sync?excludeDomains=true", - fn: { r in - r.setValue( - "Bearer " + loginData.access_token, forHTTPHeaderField: "Authorization") - })) - - let syncData = try JSONDecoder().decode(SyncResponse.self, from: syncDataJson) - - ///////////////////////////// Initialize crypto ///////////////////////////// - - try await client.crypto().initializeUserCrypto( - req: InitUserCryptoRequest( - kdfParams: kdf, - email: EMAIL, - privateKey: loginData.PrivateKey, - method: InitUserCryptoMethod.password( - password: PASSWORD, - userKey: loginData.Key - ) - )) - - try await client.crypto().initializeOrgCrypto( - req: InitOrgCryptoRequest( - organizationKeys: Dictionary.init( - uniqueKeysWithValues: syncData.profile.organizations.map { ($0.id, $0.key) } - ) - )) - - ///////////////////////////// Decrypt some folders ///////////////////////////// - - let dateFormatter = ISO8601DateFormatter() - dateFormatter.formatOptions = [.withFractionalSeconds] - - let decryptedFolders = try await client.vault().folders().decryptList( - folders: syncData.folders.map { - Folder( - id: $0.id, name: $0.name, - revisionDate: dateFormatter.date(from: $0.revisionDate)!) - }) - print(decryptedFolders) - } + + client = Client(settings: nil) } - + + @State var setupBiometrics: Bool = true + @State var outputText: String = "" + var body: some View { VStack { - Image(systemName: "globe") - .imageScale(.large) - .foregroundColor(.accentColor) - Text("Hello " + msg) + Toggle("Setup biometric unlock after login", isOn: $setupBiometrics).padding(.init(top: 0, leading: 20, bottom: 0, trailing: 20)) + + Button(action: { + Task { + do { + try await clientExamplePassword(clientAuth: client.auth(), clientCrypto: client.crypto(), setupBiometrics: setupBiometrics) + try await decryptVault(clientCrypto: client.crypto(), clientVault: client.vault()) + } catch { + print("ERROR:", error) + } + } + }, label: { + Text("Login with username + password") + }) + + Divider().padding(30) + + Button(action: { + Task { + do { + try await clientExampleBiometrics(clientCrypto: client.crypto()) + try await decryptVault(clientCrypto: client.crypto(), clientVault: client.vault()) + } catch { + print("ERROR:", error) + } + } + }, label: { + Text("Unlock with biometrics") + }) + + Button(action: { + client = Client(settings: nil) + }, label: { + Text("Lock & reset client") + }).padding() + + Text("Output: " + outputText).padding(.top) } .padding() } + + func clientExamplePassword(clientAuth: ClientAuthProtocol, clientCrypto: ClientCryptoProtocol, setupBiometrics: Bool) async throws { + ////////////////////////////// Get master password hash ////////////////////////////// + + struct PreloginRequest: Codable { let email: String } + struct PreloginResponse: Codable { + let kdf: UInt32 + let kdfIterations: UInt32 + let kdfMemory: UInt32? + let kdfParallelism: UInt32? + + } + + let (preloginDataJson, _) = try await http.data( + for: request( + method: "POST", url: IDENTITY_URL + "accounts/prelogin", + fn: { r in + r.setValue("application/json", forHTTPHeaderField: "Content-Type") + r.httpBody = try JSONEncoder().encode(PreloginRequest(email: EMAIL)) + })) + let preloginData = try JSONDecoder().decode( + PreloginResponse.self, from: preloginDataJson) + + let kdf: Kdf + if preloginData.kdf == 0 { + kdf = Kdf.pbkdf2(iterations: preloginData.kdfIterations) + } else { + kdf = Kdf.argon2id( + iterations: preloginData.kdfIterations, memory: preloginData.kdfMemory!, + parallelism: preloginData.kdfParallelism!) + } + + let passwordHash = try await clientAuth.hashPassword( + email: EMAIL, password: PASSWORD, kdfParams: kdf) + + ///////////////////////////// Login ///////////////////////////// + + struct LoginResponse: Codable { + let Key: String + let PrivateKey: String + let access_token: String + let refresh_token: String + } + + let (loginDataJson, _) = try await http.data( + for: request( + method: "POST", url: IDENTITY_URL + "connect/token", + fn: { r in + r.setValue( + EMAIL.data(using: .utf8)?.base64EncodedString(), + forHTTPHeaderField: "Auth-Email") + r.setValue( + "application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") + + var comp = URLComponents() + comp.queryItems = [ + URLQueryItem(name: "scope", value: "api offline_access"), + URLQueryItem(name: "client_id", value: "web"), + URLQueryItem(name: "deviceType", value: "12"), + URLQueryItem( + name: "deviceIdentifier", + value: "0745d426-8dab-484a-9816-4959721d77c7"), + URLQueryItem(name: "deviceName", value: "edge"), + URLQueryItem(name: "grant_type", value: "password"), + URLQueryItem(name: "username", value: EMAIL), + URLQueryItem(name: "password", value: passwordHash), + ] + r.httpBody = comp.percentEncodedQuery! + .replacingOccurrences(of: "@", with: "%40") + .replacingOccurrences(of: "+", with: "%2B") + .data(using: .utf8) + })) + let loginData = try JSONDecoder().decode(LoginResponse.self, from: loginDataJson) + + try await clientCrypto.initializeUserCrypto( + req: InitUserCryptoRequest( + kdfParams: kdf, + email: EMAIL, + privateKey: loginData.PrivateKey, + method: InitUserCryptoMethod.password( + password: PASSWORD, + userKey: loginData.Key + ) + )) + + accessToken = loginData.access_token + + if (setupBiometrics) { + let defaults = UserDefaults.standard + defaults.set(loginData.PrivateKey, forKey: "privateKey") + defaults.set(preloginData.kdf, forKey: "kdfType") + defaults.set(preloginData.kdfIterations, forKey: "kdfIterations") + defaults.set(preloginData.kdfMemory, forKey: "kdfMemory") + defaults.set(preloginData.kdfParallelism, forKey: "kdfParallelism") + defaults.synchronize() + + let key = try await clientCrypto.getUserEncryptionKey() + biometricStoreValue(value: key) + } + } + + func clientExampleBiometrics(clientCrypto: ClientCryptoProtocol) async throws { + let defaults = UserDefaults.standard + let privateKey = defaults.string(forKey: "privateKey")! + let kdf = if defaults.integer(forKey: "kdfType") == 0 { + Kdf.pbkdf2(iterations: UInt32(defaults.integer(forKey: "kdfIterations"))) + } else { + Kdf.argon2id( + iterations: UInt32(defaults.integer(forKey: "kdfIterations")), + memory: UInt32(defaults.integer(forKey: "kdfMemory")), + parallelism: UInt32(defaults.integer(forKey: "kdfParallelism")) + ) + } + + let key = biometricRetrieveValue()! + + try await clientCrypto.initializeUserCrypto(req: InitUserCryptoRequest( + kdfParams: kdf, + email: EMAIL, + privateKey: privateKey, + method: InitUserCryptoMethod.decryptedKey( + decryptedUserKey: key + ) + )) + } + + func decryptVault(clientCrypto: ClientCryptoProtocol, clientVault: ClientVaultProtocol) async throws { + ///////////////////////////// Sync ///////////////////////////// + + struct SyncOrganization: Codable { + let id: String + let key: String + } + struct SyncProfile: Codable { + let organizations: [SyncOrganization] + + } + struct SyncFolder: Codable { + let id: String + let name: String + let revisionDate: String + } + struct SyncResponse: Codable { + let profile: SyncProfile + let folders: [SyncFolder] + } + + let (syncDataJson, _) = try await http.data( + for: request( + method: "GET", url: API_URL + "sync?excludeDomains=true", + fn: { r in + r.setValue( + "Bearer " + accessToken, forHTTPHeaderField: "Authorization") + })) + + let syncData = try JSONDecoder().decode(SyncResponse.self, from: syncDataJson) + + ///////////////////////////// Initialize org crypto ///////////////////////////// + + try await clientCrypto.initializeOrgCrypto( + req: InitOrgCryptoRequest( + organizationKeys: Dictionary.init( + uniqueKeysWithValues: syncData.profile.organizations.map { ($0.id, $0.key) } + ) + )) + + ///////////////////////////// Decrypt some folders ///////////////////////////// + + let dateFormatter = ISO8601DateFormatter() + dateFormatter.formatOptions = [.withFractionalSeconds] + + let decryptedFolders = try await clientVault.folders().decryptList( + folders: syncData.folders.map { + Folder( + id: $0.id, name: $0.name, + revisionDate: dateFormatter.date(from: $0.revisionDate)!) + }) + print(decryptedFolders) + } } struct ContentView_Previews: PreviewProvider { @@ -202,7 +287,7 @@ extension IgnoreHttpsDelegate: URLSessionDelegate { ) { //Trust the certificate even if not valid let urlCredential = URLCredential(trust: challenge.protectionSpace.serverTrust!) - + completionHandler(.useCredential, urlCredential) } } From 47e1a5de5ee220eda5cff77fb0cad8009a3f3b13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Thu, 23 Nov 2023 16:38:56 +0100 Subject: [PATCH 014/157] Improve MAC validation (#333) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Change the name of the function `validate_mac` to the more appropiate `generate_mac`, and change the mac comparison to be constant time with the use of the `subtle` crate. Note that `hmac.finalize()` returns a type `CtOutput` that supports constant time comparison by using the `subtle` crate internally, but `CtOutput` doesn't allow getting a reference to the internal array which would complicate the implementation of `EncString`, so I chose to use the `subtle` constant time comparison directly. --- Cargo.lock | 1 + crates/bitwarden/Cargo.toml | 1 + crates/bitwarden/src/crypto/aes_ops.rs | 15 ++++++++------- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c040eb279..fb1768bee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -351,6 +351,7 @@ dependencies = [ "serde_repr", "sha1", "sha2", + "subtle", "thiserror", "tokio", "uniffi", diff --git a/crates/bitwarden/Cargo.toml b/crates/bitwarden/Cargo.toml index fea9a2fd7..31e39c350 100644 --- a/crates/bitwarden/Cargo.toml +++ b/crates/bitwarden/Cargo.toml @@ -42,6 +42,7 @@ pbkdf2 = { version = ">=0.12.1, <0.13", default-features = false } argon2 = { version = ">=0.5.0, <0.6", features = [ "alloc", ], default-features = false } +subtle = ">=2.5.0, <3.0" rand = ">=0.8.5, <0.9" num-bigint = ">=0.4, <0.5" num-traits = ">=0.2.15, <0.3" diff --git a/crates/bitwarden/src/crypto/aes_ops.rs b/crates/bitwarden/src/crypto/aes_ops.rs index 3deedabd8..182986c3b 100644 --- a/crates/bitwarden/src/crypto/aes_ops.rs +++ b/crates/bitwarden/src/crypto/aes_ops.rs @@ -13,6 +13,7 @@ use aes::cipher::{ BlockEncryptMut, KeyIvInit, }; use hmac::Mac; +use subtle::ConstantTimeEq; use crate::{ crypto::{PbkdfSha256Hmac, PBKDF_SHA256_HMAC_OUT_SIZE}, @@ -47,8 +48,8 @@ pub fn decrypt_aes256_hmac( mac_key: GenericArray, key: GenericArray, ) -> Result> { - let res = validate_mac(&mac_key, iv, &data)?; - if res != *mac { + let res = generate_mac(&mac_key, iv, &data)?; + if res.ct_ne(mac).into() { return Err(CryptoError::InvalidMac.into()); } decrypt_aes256(iv, data, key) @@ -83,7 +84,7 @@ pub fn encrypt_aes256_hmac( ) -> Result<([u8; 16], [u8; 32], Vec)> { let rng = rand::thread_rng(); let (iv, data) = encrypt_aes256_internal(rng, data_dec, key); - let mac = validate_mac(&mac_key, &iv, &data)?; + let mac = generate_mac(&mac_key, &iv, &data)?; Ok((iv, mac, data)) } @@ -106,8 +107,8 @@ fn encrypt_aes256_internal( (iv, data) } -/// Validate a MAC using HMAC-SHA256. -fn validate_mac(mac_key: &[u8], iv: &[u8], data: &[u8]) -> Result<[u8; 32]> { +/// Generate a MAC using HMAC-SHA256. +fn generate_mac(mac_key: &[u8], iv: &[u8], data: &[u8]) -> Result<[u8; 32]> { let mut hmac = PbkdfSha256Hmac::new_from_slice(mac_key).expect("HMAC can take key of any size"); hmac.update(iv); hmac.update(data); @@ -156,13 +157,13 @@ mod tests { } #[test] - fn test_validate_mac() { + fn test_generate_mac() { let mac_key = generate_vec(16, 0, 16); let iv = generate_vec(16, 0, 16); let data = generate_vec(16, 0, 16); - let result = validate_mac(&mac_key, &iv, &data); + let result = generate_mac(&mac_key, &iv, &data); assert!(result.is_ok()); let mac = result.unwrap(); From a026cdaf44025900062a197251281e4a1ff88022 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 23 Nov 2023 17:46:11 +0100 Subject: [PATCH 015/157] Sync renovate config to be inline with other repos (#342) --- .github/renovate.json | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/.github/renovate.json b/.github/renovate.json index 71e131e43..16222e52c 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -2,44 +2,26 @@ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:base", - "schedule:weekends", ":combinePatchMinorReleases", ":dependencyDashboard", ":maintainLockFilesWeekly", ":prConcurrentLimit10", ":rebaseStalePrs", - ":separateMajorReleases" + ":separateMajorReleases", + "group:monorepos", + "schedule:weekends" ], "separateMajorMinor": true, "enabledManagers": ["cargo", "github-actions", "npm", "nuget"], + "commitMessagePrefix": "[deps]:", + "commitMessageTopic": "{{depName}}", "packageRules": [ - { - "groupName": "npm minor", - "matchManagers": ["npm"], - "matchUpdateTypes": ["minor", "patch"] - }, - { - "matchManagers": ["cargo"], - "matchUpdateTypes": ["patch"], - "enabled": false - }, - { - "matchManagers": ["cargo"], - "matchUpdateTypes": ["minor"], - "matchCurrentVersion": ">=1.0.0", - "enabled": false - }, { "matchManagers": ["cargo"], "matchPackagePatterns": ["pyo3*"], "matchUpdateTypes": ["minor", "patch"], "groupName": "pyo3 non-major" }, - { - "groupName": "nuget minor", - "matchManagers": ["nuget"], - "matchUpdateTypes": ["minor", "patch"] - }, { "groupName": "gh minor", "matchManagers": ["github-actions"], From 2f04f8c3223f4dd1ac9a59d76c2dd8a82b7ec3c8 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 23 Nov 2023 17:49:50 +0100 Subject: [PATCH 016/157] Use reqwest header constants (#343) --- crates/bitwarden/src/auth/api/request/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bitwarden/src/auth/api/request/mod.rs b/crates/bitwarden/src/auth/api/request/mod.rs index 1277fa969..85b36a983 100644 --- a/crates/bitwarden/src/auth/api/request/mod.rs +++ b/crates/bitwarden/src/auth/api/request/mod.rs @@ -35,10 +35,10 @@ async fn send_identity_connect_request( &configurations.identity.base_path )) .header( - "Content-Type", + reqwest::header::CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8", ) - .header("Accept", "application/json") + .header(reqwest::header::ACCEPT, "application/json") .header("Device-Type", configurations.device_type as usize); if let Some(ref user_agent) = configurations.identity.user_agent { From 94dae37ca20279f5c139a6e88f0f3462088c8ba8 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 23 Nov 2023 18:29:15 +0100 Subject: [PATCH 017/157] Sort cargo.toml files (#344) --- crates/bitwarden-c/Cargo.toml | 1 - crates/bitwarden-json/Cargo.toml | 2 +- crates/bitwarden-napi/Cargo.toml | 6 ++-- crates/bitwarden-py/Cargo.toml | 1 - crates/bitwarden-uniffi/Cargo.toml | 2 +- crates/bitwarden-wasm/Cargo.toml | 9 +++--- crates/bitwarden/Cargo.toml | 52 ++++++++++++++---------------- crates/bw/Cargo.toml | 8 ++--- crates/bws/Cargo.toml | 7 ++-- crates/sdk-schemas/Cargo.toml | 4 +-- 10 files changed, 43 insertions(+), 49 deletions(-) diff --git a/crates/bitwarden-c/Cargo.toml b/crates/bitwarden-c/Cargo.toml index 9181bedae..20f3f5229 100644 --- a/crates/bitwarden-c/Cargo.toml +++ b/crates/bitwarden-c/Cargo.toml @@ -4,7 +4,6 @@ version = "0.1.0" edition = "2021" rust-version = "1.57" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] crate-type = ["lib", "staticlib", "cdylib"] bench = false diff --git a/crates/bitwarden-json/Cargo.toml b/crates/bitwarden-json/Cargo.toml index 579a8bdab..f7c0fd58d 100644 --- a/crates/bitwarden-json/Cargo.toml +++ b/crates/bitwarden-json/Cargo.toml @@ -18,9 +18,9 @@ internal = ["bitwarden/internal"] # Internal testing methods secrets = ["bitwarden/secrets"] # Secrets manager API [dependencies] +log = ">=0.4.18, <0.5" schemars = ">=0.8.12, <0.9" serde = { version = ">=1.0, <2.0", features = ["derive"] } serde_json = ">=1.0.96, <2.0" -log = ">=0.4.18, <0.5" bitwarden = { path = "../bitwarden" } diff --git a/crates/bitwarden-napi/Cargo.toml b/crates/bitwarden-napi/Cargo.toml index 505b68e57..2e86fefe2 100644 --- a/crates/bitwarden-napi/Cargo.toml +++ b/crates/bitwarden-napi/Cargo.toml @@ -16,10 +16,10 @@ rust-version = "1.57" crate-type = ["cdylib", "rlib"] [dependencies] -napi = {version="2", features=["async"]} -napi-derive = "2" +env_logger = "0.10.0" log = "0.4.18" -env_logger="0.10.0" +napi = { version = "2", features = ["async"] } +napi-derive = "2" bitwarden-json = { path = "../bitwarden-json", version = "0.3.0", features = ["secrets"] } diff --git a/crates/bitwarden-py/Cargo.toml b/crates/bitwarden-py/Cargo.toml index 613203c84..5c1fa5cac 100644 --- a/crates/bitwarden-py/Cargo.toml +++ b/crates/bitwarden-py/Cargo.toml @@ -4,7 +4,6 @@ version = "0.1.0" edition = "2021" rust-version = "1.57" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] name = "bitwarden_py" crate-type = ["cdylib"] diff --git a/crates/bitwarden-uniffi/Cargo.toml b/crates/bitwarden-uniffi/Cargo.toml index 53da0eb8d..098c5b2bf 100644 --- a/crates/bitwarden-uniffi/Cargo.toml +++ b/crates/bitwarden-uniffi/Cargo.toml @@ -18,8 +18,8 @@ chrono = { version = ">=0.4.26, <0.5", features = [ "std", ], default-features = false } env_logger = "0.10.0" -uniffi = "=0.25.1" schemars = { version = ">=0.8, <0.9", optional = true } +uniffi = "=0.25.1" bitwarden = { path = "../bitwarden", features = ["mobile", "internal"] } diff --git a/crates/bitwarden-wasm/Cargo.toml b/crates/bitwarden-wasm/Cargo.toml index add03f0f3..fcb0c2b15 100644 --- a/crates/bitwarden-wasm/Cargo.toml +++ b/crates/bitwarden-wasm/Cargo.toml @@ -4,18 +4,17 @@ version = "0.1.0" edition = "2021" rust-version = "1.57" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] crate-type = ["cdylib", "rlib"] [dependencies] -js-sys = "0.3.63" -serde = {version = "1.0.163", features = ["derive"] } -wasm-bindgen = { version = "=0.2.87", features = ["serde-serialize"] } -wasm-bindgen-futures = "0.4.36" console_error_panic_hook = "0.1.7" console_log = { version = "1.0.0", features = ["color"] } +js-sys = "0.3.63" log = "0.4.18" +serde = { version = "1.0.163", features = ["derive"] } +wasm-bindgen = { version = "=0.2.87", features = ["serde-serialize"] } +wasm-bindgen-futures = "0.4.36" bitwarden-json = { path = "../bitwarden-json", features = ["secrets"] } diff --git a/crates/bitwarden/Cargo.toml b/crates/bitwarden/Cargo.toml index 31e39c350..b0135dfa3 100644 --- a/crates/bitwarden/Cargo.toml +++ b/crates/bitwarden/Cargo.toml @@ -20,44 +20,42 @@ internal = [] # Internal testing methods mobile = ["uniffi", "internal"] # Mobile-specific features [dependencies] +aes = ">=0.8.2, <0.9" +argon2 = { version = ">=0.5.0, <0.6", features = [ + "alloc", +], default-features = false } +assert_matches = ">=1.5.0, <2.0" base64 = ">=0.21.2, <0.22" +bitwarden-api-api = { path = "../bitwarden-api-api", version = "=0.2.2" } +bitwarden-api-identity = { path = "../bitwarden-api-identity", version = "=0.2.2" } +cbc = { version = ">=0.1.2, <0.2", features = ["alloc"] } +chrono = { version = ">=0.4.26, <0.5", features = [ + "serde", + "std", +], default-features = false } +# We don't use this directly (it's used by rand), but we need it here to enable WASM support +getrandom = { version = ">=0.2.9", features = ["js"] } +hkdf = ">=0.12.3, <0.13" +hmac = ">=0.12.1, <0.13" lazy_static = ">=1.4.0, <2.0" +log = ">=0.4.18, <0.5" +num-bigint = ">=0.4, <0.5" +num-traits = ">=0.2.15, <0.3" +pbkdf2 = { version = ">=0.12.1, <0.13", default-features = false } +rand = ">=0.8.5, <0.9" reqwest = { version = ">=0.11, <0.12", features = ["json"] } +rsa = ">=0.9.2, <0.10" +schemars = { version = ">=0.8, <0.9", features = ["uuid1", "chrono"] } serde = { version = ">=1.0, <2.0", features = ["derive"] } serde_json = ">=1.0.96, <2.0" serde_qs = ">=0.12.0, <0.13" serde_repr = ">=0.1.12, <0.2" -schemars = { version = ">=0.8, <0.9", features = ["uuid1", "chrono"] } -log = ">=0.4.18, <0.5" -assert_matches = ">=1.5.0, <2.0" -thiserror = ">=1.0.40, <2.0" -aes = ">=0.8.2, <0.9" -cbc = { version = ">=0.1.2, <0.2", features = ["alloc"] } -hkdf = ">=0.12.3, <0.13" -hmac = ">=0.12.1, <0.13" -rsa = ">=0.9.2, <0.10" sha1 = ">=0.10.5, <0.11" sha2 = ">=0.10.6, <0.11" -pbkdf2 = { version = ">=0.12.1, <0.13", default-features = false } -argon2 = { version = ">=0.5.0, <0.6", features = [ - "alloc", -], default-features = false } subtle = ">=2.5.0, <3.0" -rand = ">=0.8.5, <0.9" -num-bigint = ">=0.4, <0.5" -num-traits = ">=0.2.15, <0.3" -uuid = { version = ">=1.3.3, <2.0", features = ["serde"] } -chrono = { version = ">=0.4.26, <0.5", features = [ - "serde", - "std", -], default-features = false } +thiserror = ">=1.0.40, <2.0" uniffi = { version = "=0.25.1", optional = true } - -# We don't use this directly (it's used by rand), but we need it here to enable WASM support -getrandom = { version = ">=0.2.9", features = ["js"] } - -bitwarden-api-identity = { path = "../bitwarden-api-identity", version = "=0.2.2" } -bitwarden-api-api = { path = "../bitwarden-api-api", version = "=0.2.2" } +uuid = { version = ">=1.3.3, <2.0", features = ["serde"] } [dev-dependencies] rand_chacha = "0.3.1" diff --git a/crates/bw/Cargo.toml b/crates/bw/Cargo.toml index 0793470b3..349087818 100644 --- a/crates/bw/Cargo.toml +++ b/crates/bw/Cargo.toml @@ -12,15 +12,13 @@ Bitwarden Password Manager CLI """ keywords = ["bitwarden", "password-manager", "cli"] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] clap = { version = "4.3.0", features = ["derive", "env"] } -tokio = { version = "1.28.2", features = ["rt-multi-thread", "macros"] } -log = "0.4.18" -env_logger = "0.10.0" color-eyre = "0.6" +env_logger = "0.10.0" inquire = "0.6.2" +log = "0.4.18" +tokio = { version = "1.28.2", features = ["rt-multi-thread", "macros"] } bitwarden = { path = "../bitwarden", version = "0.3.1", features = [ "internal", diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 9ccd5c546..91d75de69 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -12,8 +12,6 @@ Bitwarden Secrets Manager CLI """ keywords = ["bitwarden", "secrets-manager", "cli"] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] bat = { version = "0.24.0", features = [ "regex-onig", @@ -29,6 +27,10 @@ comfy-table = "^7.0.1" directories = "5.0.1" env_logger = "0.10.0" log = "0.4.18" +regex = { version = "1.10.2", features = [ + "std", + "perf", +], default-features = false } serde = "^1.0.163" serde_json = "^1.0.96" serde_yaml = "0.9" @@ -37,7 +39,6 @@ thiserror = "1.0.40" tokio = { version = "1.28.2", features = ["rt-multi-thread", "macros"] } toml = "0.8.0" uuid = { version = "^1.3.3", features = ["serde"] } -regex = { version = "1.10.2", features=["std", "perf"], default-features=false } bitwarden = { path = "../bitwarden", version = "0.3.1", features = ["secrets"] } diff --git a/crates/sdk-schemas/Cargo.toml b/crates/sdk-schemas/Cargo.toml index 64ce34274..86a73cd19 100644 --- a/crates/sdk-schemas/Cargo.toml +++ b/crates/sdk-schemas/Cargo.toml @@ -12,10 +12,10 @@ internal = [ ] [dependencies] -schemars = { version = "0.8.12", features = ["preserve_order"] } -serde_json = "1.0.96" anyhow = "1.0.71" itertools = "0.12.0" +schemars = { version = "0.8.12", features = ["preserve_order"] } +serde_json = "1.0.96" bitwarden = { path = "../bitwarden" } bitwarden-json = { path = "../bitwarden-json" } From 62c7c1932532eeba68e278c11c9b6c0a2e4de062 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 24 Nov 2023 11:47:53 +0100 Subject: [PATCH 018/157] [deps]: Update Rust crate thiserror to 1.0.50 (#345) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bws/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 91d75de69..fe94dfcb2 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -35,7 +35,7 @@ serde = "^1.0.163" serde_json = "^1.0.96" serde_yaml = "0.9" supports-color = "2.0.0" -thiserror = "1.0.40" +thiserror = "1.0.50" tokio = { version = "1.28.2", features = ["rt-multi-thread", "macros"] } toml = "0.8.0" uuid = { version = "^1.3.3", features = ["serde"] } From 03c5d6b57713c0c2a56168e12e1a72f315580794 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 24 Nov 2023 11:48:06 +0100 Subject: [PATCH 019/157] [deps]: Update Rust crate toml to 0.8.8 (#346) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bws/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index fe94dfcb2..225c99bcd 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -37,7 +37,7 @@ serde_yaml = "0.9" supports-color = "2.0.0" thiserror = "1.0.50" tokio = { version = "1.28.2", features = ["rt-multi-thread", "macros"] } -toml = "0.8.0" +toml = "0.8.8" uuid = { version = "^1.3.3", features = ["serde"] } bitwarden = { path = "../bitwarden", version = "0.3.1", features = ["secrets"] } From 5407db721214d35b7498a46a99c71a05087e4929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Fri, 24 Nov 2023 18:27:01 +0100 Subject: [PATCH 020/157] Fix VSCode tasks (#347) ## Type of change ``` - [x] Bug fix - [ ] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Fix invalid path in VSCode tasks --- .vscode/tasks.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 55dc10eb2..fa97b8fc7 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -6,7 +6,7 @@ "command": "build", "problemMatcher": ["$rustc"], "options": { - "cwd": "${workspaceFolder}/bitwarden-c/" + "cwd": "${workspaceFolder}/crates/bitwarden-c/" }, "group": { "kind": "build", @@ -19,7 +19,7 @@ "command": "build", "args": ["--release"], "options": { - "cwd": "${workspaceFolder}/bitwarden-c/" + "cwd": "${workspaceFolder}/crates/bitwarden-c/" }, "problemMatcher": ["$rustc"], "label": "rust: bitwarden-c release build" From 422a9e4cfb5aa7d3e7f022d6a1fc539f709019d4 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 27 Nov 2023 11:15:58 +0100 Subject: [PATCH 021/157] Change all dependencies to be ranges (#357) --- crates/bitwarden-api-api/Cargo.toml | 14 +++++++------- crates/bitwarden-api-identity/Cargo.toml | 14 +++++++------- crates/bitwarden/Cargo.toml | 6 +++--- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/crates/bitwarden-api-api/Cargo.toml b/crates/bitwarden-api-api/Cargo.toml index 518cc33ed..2e05088e0 100644 --- a/crates/bitwarden-api-api/Cargo.toml +++ b/crates/bitwarden-api-api/Cargo.toml @@ -13,14 +13,14 @@ categories = ["api-bindings"] edition = "2018" [dependencies] -serde = "^1.0.163" -serde_derive = "^1.0.163" -serde_json = "^1.0.96" -serde_repr = "^0.1.12" -url = "^2.3.1" -uuid = { version = "^1.3.3", features = ["serde"] } +serde = ">=1.0.163, <2" +serde_derive = ">=1.0.163, <2" +serde_json = ">=1.0.96, <2" +serde_repr = ">=0.1.12, <0.2" +url = ">=2.3.1, <3" +uuid = { version = ">=1.3.3, <2", features = ["serde"] } [dependencies.reqwest] -version = "^0.11.18" +version = ">=0.11.18, <0.12" features = ["json", "multipart"] [dev-dependencies] diff --git a/crates/bitwarden-api-identity/Cargo.toml b/crates/bitwarden-api-identity/Cargo.toml index 70be0560b..7207c5f8e 100644 --- a/crates/bitwarden-api-identity/Cargo.toml +++ b/crates/bitwarden-api-identity/Cargo.toml @@ -13,14 +13,14 @@ categories = ["api-bindings"] edition = "2018" [dependencies] -serde = "^1.0.163" -serde_derive = "^1.0.163" -serde_json = "^1.0.96" -serde_repr = "^0.1.12" -url = "^2.3.1" -uuid = { version = "^1.3.3", features = ["serde"] } +serde = ">=1.0.163, <2" +serde_derive = ">=1.0.163, <2" +serde_json = ">=1.0.96, <2" +serde_repr = ">=0.1.12, <0.2" +url = ">=2.3.1, <3" +uuid = { version = ">=1.3.3, <2", features = ["serde"] } [dependencies.reqwest] -version = "^0.11.18" +version = ">=0.11.18, <0.12" features = ["json", "multipart"] [dev-dependencies] diff --git a/crates/bitwarden/Cargo.toml b/crates/bitwarden/Cargo.toml index b0135dfa3..e52c03723 100644 --- a/crates/bitwarden/Cargo.toml +++ b/crates/bitwarden/Cargo.toml @@ -34,7 +34,7 @@ chrono = { version = ">=0.4.26, <0.5", features = [ "std", ], default-features = false } # We don't use this directly (it's used by rand), but we need it here to enable WASM support -getrandom = { version = ">=0.2.9", features = ["js"] } +getrandom = { version = ">=0.2.9, <0.3", features = ["js"] } hkdf = ">=0.12.3, <0.13" hmac = ">=0.12.1, <0.13" lazy_static = ">=1.4.0, <2.0" @@ -59,5 +59,5 @@ uuid = { version = ">=1.3.3, <2.0", features = ["serde"] } [dev-dependencies] rand_chacha = "0.3.1" -tokio = { version = "1.28.2", features = ["rt", "macros"] } -wiremock = "0.5.18" +tokio = { version = "1.34.0", features = ["rt", "macros"] } +wiremock = "0.5.21" From afe290dae16adee7ca543418059f3bcb1f1e85e2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 11:16:24 +0100 Subject: [PATCH 022/157] [deps]: Update Rust crate log to 0.4.20 (#352) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bitwarden-napi/Cargo.toml | 2 +- crates/bitwarden-wasm/Cargo.toml | 2 +- crates/bw/Cargo.toml | 2 +- crates/bws/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bitwarden-napi/Cargo.toml b/crates/bitwarden-napi/Cargo.toml index 2e86fefe2..5139d17ca 100644 --- a/crates/bitwarden-napi/Cargo.toml +++ b/crates/bitwarden-napi/Cargo.toml @@ -17,7 +17,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] env_logger = "0.10.0" -log = "0.4.18" +log = "0.4.20" napi = { version = "2", features = ["async"] } napi-derive = "2" diff --git a/crates/bitwarden-wasm/Cargo.toml b/crates/bitwarden-wasm/Cargo.toml index fcb0c2b15..0c8a60c56 100644 --- a/crates/bitwarden-wasm/Cargo.toml +++ b/crates/bitwarden-wasm/Cargo.toml @@ -11,7 +11,7 @@ crate-type = ["cdylib", "rlib"] console_error_panic_hook = "0.1.7" console_log = { version = "1.0.0", features = ["color"] } js-sys = "0.3.63" -log = "0.4.18" +log = "0.4.20" serde = { version = "1.0.163", features = ["derive"] } wasm-bindgen = { version = "=0.2.87", features = ["serde-serialize"] } wasm-bindgen-futures = "0.4.36" diff --git a/crates/bw/Cargo.toml b/crates/bw/Cargo.toml index 349087818..85df34109 100644 --- a/crates/bw/Cargo.toml +++ b/crates/bw/Cargo.toml @@ -17,7 +17,7 @@ clap = { version = "4.3.0", features = ["derive", "env"] } color-eyre = "0.6" env_logger = "0.10.0" inquire = "0.6.2" -log = "0.4.18" +log = "0.4.20" tokio = { version = "1.28.2", features = ["rt-multi-thread", "macros"] } bitwarden = { path = "../bitwarden", version = "0.3.1", features = [ diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 225c99bcd..a35d7f642 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -26,7 +26,7 @@ color-eyre = "0.6" comfy-table = "^7.0.1" directories = "5.0.1" env_logger = "0.10.0" -log = "0.4.18" +log = "0.4.20" regex = { version = "1.10.2", features = [ "std", "perf", From 8815cfd1ddcd0f32c339c7bbd5ff6d9d96ceffbe Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 10:17:17 +0000 Subject: [PATCH 023/157] [deps]: Update Rust crate anyhow to 1.0.75 (#348) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/sdk-schemas/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/sdk-schemas/Cargo.toml b/crates/sdk-schemas/Cargo.toml index 86a73cd19..017d74a10 100644 --- a/crates/sdk-schemas/Cargo.toml +++ b/crates/sdk-schemas/Cargo.toml @@ -12,7 +12,7 @@ internal = [ ] [dependencies] -anyhow = "1.0.71" +anyhow = "1.0.75" itertools = "0.12.0" schemars = { version = "0.8.12", features = ["preserve_order"] } serde_json = "1.0.96" From 736f2d03780cf7feb2aef36b4b928eef849dc888 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 10:17:33 +0000 Subject: [PATCH 024/157] [deps]: Update Rust crate chrono to 0.4.31 (#349) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bws/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index a35d7f642..ebf917ab1 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -16,7 +16,7 @@ keywords = ["bitwarden", "secrets-manager", "cli"] bat = { version = "0.24.0", features = [ "regex-onig", ], default-features = false } -chrono = { version = "0.4.26", features = [ +chrono = { version = "0.4.31", features = [ "clock", "std", ], default-features = false } From 8073952c0c5c5376832bb34624462a9df3abce40 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 11:27:22 +0100 Subject: [PATCH 025/157] [deps]: Update Rust crate env_logger to 0.10.1 (#350) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bitwarden-napi/Cargo.toml | 2 +- crates/bitwarden-uniffi/Cargo.toml | 2 +- crates/bw/Cargo.toml | 2 +- crates/bws/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bitwarden-napi/Cargo.toml b/crates/bitwarden-napi/Cargo.toml index 5139d17ca..8ab71c56d 100644 --- a/crates/bitwarden-napi/Cargo.toml +++ b/crates/bitwarden-napi/Cargo.toml @@ -16,7 +16,7 @@ rust-version = "1.57" crate-type = ["cdylib", "rlib"] [dependencies] -env_logger = "0.10.0" +env_logger = "0.10.1" log = "0.4.20" napi = { version = "2", features = ["async"] } napi-derive = "2" diff --git a/crates/bitwarden-uniffi/Cargo.toml b/crates/bitwarden-uniffi/Cargo.toml index 098c5b2bf..474f5f299 100644 --- a/crates/bitwarden-uniffi/Cargo.toml +++ b/crates/bitwarden-uniffi/Cargo.toml @@ -17,7 +17,7 @@ chrono = { version = ">=0.4.26, <0.5", features = [ "serde", "std", ], default-features = false } -env_logger = "0.10.0" +env_logger = "0.10.1" schemars = { version = ">=0.8, <0.9", optional = true } uniffi = "=0.25.1" diff --git a/crates/bw/Cargo.toml b/crates/bw/Cargo.toml index 85df34109..244ecf51e 100644 --- a/crates/bw/Cargo.toml +++ b/crates/bw/Cargo.toml @@ -15,7 +15,7 @@ keywords = ["bitwarden", "password-manager", "cli"] [dependencies] clap = { version = "4.3.0", features = ["derive", "env"] } color-eyre = "0.6" -env_logger = "0.10.0" +env_logger = "0.10.1" inquire = "0.6.2" log = "0.4.20" tokio = { version = "1.28.2", features = ["rt-multi-thread", "macros"] } diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index ebf917ab1..bf91adda8 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -25,7 +25,7 @@ clap_complete = "4.3.2" color-eyre = "0.6" comfy-table = "^7.0.1" directories = "5.0.1" -env_logger = "0.10.0" +env_logger = "0.10.1" log = "0.4.20" regex = { version = "1.10.2", features = [ "std", From 9b752bafcd5a32c0ceb671f435d7355e71180e93 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 11:27:33 +0100 Subject: [PATCH 026/157] [deps]: Update Rust crate schemars to 0.8.16 (#354) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/sdk-schemas/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/sdk-schemas/Cargo.toml b/crates/sdk-schemas/Cargo.toml index 017d74a10..f3e70c04c 100644 --- a/crates/sdk-schemas/Cargo.toml +++ b/crates/sdk-schemas/Cargo.toml @@ -14,7 +14,7 @@ internal = [ [dependencies] anyhow = "1.0.75" itertools = "0.12.0" -schemars = { version = "0.8.12", features = ["preserve_order"] } +schemars = { version = "0.8.16", features = ["preserve_order"] } serde_json = "1.0.96" bitwarden = { path = "../bitwarden" } From b88ce1341cfbb683b006046a8bd25d434439a74a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 11:33:35 +0100 Subject: [PATCH 027/157] [deps]: Update Rust crate tokio to 1.34.0 (#358) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bitwarden-py/Cargo.toml | 2 +- crates/bw/Cargo.toml | 2 +- crates/bws/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bitwarden-py/Cargo.toml b/crates/bitwarden-py/Cargo.toml index 5c1fa5cac..94e9e6a43 100644 --- a/crates/bitwarden-py/Cargo.toml +++ b/crates/bitwarden-py/Cargo.toml @@ -18,7 +18,7 @@ bitwarden-json = { path = "../bitwarden-json", features = ["secrets"] } pyo3-build-config = { version = "0.20.0" } [target.'cfg(not(target_arch="wasm32"))'.dependencies] -tokio = { version = "1.28.2", features = ["rt-multi-thread", "macros"] } +tokio = { version = "1.34.0", features = ["rt-multi-thread", "macros"] } pyo3-asyncio = { version = "0.20.0", features = [ "attributes", "tokio-runtime", diff --git a/crates/bw/Cargo.toml b/crates/bw/Cargo.toml index 244ecf51e..4659caac8 100644 --- a/crates/bw/Cargo.toml +++ b/crates/bw/Cargo.toml @@ -18,7 +18,7 @@ color-eyre = "0.6" env_logger = "0.10.1" inquire = "0.6.2" log = "0.4.20" -tokio = { version = "1.28.2", features = ["rt-multi-thread", "macros"] } +tokio = { version = "1.34.0", features = ["rt-multi-thread", "macros"] } bitwarden = { path = "../bitwarden", version = "0.3.1", features = [ "internal", diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index bf91adda8..649015b10 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -36,7 +36,7 @@ serde_json = "^1.0.96" serde_yaml = "0.9" supports-color = "2.0.0" thiserror = "1.0.50" -tokio = { version = "1.28.2", features = ["rt-multi-thread", "macros"] } +tokio = { version = "1.34.0", features = ["rt-multi-thread", "macros"] } toml = "0.8.8" uuid = { version = "^1.3.3", features = ["serde"] } From 6f84712b7a611c3aeaf2ce7c1cc5e0f843391770 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 11:34:58 +0100 Subject: [PATCH 028/157] [deps]: Update Rust crate clap to 4.4.8 (#359) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bitwarden-cli/Cargo.toml | 2 +- crates/bw/Cargo.toml | 2 +- crates/bws/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bitwarden-cli/Cargo.toml b/crates/bitwarden-cli/Cargo.toml index 606e24856..bedf57e58 100644 --- a/crates/bitwarden-cli/Cargo.toml +++ b/crates/bitwarden-cli/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" rust-version = "1.57" [dependencies] -clap = { version = "4.3.0", features = ["derive"] } +clap = { version = "4.4.8", features = ["derive"] } color-eyre = "0.6" inquire = "0.6.2" supports-color = "2.0.0" diff --git a/crates/bw/Cargo.toml b/crates/bw/Cargo.toml index 4659caac8..4c2d6b012 100644 --- a/crates/bw/Cargo.toml +++ b/crates/bw/Cargo.toml @@ -13,7 +13,7 @@ Bitwarden Password Manager CLI keywords = ["bitwarden", "password-manager", "cli"] [dependencies] -clap = { version = "4.3.0", features = ["derive", "env"] } +clap = { version = "4.4.8", features = ["derive", "env"] } color-eyre = "0.6" env_logger = "0.10.1" inquire = "0.6.2" diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 649015b10..770f5f6db 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -20,7 +20,7 @@ chrono = { version = "0.4.31", features = [ "clock", "std", ], default-features = false } -clap = { version = "4.3.0", features = ["derive", "env", "string"] } +clap = { version = "4.4.8", features = ["derive", "env", "string"] } clap_complete = "4.3.2" color-eyre = "0.6" comfy-table = "^7.0.1" From a2597217349c3fac94ed8606fdfdee2bcfa091fa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 11:35:22 +0100 Subject: [PATCH 029/157] [deps]: Update Rust crate tempfile to 3.8.1 (#362) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bw/Cargo.toml | 2 +- crates/bws/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bw/Cargo.toml b/crates/bw/Cargo.toml index 4c2d6b012..9ceb2b153 100644 --- a/crates/bw/Cargo.toml +++ b/crates/bw/Cargo.toml @@ -27,4 +27,4 @@ bitwarden = { path = "../bitwarden", version = "0.3.1", features = [ bitwarden-cli = { path = "../bitwarden-cli", version = "0.1.0" } [dev-dependencies] -tempfile = "3.5.0" +tempfile = "3.8.1" diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 770f5f6db..c8afd73e4 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -43,7 +43,7 @@ uuid = { version = "^1.3.3", features = ["serde"] } bitwarden = { path = "../bitwarden", version = "0.3.1", features = ["secrets"] } [dev-dependencies] -tempfile = "3.5.0" +tempfile = "3.8.1" [target.'cfg(target_os = "linux")'.dependencies] openssl = { version = "0.10", features = ["vendored"] } From 78c5fa23174902dd33eaccf12cb25d076e560758 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 10:36:13 +0000 Subject: [PATCH 030/157] [deps]: Update Rust crate serde_json to v1.0.108 (#355) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bws/Cargo.toml | 2 +- crates/sdk-schemas/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index c8afd73e4..2a1561b8e 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -32,7 +32,7 @@ regex = { version = "1.10.2", features = [ "perf", ], default-features = false } serde = "^1.0.163" -serde_json = "^1.0.96" +serde_json = "^1.0.108" serde_yaml = "0.9" supports-color = "2.0.0" thiserror = "1.0.50" diff --git a/crates/sdk-schemas/Cargo.toml b/crates/sdk-schemas/Cargo.toml index f3e70c04c..2e7706200 100644 --- a/crates/sdk-schemas/Cargo.toml +++ b/crates/sdk-schemas/Cargo.toml @@ -15,7 +15,7 @@ internal = [ anyhow = "1.0.75" itertools = "0.12.0" schemars = { version = "0.8.16", features = ["preserve_order"] } -serde_json = "1.0.96" +serde_json = "1.0.108" bitwarden = { path = "../bitwarden" } bitwarden-json = { path = "../bitwarden-json" } From 520608dbe0e4fa245cd9414d3b909295195e85d3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 11:36:36 +0100 Subject: [PATCH 031/157] [deps]: Update Rust crate supports-color to 2.1.0 (#361) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bitwarden-cli/Cargo.toml | 2 +- crates/bws/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bitwarden-cli/Cargo.toml b/crates/bitwarden-cli/Cargo.toml index bedf57e58..0966f7885 100644 --- a/crates/bitwarden-cli/Cargo.toml +++ b/crates/bitwarden-cli/Cargo.toml @@ -8,4 +8,4 @@ rust-version = "1.57" clap = { version = "4.4.8", features = ["derive"] } color-eyre = "0.6" inquire = "0.6.2" -supports-color = "2.0.0" +supports-color = "2.1.0" diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 2a1561b8e..32a189988 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -34,7 +34,7 @@ regex = { version = "1.10.2", features = [ serde = "^1.0.163" serde_json = "^1.0.108" serde_yaml = "0.9" -supports-color = "2.0.0" +supports-color = "2.1.0" thiserror = "1.0.50" tokio = { version = "1.34.0", features = ["rt-multi-thread", "macros"] } toml = "0.8.8" From 57352eb3bf13a445da6f0dfe297748be8d5f0393 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 11:40:15 +0100 Subject: [PATCH 032/157] [deps]: Update Rust crate clap_complete to 4.4.4 (#360) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bws/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 32a189988..39cf92a85 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -21,7 +21,7 @@ chrono = { version = "0.4.31", features = [ "std", ], default-features = false } clap = { version = "4.4.8", features = ["derive", "env", "string"] } -clap_complete = "4.3.2" +clap_complete = "4.4.4" color-eyre = "0.6" comfy-table = "^7.0.1" directories = "5.0.1" From 7a216991aefdec3e2e6b02f38436eb8c68dae91f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 11:42:15 +0100 Subject: [PATCH 033/157] [deps]: Update Rust crate uuid to ^1.6.1 (#365) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- crates/bws/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fb1768bee..232b823b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3618,9 +3618,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58fe91d841bc04822c9801002db4ea904b9e4b8e6bbad25127b46eff8dc516b" +checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" dependencies = [ "serde", ] diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 39cf92a85..3580446b9 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -38,7 +38,7 @@ supports-color = "2.1.0" thiserror = "1.0.50" tokio = { version = "1.34.0", features = ["rt-multi-thread", "macros"] } toml = "0.8.8" -uuid = { version = "^1.3.3", features = ["serde"] } +uuid = { version = "^1.6.1", features = ["serde"] } bitwarden = { path = "../bitwarden", version = "0.3.1", features = ["secrets"] } From cd93d42ae3bd82b353005d5b1af28f047da769a7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 11:42:47 +0100 Subject: [PATCH 034/157] [deps]: Update Rust crate comfy-table to ^7.1.0 (#363) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bws/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 3580446b9..c0dfc7bac 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -23,7 +23,7 @@ chrono = { version = "0.4.31", features = [ clap = { version = "4.4.8", features = ["derive", "env", "string"] } clap_complete = "4.4.4" color-eyre = "0.6" -comfy-table = "^7.0.1" +comfy-table = "^7.1.0" directories = "5.0.1" env_logger = "0.10.1" log = "0.4.20" From b9ca9e5928441beb98d3ad98ee2b2a66a0d0faf7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 12:38:14 +0100 Subject: [PATCH 035/157] [deps]: Update Rust crate napi-build to 2.1.0 (#364) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bitwarden-napi/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-napi/Cargo.toml b/crates/bitwarden-napi/Cargo.toml index 8ab71c56d..63eae2b8b 100644 --- a/crates/bitwarden-napi/Cargo.toml +++ b/crates/bitwarden-napi/Cargo.toml @@ -24,7 +24,7 @@ napi-derive = "2" bitwarden-json = { path = "../bitwarden-json", version = "0.3.0", features = ["secrets"] } [build-dependencies] -napi-build = "2.0.1" +napi-build = "2.1.0" [profile.release] lto = true From 2c01cd3ff4d2fca565e14dfa479e44ee3d68cce7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 13:07:30 +0100 Subject: [PATCH 036/157] [deps]: Update Rust crate serde to v1.0.193 (#367) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- crates/bitwarden-wasm/Cargo.toml | 2 +- crates/bws/Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 232b823b4..ff741ead5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2778,18 +2778,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.192" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.192" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", diff --git a/crates/bitwarden-wasm/Cargo.toml b/crates/bitwarden-wasm/Cargo.toml index 0c8a60c56..7ad0d1c3e 100644 --- a/crates/bitwarden-wasm/Cargo.toml +++ b/crates/bitwarden-wasm/Cargo.toml @@ -12,7 +12,7 @@ console_error_panic_hook = "0.1.7" console_log = { version = "1.0.0", features = ["color"] } js-sys = "0.3.63" log = "0.4.20" -serde = { version = "1.0.163", features = ["derive"] } +serde = { version = "1.0.193", features = ["derive"] } wasm-bindgen = { version = "=0.2.87", features = ["serde-serialize"] } wasm-bindgen-futures = "0.4.36" diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index c0dfc7bac..95dbef003 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -31,7 +31,7 @@ regex = { version = "1.10.2", features = [ "std", "perf", ], default-features = false } -serde = "^1.0.163" +serde = "^1.0.193" serde_json = "^1.0.108" serde_yaml = "0.9" supports-color = "2.1.0" From af31957425d007926f0ec72a9ae1b5c434244f5d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 13:07:56 +0100 Subject: [PATCH 037/157] [deps]: Update Rust crate async-lock to 3.1.2 (#368) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 16 ++++++++-------- crates/bitwarden-uniffi/Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ff741ead5..2a4d3b6aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -203,11 +203,11 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.1.1" +version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655b9c7fe787d3b25cc0f804a1a8401790f0c5bc395beb5a64dc77d8de079105" +checksum = "dea8b3453dd7cc96711834b75400d671b73e3656975fa68d9f277163b7f7e316" dependencies = [ - "event-listener 3.1.0", + "event-listener 4.0.0", "event-listener-strategy", "pin-project-lite", ] @@ -1065,9 +1065,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" -version = "3.1.0" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" +checksum = "770d968249b5d99410d61f5bf89057f3199a077a04d087092f58e7d10692baae" dependencies = [ "concurrent-queue", "parking", @@ -1076,11 +1076,11 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96b852f1345da36d551b9473fa1e2b1eb5c5195585c6c018118bc92a8d91160" +checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" dependencies = [ - "event-listener 3.1.0", + "event-listener 4.0.0", "pin-project-lite", ] diff --git a/crates/bitwarden-uniffi/Cargo.toml b/crates/bitwarden-uniffi/Cargo.toml index 474f5f299..405642d71 100644 --- a/crates/bitwarden-uniffi/Cargo.toml +++ b/crates/bitwarden-uniffi/Cargo.toml @@ -12,7 +12,7 @@ crate-type = ["lib", "staticlib", "cdylib"] bench = false [dependencies] -async-lock = "3.0.0" +async-lock = "3.1.2" chrono = { version = ">=0.4.26, <0.5", features = [ "serde", "std", From 92a67b194e59ba445531abbdd76971c250d56f3e Mon Sep 17 00:00:00 2001 From: Colton Hurst Date: Mon, 27 Nov 2023 11:00:44 -0500 Subject: [PATCH 038/157] SM-874: Fix Python Integration (#229) ## Type of change ``` - [x] Bug fix - [ ] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective The Python integration is out of date and is not currently working. This PR fixes the Python integration by adding the ability to log in via access token, which is currently the supported way to interact with the Bitwarden Secrets Manager SDK. This PR also adds a `ProjectsClient` to `bitwarden_client.py` for easy use, and updates the error handling on Project or Secret deletes in the SDK. ## Code changes - **crates/sdk-schemas/src/main.rs:** We need the `AccessTokenLoginResponse` struct to be generated so access token auth is available to integrations - **languages/python/BitwardenClient/bitwarden_client.py:** - Import the following from `.schemas`: - `AccessTokenLoginRequest` - `AccessTokenLoginResponse` - `ResponseForAccessTokenLoginResponse` - Define a new function `access_token_login` to support authenticating with an access token - Add the `project_ids` parameter to the `create` and `update` functions for `SecretsClient` - Add a `ProjectsClient` class and `projects()` to the `BitwardenClient` - Removed the password login methods, as they are not supported - Removed the imports that are not needed, and add one that are (like `sys`, etc.) - **languages/python/README.md:** Update the readme instructions, give more examples - **languages/python/login.py:** Renamed `login.py` to `example.py` - **languages/python/example.py:** Update the example to showcase auth with an access token ## Before you submit - Please add **unit tests** where it makes sense to do so (encouraged but not required) --- crates/sdk-schemas/src/main.rs | 1 + .../BitwardenClient/bitwarden_client.py | 90 +++++++++++++------ languages/python/README.md | 6 +- languages/python/example.py | 44 +++++++++ languages/python/login.py | 24 ----- 5 files changed, 115 insertions(+), 50 deletions(-) create mode 100644 languages/python/example.py delete mode 100644 languages/python/login.py diff --git a/crates/sdk-schemas/src/main.rs b/crates/sdk-schemas/src/main.rs index 9d7e52a47..ef7a2db83 100644 --- a/crates/sdk-schemas/src/main.rs +++ b/crates/sdk-schemas/src/main.rs @@ -99,6 +99,7 @@ fn main() -> Result<()> { write_schema_for_response! { bitwarden::auth::login::ApiKeyLoginResponse, bitwarden::auth::login::PasswordLoginResponse, + bitwarden::auth::login::AccessTokenLoginResponse, bitwarden::secrets_manager::secrets::SecretIdentifiersResponse, bitwarden::secrets_manager::secrets::SecretResponse, bitwarden::secrets_manager::secrets::SecretsResponse, diff --git a/languages/python/BitwardenClient/bitwarden_client.py b/languages/python/BitwardenClient/bitwarden_client.py index 6a0a77ed8..cb669a40e 100644 --- a/languages/python/BitwardenClient/bitwarden_client.py +++ b/languages/python/BitwardenClient/bitwarden_client.py @@ -1,8 +1,8 @@ import json -from typing import Any, List +from typing import Any, List, Optional +from uuid import UUID import bitwarden_py -from .schemas import ClientSettings, Command, PasswordLoginRequest, PasswordLoginResponse, ResponseForPasswordLoginResponse, ResponseForSecretIdentifiersResponse, ResponseForSecretResponse, ResponseForSecretsDeleteResponse, ResponseForSyncResponse, ResponseForUserAPIKeyResponse, SecretCreateRequest, SecretGetRequest, SecretIdentifiersRequest, SecretIdentifiersResponse, SecretPutRequest, SecretResponse, SecretVerificationRequest, SecretsCommand, SecretsDeleteRequest, SecretsDeleteResponse, SyncRequest, SyncResponse, UserAPIKeyResponse - +from .schemas import ClientSettings, Command, ResponseForSecretIdentifiersResponse, ResponseForSecretResponse, ResponseForSecretsDeleteResponse, SecretCreateRequest, SecretGetRequest, SecretIdentifiersRequest, SecretIdentifiersResponse, SecretPutRequest, SecretResponse, SecretsCommand, SecretsDeleteRequest, SecretsDeleteResponse, AccessTokenLoginRequest, AccessTokenLoginResponse, ResponseForAccessTokenLoginResponse, ResponseForProjectResponse, ProjectsCommand, ProjectCreateRequest, ProjectGetRequest, ProjectPutRequest, ProjectsListRequest, ResponseForProjectsResponse, ResponseForProjectsDeleteResponse, ProjectsDeleteRequest class BitwardenClient: def __init__(self, settings: ClientSettings = None): @@ -12,32 +12,25 @@ def __init__(self, settings: ClientSettings = None): settings_json = json.dumps(settings.to_dict()) self.inner = bitwarden_py.BitwardenClient(settings_json) - def password_login(self, email: str, password: str) -> ResponseForPasswordLoginResponse: - result = self._run_command( - Command(password_login=PasswordLoginRequest(email, password)) - ) - return ResponseForPasswordLoginResponse.from_dict(result) - - def get_user_api_key(self, secret: str, is_otp: bool = False) -> ResponseForUserAPIKeyResponse: - result = self._run_command( - Command(get_user_api_key=SecretVerificationRequest( - secret if not is_otp else None, secret if is_otp else None)) + def access_token_login(self, access_token: str): + self._run_command( + Command(access_token_login=AccessTokenLoginRequest(access_token)) ) - return ResponseForUserAPIKeyResponse.from_dict(result) - - def sync(self, exclude_subdomains: bool = False) -> ResponseForSyncResponse: - result = self._run_command( - Command(sync=SyncRequest(exclude_subdomains)) - ) - return ResponseForSyncResponse.from_dict(result) def secrets(self): return SecretsClient(self) + def projects(self): + return ProjectsClient(self) + def _run_command(self, command: Command) -> Any: response_json = self.inner.run_command(json.dumps(command.to_dict())) - return json.loads(response_json) + response = json.loads(response_json) + if response["success"] == False: + raise Exception(response["errorMessage"]) + + return response class SecretsClient: def __init__(self, client: BitwardenClient): @@ -52,10 +45,12 @@ def get(self, id: str) -> ResponseForSecretResponse: def create(self, key: str, note: str, organization_id: str, - value: str) -> ResponseForSecretResponse: + value: str, + project_ids: Optional[List[UUID]] = None + ) -> ResponseForSecretResponse: result = self.client._run_command( Command(secrets=SecretsCommand( - create=SecretCreateRequest(key, note, organization_id, value))) + create=SecretCreateRequest(key, note, organization_id, value, project_ids))) ) return ResponseForSecretResponse.from_dict(result) @@ -70,10 +65,12 @@ def update(self, id: str, key: str, note: str, organization_id: str, - value: str) -> ResponseForSecretResponse: + value: str, + project_ids: Optional[List[UUID]] = None + ) -> ResponseForSecretResponse: result = self.client._run_command( Command(secrets=SecretsCommand(update=SecretPutRequest( - id, key, note, organization_id, value))) + id, key, note, organization_id, value, project_ids))) ) return ResponseForSecretResponse.from_dict(result) @@ -82,3 +79,46 @@ def delete(self, ids: List[str]) -> ResponseForSecretsDeleteResponse: Command(secrets=SecretsCommand(delete=SecretsDeleteRequest(ids))) ) return ResponseForSecretsDeleteResponse.from_dict(result) + +class ProjectsClient: + def __init__(self, client: BitwardenClient): + self.client = client + + def get(self, id: str) -> ResponseForProjectResponse: + result = self.client._run_command( + Command(projects=ProjectsCommand(get=ProjectGetRequest(id))) + ) + return ResponseForProjectResponse.from_dict(result) + + def create(self, + name: str, + organization_id: str, + ) -> ResponseForProjectResponse: + result = self.client._run_command( + Command(projects=ProjectsCommand( + create=ProjectCreateRequest(name, organization_id))) + ) + return ResponseForProjectResponse.from_dict(result) + + def list(self, organization_id: str) -> ResponseForProjectsResponse: + result = self.client._run_command( + Command(projects=ProjectsCommand( + list=ProjectsListRequest(organization_id))) + ) + return ResponseForProjectsResponse.from_dict(result) + + def update(self, id: str, + name: str, + organization_id: str, + ) -> ResponseForProjectResponse: + result = self.client._run_command( + Command(projects=ProjectsCommand(update=ProjectPutRequest( + id, name, organization_id))) + ) + return ResponseForProjectResponse.from_dict(result) + + def delete(self, ids: List[str]) -> ResponseForProjectsDeleteResponse: + result = self.client._run_command( + Command(projects=ProjectsCommand(delete=ProjectsDeleteRequest(ids))) + ) + return ResponseForProjectsDeleteResponse.from_dict(result) diff --git a/languages/python/README.md b/languages/python/README.md index 03a4ce572..871be274f 100644 --- a/languages/python/README.md +++ b/languages/python/README.md @@ -9,6 +9,10 @@ ```bash pip install setuptools_rust ``` +- dateutil + ```bash + pip install python-dateutil + ``` # Installation @@ -18,7 +22,7 @@ From the `languages/python/` directory, python3 ./setup.py develop ``` -Move the the resulting `.so` file to `bitwarden_py.so`, if it isn't already there. +Rename the the resulting `.so` file to `bitwarden_py.so`, if it isn't already there. # Run diff --git a/languages/python/example.py b/languages/python/example.py new file mode 100644 index 000000000..2ee1cb280 --- /dev/null +++ b/languages/python/example.py @@ -0,0 +1,44 @@ +import json +import logging +import sys +from BitwardenClient.bitwarden_client import BitwardenClient +from BitwardenClient.schemas import client_settings_from_dict, DeviceType + +# Create the BitwardenClient, which is used to interact with the SDK +client = BitwardenClient(client_settings_from_dict({ + "apiUrl": "http://localhost:4000", + "deviceType": DeviceType.SDK, + "identityUrl": "http://localhost:33656", + "userAgent": "Python", +})) + +# Add some logging & set the org id +logging.basicConfig(level=logging.DEBUG) +organization_id = "org_id_here" + +# Attempt to authenticate with the Secrets Manager Access Token +client.access_token_login("access_token_here") + +# -- Example Project Commands -- + +project = client.projects().create("ProjectName", organization_id) +project2 = client.projects().create("Project - Don't Delete Me!", organization_id) +updated_project = client.projects().update(project.data.id, "Cool New Project Name", organization_id) +get_that_project = client.projects().get(project.data.id) + +input("Press Enter to delete the project...") +client.projects().delete([project.data.id]) + +print(client.projects().list(organization_id)) + +# -- Example Secret Commands -- + +secret = client.secrets().create("TEST_SECRET", "This is a test secret", organization_id, "Secret1234!", [project2.data.id]) +secret2 = client.secrets().create("Secret - Don't Delete Me!", "This is a test secret that will stay", organization_id, "Secret1234!", [project2.data.id]) +secret_updated = client.secrets().update(secret.data.id, "TEST_SECRET_UPDATED", "This as an updated test secret", organization_id, "Secret1234!_updated", [project2.data.id]) +secret_retrieved = client.secrets().get(secret.data.id) + +input("Press Enter to delete the secret...") +client.secrets().delete([secret.data.id]) + +print(client.secrets().list(organization_id)) diff --git a/languages/python/login.py b/languages/python/login.py deleted file mode 100644 index 9b756a005..000000000 --- a/languages/python/login.py +++ /dev/null @@ -1,24 +0,0 @@ -import json -import logging -from BitwardenClient.bitwarden_client import BitwardenClient -from BitwardenClient.schemas import client_settings_from_dict - -client = BitwardenClient(client_settings_from_dict({ - "api_url": "http://localhost:4000", - "identity_url": "http://localhost:33656", - "user_agent": "Python", -})) - -logging.basicConfig(level=logging.DEBUG) - -result = client.password_login("test@bitwarden.com", "asdfasdf") -print(result) -print(client.get_user_api_key("asdfasdf")) - -sync = client.sync() - -secret = client.secrets().create("TEST_SECRET", "This is a test secret", - sync.data.profile.organizations[0].id, "Secret1234!") -print(secret) - -client.secrets().delete([secret.data.id]) From 56fb552b312c47340122ad7c6181c55e878ae929 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 28 Nov 2023 15:06:50 +0100 Subject: [PATCH 039/157] Lock SDK to 6.0.100 and ignore with Renovate updates (#294) --- .github/renovate.json | 3 ++- languages/csharp/global.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/renovate.json b/.github/renovate.json index 16222e52c..42f5f93f2 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -27,5 +27,6 @@ "matchManagers": ["github-actions"], "matchUpdateTypes": ["minor", "patch"] } - ] + ], + "ignoreDeps": ["dotnet-sdk"] } diff --git a/languages/csharp/global.json b/languages/csharp/global.json index 527fd31d3..10b65be86 100644 --- a/languages/csharp/global.json +++ b/languages/csharp/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "6.0.413", + "version": "6.0.100", "rollForward": "latestFeature" } } From f18020de098c43ce30fbc81a6424a707f1aa593e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 10:06:16 -0500 Subject: [PATCH 040/157] [deps]: Update gh minor (#366) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-android.yml | 2 +- .github/workflows/publish-rust-crates.yml | 2 +- .github/workflows/release-cli.yml | 2 +- .github/workflows/release-napi.yml | 2 +- .github/workflows/version-bump.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 110e3758b..d04d15a45 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -102,7 +102,7 @@ jobs: run: ./build-schemas.sh - name: Publish - uses: gradle/gradle-build-action@842c587ad8aa4c68eeba24c396e15af4c2e9f30a # v2.9.0 + uses: gradle/gradle-build-action@87a9a15658c426a54dd469d4fc7dc1a73ca9d4a6 # v2.10.0 with: arguments: sdk:publish build-root-directory: languages/kotlin diff --git a/.github/workflows/publish-rust-crates.yml b/.github/workflows/publish-rust-crates.yml index 468570cd8..b5a03ad47 100644 --- a/.github/workflows/publish-rust-crates.yml +++ b/.github/workflows/publish-rust-crates.yml @@ -103,7 +103,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Login to Azure - uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + uses: Azure/login@4c88f01b0e3a5600e08a37889921afd060f75cf0 # v1.5.0 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index 4c52529e8..9ef782b7f 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -128,7 +128,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Login to Azure - uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + uses: Azure/login@4c88f01b0e3a5600e08a37889921afd060f75cf0 # v1.5.0 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} diff --git a/.github/workflows/release-napi.yml b/.github/workflows/release-napi.yml index daa450c0b..1e7b2e022 100644 --- a/.github/workflows/release-napi.yml +++ b/.github/workflows/release-napi.yml @@ -126,7 +126,7 @@ jobs: run: npm run tsc - name: Login to Azure - uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + uses: Azure/login@4c88f01b0e3a5600e08a37889921afd060f75cf0 # v1.5.0 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index d902c5700..9fb441fa9 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -43,7 +43,7 @@ jobs: run: cargo install cargo-edit - name: Login to Azure - Prod Subscription - uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + uses: Azure/login@4c88f01b0e3a5600e08a37889921afd060f75cf0 # v1.5.0 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} From 31c005f9d53656b39088f700a798789add32b1b7 Mon Sep 17 00:00:00 2001 From: Luka Potkonjak <46886659+lukpotSym@users.noreply.github.com> Date: Tue, 28 Nov 2023 18:59:07 +0100 Subject: [PATCH 041/157] Java wrapper for SDK (#179) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Type of change ``` - [ ] Bug fix - [x] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Added a Maven-based Java project that wraps native C library for the SDK and exposed its commands through BitwardenClient class. ## Code changes - Updated schemas.ts to generate Java classes for the API - difference with current generators is that it creates multiple files - Added BitWardenClient class as a wrapper for the native SDK library - BitwardenLibrary inteface uses JNA to link to the native lib - AutoCloseable inteface implemented to deal with freeing memory, client can be used with try-with-resources or close() can be called explicitly (finalize() method is deprecated in newer Java versions) --------- Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com> Co-authored-by: Daniel GarcΓ­a Co-authored-by: Oscar Hinton --- .github/workflows/build-java.yml | 68 +++++ .github/workflows/generate_schemas.yml | 7 + .gitignore | 1 + .vscode/tasks.json | 11 + languages/java/.gitignore | 3 + languages/java/README.md | 73 +++++ languages/java/build.gradle | 90 +++++++ .../java/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 63721 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 + languages/java/gradlew | 249 ++++++++++++++++++ languages/java/gradlew.bat | 92 +++++++ languages/java/settings.gradle | 5 + .../com/bitwarden/sdk/BitwardenClient.java | 87 ++++++ .../sdk/BitwardenClientException.java | 8 + .../com/bitwarden/sdk/BitwardenLibrary.java | 13 + .../com/bitwarden/sdk/BitwardenSettings.java | 32 +++ .../java/com/bitwarden/sdk/CommandRunner.java | 45 ++++ .../com/bitwarden/sdk/ExampleProgram.java | 42 +++ .../com/bitwarden/sdk/ProjectsClient.java | 109 ++++++++ .../java/com/bitwarden/sdk/SecretsClient.java | 115 ++++++++ .../com/bitwarden/sdk/ThrowingFunction.java | 7 + support/scripts/schemas.ts | 25 +- 22 files changed, 1088 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build-java.yml create mode 100644 languages/java/.gitignore create mode 100644 languages/java/README.md create mode 100644 languages/java/build.gradle create mode 100644 languages/java/gradle/wrapper/gradle-wrapper.jar create mode 100644 languages/java/gradle/wrapper/gradle-wrapper.properties create mode 100755 languages/java/gradlew create mode 100644 languages/java/gradlew.bat create mode 100644 languages/java/settings.gradle create mode 100644 languages/java/src/main/java/com/bitwarden/sdk/BitwardenClient.java create mode 100644 languages/java/src/main/java/com/bitwarden/sdk/BitwardenClientException.java create mode 100644 languages/java/src/main/java/com/bitwarden/sdk/BitwardenLibrary.java create mode 100644 languages/java/src/main/java/com/bitwarden/sdk/BitwardenSettings.java create mode 100644 languages/java/src/main/java/com/bitwarden/sdk/CommandRunner.java create mode 100644 languages/java/src/main/java/com/bitwarden/sdk/ExampleProgram.java create mode 100644 languages/java/src/main/java/com/bitwarden/sdk/ProjectsClient.java create mode 100644 languages/java/src/main/java/com/bitwarden/sdk/SecretsClient.java create mode 100644 languages/java/src/main/java/com/bitwarden/sdk/ThrowingFunction.java diff --git a/.github/workflows/build-java.yml b/.github/workflows/build-java.yml new file mode 100644 index 000000000..e500424dd --- /dev/null +++ b/.github/workflows/build-java.yml @@ -0,0 +1,68 @@ +name: Build Java SDK + +on: + pull_request: + branches: + - master + +jobs: + generate_schemas: + uses: ./.github/workflows/generate_schemas.yml + + build_rust: + uses: ./.github/workflows/build-rust-cross-platform.yml + + build_java: + name: Build Java + runs-on: ubuntu-22.04 + needs: + - generate_schemas + - build_rust + + steps: + - name: Checkout Repository + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + + - name: Download Java schemas artifact + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + with: + name: sdk-schemas-java + path: languages/java/src/main/java/bit/sdk/schema/ + + - name: Setup Java + uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0 + with: + distribution: temurin + java-version: 17 + + - name: Download x86_64-apple-darwin files + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + with: + name: libbitwarden_c_files-x86_64-apple-darwin + path: languages/java/src/main/resources/darwin-x64 + + - name: Download aarch64-apple-darwin files + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + with: + name: libbitwarden_c_files-aarch64-apple-darwin + path: languages/java/src/main/resources/darwin-aarch64 + + - name: Download x86_64-unknown-linux-gnu files + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + with: + name: libbitwarden_c_files-x86_64-unknown-linux-gnu + path: languages/java/src/main/resources/ubuntu-x64 + + - name: Download x86_64-pc-windows-msvc files + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + with: + name: libbitwarden_c_files-x86_64-pc-windows-msvc + path: languages/java/src/main/resources/windows-x64 + + - name: Publish Maven + uses: gradle/gradle-build-action@b5126f31dbc19dd434c3269bf8c28c315e121da2 # v2.8.1 + with: + arguments: publish + build-root-directory: languages/java + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/generate_schemas.yml b/.github/workflows/generate_schemas.yml index 5f5742da0..675f53c1e 100644 --- a/.github/workflows/generate_schemas.yml +++ b/.github/workflows/generate_schemas.yml @@ -63,3 +63,10 @@ jobs: name: sdk-schemas-json path: ${{ github.workspace }}/support/schemas/* if-no-files-found: error + + - name: Upload java schemas artifact + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + with: + name: sdk-schemas-java + path: ${{ github.workspace }}/languages/java/src/main/java/com/bitwarden/sdk/schema/* + if-no-files-found: error diff --git a/.gitignore b/.gitignore index 3f459493b..38b074349 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,4 @@ crates/bitwarden-napi/src-ts/bitwarden_client/schemas.ts languages/csharp/Bitwarden.Sdk/schemas.cs languages/js_webassembly/bitwarden_client/schemas.ts languages/python/BitwardenClient/schemas.py +languages/java/src/main/java/com/bitwarden/sdk/schema diff --git a/.vscode/tasks.json b/.vscode/tasks.json index fa97b8fc7..71400c06e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -46,6 +46,17 @@ "options": { "cwd": "${workspaceFolder}/languages/python" } + }, + { + "label": "buildJava", + "type": "shell", + "command": "gradle", + "args": ["build"], + "dependsOrder": "sequence", + "dependsOn": ["rust: bitwarden-c build"], + "options": { + "cwd": "${workspaceFolder}/languages/java" + } } ] } diff --git a/languages/java/.gitignore b/languages/java/.gitignore new file mode 100644 index 000000000..b5b6c5697 --- /dev/null +++ b/languages/java/.gitignore @@ -0,0 +1,3 @@ +target +.gradle +src/main/resources diff --git a/languages/java/README.md b/languages/java/README.md new file mode 100644 index 000000000..06f1ffec3 --- /dev/null +++ b/languages/java/README.md @@ -0,0 +1,73 @@ +# Bitwarden Secrets Manager SDK + +Java bindings for interacting with the [Bitwarden Secrets Manager]. This is a beta release and might be missing some +functionality. + +## Create access token + +Review the help documentation on [Access Tokens] + +## Usage code snippets + +### Create new Bitwarden client + +```java +BitwardenSettings bitwardenSettings = new BitwardenSettings(); +bitwardenSettings.setApiUrl("https://api.bitwarden.com"); +bitwardenSettings.setIdentityUrl("https://identity.bitwarden.com"); +BitwardenClient bitwardenClient = new BitwardenClient(bitwardenSettings); +bitwardenClient.accessTokenLogin(""); +``` + +### Create new project + +```java +UUID organizationId = UUID.fromString(""); +var projectResponse = bitwardenClient.projects().create(organizationId, "TestProject"); +``` + +### List all projects + +```java +var projectsResponse = bitwardenClient.projects().list(organizationId); +``` + +### Update project + +```java +UUID projectId = projectResponse.getID(); +projectResponse = bitwardenClient.projects().get(projectId); +projectResponse = bitwardenClient.projects.update(projectId, organizationId, "TestProjectUpdated"); +``` + +### Add new secret + +```java +String key = "key"; +String value = "value"; +String note = "note"; +var secretResponse = bitwardenClient.secrets().create(key, value, note, organizationId, new UUID[]{projectId}); +UUID secretId = secretResponse.getID(); +``` + +### Update secret + +```java +bitwardenClient.secrets().update(secretId, key2, value2, note2, organizationId, new UUID[]{projectId}); +``` + +### List secrets + +```java +var secretIdentifiersResponse secretIdentifiersResponse = bitwardenClient.secrets().list(organizationId); +``` + +# Delete secret or project + +```java +bitwardenClient.secrets().delete(new UUID[]{secretId}); +bitwardenClient.projects().delete(new UUID[]{projectId}); +``` + +[Access Tokens]: https://bitwarden.com/help/access-tokens/ +[Bitwarden Secrets Manager]: https://bitwarden.com/products/secrets-manager/ \ No newline at end of file diff --git a/languages/java/build.gradle b/languages/java/build.gradle new file mode 100644 index 000000000..dc17386c4 --- /dev/null +++ b/languages/java/build.gradle @@ -0,0 +1,90 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +plugins { + id 'java-library' + id 'maven-publish' +} + +repositories { + mavenLocal() + maven { + url = uri('https://repo.maven.apache.org/maven2/') + } + + dependencies { + api 'com.fasterxml.jackson.core:jackson-core:2.9.10' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.10' + api 'net.java.dev.jna:jna-platform:5.12.1' + } + + description = 'BitwardenSDK' + java.sourceCompatibility = JavaVersion.VERSION_1_8 + + publishing { + publications { + maven(MavenPublication) { + groupId = 'com.bitwarden' + artifactId = 'sdk' + + // Determine the version from the git history. + // + // PRs: use the branch name. + // Master: Grab it from `crates/bitwarden/Cargo.toml` + + def branchName = "git branch --show-current".execute().text.trim() + + if (branchName == "master") { + def content = ['grep', '-o', '^version = ".*"', '../../crates/bitwarden/Cargo.toml'].execute().text.trim() + def match = ~/version = "(.*)"/ + def matcher = match.matcher(content) + matcher.find() + + version = "${matcher.group(1)}-SNAPSHOT" + } else { + // branchName-SNAPSHOT + version = "${branchName.replaceAll('/', '-')}-SNAPSHOT" + } + + afterEvaluate { + from components.java + } + } + } + repositories { + maven { + name = "GitHubPackages" + url = "https://maven.pkg.github.com/bitwarden/sdk" + credentials { + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } + } + } + } +} + +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' +} + +tasks.withType(Javadoc) { + options.encoding = 'UTF-8' +} + +// Gradle build requires GitHub workflow to copy native library to resources +// Uncomment copyNativeLib and jar tasks to use the local build (modify architecture if needed) +//tasks.register('copyNativeLib', Copy) { +// delete 'src/main/resources/darwin-aarch64' +// from '../../target/debug' +// include '*libbitwarden_c*.dylib' +// include '*libbitwarden_c*.so' +// include '*bitwarden_c*.dll' +// into 'src/main/resources/darwin-aarch64' +//} +// +//jar { +// dependsOn tasks.named("copyNativeLib").get() +// from 'src/main/resources' +//} diff --git a/languages/java/gradle/wrapper/gradle-wrapper.jar b/languages/java/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7f93135c49b765f8051ef9d0a6055ff8e46073d8 GIT binary patch literal 63721 zcmb5Wb9gP!wgnp7wrv|bwr$&XvSZt}Z6`anZSUAlc9NHKf9JdJ;NJVr`=eI(_pMp0 zy1VAAG3FfAOI`{X1O)&90s;U4K;XLp008~hCjbEC_fbYfS%6kTR+JtXK>nW$ZR+`W ze|#J8f4A@M|F5BpfUJb5h>|j$jOe}0oE!`Zf6fM>CR?!y@zU(cL8NsKk`a z6tx5mAkdjD;J=LcJ;;Aw8p!v#ouk>mUDZF@ zK>yvw%+bKu+T{Nk@LZ;zkYy0HBKw06_IWcMHo*0HKpTsEFZhn5qCHH9j z)|XpN&{`!0a>Vl+PmdQc)Yg4A(AG-z!+@Q#eHr&g<9D?7E)_aEB?s_rx>UE9TUq|? z;(ggJt>9l?C|zoO@5)tu?EV0x_7T17q4fF-q3{yZ^ipUbKcRZ4Qftd!xO(#UGhb2y>?*@{xq%`(-`2T^vc=#< zx!+@4pRdk&*1ht2OWk^Z5IAQ0YTAXLkL{(D*$gENaD)7A%^XXrCchN&z2x+*>o2FwPFjWpeaL=!tzv#JOW#( z$B)Nel<+$bkH1KZv3&-}=SiG~w2sbDbAWarg%5>YbC|}*d9hBjBkR(@tyM0T)FO$# zPtRXukGPnOd)~z=?avu+4Co@wF}1T)-uh5jI<1$HLtyDrVak{gw`mcH@Q-@wg{v^c zRzu}hMKFHV<8w}o*yg6p@Sq%=gkd~;`_VGTS?L@yVu`xuGy+dH6YOwcP6ZE`_0rK% zAx5!FjDuss`FQ3eF|mhrWkjux(Pny^k$u_)dyCSEbAsecHsq#8B3n3kDU(zW5yE|( zgc>sFQywFj5}U*qtF9Y(bi*;>B7WJykcAXF86@)z|0-Vm@jt!EPoLA6>r)?@DIobIZ5Sx zsc@OC{b|3%vaMbyeM|O^UxEYlEMHK4r)V-{r)_yz`w1*xV0|lh-LQOP`OP`Pk1aW( z8DSlGN>Ts|n*xj+%If~+E_BxK)~5T#w6Q1WEKt{!Xtbd`J;`2a>8boRo;7u2M&iOop4qcy<)z023=oghSFV zST;?S;ye+dRQe>ygiJ6HCv4;~3DHtJ({fWeE~$H@mKn@Oh6Z(_sO>01JwH5oA4nvK zr5Sr^g+LC zLt(i&ecdmqsIJGNOSUyUpglvhhrY8lGkzO=0USEKNL%8zHshS>Qziu|`eyWP^5xL4 zRP122_dCJl>hZc~?58w~>`P_s18VoU|7(|Eit0-lZRgLTZKNq5{k zE?V=`7=R&ro(X%LTS*f+#H-mGo_j3dm@F_krAYegDLk6UV{`UKE;{YSsn$ z(yz{v1@p|p!0>g04!eRSrSVb>MQYPr8_MA|MpoGzqyd*$@4j|)cD_%^Hrd>SorF>@ zBX+V<@vEB5PRLGR(uP9&U&5=(HVc?6B58NJT_igiAH*q~Wb`dDZpJSKfy5#Aag4IX zj~uv74EQ_Q_1qaXWI!7Vf@ZrdUhZFE;L&P_Xr8l@GMkhc#=plV0+g(ki>+7fO%?Jb zl+bTy7q{w^pTb{>(Xf2q1BVdq?#f=!geqssXp z4pMu*q;iiHmA*IjOj4`4S&|8@gSw*^{|PT}Aw~}ZXU`6=vZB=GGeMm}V6W46|pU&58~P+?LUs%n@J}CSrICkeng6YJ^M? zS(W?K4nOtoBe4tvBXs@@`i?4G$S2W&;$z8VBSM;Mn9 zxcaEiQ9=vS|bIJ>*tf9AH~m&U%2+Dim<)E=}KORp+cZ^!@wI`h1NVBXu{@%hB2Cq(dXx_aQ9x3mr*fwL5!ZryQqi|KFJuzvP zK1)nrKZ7U+B{1ZmJub?4)Ln^J6k!i0t~VO#=q1{?T)%OV?MN}k5M{}vjyZu#M0_*u z8jwZKJ#Df~1jcLXZL7bnCEhB6IzQZ-GcoQJ!16I*39iazoVGugcKA{lhiHg4Ta2fD zk1Utyc5%QzZ$s3;p0N+N8VX{sd!~l*Ta3|t>lhI&G`sr6L~G5Lul`>m z{!^INm?J|&7X=;{XveF!(b*=?9NAp4y&r&N3(GKcW4rS(Ejk|Lzs1PrxPI_owB-`H zg3(Rruh^&)`TKA6+_!n>RdI6pw>Vt1_j&+bKIaMTYLiqhZ#y_=J8`TK{Jd<7l9&sY z^^`hmi7^14s16B6)1O;vJWOF$=$B5ONW;;2&|pUvJlmeUS&F;DbSHCrEb0QBDR|my zIs+pE0Y^`qJTyH-_mP=)Y+u^LHcuZhsM3+P||?+W#V!_6E-8boP#R-*na4!o-Q1 zVthtYhK{mDhF(&7Okzo9dTi03X(AE{8cH$JIg%MEQca`S zy@8{Fjft~~BdzWC(di#X{ny;!yYGK9b@=b|zcKZ{vv4D8i+`ilOPl;PJl{!&5-0!w z^fOl#|}vVg%=n)@_e1BrP)`A zKPgs`O0EO}Y2KWLuo`iGaKu1k#YR6BMySxQf2V++Wo{6EHmK>A~Q5o73yM z-RbxC7Qdh0Cz!nG+7BRZE>~FLI-?&W_rJUl-8FDIaXoNBL)@1hwKa^wOr1($*5h~T zF;%f^%<$p8Y_yu(JEg=c_O!aZ#)Gjh$n(hfJAp$C2he555W5zdrBqjFmo|VY+el;o z=*D_w|GXG|p0**hQ7~9-n|y5k%B}TAF0iarDM!q-jYbR^us(>&y;n^2l0C%@2B}KM zyeRT9)oMt97Agvc4sEKUEy%MpXr2vz*lb zh*L}}iG>-pqDRw7ud{=FvTD?}xjD)w{`KzjNom-$jS^;iw0+7nXSnt1R@G|VqoRhE%12nm+PH?9`(4rM0kfrZzIK9JU=^$YNyLvAIoxl#Q)xxDz!^0@zZ zSCs$nfcxK_vRYM34O<1}QHZ|hp4`ioX3x8(UV(FU$J@o%tw3t4k1QPmlEpZa2IujG&(roX_q*%e`Hq|);0;@k z0z=fZiFckp#JzW0p+2A+D$PC~IsakhJJkG(c;CqAgFfU0Z`u$PzG~-9I1oPHrCw&)@s^Dc~^)#HPW0Ra}J^=|h7Fs*<8|b13ZzG6MP*Q1dkoZ6&A^!}|hbjM{2HpqlSXv_UUg1U4gn z3Q)2VjU^ti1myodv+tjhSZp%D978m~p& z43uZUrraHs80Mq&vcetqfQpQP?m!CFj)44t8Z}k`E798wxg&~aCm+DBoI+nKq}&j^ zlPY3W$)K;KtEajks1`G?-@me7C>{PiiBu+41#yU_c(dITaqE?IQ(DBu+c^Ux!>pCj zLC|HJGU*v+!it1(;3e`6igkH(VA)-S+k(*yqxMgUah3$@C zz`7hEM47xr>j8^g`%*f=6S5n>z%Bt_Fg{Tvmr+MIsCx=0gsu_sF`q2hlkEmisz#Fy zj_0;zUWr;Gz}$BS%Y`meb(=$d%@Crs(OoJ|}m#<7=-A~PQbyN$x%2iXP2@e*nO0b7AwfH8cCUa*Wfu@b)D_>I*%uE4O3 z(lfnB`-Xf*LfC)E}e?%X2kK7DItK6Tf<+M^mX0Ijf_!IP>7c8IZX%8_#0060P{QMuV^B9i<^E`_Qf0pv9(P%_s8D`qvDE9LK9u-jB}J2S`(mCO&XHTS04Z5Ez*vl^T%!^$~EH8M-UdwhegL>3IQ*)(MtuH2Xt1p!fS4o~*rR?WLxlA!sjc2(O znjJn~wQ!Fp9s2e^IWP1C<4%sFF}T4omr}7+4asciyo3DntTgWIzhQpQirM$9{EbQd z3jz9vS@{aOqTQHI|l#aUV@2Q^Wko4T0T04Me4!2nsdrA8QY1%fnAYb~d2GDz@lAtfcHq(P7 zaMBAGo}+NcE-K*@9y;Vt3*(aCaMKXBB*BJcD_Qnxpt75r?GeAQ}*|>pYJE=uZb73 zC>sv)18)q#EGrTG6io*}JLuB_jP3AU1Uiu$D7r|2_zlIGb9 zjhst#ni)Y`$)!fc#reM*$~iaYoz~_Cy7J3ZTiPm)E?%`fbk`3Tu-F#`{i!l5pNEn5 zO-Tw-=TojYhzT{J=?SZj=Z8#|eoF>434b-DXiUsignxXNaR3 zm_}4iWU$gt2Mw5NvZ5(VpF`?X*f2UZDs1TEa1oZCif?Jdgr{>O~7}-$|BZ7I(IKW`{f;@|IZFX*R8&iT= zoWstN8&R;}@2Ka%d3vrLtR|O??ben;k8QbS-WB0VgiCz;<$pBmIZdN!aalyCSEm)crpS9dcD^Y@XT1a3+zpi-`D}e#HV<} z$Y(G&o~PvL-xSVD5D?JqF3?B9rxGWeb=oEGJ3vRp5xfBPlngh1O$yI95EL+T8{GC@ z98i1H9KhZGFl|;`)_=QpM6H?eDPpw~^(aFQWwyXZ8_EEE4#@QeT_URray*mEOGsGc z6|sdXtq!hVZo=d#+9^@lm&L5|q&-GDCyUx#YQiccq;spOBe3V+VKdjJA=IL=Zn%P} zNk=_8u}VhzFf{UYZV0`lUwcD&)9AFx0@Fc6LD9A6Rd1=ga>Mi0)_QxM2ddCVRmZ0d z+J=uXc(?5JLX3=)e)Jm$HS2yF`44IKhwRnm2*669_J=2LlwuF5$1tAo@ROSU@-y+;Foy2IEl2^V1N;fk~YR z?&EP8#t&m0B=?aJeuz~lHjAzRBX>&x=A;gIvb>MD{XEV zV%l-+9N-)i;YH%nKP?>f`=?#`>B(`*t`aiPLoQM(a6(qs4p5KFjDBN?8JGrf3z8>= zi7sD)c)Nm~x{e<^jy4nTx${P~cwz_*a>%0_;ULou3kHCAD7EYkw@l$8TN#LO9jC( z1BeFW`k+bu5e8Ns^a8dPcjEVHM;r6UX+cN=Uy7HU)j-myRU0wHd$A1fNI~`4;I~`zC)3ul#8#^rXVSO*m}Ag>c%_;nj=Nv$rCZ z*~L@C@OZg%Q^m)lc-kcX&a*a5`y&DaRxh6O*dfhLfF+fU5wKs(1v*!TkZidw*)YBP za@r`3+^IHRFeO%!ai%rxy;R;;V^Fr=OJlpBX;(b*3+SIw}7= zIq$*Thr(Zft-RlY)D3e8V;BmD&HOfX+E$H#Y@B3?UL5L~_fA-@*IB-!gItK7PIgG9 zgWuGZK_nuZjHVT_Fv(XxtU%)58;W39vzTI2n&)&4Dmq7&JX6G>XFaAR{7_3QB6zsT z?$L8c*WdN~nZGiscY%5KljQARN;`w$gho=p006z;n(qIQ*Zu<``TMO3n0{ARL@gYh zoRwS*|Niw~cR!?hE{m*y@F`1)vx-JRfqET=dJ5_(076st(=lFfjtKHoYg`k3oNmo_ zNbQEw8&sO5jAYmkD|Zaz_yUb0rC})U!rCHOl}JhbYIDLzLvrZVw0~JO`d*6f;X&?V=#T@ND*cv^I;`sFeq4 z##H5;gpZTb^0Hz@3C*~u0AqqNZ-r%rN3KD~%Gw`0XsIq$(^MEb<~H(2*5G^<2(*aI z%7}WB+TRlMIrEK#s0 z93xn*Ohb=kWFc)BNHG4I(~RPn-R8#0lqyBBz5OM6o5|>x9LK@%HaM}}Y5goCQRt2C z{j*2TtT4ne!Z}vh89mjwiSXG=%DURar~=kGNNaO_+Nkb+tRi~Rkf!7a$*QlavziD( z83s4GmQ^Wf*0Bd04f#0HX@ua_d8 z23~z*53ePD6@xwZ(vdl0DLc=>cPIOPOdca&MyR^jhhKrdQO?_jJh`xV3GKz&2lvP8 zEOwW6L*ufvK;TN{=S&R@pzV^U=QNk^Ec}5H z+2~JvEVA{`uMAr)?Kf|aW>33`)UL@bnfIUQc~L;TsTQ6>r-<^rB8uoNOJ>HWgqMI8 zSW}pZmp_;z_2O5_RD|fGyTxaxk53Hg_3Khc<8AUzV|ZeK{fp|Ne933=1&_^Dbv5^u zB9n=*)k*tjHDRJ@$bp9mrh}qFn*s}npMl5BMDC%Hs0M0g-hW~P*3CNG06G!MOPEQ_ zi}Qs-6M8aMt;sL$vlmVBR^+Ry<64jrm1EI1%#j?c?4b*7>)a{aDw#TfTYKq+SjEFA z(aJ&z_0?0JB83D-i3Vh+o|XV4UP+YJ$9Boid2^M2en@APw&wx7vU~t$r2V`F|7Qfo z>WKgI@eNBZ-+Og<{u2ZiG%>YvH2L3fNpV9J;WLJoBZda)01Rn;o@){01{7E#ke(7U zHK>S#qZ(N=aoae*4X!0A{)nu0R_sKpi1{)u>GVjC+b5Jyl6#AoQ-1_3UDovNSo`T> z?c-@7XX*2GMy?k?{g)7?Sv;SJkmxYPJPs!&QqB12ejq`Lee^-cDveVWL^CTUldb(G zjDGe(O4P=S{4fF=#~oAu>LG>wrU^z_?3yt24FOx>}{^lCGh8?vtvY$^hbZ)9I0E3r3NOlb9I?F-Yc=r$*~l`4N^xzlV~N zl~#oc>U)Yjl0BxV>O*Kr@lKT{Z09OXt2GlvE38nfs+DD7exl|&vT;)>VFXJVZp9Np zDK}aO;R3~ag$X*|hRVY3OPax|PG`@_ESc8E!mHRByJbZQRS38V2F__7MW~sgh!a>98Q2%lUNFO=^xU52|?D=IK#QjwBky-C>zOWlsiiM&1n z;!&1((Xn1$9K}xabq~222gYvx3hnZPg}VMF_GV~5ocE=-v>V=T&RsLBo&`)DOyIj* zLV{h)JU_y*7SdRtDajP_Y+rBkNN*1_TXiKwHH2&p51d(#zv~s#HwbNy?<+(=9WBvo zw2hkk2Dj%kTFhY+$T+W-b7@qD!bkfN#Z2ng@Pd=i3-i?xYfs5Z*1hO?kd7Sp^9`;Y zM2jeGg<-nJD1er@Pc_cSY7wo5dzQX44=%6rn}P_SRbpzsA{6B+!$3B0#;}qwO37G^ zL(V_5JK`XT?OHVk|{_$vQ|oNEpab*BO4F zUTNQ7RUhnRsU`TK#~`)$icsvKh~(pl=3p6m98@k3P#~upd=k*u20SNcb{l^1rUa)>qO997)pYRWMncC8A&&MHlbW?7i^7M`+B$hH~Y|J zd>FYOGQ;j>Zc2e7R{KK7)0>>nn_jYJy&o@sK!4G>-rLKM8Hv)f;hi1D2fAc$+six2 zyVZ@wZ6x|fJ!4KrpCJY=!Mq0;)X)OoS~{Lkh6u8J`eK%u0WtKh6B>GW_)PVc zl}-k`p09qwGtZ@VbYJC!>29V?Dr>>vk?)o(x?!z*9DJ||9qG-&G~#kXxbw{KKYy}J zQKa-dPt~M~E}V?PhW0R26xdA%1T*%ra6SguGu50YHngOTIv)@N|YttEXo#OZfgtP7;H?EeZZxo<}3YlYxtBq znJ!WFR^tmGf0Py}N?kZ(#=VtpC@%xJkDmfcCoBTxq zr_|5gP?u1@vJZbxPZ|G0AW4=tpb84gM2DpJU||(b8kMOV1S3|(yuwZJ&rIiFW(U;5 zUtAW`O6F6Zy+eZ1EDuP~AAHlSY-+A_eI5Gx)%*uro5tljy}kCZU*_d7)oJ>oQSZ3* zneTn`{gnNC&uJd)0aMBzAg021?YJ~b(fmkwZAd696a=0NzBAqBN54KuNDwa*no(^O z6p05bioXUR^uXjpTol*ppHp%1v9e)vkoUAUJyBx3lw0UO39b0?^{}yb!$yca(@DUn zCquRF?t=Zb9`Ed3AI6|L{eX~ijVH`VzSMheKoP7LSSf4g>md>`yi!TkoG5P>Ofp+n z(v~rW+(5L96L{vBb^g51B=(o)?%%xhvT*A5btOpw(TKh^g^4c zw>0%X!_0`{iN%RbVk+A^f{w-4-SSf*fu@FhruNL##F~sF24O~u zyYF<3el2b$$wZ_|uW#@Ak+VAGk#e|kS8nL1g>2B-SNMjMp^8;-FfeofY2fphFHO!{ z*!o4oTb{4e;S<|JEs<1_hPsmAlVNk?_5-Fp5KKU&d#FiNW~Y+pVFk@Cua1I{T+1|+ zHx6rFMor)7L)krbilqsWwy@T+g3DiH5MyVf8Wy}XbEaoFIDr~y;@r&I>FMW{ z?Q+(IgyebZ)-i4jNoXQhq4Muy9Fv+OxU;9_Jmn+<`mEC#%2Q_2bpcgzcinygNI!&^ z=V$)o2&Yz04~+&pPWWn`rrWxJ&}8khR)6B(--!9Q zubo}h+1T)>a@c)H^i``@<^j?|r4*{;tQf78(xn0g39IoZw0(CwY1f<%F>kEaJ zp9u|IeMY5mRdAlw*+gSN^5$Q)ShM<~E=(c8QM+T-Qk)FyKz#Sw0EJ*edYcuOtO#~Cx^(M7w5 z3)rl#L)rF|(Vun2LkFr!rg8Q@=r>9p>(t3Gf_auiJ2Xx9HmxYTa|=MH_SUlYL`mz9 zTTS$`%;D-|Jt}AP1&k7PcnfFNTH0A-*FmxstjBDiZX?}%u%Yq94$fUT&z6od+(Uk> zuqsld#G(b$G8tus=M!N#oPd|PVFX)?M?tCD0tS%2IGTfh}3YA3f&UM)W$_GNV8 zQo+a(ml2Km4o6O%gKTCSDNq+#zCTIQ1*`TIJh~k6Gp;htHBFnne))rlFdGqwC6dx2+La1&Mnko*352k0y z+tQcwndQlX`nc6nb$A9?<-o|r*%aWXV#=6PQic0Ok_D;q>wbv&j7cKc!w4~KF#-{6 z(S%6Za)WpGIWf7jZ3svNG5OLs0>vCL9{V7cgO%zevIVMH{WgP*^D9ws&OqA{yr|m| zKD4*07dGXshJHd#e%x%J+qmS^lS|0Bp?{drv;{@{l9ArPO&?Q5=?OO9=}h$oVe#3b z3Yofj&Cb}WC$PxmRRS)H%&$1-)z7jELS}!u!zQ?A^Y{Tv4QVt*vd@uj-^t2fYRzQj zfxGR>-q|o$3sGn^#VzZ!QQx?h9`njeJry}@x?|k0-GTTA4y3t2E`3DZ!A~D?GiJup z)8%PK2^9OVRlP(24P^4_<|D=H^7}WlWu#LgsdHzB%cPy|f8dD3|A^mh4WXxhLTVu_ z@abE{6Saz|Y{rXYPd4$tfPYo}ef(oQWZ=4Bct-=_9`#Qgp4ma$n$`tOwq#&E18$B; z@Bp)bn3&rEi0>fWWZ@7k5WazfoX`SCO4jQWwVuo+$PmSZn^Hz?O(-tW@*DGxuf)V1 zO_xm&;NVCaHD4dqt(-MlszI3F-p?0!-e$fbiCeuaw66h^TTDLWuaV<@C-`=Xe5WL) zwooG7h>4&*)p3pKMS3O!4>-4jQUN}iAMQ)2*70?hP~)TzzR?-f@?Aqy$$1Iy8VGG$ zMM?8;j!pUX7QQD$gRc_#+=raAS577ga-w?jd`vCiN5lu)dEUkkUPl9!?{$IJNxQys z*E4e$eF&n&+AMRQR2gcaFEjAy*r)G!s(P6D&TfoApMFC_*Ftx0|D0@E-=B7tezU@d zZ{hGiN;YLIoSeRS;9o%dEua4b%4R3;$SugDjP$x;Z!M!@QibuSBb)HY!3zJ7M;^jw zlx6AD50FD&p3JyP*>o+t9YWW8(7P2t!VQQ21pHJOcG_SXQD;(5aX#M6x##5H_Re>6lPyDCjxr*R(+HE%c&QN+b^tbT zXBJk?p)zhJj#I?&Y2n&~XiytG9!1ox;bw5Rbj~)7c(MFBb4>IiRATdhg zmiEFlj@S_hwYYI(ki{}&<;_7(Z0Qkfq>am z&LtL=2qc7rWguk3BtE4zL41@#S;NN*-jWw|7Kx7H7~_%7fPt;TIX}Ubo>;Rmj94V> zNB1=;-9AR7s`Pxn}t_6^3ahlq53e&!Lh85uG zec0vJY_6e`tg7LgfrJ3k!DjR)Bi#L@DHIrZ`sK=<5O0Ip!fxGf*OgGSpP@Hbbe&$9 z;ZI}8lEoC2_7;%L2=w?tb%1oL0V+=Z`7b=P&lNGY;yVBazXRYu;+cQDKvm*7NCxu&i;zub zAJh#11%?w>E2rf2e~C4+rAb-&$^vsdACs7 z@|Ra!OfVM(ke{vyiqh7puf&Yp6cd6{DptUteYfIRWG3pI+5< zBVBI_xkBAc<(pcb$!Y%dTW(b;B;2pOI-(QCsLv@U-D1XJ z(Gk8Q3l7Ws46Aktuj>|s{$6zA&xCPuXL-kB`CgYMs}4IeyG*P51IDwW?8UNQd+$i~ zlxOPtSi5L|gJcF@DwmJA5Ju8HEJ>o{{upwIpb!f{2(vLNBw`7xMbvcw<^{Fj@E~1( z?w`iIMieunS#>nXlmUcSMU+D3rX28f?s7z;X=se6bo8;5vM|O^(D6{A9*ChnGH!RG zP##3>LDC3jZPE4PH32AxrqPk|yIIrq~`aL-=}`okhNu9aT%q z1b)7iJ)CN=V#Ly84N_r7U^SH2FGdE5FpTO2 z630TF$P>GNMu8`rOytb(lB2};`;P4YNwW1<5d3Q~AX#P0aX}R2b2)`rgkp#zTxcGj zAV^cvFbhP|JgWrq_e`~exr~sIR$6p5V?o4Wym3kQ3HA+;Pr$bQ0(PmADVO%MKL!^q z?zAM8j1l4jrq|5X+V!8S*2Wl@=7*pPgciTVK6kS1Ge zMsd_u6DFK$jTnvVtE;qa+8(1sGBu~n&F%dh(&c(Zs4Fc#A=gG^^%^AyH}1^?|8quj zl@Z47h$){PlELJgYZCIHHL= z{U8O>Tw4x3<1{?$8>k-P<}1y9DmAZP_;(3Y*{Sk^H^A=_iSJ@+s5ktgwTXz_2$~W9>VVZsfwCm@s0sQ zeB50_yu@uS+e7QoPvdCwDz{prjo(AFwR%C?z`EL{1`|coJHQTk^nX=tvs1<0arUOJ z!^`*x&&BvTYmemyZ)2p~{%eYX=JVR?DYr(rNgqRMA5E1PR1Iw=prk=L2ldy3r3Vg@27IZx43+ywyzr-X*p*d@tZV+!U#~$-q=8c zgdSuh#r?b4GhEGNai)ayHQpk>5(%j5c@C1K3(W1pb~HeHpaqijJZa-e6vq_8t-^M^ zBJxq|MqZc?pjXPIH}70a5vt!IUh;l}<>VX<-Qcv^u@5(@@M2CHSe_hD$VG-eiV^V( zj7*9T0?di?P$FaD6oo?)<)QT>Npf6Og!GO^GmPV(Km0!=+dE&bk#SNI+C9RGQ|{~O*VC+tXK3!n`5 zHfl6>lwf_aEVV3`0T!aHNZLsj$paS$=LL(?b!Czaa5bbSuZ6#$_@LK<(7yrrl+80| z{tOFd=|ta2Z`^ssozD9BINn45NxUeCQis?-BKmU*Kt=FY-NJ+)8S1ecuFtN-M?&42 zl2$G>u!iNhAk*HoJ^4v^9#ORYp5t^wDj6|lx~5w45#E5wVqI1JQ~9l?nPp1YINf++ zMAdSif~_ETv@Er(EFBI^@L4BULFW>)NI+ejHFP*T}UhWNN`I)RRS8za? z*@`1>9ZB}An%aT5K=_2iQmfE;GcBVHLF!$`I99o5GO`O%O_zLr9AG18>&^HkG(;=V z%}c!OBQ~?MX(9h~tajX{=x)+!cbM7$YzTlmsPOdp2L-?GoW`@{lY9U3f;OUo*BwRB z8A+nv(br0-SH#VxGy#ZrgnGD(=@;HME;yd46EgWJ`EL%oXc&lFpc@Y}^>G(W>h_v_ zlN!`idhX+OjL+~T?19sroAFVGfa5tX-D49w$1g2g_-T|EpHL6}K_aX4$K=LTvwtlF zL*z}j{f+Uoe7{-px3_5iKPA<_7W=>Izkk)!l9ez2w%vi(?Y;i8AxRNLSOGDzNoqoI zP!1uAl}r=_871(G?y`i&)-7{u=%nxk7CZ_Qh#!|ITec zwQn`33GTUM`;D2POWnkqngqJhJRlM>CTONzTG}>^Q0wUunQyn|TAiHzyX2_%ATx%P z%7gW)%4rA9^)M<_%k@`Y?RbC<29sWU&5;@|9thf2#zf8z12$hRcZ!CSb>kUp=4N#y zl3hE#y6>kkA8VY2`W`g5Ip?2qC_BY$>R`iGQLhz2-S>x(RuWv)SPaGdl^)gGw7tjR zH@;jwk!jIaCgSg_*9iF|a);sRUTq30(8I(obh^|}S~}P4U^BIGYqcz;MPpC~Y@k_m zaw4WG1_vz2GdCAX!$_a%GHK**@IrHSkGoN>)e}>yzUTm52on`hYot7cB=oA-h1u|R ztH$11t?54Qg2L+i33FPFKKRm1aOjKST{l1*(nps`>sv%VqeVMWjl5+Gh+9);hIP8? zA@$?}Sc z3qIRpba+y5yf{R6G(u8Z^vkg0Fu&D-7?1s=QZU`Ub{-!Y`I?AGf1VNuc^L3v>)>i# z{DV9W$)>34wnzAXUiV^ZpYKw>UElrN_5Xj6{r_3| z$X5PK`e5$7>~9Dj7gK5ash(dvs`vwfk}&RD`>04;j62zoXESkFBklYaKm5seyiX(P zqQ-;XxlV*yg?Dhlx%xt!b0N3GHp@(p$A;8|%# zZ5m2KL|{on4nr>2_s9Yh=r5ScQ0;aMF)G$-9-Ca6%wA`Pa)i?NGFA|#Yi?{X-4ZO_ z^}%7%vkzvUHa$-^Y#aA+aiR5sa%S|Ebyn`EV<3Pc?ax_f>@sBZF1S;7y$CXd5t5=WGsTKBk8$OfH4v|0?0I=Yp}7c=WBSCg!{0n)XmiU;lfx)**zZaYqmDJelxk$)nZyx5`x$6R|fz(;u zEje5Dtm|a%zK!!tk3{i9$I2b{vXNFy%Bf{50X!x{98+BsDr_u9i>G5%*sqEX|06J0 z^IY{UcEbj6LDwuMh7cH`H@9sVt1l1#8kEQ(LyT@&+K}(ReE`ux8gb0r6L_#bDUo^P z3Ka2lRo52Hdtl_%+pwVs14=q`{d^L58PsU@AMf(hENumaxM{7iAT5sYmWh@hQCO^ zK&}ijo=`VqZ#a3vE?`7QW0ZREL17ZvDfdqKGD?0D4fg{7v%|Yj&_jcKJAB)>=*RS* zto8p6@k%;&^ZF>hvXm&$PCuEp{uqw3VPG$9VMdW5$w-fy2CNNT>E;>ejBgy-m_6`& z97L1p{%srn@O_JQgFpa_#f(_)eb#YS>o>q3(*uB;uZb605(iqM$=NK{nHY=+X2*G) zO3-_Xh%aG}fHWe*==58zBwp%&`mge<8uq8;xIxOd=P%9EK!34^E9sk|(Zq1QSz-JVeP12Fp)-`F|KY$LPwUE?rku zY@OJ)Z9A!ojfzfeyJ9;zv2EM7ZQB)AR5xGa-tMn^bl)FmoIiVyJ@!~@%{}qXXD&Ns zPnfe5U+&ohKefILu_1mPfLGuapX@btta5C#gPB2cjk5m4T}Nfi+Vfka!Yd(L?-c~5 z#ZK4VeQEXNPc4r$K00Fg>g#_W!YZ)cJ?JTS<&68_$#cZT-ME`}tcwqg3#``3M3UPvn+pi}(VNNx6y zFIMVb6OwYU(2`at$gHba*qrMVUl8xk5z-z~fb@Q3Y_+aXuEKH}L+>eW__!IAd@V}L zkw#s%H0v2k5-=vh$^vPCuAi22Luu3uKTf6fPo?*nvj$9(u)4$6tvF-%IM+3pt*cgs z_?wW}J7VAA{_~!?))?s6{M=KPpVhg4fNuU*|3THp@_(q!b*hdl{fjRVFWtu^1dV(f z6iOux9hi&+UK=|%M*~|aqFK{Urfl!TA}UWY#`w(0P!KMe1Si{8|o))Gy6d7;!JQYhgMYmXl?3FfOM2nQGN@~Ap6(G z3+d_5y@=nkpKAhRqf{qQ~k7Z$v&l&@m7Ppt#FSNzKPZM z8LhihcE6i=<(#87E|Wr~HKvVWhkll4iSK$^mUHaxgy8*K$_Zj;zJ`L$naPj+^3zTi z-3NTaaKnD5FPY-~?Tq6QHnmDDRxu0mh0D|zD~Y=vv_qig5r-cIbCpxlju&8Sya)@{ zsmv6XUSi)@(?PvItkiZEeN*)AE~I_?#+Ja-r8$(XiXei2d@Hi7Rx8+rZZb?ZLa{;@*EHeRQ-YDadz~M*YCM4&F-r;E#M+@CSJMJ0oU|PQ^ z=E!HBJDMQ2TN*Y(Ag(ynAL8%^v;=~q?s4plA_hig&5Z0x_^Oab!T)@6kRN$)qEJ6E zNuQjg|G7iwU(N8pI@_6==0CL;lRh1dQF#wePhmu@hADFd3B5KIH#dx(2A zp~K&;Xw}F_N6CU~0)QpQk7s$a+LcTOj1%=WXI(U=Dv!6 z{#<#-)2+gCyyv=Jw?Ab#PVkxPDeH|sAxyG`|Ys}A$PW4TdBv%zDz z^?lwrxWR<%Vzc8Sgt|?FL6ej_*e&rhqJZ3Y>k=X(^dytycR;XDU16}Pc9Vn0>_@H+ zQ;a`GSMEG64=JRAOg%~L)x*w{2re6DVprNp+FcNra4VdNjiaF0M^*>CdPkt(m150rCue?FVdL0nFL$V%5y6N z%eLr5%YN7D06k5ji5*p4v$UMM)G??Q%RB27IvH7vYr_^3>1D-M66#MN8tWGw>WED} z5AhlsanO=STFYFs)Il_0i)l)f<8qn|$DW7ZXhf5xI;m+7M5-%P63XFQrG9>DMqHc} zsgNU9nR`b}E^mL5=@7<1_R~j@q_2U^3h|+`7YH-?C=vme1C3m`Fe0HC>pjt6f_XMh zy~-i-8R46QNYneL4t@)<0VU7({aUO?aH`z4V2+kxgH5pYD5)wCh75JqQY)jIPN=U6 z+qi8cGiOtXG2tXm;_CfpH9ESCz#i5B(42}rBJJF$jh<1sbpj^8&L;gzGHb8M{of+} zzF^8VgML2O9nxBW7AvdEt90vp+#kZxWf@A)o9f9}vKJy9NDBjBW zSt=Hcs=YWCwnfY1UYx*+msp{g!w0HC<_SM!VL1(I2PE?CS}r(eh?{I)mQixmo5^p# zV?2R!R@3GV6hwTCrfHiK#3Orj>I!GS2kYhk1S;aFBD_}u2v;0HYFq}Iz1Z(I4oca4 zxquja8$+8JW_EagDHf$a1OTk5S97umGSDaj)gH=fLs9>_=XvVj^Xj9a#gLdk=&3tl zfmK9MNnIX9v{?%xdw7568 zNrZ|roYs(vC4pHB5RJ8>)^*OuyNC>x7ad)tB_}3SgQ96+-JT^Qi<`xi=)_=$Skwv~ zdqeT9Pa`LYvCAn&rMa2aCDV(TMI#PA5g#RtV|CWpgDYRA^|55LLN^uNh*gOU>Z=a06qJ;$C9z8;n-Pq=qZnc1zUwJ@t)L;&NN+E5m zRkQ(SeM8=l-aoAKGKD>!@?mWTW&~)uF2PYUJ;tB^my`r9n|Ly~0c%diYzqs9W#FTjy?h&X3TnH zXqA{QI82sdjPO->f=^K^f>N`+B`q9&rN0bOXO79S&a9XX8zund(kW7O76f4dcWhIu zER`XSMSFbSL>b;Rp#`CuGJ&p$s~G|76){d?xSA5wVg##_O0DrmyEYppyBr%fyWbbv zp`K84JwRNP$d-pJ!Qk|(RMr?*!wi1if-9G#0p>>1QXKXWFy)eB3ai)l3601q8!9JC zvU#ZWWDNKq9g6fYs?JQ)Q4C_cgTy3FhgKb8s&m)DdmL5zhNK#8wWg!J*7G7Qhe9VU zha?^AQTDpYcuN!B+#1dE*X{<#!M%zfUQbj=zLE{dW0XeQ7-oIsGY6RbkP2re@Q{}r_$iiH0xU%iN*ST`A)-EH6eaZB$GA#v)cLi z*MpA(3bYk$oBDKAzu^kJoSUsDd|856DApz={3u8sbQV@JnRkp2nC|)m;#T=DvIL-O zI4vh;g7824l}*`_p@MT4+d`JZ2%6NQh=N9bmgJ#q!hK@_<`HQq3}Z8Ij>3%~<*= zcv=!oT#5xmeGI92lqm9sGVE%#X$ls;St|F#u!?5Y7syhx6q#MVRa&lBmmn%$C0QzU z);*ldgwwCmzM3uglr}!Z2G+?& zf%Dpo&mD%2ZcNFiN-Z0f;c_Q;A%f@>26f?{d1kxIJD}LxsQkB47SAdwinfMILZdN3 zfj^HmTzS3Ku5BxY>ANutS8WPQ-G>v4^_Qndy==P3pDm+Xc?>rUHl-4+^%Sp5atOja z2oP}ftw-rqnb}+khR3CrRg^ibi6?QYk1*i^;kQGirQ=uB9Sd1NTfT-Rbv;hqnY4neE5H1YUrjS2m+2&@uXiAo- zrKUX|Ohg7(6F(AoP~tj;NZlV#xsfo-5reuQHB$&EIAhyZk;bL;k9ouDmJNBAun;H& zn;Of1z_Qj`x&M;5X;{s~iGzBQTY^kv-k{ksbE*Dl%Qf%N@hQCfY~iUw!=F-*$cpf2 z3wix|aLBV0b;W@z^%7S{>9Z^T^fLOI68_;l@+Qzaxo`nAI8emTV@rRhEKZ z?*z_{oGdI~R*#<2{bkz$G~^Qef}$*4OYTgtL$e9q!FY7EqxJ2`zk6SQc}M(k(_MaV zSLJnTXw&@djco1~a(vhBl^&w=$fa9{Sru>7g8SHahv$&Bl(D@(Zwxo_3r=;VH|uc5 zi1Ny)J!<(KN-EcQ(xlw%PNwK8U>4$9nVOhj(y0l9X^vP1TA>r_7WtSExIOsz`nDOP zs}d>Vxb2Vo2e5x8p(n~Y5ggAyvib>d)6?)|E@{FIz?G3PVGLf7-;BxaP;c?7ddH$z zA+{~k^V=bZuXafOv!RPsE1GrR3J2TH9uB=Z67gok+u`V#}BR86hB1xl}H4v`F+mRfr zYhortD%@IGfh!JB(NUNSDh+qDz?4ztEgCz&bIG-Wg7w-ua4ChgQR_c+z8dT3<1?uX z*G(DKy_LTl*Ea!%v!RhpCXW1WJO6F`bgS-SB;Xw9#! z<*K}=#wVu9$`Yo|e!z-CPYH!nj7s9dEPr-E`DXUBu0n!xX~&|%#G=BeM?X@shQQMf zMvr2!y7p_gD5-!Lnm|a@z8Of^EKboZsTMk%5VsJEm>VsJ4W7Kv{<|#4f-qDE$D-W>gWT%z-!qXnDHhOvLk=?^a1*|0j z{pW{M0{#1VcR5;F!!fIlLVNh_Gj zbnW(_j?0c2q$EHIi@fSMR{OUKBcLr{Y&$hrM8XhPByyZaXy|dd&{hYQRJ9@Fn%h3p7*VQolBIV@Eq`=y%5BU~3RPa^$a?ixp^cCg z+}Q*X+CW9~TL29@OOng(#OAOd!)e$d%sr}^KBJ-?-X&|4HTmtemxmp?cT3uA?md4% zT8yZ0U;6Rg6JHy3fJae{6TMGS?ZUX6+gGTT{Q{)SI85$5FD{g-eR%O0KMpWPY`4@O zx!hen1*8^E(*}{m^V_?}(b5k3hYo=T+$&M32+B`}81~KKZhY;2H{7O-M@vbCzuX0n zW-&HXeyr1%I3$@ns-V1~Lb@wIpkmx|8I~ob1Of7i6BTNysEwI}=!nU%q7(V_^+d*G z7G;07m(CRTJup!`cdYi93r^+LY+`M*>aMuHJm(A8_O8C#A*$!Xvddgpjx5)?_EB*q zgE8o5O>e~9IiSC@WtZpF{4Bj2J5eZ>uUzY%TgWF7wdDE!fSQIAWCP)V{;HsU3ap?4 znRsiiDbtN7i9hapO;(|Ew>Ip2TZSvK9Z^N21%J?OiA_&eP1{(Pu_=%JjKy|HOardq ze?zK^K zA%sjF64*Wufad%H<) z^|t>e*h+Z1#l=5wHexzt9HNDNXgM=-OPWKd^5p!~%SIl>Fo&7BvNpbf8{NXmH)o{r zO=aBJ;meX1^{O%q;kqdw*5k!Y7%t_30 zy{nGRVc&5qt?dBwLs+^Sfp;f`YVMSB#C>z^a9@fpZ!xb|b-JEz1LBX7ci)V@W+kvQ89KWA0T~Lj$aCcfW#nD5bt&Y_< z-q{4ZXDqVg?|0o)j1%l0^_it0WF*LCn-+)c!2y5yS7aZIN$>0LqNnkujV*YVes(v$ zY@_-!Q;!ZyJ}Bg|G-~w@or&u0RO?vlt5*9~yeoPV_UWrO2J54b4#{D(D>jF(R88u2 zo#B^@iF_%S>{iXSol8jpmsZuJ?+;epg>k=$d`?GSegAVp3n$`GVDvK${N*#L_1`44 z{w0fL{2%)0|E+qgZtjX}itZz^KJt4Y;*8uSK}Ft38+3>j|K(PxIXXR-t4VopXo#9# zt|F{LWr-?34y`$nLBVV_*UEgA6AUI65dYIbqpNq9cl&uLJ0~L}<=ESlOm?Y-S@L*d z<7vt}`)TW#f%Rp$Q}6@3=j$7Tze@_uZO@aMn<|si{?S}~maII`VTjs&?}jQ4_cut9$)PEqMukwoXobzaKx^MV z2fQwl+;LSZ$qy%Tys0oo^K=jOw$!YwCv^ei4NBVauL)tN%=wz9M{uf{IB(BxK|lT*pFkmNK_1tV`nb%jH=a0~VNq2RCKY(rG7jz!-D^k)Ec)yS%17pE#o6&eY+ z^qN(hQT$}5F(=4lgNQhlxj?nB4N6ntUY6(?+R#B?W3hY_a*)hnr4PA|vJ<6p`K3Z5Hy z{{8(|ux~NLUW=!?9Qe&WXMTAkQnLXg(g=I@(VG3{HE13OaUT|DljyWXPs2FE@?`iU z4GQlM&Q=T<4&v@Fe<+TuXiZQT3G~vZ&^POfmI1K2h6t4eD}Gk5XFGpbj1n_g*{qmD6Xy z`6Vv|lLZtLmrnv*{Q%xxtcWVj3K4M%$bdBk_a&ar{{GWyu#ljM;dII;*jP;QH z#+^o-A4np{@|Mz+LphTD0`FTyxYq#wY)*&Ls5o{0z9yg2K+K7ZN>j1>N&;r+Z`vI| zDzG1LJZ+sE?m?>x{5LJx^)g&pGEpY=fQ-4}{x=ru;}FL$inHemOg%|R*ZXPodU}Kh zFEd5#+8rGq$Y<_?k-}r5zgQ3jRV=ooHiF|@z_#D4pKVEmn5CGV(9VKCyG|sT9nc=U zEoT67R`C->KY8Wp-fEcjjFm^;Cg(ls|*ABVHq8clBE(;~K^b+S>6uj70g? z&{XQ5U&!Z$SO7zfP+y^8XBbiu*Cv-yJG|l-oe*!s5$@Lh_KpxYL2sx`B|V=dETN>5K+C+CU~a_3cI8{vbu$TNVdGf15*>D zz@f{zIlorkY>TRh7mKuAlN9A0>N>SV`X)+bEHms=mfYTMWt_AJtz_h+JMmrgH?mZt zm=lfdF`t^J*XLg7v+iS)XZROygK=CS@CvUaJo&w2W!Wb@aa?~Drtf`JV^cCMjngVZ zv&xaIBEo8EYWuML+vxCpjjY^s1-ahXJzAV6hTw%ZIy!FjI}aJ+{rE&u#>rs)vzuxz z+$5z=7W?zH2>Eb32dvgHYZtCAf!=OLY-pb4>Ae79rd68E2LkVPj-|jFeyqtBCCwiW zkB@kO_(3wFq)7qwV}bA=zD!*@UhT`geq}ITo%@O(Z5Y80nEX~;0-8kO{oB6|(4fQh z);73T!>3@{ZobPwRv*W?7m0Ml9GmJBCJd&6E?hdj9lV= z4flNfsc(J*DyPv?RCOx!MSvk(M952PJ-G|JeVxWVjN~SNS6n-_Ge3Q;TGE;EQvZg86%wZ`MB zSMQua(i*R8a75!6$QRO^(o7sGoomb+Y{OMy;m~Oa`;P9Yqo>?bJAhqXxLr7_3g_n>f#UVtxG!^F#1+y@os6x(sg z^28bsQ@8rw%Gxk-stAEPRbv^}5sLe=VMbkc@Jjimqjvmd!3E7+QnL>|(^3!R} zD-l1l7*Amu@j+PWLGHXXaFG0Ct2Q=}5YNUxEQHCAU7gA$sSC<5OGylNnQUa>>l%sM zyu}z6i&({U@x^hln**o6r2s-(C-L50tQvz|zHTqW!ir?w&V23tuYEDJVV#5pE|OJu z7^R!A$iM$YCe?8n67l*J-okwfZ+ZTkGvZ)tVPfR;|3gyFjF)8V zyXXN=!*bpyRg9#~Bg1+UDYCt0 ztp4&?t1X0q>uz;ann$OrZs{5*r`(oNvw=$7O#rD|Wuv*wIi)4b zGtq4%BX+kkagv3F9Id6~-c+1&?zny%w5j&nk9SQfo0k4LhdSU_kWGW7axkfpgR`8* z!?UTG*Zi_baA1^0eda8S|@&F z{)Rad0kiLjB|=}XFJhD(S3ssKlveFFmkN{Vl^_nb!o5M!RC=m)V&v2%e?ZoRC@h3> zJ(?pvToFd`*Zc@HFPL#=otWKwtuuQ_dT-Hr{S%pQX<6dqVJ8;f(o)4~VM_kEQkMR+ zs1SCVi~k>M`u1u2xc}>#D!V&6nOOh-E$O&SzYrjJdZpaDv1!R-QGA141WjQe2s0J~ zQ;AXG)F+K#K8_5HVqRoRM%^EduqOnS(j2)|ctA6Q^=|s_WJYU;Z%5bHp08HPL`YF2 zR)Ad1z{zh`=sDs^&V}J z%$Z$!jd7BY5AkT?j`eqMs%!Gm@T8)4w3GYEX~IwgE~`d|@T{WYHkudy(47brgHXx& zBL1yFG6!!!VOSmDxBpefy2{L_u5yTwja&HA!mYA#wg#bc-m%~8aRR|~AvMnind@zs zy>wkShe5&*un^zvSOdlVu%kHsEo>@puMQ`b1}(|)l~E{5)f7gC=E$fP(FC2=F<^|A zxeIm?{EE!3sO!Gr7e{w)Dx(uU#3WrFZ>ibmKSQ1tY?*-Nh1TDHLe+k*;{Rp!Bmd_m zb#^kh`Y*8l|9Cz2e{;RL%_lg{#^Ar+NH|3z*Zye>!alpt{z;4dFAw^^H!6ING*EFc z_yqhr8d!;%nHX9AKhFQZBGrSzfzYCi%C!(Q5*~hX>)0N`vbhZ@N|i;_972WSx*>LH z87?en(;2_`{_JHF`Sv6Wlps;dCcj+8IJ8ca6`DsOQCMb3n# z3)_w%FuJ3>fjeOOtWyq)ag|PmgQbC-s}KRHG~enBcIwqIiGW8R8jFeBNY9|YswRY5 zjGUxdGgUD26wOpwM#8a!Nuqg68*dG@VM~SbOroL_On0N6QdT9?)NeB3@0FCC?Z|E0 z6TPZj(AsPtwCw>*{eDEE}Gby>0q{*lI+g2e&(YQrsY&uGM{O~}(oM@YWmb*F zA0^rr5~UD^qmNljq$F#ARXRZ1igP`MQx4aS6*MS;Ot(1L5jF2NJ;de!NujUYg$dr# z=TEL_zTj2@>ZZN(NYCeVX2==~=aT)R30gETO{G&GM4XN<+!&W&(WcDP%oL8PyIVUC zs5AvMgh6qr-2?^unB@mXK*Dbil^y-GTC+>&N5HkzXtozVf93m~xOUHn8`HpX=$_v2 z61H;Z1qK9o;>->tb8y%#4H)765W4E>TQ1o0PFj)uTOPEvv&}%(_mG0ISmyhnQV33Z$#&yd{ zc{>8V8XK$3u8}04CmAQ#I@XvtmB*s4t8va?-IY4@CN>;)mLb_4!&P3XSw4pA_NzDb zORn!blT-aHk1%Jpi>T~oGLuh{DB)JIGZ9KOsciWs2N7mM1JWM+lna4vkDL?Q)z_Ct z`!mi0jtr+4*L&N7jk&LodVO#6?_qRGVaucqVB8*us6i3BTa^^EI0x%EREQSXV@f!lak6Wf1cNZ8>*artIJ(ADO*=<-an`3zB4d*oO*8D1K!f z*A@P1bZCNtU=p!742MrAj%&5v%Xp_dSX@4YCw%F|%Dk=u|1BOmo)HsVz)nD5USa zR~??e61sO(;PR)iaxK{M%QM_rIua9C^4ppVS$qCT9j2%?*em?`4Z;4@>I(c%M&#cH z>4}*;ej<4cKkbCAjjDsyKS8rIm90O)Jjgyxj5^venBx&7B!xLmzxW3jhj7sR(^3Fz z84EY|p1NauwXUr;FfZjdaAfh%ivyp+^!jBjJuAaKa!yCq=?T_)R!>16?{~p)FQ3LDoMyG%hL#pR!f@P%*;#90rs_y z@9}@r1BmM-SJ#DeuqCQk=J?ixDSwL*wh|G#us;dd{H}3*-Y7Tv5m=bQJMcH+_S`zVtf;!0kt*(zwJ zs+kedTm!A}cMiM!qv(c$o5K%}Yd0|nOd0iLjus&;s0Acvoi-PFrWm?+q9f^FslxGi z6ywB`QpL$rJzWDg(4)C4+!2cLE}UPCTBLa*_=c#*$b2PWrRN46$y~yST3a2$7hEH= zNjux+wna^AzQ=KEa_5#9Ph=G1{S0#hh1L3hQ`@HrVnCx{!fw_a0N5xV(iPdKZ-HOM za)LdgK}1ww*C_>V7hbQnTzjURJL`S%`6nTHcgS+dB6b_;PY1FsrdE8(2K6FN>37!62j_cBlui{jO^$dPkGHV>pXvW0EiOA zqW`YaSUBWg_v^Y5tPJfWLcLpsA8T zG)!x>pKMpt!lv3&KV!-um= zKCir6`bEL_LCFx4Z5bAFXW$g3Cq`?Q%)3q0r852XI*Der*JNuKUZ`C{cCuu8R8nkt z%pnF>R$uY8L+D!V{s^9>IC+bmt<05h**>49R*#vpM*4i0qRB2uPbg8{{s#9yC;Z18 zD7|4m<9qneQ84uX|J&f-g8a|nFKFt34@Bt{CU`v(SYbbn95Q67*)_Esl_;v291s=9 z+#2F2apZU4Tq=x+?V}CjwD(P=U~d<=mfEFuyPB`Ey82V9G#Sk8H_Ob_RnP3s?)S_3 zr%}Pb?;lt_)Nf>@zX~D~TBr;-LS<1I##8z`;0ZCvI_QbXNh8Iv)$LS=*gHr;}dgb=w5$3k2la1keIm|=7<-JD>)U%=Avl0Vj@+&vxn zt-)`vJxJr88D&!}2^{GPXc^nmRf#}nb$4MMkBA21GzB`-Or`-3lq^O^svO7Vs~FdM zv`NvzyG+0T!P8l_&8gH|pzE{N(gv_tgDU7SWeiI-iHC#0Ai%Ixn4&nt{5y3(GQs)i z&uA;~_0shP$0Wh0VooIeyC|lak__#KVJfxa7*mYmZ22@(<^W}FdKjd*U1CqSjNKW% z*z$5$=t^+;Ui=MoDW~A7;)Mj%ibX1_p4gu>RC}Z_pl`U*{_z@+HN?AF{_W z?M_X@o%w8fgFIJ$fIzBeK=v#*`mtY$HC3tqw7q^GCT!P$I%=2N4FY7j9nG8aIm$c9 zeKTxVKN!UJ{#W)zxW|Q^K!3s;(*7Gbn;e@pQBCDS(I|Y0euK#dSQ_W^)sv5pa%<^o zyu}3d?Lx`)3-n5Sy9r#`I{+t6x%I%G(iewGbvor&I^{lhu-!#}*Q3^itvY(^UWXgvthH52zLy&T+B)Pw;5>4D6>74 zO_EBS)>l!zLTVkX@NDqyN2cXTwsUVao7$HcqV2%t$YzdAC&T)dwzExa3*kt9d(}al zA~M}=%2NVNUjZiO7c>04YH)sRelXJYpWSn^aC$|Ji|E13a^-v2MB!Nc*b+=KY7MCm zqIteKfNkONq}uM;PB?vvgQvfKLPMB8u5+Am=d#>g+o&Ysb>dX9EC8q?D$pJH!MTAqa=DS5$cb+;hEvjwVfF{4;M{5U&^_+r zvZdu_rildI!*|*A$TzJ&apQWV@p{!W`=?t(o0{?9y&vM)V)ycGSlI3`;ps(vf2PUq zX745#`cmT*ra7XECC0gKkpu2eyhFEUb?;4@X7weEnLjXj_F~?OzL1U1L0|s6M+kIhmi%`n5vvDALMagi4`wMc=JV{XiO+^ z?s9i7;GgrRW{Mx)d7rj)?(;|b-`iBNPqdwtt%32se@?w4<^KU&585_kZ=`Wy^oLu9 z?DQAh5z%q;UkP48jgMFHTf#mj?#z|=w= z(q6~17Vn}P)J3M?O)x))%a5+>TFW3No~TgP;f}K$#icBh;rSS+R|}l鯊%1Et zwk~hMkhq;MOw^Q5`7oC{CUUyTw9x>^%*FHx^qJw(LB+E0WBX@{Ghw;)6aA-KyYg8p z7XDveQOpEr;B4je@2~usI5BlFadedX^ma{b{ypd|RNYqo#~d*mj&y`^iojR}s%~vF z(H!u`yx68D1Tj(3(m;Q+Ma}s2n#;O~bcB1`lYk%Irx60&-nWIUBr2x&@}@76+*zJ5 ze&4?q8?m%L9c6h=J$WBzbiTf1Z-0Eb5$IZs>lvm$>1n_Mezp*qw_pr8<8$6f)5f<@ zyV#tzMCs51nTv_5ca`x`yfE5YA^*%O_H?;tWYdM_kHPubA%vy47i=9>Bq) zRQ&0UwLQHeswmB1yP)+BiR;S+Vc-5TX84KUA;8VY9}yEj0eESSO`7HQ4lO z4(CyA8y1G7_C;6kd4U3K-aNOK!sHE}KL_-^EDl(vB42P$2Km7$WGqNy=%fqB+ zSLdrlcbEH=T@W8V4(TgoXZ*G1_aq$K^@ek=TVhoKRjw;HyI&coln|uRr5mMOy2GXP zwr*F^Y|!Sjr2YQXX(Fp^*`Wk905K%$bd03R4(igl0&7IIm*#f`A!DCarW9$h$z`kYk9MjjqN&5-DsH@8xh63!fTNPxWsFQhNv z#|3RjnP$Thdb#Ys7M+v|>AHm0BVTw)EH}>x@_f4zca&3tXJhTZ8pO}aN?(dHo)44Z z_5j+YP=jMlFqwvf3lq!57-SAuRV2_gJ*wsR_!Y4Z(trO}0wmB9%f#jNDHPdQGHFR; zZXzS-$`;7DQ5vF~oSgP3bNV$6Z(rwo6W(U07b1n3UHqml>{=6&-4PALATsH@Bh^W? z)ob%oAPaiw{?9HfMzpGb)@Kys^J$CN{uf*HX?)z=g`J(uK1YO^8~s1(ZIbG%Et(|q z$D@_QqltVZu9Py4R0Ld8!U|#`5~^M=b>fnHthzKBRr=i+w@0Vr^l|W;=zFT#PJ?*a zbC}G#It}rQP^Ait^W&aa6B;+0gNvz4cWUMzpv(1gvfw-X4xJ2Sv;mt;zb2Tsn|kSS zo*U9N?I{=-;a-OybL4r;PolCfiaL=y@o9{%`>+&FI#D^uy#>)R@b^1ue&AKKwuI*` zx%+6r48EIX6nF4o;>)zhV_8(IEX})NGU6Vs(yslrx{5fII}o3SMHW7wGtK9oIO4OM&@@ECtXSICLcPXoS|{;=_yj>hh*%hP27yZwOmj4&Lh z*Nd@OMkd!aKReoqNOkp5cW*lC)&C$P?+H3*%8)6HcpBg&IhGP^77XPZpc%WKYLX$T zsSQ$|ntaVVOoRat$6lvZO(G-QM5s#N4j*|N_;8cc2v_k4n6zx9c1L4JL*83F-C1Cn zaJhd;>rHXB%%ZN=3_o3&Qd2YOxrK~&?1=UuN9QhL$~OY-Qyg&})#ez*8NpQW_*a&kD&ANjedxT0Ar z<6r{eaVz3`d~+N~vkMaV8{F?RBVemN(jD@S8qO~L{rUw#=2a$V(7rLE+kGUZ<%pdr z?$DP|Vg#gZ9S}w((O2NbxzQ^zTot=89!0^~hE{|c9q1hVzv0?YC5s42Yx($;hAp*E zyoGuRyphQY{Q2ee0Xx`1&lv(l-SeC$NEyS~8iil3_aNlnqF_G|;zt#F%1;J)jnPT& z@iU0S;wHJ2$f!juqEzPZeZkjcQ+Pa@eERSLKsWf=`{R@yv7AuRh&ALRTAy z8=g&nxsSJCe!QLchJ=}6|LshnXIK)SNd zRkJNiqHwKK{SO;N5m5wdL&qK`v|d?5<4!(FAsDxR>Ky#0#t$8XCMptvNo?|SY?d8b z`*8dVBlXTUanlh6n)!EHf2&PDG8sXNAt6~u-_1EjPI1|<=33T8 zEnA00E!`4Ave0d&VVh0e>)Dc}=FfAFxpsC1u9ATfQ`-Cu;mhc8Z>2;uyXtqpLb7(P zd2F9<3cXS} znMg?{&8_YFTGRQZEPU-XPq55%51}RJpw@LO_|)CFAt62-_!u_Uq$csc+7|3+TV_!h z+2a7Yh^5AA{q^m|=KSJL+w-EWDBc&I_I1vOr^}P8i?cKMhGy$CP0XKrQzCheG$}G# zuglf8*PAFO8%xop7KSwI8||liTaQ9NCAFarr~psQt)g*pC@9bORZ>m`_GA`_K@~&% zijH0z;T$fd;-Liw8%EKZas>BH8nYTqsK7F;>>@YsE=Rqo?_8}UO-S#|6~CAW0Oz1} z3F(1=+#wrBJh4H)9jTQ_$~@#9|Bc1Pd3rAIA_&vOpvvbgDJOM(yNPhJJq2%PCcMaI zrbe~toYzvkZYQ{ea(Wiyu#4WB#RRN%bMe=SOk!CbJZv^m?Flo5p{W8|0i3`hI3Np# zvCZqY%o258CI=SGb+A3yJe~JH^i{uU`#U#fvSC~rWTq+K`E%J@ zasU07&pB6A4w3b?d?q}2=0rA#SA7D`X+zg@&zm^iA*HVi z009#PUH<%lk4z~p^l0S{lCJk1Uxi=F4e_DwlfHA`X`rv(|JqWKAA5nH+u4Da+E_p+ zVmH@lg^n4ixs~*@gm_dgQ&eDmE1mnw5wBz9Yg?QdZwF|an67Xd*x!He)Gc8&2!urh z4_uXzbYz-aX)X1>&iUjGp;P1u8&7TID0bTH-jCL&Xk8b&;;6p2op_=y^m@Nq*0{#o!!A;wNAFG@0%Z9rHo zcJs?Th>Ny6+hI`+1XoU*ED$Yf@9f91m9Y=#N(HJP^Y@ZEYR6I?oM{>&Wq4|v0IB(p zqX#Z<_3X(&{H+{3Tr|sFy}~=bv+l=P;|sBz$wk-n^R`G3p0(p>p=5ahpaD7>r|>pm zv;V`_IR@tvZreIuv2EM7ZQHhO+qUgw#kOs%*ekY^n|=1#x9&c;Ro&I~{rG-#_3ZB1 z?|9}IFdbP}^DneP*T-JaoYHt~r@EfvnPE5EKUwIxjPbsr$% zfWW83pgWST7*B(o=kmo)74$8UU)v0{@4DI+ci&%=#90}!CZz|rnH+Mz=HN~97G3~@ z;v5(9_2%eca(9iu@J@aqaMS6*$TMw!S>H(b z4(*B!|H|8&EuB%mITr~O?vVEf%(Gr)6E=>H~1VR z&1YOXluJSG1!?TnT)_*YmJ*o_Q@om~(GdrhI{$Fsx_zrkupc#y{DK1WOUR>tk>ZE) ziOLoBkhZZ?0Uf}cm>GsA>Rd6V8@JF)J*EQlQ<=JD@m<)hyElXR0`pTku*3MU`HJn| zIf7$)RlK^pW-$87U;431;Ye4Ie+l~_B3*bH1>*yKzn23cH0u(i5pXV! z4K?{3oF7ZavmmtTq((wtml)m6i)8X6ot_mrE-QJCW}Yn!(3~aUHYG=^fA<^~`e3yc z-NWTb{gR;DOUcK#zPbN^D*e=2eR^_!(!RKkiwMW@@yYtEoOp4XjOGgzi`;=8 zi3`Ccw1%L*y(FDj=C7Ro-V?q)-%p?Ob2ZElu`eZ99n14-ZkEV#y5C+{Pq87Gu3&>g zFy~Wk7^6v*)4pF3@F@rE__k3ikx(hzN3@e*^0=KNA6|jC^B5nf(XaoQaZN?Xi}Rn3 z$8&m*KmWvPaUQ(V<#J+S&zO|8P-#!f%7G+n_%sXp9=J%Z4&9OkWXeuZN}ssgQ#Tcj z8p6ErJQJWZ+fXLCco=RN8D{W%+*kko*2-LEb))xcHwNl~Xmir>kmAxW?eW50Osw3# zki8Fl$#fvw*7rqd?%E?}ZX4`c5-R&w!Y0#EBbelVXSng+kUfeUiqofPehl}$ormli zg%r)}?%=?_pHb9`Cq9Z|B`L8b>(!+8HSX?`5+5mm81AFXfnAt1*R3F z%b2RPIacKAddx%JfQ8l{3U|vK@W7KB$CdLqn@wP^?azRks@x8z59#$Q*7q!KilY-P zHUbs(IFYRGG1{~@RF;Lqyho$~7^hNC`NL3kn^Td%A7dRgr_&`2k=t+}D-o9&C!y^? z6MsQ=tc3g0xkK(O%DzR9nbNB(r@L;1zQrs8mzx&4dz}?3KNYozOW5;=w18U6$G4U2 z#2^qRLT*Mo4bV1Oeo1PKQ2WQS2Y-hv&S|C7`xh6=Pj7MNLC5K-zokZ67S)C;(F0Dd zloDK2_o1$Fmza>EMj3X9je7e%Q`$39Dk~GoOj89-6q9|_WJlSl!!+*{R=tGp z8u|MuSwm^t7K^nUe+^0G3dkGZr3@(X+TL5eah)K^Tn zXEtHmR9UIaEYgD5Nhh(s*fcG_lh-mfy5iUF3xxpRZ0q3nZ=1qAtUa?(LnT9I&~uxX z`pV?+=|-Gl(kz?w!zIieXT}o}7@`QO>;u$Z!QB${a08_bW0_o@&9cjJUXzVyNGCm8 zm=W+$H!;_Kzp6WQqxUI;JlPY&`V}9C$8HZ^m?NvI*JT@~BM=()T()Ii#+*$y@lTZBkmMMda>7s#O(1YZR+zTG@&}!EXFG{ zEWPSDI5bFi;NT>Yj*FjH((=oe%t%xYmE~AGaOc4#9K_XsVpl<4SP@E!TgC0qpe1oi zNpxU2b0(lEMcoibQ-G^cxO?ySVW26HoBNa;n0}CWL*{k)oBu1>F18X061$SP{Gu67 z-v-Fa=Fl^u3lnGY^o5v)Bux}bNZ~ z5pL+7F_Esoun8^5>z8NFoIdb$sNS&xT8_|`GTe8zSXQzs4r^g0kZjg(b0bJvz`g<70u9Z3fQILX1Lj@;@+##bP|FAOl)U^9U>0rx zGi)M1(Hce)LAvQO-pW!MN$;#ZMX?VE(22lTlJrk#pB0FJNqVwC+*%${Gt#r_tH9I_ z;+#)#8cWAl?d@R+O+}@1A^hAR1s3UcW{G+>;X4utD2d9X(jF555}!TVN-hByV6t+A zdFR^aE@GNNgSxxixS2p=on4(+*+f<8xrwAObC)D5)4!z7)}mTpb7&ofF3u&9&wPS< zB62WHLGMhmrmOAgmJ+|c>qEWTD#jd~lHNgT0?t-p{T=~#EMcB| z=AoDKOL+qXCfk~F)-Rv**V}}gWFl>liXOl7Uec_8v)(S#av99PX1sQIVZ9eNLkhq$ zt|qu0b?GW_uo}TbU8!jYn8iJeIP)r@;!Ze_7mj{AUV$GEz6bDSDO=D!&C9!M@*S2! zfGyA|EPlXGMjkH6x7OMF?gKL7{GvGfED=Jte^p=91FpCu)#{whAMw`vSLa`K#atdN zThnL+7!ZNmP{rc=Z>%$meH;Qi1=m1E3Lq2D_O1-X5C;!I0L>zur@tPAC9*7Jeh)`;eec}1`nkRP(%iv-`N zZ@ip-g|7l6Hz%j%gcAM}6-nrC8oA$BkOTz^?dakvX?`^=ZkYh%vUE z9+&)K1UTK=ahYiaNn&G5nHUY5niLGus@p5E2@RwZufRvF{@$hW{;{3QhjvEHMvduO z#Wf-@oYU4ht?#uP{N3utVzV49mEc9>*TV_W2TVC`6+oI)zAjy$KJrr=*q##&kobiQ z1vNbya&OVjK`2pdRrM?LuK6BgrLN7H_3m z!qpNKg~87XgCwb#I=Q&0rI*l$wM!qTkXrx1ko5q-f;=R2fImRMwt5Qs{P*p^z@9ex z`2#v(qE&F%MXlHpdO#QEZyZftn4f05ab^f2vjxuFaat2}jke{j?5GrF=WYBR?gS(^ z9SBiNi}anzBDBRc+QqizTTQuJrzm^bNA~A{j%ugXP7McZqJ}65l10({wk++$=e8O{ zxWjG!Qp#5OmI#XRQQM?n6?1ztl6^D40hDJr?4$Wc&O_{*OfMfxe)V0=e{|N?J#fgE>j9jAajze$iN!*yeF%jJU#G1c@@rm zolGW!j?W6Q8pP=lkctNFdfgUMg92wlM4E$aks1??M$~WQfzzzXtS)wKrr2sJeCN4X zY(X^H_c^PzfcO8Bq(Q*p4c_v@F$Y8cHLrH$`pJ2}=#*8%JYdqsqnGqEdBQMpl!Ot04tUGSXTQdsX&GDtjbWD=prcCT9(+ z&UM%lW%Q3yrl1yiYs;LxzIy>2G}EPY6|sBhL&X&RAQrSAV4Tlh2nITR?{6xO9ujGu zr*)^E`>o!c=gT*_@6S&>0POxcXYNQd&HMw6<|#{eSute2C3{&h?Ah|cw56-AP^f8l zT^kvZY$YiH8j)sk7_=;gx)vx-PW`hbSBXJGCTkpt;ap(}G2GY=2bbjABU5)ty%G#x zAi07{Bjhv}>OD#5zh#$0w;-vvC@^}F! z#X$@)zIs1L^E;2xDAwEjaXhTBw2<{&JkF*`;c3<1U@A4MaLPe{M5DGGkL}#{cHL%* zYMG+-Fm0#qzPL#V)TvQVI|?_M>=zVJr9>(6ib*#z8q@mYKXDP`k&A4A};xMK0h=yrMp~JW{L?mE~ph&1Y1a#4%SO)@{ zK2juwynUOC)U*hVlJU17%llUxAJFuKZh3K0gU`aP)pc~bE~mM!i1mi!~LTf>1Wp< zuG+ahp^gH8g8-M$u{HUWh0m^9Rg@cQ{&DAO{PTMudV6c?ka7+AO& z746QylZ&Oj`1aqfu?l&zGtJnpEQOt;OAFq19MXTcI~`ZcoZmyMrIKDFRIDi`FH)w; z8+*8tdevMDv*VtQi|e}CnB_JWs>fhLOH-+Os2Lh!&)Oh2utl{*AwR)QVLS49iTp{6 z;|172Jl!Ml17unF+pd+Ff@jIE-{Oxv)5|pOm@CkHW?{l}b@1>Pe!l}VccX#xp@xgJ zyE<&ep$=*vT=}7vtvif0B?9xw_3Gej7mN*dOHdQPtW5kA5_zGD zpA4tV2*0E^OUimSsV#?Tg#oiQ>%4D@1F5@AHwT8Kgen$bSMHD3sXCkq8^(uo7CWk`mT zuslYq`6Yz;L%wJh$3l1%SZv#QnG3=NZ=BK4yzk#HAPbqXa92;3K5?0kn4TQ`%E%X} z&>Lbt!!QclYKd6+J7Nl@xv!uD%)*bY-;p`y^ZCC<%LEHUi$l5biu!sT3TGGSTPA21 zT8@B&a0lJHVn1I$I3I1I{W9fJAYc+8 zVj8>HvD}&O`TqU2AAb={?eT;0hyL(R{|h23=4fDSZKC32;wWxsVj`P z3J3{M$PwdH!ro*Cn!D&=jnFR>BNGR<<|I8CI@+@658Dy(lhqbhXfPTVecY@L8%`3Q z1Fux2w?2C3th60jI~%OC9BtpNF$QPqcG+Pz96qZJ71_`0o0w_q7|h&O>`6U+^BA&5 zXd5Zp1Xkw~>M%RixTm&OqpNl8Q+ue=92Op_>T~_9UON?ZM2c0aGm=^A4ejrXj3dV9 zhh_bCt-b9`uOX#cFLj!vhZ#lS8Tc47OH>*)y#{O9?AT~KR9LntM|#l#Dlm^8{nZdk zjMl#>ZM%#^nK2TPzLcKxqx24P7R1FPlBy7LSBrRvx>fE$9AJ;7{PQm~^LBX^k#6Zq zw*Z(zJC|`!6_)EFR}8|n8&&Rbj8y028~P~sFXBFRt+tmqH-S3<%N;C&WGH!f3{7cm zy_fCAb9@HqaXa1Y5vFbxWf%#zg6SI$C+Uz5=CTO}e|2fjWkZ;Dx|84Ow~bkI=LW+U zuq;KSv9VMboRvs9)}2PAO|b(JCEC_A0wq{uEj|3x@}*=bOd zwr{TgeCGG>HT<@Zeq8y}vTpwDg#UBvD)BEs@1KP$^3$sh&_joQPn{hjBXmLPJ{tC) z*HS`*2+VtJO{|e$mM^|qv1R*8i(m1`%)}g=SU#T#0KlTM2RSvYUc1fP+va|4;5}Bfz98UvDCpq7}+SMV&;nX zQw~N6qOX{P55{#LQkrZk(e5YGzr|(B;Q;ju;2a`q+S9bsEH@i1{_Y0;hWYn1-79jl z5c&bytD*k)GqrVcHn6t-7kinadiD>B{Tl`ZY@`g|b~pvHh5!gKP4({rp?D0aFd_cN zhHRo4dd5^S6ViN(>(28qZT6E>??aRhc($kP`>@<+lIKS5HdhjVU;>f7<4))E*5|g{ z&d1}D|vpuV^eRj5j|xx9nwaCxXFG?Qbjn~_WSy=N}P0W>MP zG-F%70lX5Xr$a)2i6?i|iMyM|;Jtf*hO?=Jxj12oz&>P=1#h~lf%#fc73M2_(SUM- zf&qnjS80|_Y0lDgl&I?*eMumUklLe_=Td!9G@eR*tcPOgIShJipp3{A10u(4eT~DY zHezEj8V+7m!knn7)W!-5QI3=IvC^as5+TW1@Ern@yX| z7Nn~xVx&fGSr+L%4iohtS3w^{-H1A_5=r&x8}R!YZvp<2T^YFvj8G_vm}5q;^UOJf ztl=X3iL;;^^a#`t{Ae-%5Oq{?M#s6Npj+L(n-*LMI-yMR{)qki!~{5z{&`-iL}lgW zxo+tnvICK=lImjV$Z|O_cYj_PlEYCzu-XBz&XC-JVxUh9;6*z4fuBG+H{voCC;`~GYV|hj%j_&I zDZCj>Q_0RCwFauYoVMiUSB+*Mx`tg)bWmM^SwMA+?lBg12QUF_x2b)b?qb88K-YUd z0dO}3k#QirBV<5%jL$#wlf!60dizu;tsp(7XLdI=eQs?P`tOZYMjVq&jE)qK*6B^$ zBe>VvH5TO>s>izhwJJ$<`a8fakTL!yM^Zfr2hV9`f}}VVUXK39p@G|xYRz{fTI+Yq z20d=)iwjuG9RB$%$^&8#(c0_j0t_C~^|n+c`Apu|x7~;#cS-s=X1|C*YxX3ailhg_|0`g!E&GZJEr?bh#Tpb8siR=JxWKc{#w7g zWznLwi;zLFmM1g8V5-P#RsM@iX>TK$xsWuujcsVR^7TQ@!+vCD<>Bk9tdCo7Mzgq5 zv8d>dK9x8C@Qoh01u@3h0X_`SZluTb@5o;{4{{eF!-4405x8X7hewZWpz z2qEi4UTiXTvsa(0X7kQH{3VMF>W|6;6iTrrYD2fMggFA&-CBEfSqPlQDxqsa>{e2M z(R5PJ7uOooFc|9GU0ELA%m4&4Ja#cQpNw8i8ACAoK6?-px+oBl_yKmenZut#Xumjz zk8p^OV2KY&?5MUwGrBOo?ki`Sxo#?-Q4gw*Sh0k`@ zFTaYK2;}%Zk-68`#5DXU$2#=%YL#S&MTN8bF+!J2VT6x^XBci6O)Q#JfW{YMz) zOBM>t2rSj)n#0a3cjvu}r|k3od6W(SN}V-cL?bi*Iz-8uOcCcsX0L>ZXjLqk zZu2uHq5B|Kt>e+=pPKu=1P@1r9WLgYFq_TNV1p9pu0erHGd!+bBp!qGi+~4A(RsYN@CyXNrC&hxGmW)u5m35OmWwX`I+0yByglO`}HC4nGE^_HUs^&A(uaM zKPj^=qI{&ayOq#z=p&pnx@@k&I1JI>cttJcu@Ihljt?6p^6{|ds`0MoQwp+I{3l6` zB<9S((RpLG^>=Kic`1LnhpW2=Gu!x`m~=y;A`Qk!-w`IN;S8S930#vBVMv2vCKi}u z6<-VPrU0AnE&vzwV(CFC0gnZYcpa-l5T0ZS$P6(?9AM;`Aj~XDvt;Jua=jIgF=Fm? zdp=M$>`phx%+Gu};;-&7T|B1AcC#L4@mW5SV_^1BRbo6;2PWe$r+npRV`yc;T1mo& z+~_?7rA+(Um&o@Tddl zL_hxvWk~a)yY}%j`Y+200D%9$bWHy&;(yj{jpi?Rtz{J66ANw)UyPOm;t6FzY3$hx zcn)Ir79nhFvNa7^a{SHN7XH*|Vlsx`CddPnA&Qvh8aNhEA;mPVv;Ah=k<*u!Zq^7 z<=xs*iQTQOMMcg|(NA_auh@x`3#_LFt=)}%SQppP{E>mu_LgquAWvh<>L7tf9+~rO znwUDS52u)OtY<~!d$;m9+87aO+&`#2ICl@Y>&F{jI=H(K+@3M1$rr=*H^dye#~TyD z!){#Pyfn+|ugUu}G;a~!&&0aqQ59U@UT3|_JuBlYUpT$2+11;}JBJ`{+lQN9T@QFY z5+`t;6(TS0F?OlBTE!@7D`8#URDNqx2t6`GZ{ZgXeS@v%-eJzZOHz18aS|svxII$a zZeFjrJ*$IwX$f-Rzr_G>xbu@euGl)B7pC&S+CmDJBg$BoV~jxSO#>y z33`bupN#LDoW0feZe0%q8un0rYN|eRAnwDHQ6e_)xBTbtoZtTA=Fvk){q}9Os~6mQ zKB80VI_&6iSq`LnK7*kfHZoeX6?WE}8yjuDn=2#JG$+;-TOA1%^=DnXx%w{b=w}tS zQbU3XxtOI8E(!%`64r2`zog;5<0b4i)xBmGP^jiDZ2%HNSxIf3@wKs~uk4%3Mxz;~ zts_S~E4>W+YwI<-*-$U8*^HKDEa8oLbmqGg?3vewnaNg%Mm)W=)lcC_J+1ov^u*N3 zXJ?!BrH-+wGYziJq2Y#vyry6Z>NPgkEk+Ke`^DvNRdb>Q2Nlr#v%O@<5hbflI6EKE z9dWc0-ORk^T}jP!nkJ1imyjdVX@GrjOs%cpgA8-c&FH&$(4od#x6Y&=LiJZPINVyW z0snY$8JW@>tc2}DlrD3StQmA0Twck~@>8dSix9CyQOALcREdxoM$Sw*l!}bXKq9&r zysMWR@%OY24@e`?+#xV2bk{T^C_xSo8v2ZI=lBI*l{RciPwuE>L5@uhz@{!l)rtVlWC>)6(G)1~n=Q|S!{E9~6*fdpa*n z!()-8EpTdj=zr_Lswi;#{TxbtH$8*G=UM`I+icz7sr_SdnHXrv=?iEOF1UL+*6O;% zPw>t^kbW9X@oEXx<97%lBm-9?O_7L!DeD)Me#rwE54t~UBu9VZ zl_I1tBB~>jm@bw0Aljz8! zXBB6ATG6iByKIxs!qr%pz%wgqbg(l{65DP4#v(vqhhL{0b#0C8mq`bnqZ1OwFV z7mlZZJFMACm>h9v^2J9+^_zc1=JjL#qM5ZHaThH&n zXPTsR8(+)cj&>Un{6v*z?@VTLr{TmZ@-fY%*o2G}*G}#!bmqpoo*Ay@U!JI^Q@7gj;Kg-HIrLj4}#ec4~D2~X6vo;ghep-@&yOivYP zC19L0D`jjKy1Yi-SGPAn94(768Tcf$urAf{)1)9W58P`6MA{YG%O?|07!g9(b`8PXG1B1Sh0?HQmeJtP0M$O$hI z{5G`&9XzYhh|y@qsF1GnHN|~^ru~HVf#)lOTSrv=S@DyR$UKQk zjdEPFDz{uHM&UM;=mG!xKvp;xAGHOBo~>_=WFTmh$chpC7c`~7?36h)7$fF~Ii}8q zF|YXxH-Z?d+Q+27Rs3X9S&K3N+)OBxMHn1u(vlrUC6ckBY@@jl+mgr#KQUKo#VeFm zFwNYgv0<%~Wn}KeLeD9e1$S>jhOq&(e*I@L<=I5b(?G(zpqI*WBqf|Zge0&aoDUsC zngMRA_Kt0>La+Erl=Uv_J^p(z=!?XHpenzn$%EA`JIq#yYF?JLDMYiPfM(&Csr#f{ zdd+LJL1by?xz|D8+(fgzRs~(N1k9DSyK@LJygwaYX8dZl0W!I&c^K?7)z{2is;OkE zd$VK-(uH#AUaZrp=1z;O*n=b?QJkxu`Xsw&7yrX0?(CX=I-C#T;yi8a<{E~?vr3W> zQrpPqOW2M+AnZ&p{hqmHZU-;Q(7?- zP8L|Q0RM~sB0w1w53f&Kd*y}ofx@c z5Y6B8qGel+uT1JMot$nT1!Tim6{>oZzJXdyA+4euOLME?5Fd_85Uk%#E*ln%y{u8Q z$|?|R@Hpb~yTVK-Yr_S#%NUy7EBfYGAg>b({J|5b+j-PBpPy$Ns`PaJin4JdRfOaS zE|<HjH%NuJgsd2wOlv>~y=np%=2)$M9LS|>P)zJ+Fei5vYo_N~B0XCn+GM76 z)Xz3tg*FRVFgIl9zpESgdpWAavvVViGlU8|UFY{{gVJskg*I!ZjWyk~OW-Td4(mZ6 zB&SQreAAMqwp}rjy`HsG({l2&q5Y52<@AULVAu~rWI$UbFuZs>Sc*x+XI<+ez%$U)|a^unjpiW0l0 zj1!K0(b6$8LOjzRqQ~K&dfbMIE=TF}XFAi)$+h}5SD3lo z%%Qd>p9se=VtQG{kQ;N`sI)G^u|DN#7{aoEd zkksYP%_X$Rq08);-s6o>CGJ<}v`qs%eYf+J%DQ^2k68C%nvikRsN?$ap--f+vCS`K z#&~)f7!N^;sdUXu54gl3L=LN>FB^tuK=y2e#|hWiWUls__n@L|>xH{%8lIJTd5`w? zSwZbnS;W~DawT4OwSJVdAylbY+u5S+ZH{4hAi2&}Iv~W(UvHg(1GTZRPz`@{SOqzy z(8g&Dz=$PfRV=6FgxN~zo+G8OoPI&d-thcGVR*_^(R8COTM@bq?fDwY{}WhsQS1AK zF6R1t8!RdFmfocpJ6?9Yv~;WYi~XPgs(|>{5})j!AR!voO7y9&cMPo#80A(`za@t>cx<0;qxM@S*m(jYP)dMXr*?q0E`oL;12}VAep179uEr8c<=D zr5?A*C{eJ`z9Ee;E$8)MECqatHkbHH z&Y+ho0B$31MIB-xm&;xyaFCtg<{m~M-QDbY)fQ>Q*Xibb~8ytxZQ?QMf9!%cV zU0_X1@b4d+Pg#R!`OJ~DOrQz3@cpiGy~XSKjZQQ|^4J1puvwKeScrH8o{bscBsowomu z^f12kTvje`yEI3eEXDHJ6L+O{Jv$HVj%IKb|J{IvD*l6IG8WUgDJ*UGz z3!C%>?=dlfSJ>4U88)V+`U-!9r^@AxJBx8R;)J4Fn@`~k>8>v0M9xp90OJElWP&R5 zM#v*vtT}*Gm1^)Bv!s72T3PB0yVIjJW)H7a)ilkAvoaH?)jjb`MP>2z{%Y?}83 zUIwBKn`-MSg)=?R)1Q0z3b>dHE^)D8LFs}6ASG1|daDly_^lOSy&zIIhm*HXm1?VS=_iacG);_I9c zUQH1>i#*?oPIwBMJkzi_*>HoUe}_4o>2(SHWzqQ=;TyhAHS;Enr7!#8;sdlty&(>d zl%5cjri8`2X^Ds`jnw7>A`X|bl=U8n+3LKLy(1dAu8`g@9=5iw$R0qk)w8Vh_Dt^U zIglK}sn^)W7aB(Q>HvrX=rxB z+*L)3DiqpQ_%~|m=44LcD4-bxO3OO*LPjsh%p(k?&jvLp0py57oMH|*IMa(<|{m1(0S|x)?R-mqJ=I;_YUZA>J z62v*eSK;5w!h8J+6Z2~oyGdZ68waWfy09?4fU&m7%u~zi?YPHPgK6LDwphgaYu%0j zurtw)AYOpYKgHBrkX189mlJ`q)w-f|6>IER{5Lk97%P~a-JyCRFjejW@L>n4vt6#hq;!|m;hNE||LK3nw1{bJOy+eBJjK=QqNjI;Q6;Rp5 z&035pZDUZ#%Oa;&_7x0T<7!RW`#YBOj}F380Bq?MjjEhrvlCATPdkCTTl+2efTX$k zH&0zR1n^`C3ef~^sXzJK-)52(T}uTG%OF8yDhT76L~|^+hZ2hiSM*QA9*D5odI1>& z9kV9jC~twA5MwyOx(lsGD_ggYmztXPD`2=_V|ks_FOx!_J8!zM zTzh^cc+=VNZ&(OdN=y4Juw)@8-85lwf_#VMN!Ed(eQiRiLB2^2e`4dp286h@v@`O%_b)Y~A; zv}r6U?zs&@uD_+(_4bwoy7*uozNvp?bXFoB8?l8yG0qsm1JYzIvB_OH4_2G*IIOwT zVl%HX1562vLVcxM_RG*~w_`FbIc!(T=3>r528#%mwwMK}uEhJ()3MEby zQQjzqjWkwfI~;Fuj(Lj=Ug0y`>~C7`w&wzjK(rPw+Hpd~EvQ-ufQOiB4OMpyUKJhw zqEt~jle9d7S~LI~$6Z->J~QJ{Vdn3!c}g9}*KG^Kzr^(7VI5Gk(mHLL{itj_hG?&K4Ws0+T4gLfi3eu$N=`s36geNC?c zm!~}vG6lx9Uf^5M;bWntF<-{p^bruy~f?sk9 zcETAPQZLoJ8JzMMg<-=ju4keY@SY%Wo?u9Gx=j&dfa6LIAB|IrbORLV1-H==Z1zCM zeZcOYpm5>U2fU7V*h;%n`8 zN95QhfD994={1*<2vKLCNF)feKOGk`R#K~G=;rfq}|)s20&MCa65 zUM?xF5!&e0lF%|U!#rD@I{~OsS_?=;s_MQ_b_s=PuWdC)q|UQ&ea)DMRh5>fpQjXe z%9#*x=7{iRCtBKT#H>#v%>77|{4_slZ)XCY{s3j_r{tdpvb#|r|sbS^dU1x70$eJMU!h{Y7Kd{dl}9&vxQl6Jt1a` zHQZrWyY0?!vqf@u-fxU_@+}u(%Wm>0I#KP48tiAPYY!TdW(o|KtVI|EUB9V`CBBNaBLVih7+yMVF|GSoIQD0Jfb{ z!OXq;(>Z?O`1gap(L~bUcp>Lc@Jl-})^=6P%<~~9ywY=$iu8pJ0m*hOPzr~q`23eX zgbs;VOxxENe0UMVeN*>uCn9Gk!4siN-e>x)pIKAbQz!G)TcqIJ0`JBBaX>1-4_XO_-HCS^vr2vjv#7KltDZdyQ{tlWh4$Gm zB>|O1cBDC)yG(sbnc*@w6e%e}r*|IhpXckx&;sQCwGdKH+3oSG-2)Bf#x`@<4ETAr z0My%7RFh6ZLiZ_;X6Mu1YmXx7C$lSZ^}1h;j`EZd6@%JNUe=btBE z%s=Xmo1Ps?8G`}9+6>iaB8bgjUdXT?=trMu|4yLX^m0Dg{m7rpKNJey|EwHI+nN1e zL^>qN%5Fg)dGs4DO~uwIdXImN)QJ*Jhpj7$fq_^`{3fwpztL@WBB}OwQ#Epo-mqMO zsM$UgpFiG&d#)lzEQ{3Q;)&zTw;SzGOah-Dpm{!q7<8*)Ti_;xvV2TYXa}=faXZy? z3y?~GY@kl)>G&EvEijk9y1S`*=zBJSB1iet>0;x1Ai)*`^{pj0JMs)KAM=@UyOGtO z3y0BouW$N&TnwU6!%zS%nIrnANvZF&vB1~P5_d`x-giHuG zPJ;>XkVoghm#kZXRf>qxxEix;2;D1CC~NrbO6NBX!`&_$iXwP~P*c($EVV|669kDO zKoTLZNF4Cskh!Jz5ga9uZ`3o%7Pv`d^;a=cXI|>y;zC3rYPFLQkF*nv(r>SQvD*## z(Vo%^9g`%XwS0t#94zPq;mYGLKu4LU3;txF26?V~A0xZbU4Lmy`)>SoQX^m7fd^*E z+%{R4eN!rIk~K)M&UEzxp9dbY;_I^c} zOc{wlIrN_P(PPqi51k_$>Lt|X6A^|CGYgKAmoI#Li?;Wq%q~q*L7ehZkUrMxW67Jl zhsb~+U?33QS>eqyN{(odAkbopo=Q$Az?L+NZW>j;#~@wCDX?=L5SI|OxI~7!Pli;e zELMFcZtJY3!|=Gr2L4>z8yQ-{To>(f80*#;6`4IAiqUw`=Pg$%C?#1 z_g@hIGerILSU>=P>z{gM|DS91A4cT@PEIB^hSop!uhMo#2G;+tQSpDO_6nOnPWSLU zS;a9m^DFMXR4?*X=}d7l;nXuHk&0|m`NQn%d?8|Ab3A9l9Jh5s120ibWBdB z$5YwsK3;wvp!Kn@)Qae{ef`0#NwlRpQ}k^r>yos_Ne1;xyKLO?4)t_G4eK~wkUS2A&@_;)K0-03XGBzU+5f+uMDxC z(s8!8!RvdC#@`~fx$r)TKdLD6fWEVdEYtV#{ncT-ZMX~eI#UeQ-+H(Z43vVn%Yj9X zLdu9>o%wnWdvzA-#d6Z~vzj-}V3FQ5;axDIZ;i(95IIU=GQ4WuU{tl-{gk!5{l4_d zvvb&uE{%!iFwpymz{wh?bKr1*qzeZb5f6e6m_ozRF&zux2mlK=v_(_s^R6b5lu?_W4W3#<$zeG~Pd)^!4tzhs}-Sx$FJP>)ZGF(hVTH|C3(U zs0PO&*h_ zNA-&qZpTP$$LtIgfiCn07}XDbK#HIXdmv8zdz4TY;ifNIH-0jy(gMSByG2EF~Th#eb_TueZC` zE?3I>UTMpKQ})=C;6p!?G)M6w^u*A57bD?2X`m3X^6;&4%i_m(uGJ3Z5h`nwxM<)H z$I5m?wN>O~8`BGnZ=y^p6;0+%_0K}Dcg|K;+fEi|qoBqvHj(M&aHGqNF48~XqhtU? z^ogwBzRlOfpAJ+Rw7IED8lRbTdBdyEK$gPUpUG}j-M42xDj_&qEAQEtbs>D#dRd7Y z<&TpSZ(quQDHiCFn&0xsrz~4`4tz!CdL8m~HxZM_agu@IrBpyeL1Ft}V$HX_ZqDPm z-f89)pjuEzGdq-PRu`b1m+qBGY{zr_>{6Ss>F|xHZlJj9dt5HD$u`1*WZe)qEIuDSR)%z+|n zatVlhQ?$w#XRS7xUrFE;Y8vMGhQS5*T{ZnY=q1P?w5g$OKJ#M&e??tAmPWHMj3xhS ziGxapy?kn@$~2%ZY;M8Bc@%$pkl%Rvj!?o%agBvpQ-Q61n9kznC4ttrRNQ4%GFR5u zyv%Yo9~yxQJWJSfj z?#HY$y=O~F|2pZs22pu|_&Ajd+D(Mt!nPUG{|1nlvP`=R#kKH zO*s$r_%ss5h1YO7k0bHJ2CXN)Yd6CHn~W!R=SqkWe=&nAZu(Q1G!xgcUilM@YVei@2@a`8he z9@pM`)VB*=e7-MWgLlXlc)t;fF&-AwM{E-EX}pViFn0I0CNw2bNEnN2dj!^4(^zS3 zobUm1uQnpqk_4q{pl*n06=TfK_C>UgurKFjRXsK_LEn};=79`TB12tv6KzwSu*-C8 z;=~ohDLZylHQ|Mpx-?yql>|e=vI1Z!epyUpAcDCp4T|*RV&X`Q$0ogNwy6mFALo^@ z9=&(9txO8V@E!@6^(W0{*~CT>+-MA~vnJULBxCTUW>X5>r7*eXYUT0B6+w@lzw%n> z_VjJ<2qf|(d6jYq2(x$(ZDf!yVkfnbvNmb5c|hhZ^2TV_LBz`9w!e_V*W_(MiA7|= z&EeIIkw*+$Xd!)j8<@_<}A5;~A_>3JT*kX^@}cDoLd>Qj<`Se^wdUa(j0dp+Tl8EptwBm{9OGsdFEq zM`!pjf(Lm(`$e3FLOjqA5LnN5o!}z{ zNf}rJuZh@yUtq&ErjHeGzX4(!luV!jB&;FAP|!R_QHYw#^Z1LwTePAKJ6X&IDNO#; z)#I@Xnnzyij~C@UH~X51JCgQeF0&hTXnuoElz#m{heZRexWc0k4<>0+ClX7%0 zEBqCCld1tD9Zwkr4{?Nor19#E5-YKfB8d?qgR82-Ow2^AuNevly2*tHA|sK!ybYkX zm-sLQH72P&{vEAW6+z~O5d0qd=xW~rua~5a?ymYFSD@8&gV)E5@RNNBAj^C99+Z5Z zR@Pq55mbCQbz+Mn$d_CMW<-+?TU960agEk1J<>d>0K=pF19yN))a~4>m^G&tc*xR+yMD*S=yip-q=H zIlredHpsJV8H(32@Zxc@bX6a21dUV95Th--8pE6C&3F>pk=yv$yd6@Haw;$v4+Fcb zRwn{Qo@0`7aPa2LQOP}j9v>sjOo5Kqvn|`FLizX zB+@-u4Lw|jsvz{p^>n8Vo8H2peIqJJnMN}A)q6%$Tmig7eu^}K2 zrh$X?T|ZMsoh{6pdw1G$_T<`Ds-G=jc;qcGdK4{?dN2-XxjDNbb(7pk|3JUVCU4y; z)?LXR>f+AAu)JEiti_Zy#z5{RgsC}R(@jl%9YZ>zu~hKQ*AxbvhC378-I@{~#%Y`Z zy=a=9YpewPIC+gkEUUwtUL7|RU7=!^Aa}Mk^6uxOgRGA#JXjWLsjFUnix|Mau{hDT z7mn*z1m5g`vP(#tjT0Zy4eAY(br&!RiiXE=ZI!{sE1#^#%x^Z7t1U)b<;%Y}Q9=5v z;wpDCEZ@OE36TWT=|gxigT@VaW9BvHS05;_P(#s z8zI4XFQys}q)<`tkX$WnSarn{3e!s}4(J!=Yf>+Y>cP3f;vr63f2{|S^`_pWc)^5_!R z*(x-fuBxL51@xe!lnDBKi}Br$c$BMZ3%f2Sa6kLabiBS{pq*yj;q|k(86x`PiC{p6 z_bxCW{>Q2BA8~Ggz&0jkrcU+-$ANBsOop*ms>34K9lNYil@}jC;?cYP(m^P}nR6FV zk(M%48Z&%2Rx$A&FhOEirEhY0(dn;-k(qkTU)sFQ`+-ih+s@A8g?r8Pw+}2;35WYf zi}VO`jS`p(tc)$X$a>-#WXoW!phhatC*$}|rk>|wUU71eUJG^$c6_jwX?iSHM@6__ zvV|6%U*$sSXJu9SX?2%M^kK|}a2QJ8AhF{fuXrHZxXsI~O zGKX45!K7p*MCPEQ=gp?eu&#AW*pR{lhQR##P_*{c_DjMGL|3T3-bSJ(o$|M{ytU}> zAV>wq*uE*qFo9KvnA^@juy{x<-u*#2NvkV={Ly}ysKYB-k`K3@K#^S1Bb$8Y#0L0# z`6IkSG&|Z$ODy|VLS+y5pFJx&8tvPmMd8c9FhCyiU8~k6FwkakUd^(_ml8`rnl>JS zZV){9G*)xBqPz^LDqRwyS6w86#D^~xP4($150M)SOZRe9sn=>V#aG0Iy(_^YcPpIz8QYM-#s+n% z@Jd?xQq?Xk6=<3xSY7XYP$$yd&Spu{A#uafiIfy8gRC`o0nk{ezEDjb=q_qRAlR1d zFq^*9Gn)yTG4b}R{!+3hWQ+u3GT~8nwl2S1lpw`s0X_qpxv)g+JIkVKl${sYf_nV~B>Em>M;RlqGb5WVil(89 zs=ld@|#;dq1*vQGz=7--Br-|l) zZ%Xh@v8>B7P?~}?Cg$q9_={59l%m~O&*a6TKsCMAzG&vD>k2WDzJ6!tc!V)+oxF;h zJH;apM=wO?r_+*#;ulohuP=E>^zon}a$NnlcQ{1$SO*i=jnGVcQa^>QOILc)e6;eNTI>os=eaJ{*^DE+~jc zS}TYeOykDmJ=6O%>m`i*>&pO_S;qMySJIyP=}4E&J%#1zju$RpVAkZbEl+p%?ZP^C z*$$2b4t%a(e+%>a>d_f_<JjxI#J1x;=hPd1zFPx=6T$;;X1TD*2(edZ3f46zaAoW>L53vS_J*N8TMB|n+;LD| zC=GkQPpyDY#Am4l49chDv*gojhRj_?63&&8#doW`INATAo(qY#{q}%nf@eTIXmtU< zdB<7YWfyCmBs|c)cK>1)v&M#!yNj#4d$~pVfDWQc_ke1?fw{T1Nce_b`v|Vp5ig(H zJvRD^+ps46^hLX;=e2!2e;w9y1D@!D$c@Jc&%%%IL=+xzw55&2?darw=9g~>P z9>?Kdc$r?6c$m%x2S$sdpPl>GQZ{rC9mPS63*qjCVa?OIBj!fW zm|g?>CVfGXNjOfcyqImXR_(tXS(F{FcoNzKvG5R$IgGaxC@)i(e+$ME}vPVIhd|mx2IIE+f zM?9opQHIVgBWu)^A|RzXw!^??S!x)SZOwZaJkGjc<_}2l^eSBm!eAJG9T>EC6I_sy z?bxzDIAn&K5*mX)$RQzDA?s)-no-XF(g*yl4%+GBf`##bDXJ==AQk*xmnatI;SsLp zP9XTHq5mmS=iWu~9ES>b%Q=1aMa|ya^vj$@qz9S!ih{T8_PD%Sf_QrNKwgrXw9ldm zHRVR98*{C?_XNpJn{abA!oix_mowRMu^2lV-LPi;0+?-F(>^5#OHX-fPED zCu^l7u3E%STI}c4{J2!)9SUlGP_@!d?5W^QJXOI-Ea`hFMKjR7TluLvzC-ozCPn1`Tpy z!vlv@_Z58ILX6>nDjTp-1LlFMx~-%GA`aJvG$?8*Ihn;mH37eK**rmOEwqegf-Ccx zrIX4;{c~RK>XuTXxYo5kMiWMy)!IC{*DHG@E$hx?RwP@+wuad(P1{@%tRkyJRqD)3 zMHHHZ4boqDn>-=DgR5VlhQTpfVy182Gk;A_S8A1-;U1RR>+$62>(MUx@Nox$vTjHq z%QR=j!6Gdyb5wu7y(YUktwMuW5<@jl?m4cv4BODiT5o8qVdC0MBqGr@-YBIwnpZAY znX9(_uQjP}JJ=!~Ve9#5I~rUnN|P_3D$LqZcvBnywYhjlMSFHm`;u9GPla{5QD7(7*6Tb3Svr8;(nuAd81q$*uq6HC_&~je*Ca7hP4sJp0av{M8480wF zxASi7Qv+~@2U%Nu1Ud;s-G4CTVWIPyx!sg&8ZG0Wq zG_}i3C(6_1>q3w!EH7$Kwq8uBp2F2N7}l65mk1p*9v0&+;th=_E-W)E;w}P(j⁢ zv5o9#E7!G0XmdzfsS{efPNi`1b44~SZ4Z8fuX!I}#8g+(wxzQwUT#Xb2(tbY1+EUhGKoT@KEU9Ktl>_0 z%bjDJg;#*gtJZv!-Zs`?^}v5eKmnbjqlvnSzE@_SP|LG_PJ6CYU+6zY6>92%E+ z=j@TZf-iW4(%U{lnYxQA;7Q!b;^brF8n0D>)`q5>|WDDXLrqYU_tKN2>=#@~OE7grMnNh?UOz-O~6 z6%rHy{#h9K0AT+lDC7q4{hw^|q6*Ry;;L%Q@)Ga}$60_q%D)rv(CtS$CQbpq9|y1e zRSrN4;$Jyl{m5bZw`$8TGvb}(LpY{-cQ)fcyJv7l3S52TLXVDsphtv&aPuDk1OzCA z4A^QtC(!11`IsNx_HnSy?>EKpHJWT^wmS~hc^p^zIIh@9f6U@I2 zC=Mve{j2^)mS#U$e{@Q?SO6%LDsXz@SY+=cK_QMmXBIU)j!$ajc-zLx3V60EXJ!qC zi<%2x8Q24YN+&8U@CIlN zrZkcT9yh%LrlGS9`G)KdP(@9Eo-AQz@8GEFWcb7U=a0H^ZVbLmz{+&M7W(nXJ4sN8 zJLR7eeK(K8`2-}j(T7JsO`L!+CvbueT%izanm-^A1Dn{`1Nw`9P?cq;7no+XfC`K(GO9?O^5zNIt4M+M8LM0=7Gz8UA@Z0N+lg+cX)NfazRu z5D)~HA^(u%w^cz+@2@_#S|u>GpB+j4KzQ^&Wcl9f z&hG#bCA(Yk0D&t&aJE^xME^&E-&xGHhXn%}psEIj641H+Nl-}boj;)Zt*t(4wZ5DN z@GXF$bL=&pBq-#vkTkh>7hl%K5|3 z{`Vn9b$iR-SoGENp}bn4;fR3>9sA%X2@1L3aE9yTra;Wb#_`xWwLSLdfu+PAu+o3| zGVnpzPr=ch{uuoHjtw7+_!L_2;knQ!DuDl0R`|%jr+}jFzXtrHIKc323?JO{l&;VF z*L1+}JU7%QJOg|5|Tc|D8fN zJORAg=_vsy{ak|o);@)Yh8Lkcg@$FG3k@ep36BRa^>~UmnRPziS>Z=`Jb2x*Q#`%A zU*i3&Vg?TluO@X0O;r2Jl6LKLUOVhSqg1*qOt^|8*c7 zo(298@+r$k_wQNGHv{|$tW(T8L+4_`FQ{kEW5Jgg{yf7ey4ss_(SNKfz(N9lx&a;< je(UuV8hP?p&}TPdm1I$XmG#(RzlD&B2izSj9sl%y5~4qc literal 0 HcmV?d00001 diff --git a/languages/java/gradle/wrapper/gradle-wrapper.properties b/languages/java/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..ac72c34e8 --- /dev/null +++ b/languages/java/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/languages/java/gradlew b/languages/java/gradlew new file mode 100755 index 000000000..0adc8e1a5 --- /dev/null +++ b/languages/java/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright Β© 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions Β«$varΒ», Β«${var}Β», Β«${var:-default}Β», Β«${var+SET}Β», +# Β«${var#prefix}Β», Β«${var%suffix}Β», and Β«$( cmd )Β»; +# * compound commands having a testable exit status, especially Β«caseΒ»; +# * various built-in commands including Β«commandΒ», Β«setΒ», and Β«ulimitΒ». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/languages/java/gradlew.bat b/languages/java/gradlew.bat new file mode 100644 index 000000000..6689b85be --- /dev/null +++ b/languages/java/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/languages/java/settings.gradle b/languages/java/settings.gradle new file mode 100644 index 000000000..961795f50 --- /dev/null +++ b/languages/java/settings.gradle @@ -0,0 +1,5 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +rootProject.name = 'BitwardenSDK' diff --git a/languages/java/src/main/java/com/bitwarden/sdk/BitwardenClient.java b/languages/java/src/main/java/com/bitwarden/sdk/BitwardenClient.java new file mode 100644 index 000000000..9e1948184 --- /dev/null +++ b/languages/java/src/main/java/com/bitwarden/sdk/BitwardenClient.java @@ -0,0 +1,87 @@ +package com.bitwarden.sdk; + +import com.bitwarden.sdk.schema.*; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.sun.jna.Native; +import com.sun.jna.Pointer; + +import java.util.function.Function; + +public class BitwardenClient implements AutoCloseable { + + private final Pointer client; + + private final BitwardenLibrary library; + + private final CommandRunner commandRunner; + + private boolean isClientOpen; + + private final ProjectsClient projects; + + private final SecretsClient secrets; + + public BitwardenClient(BitwardenSettings bitwardenSettings) { + ClientSettings clientSettings = new ClientSettings(); + clientSettings.setAPIURL(bitwardenSettings.getApiUrl()); + clientSettings.setIdentityURL(bitwardenSettings.getIdentityUrl()); + clientSettings.setDeviceType(DeviceType.SDK); + clientSettings.setUserAgent("Bitwarden JAVA-SDK"); + + library = Native.load("bitwarden_c", BitwardenLibrary.class); + + try { + client = library.init(Converter.ClientSettingsToJsonString(clientSettings)); + } catch (JsonProcessingException e) { + throw new BitwardenClientException("Error while processing client settings"); + } + + commandRunner = new CommandRunner(library, client); + projects = new ProjectsClient(commandRunner); + secrets = new SecretsClient(commandRunner); + isClientOpen = true; + } + + static Function throwingFunctionWrapper(ThrowingFunction throwingFunction) { + + return i -> { + try { + return throwingFunction.accept(i); + } catch (Exception ex) { + throw new BitwardenClientException("Response deserialization failed"); + } + }; + } + + public APIKeyLoginResponse accessTokenLogin(String accessToken) { + Command command = new Command(); + AccessTokenLoginRequest accessTokenLoginRequest = new AccessTokenLoginRequest(); + accessTokenLoginRequest.setAccessToken(accessToken); + command.setAccessTokenLogin(accessTokenLoginRequest); + + ResponseForAPIKeyLoginResponse response = commandRunner.runCommand(command, + throwingFunctionWrapper(Converter::ResponseForAPIKeyLoginResponseFromJsonString)); + + if (response == null || !response.getSuccess()) { + throw new BitwardenClientException(response != null ? response.getErrorMessage() : "Login failed"); + } + + return response.getData(); + } + + public ProjectsClient projects() { + return projects; + } + + public SecretsClient secrets() { + return secrets; + } + + @Override + public void close() { + if (isClientOpen) { + library.free_mem(client); + isClientOpen = false; + } + } +} diff --git a/languages/java/src/main/java/com/bitwarden/sdk/BitwardenClientException.java b/languages/java/src/main/java/com/bitwarden/sdk/BitwardenClientException.java new file mode 100644 index 000000000..7b6025be8 --- /dev/null +++ b/languages/java/src/main/java/com/bitwarden/sdk/BitwardenClientException.java @@ -0,0 +1,8 @@ +package com.bitwarden.sdk; + +public class BitwardenClientException extends RuntimeException { + + public BitwardenClientException(String message) { + super(message); + } +} diff --git a/languages/java/src/main/java/com/bitwarden/sdk/BitwardenLibrary.java b/languages/java/src/main/java/com/bitwarden/sdk/BitwardenLibrary.java new file mode 100644 index 000000000..73d81452c --- /dev/null +++ b/languages/java/src/main/java/com/bitwarden/sdk/BitwardenLibrary.java @@ -0,0 +1,13 @@ +package com.bitwarden.sdk; + +import com.sun.jna.Library; +import com.sun.jna.Pointer; + +public interface BitwardenLibrary extends Library { + + Pointer init(String clientSettings); + + void free_mem(Pointer client); + + String run_command(String command, Pointer client); +} diff --git a/languages/java/src/main/java/com/bitwarden/sdk/BitwardenSettings.java b/languages/java/src/main/java/com/bitwarden/sdk/BitwardenSettings.java new file mode 100644 index 000000000..7112297b4 --- /dev/null +++ b/languages/java/src/main/java/com/bitwarden/sdk/BitwardenSettings.java @@ -0,0 +1,32 @@ +package com.bitwarden.sdk; + +public class BitwardenSettings { + + private String apiUrl; + + private String identityUrl; + + public BitwardenSettings() { + } + + public BitwardenSettings(String apiUrl, String identityUrl) { + this.apiUrl = apiUrl; + this.identityUrl = identityUrl; + } + + public String getApiUrl() { + return apiUrl; + } + + public void setApiUrl(String apiUrl) { + this.apiUrl = apiUrl; + } + + public String getIdentityUrl() { + return identityUrl; + } + + public void setIdentityUrl(String identityUrl) { + this.identityUrl = identityUrl; + } +} diff --git a/languages/java/src/main/java/com/bitwarden/sdk/CommandRunner.java b/languages/java/src/main/java/com/bitwarden/sdk/CommandRunner.java new file mode 100644 index 000000000..11a100814 --- /dev/null +++ b/languages/java/src/main/java/com/bitwarden/sdk/CommandRunner.java @@ -0,0 +1,45 @@ +package com.bitwarden.sdk; + +import com.bitwarden.sdk.schema.Command; +import com.bitwarden.sdk.schema.Converter; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.sun.jna.Pointer; + +import java.io.IOException; +import java.util.function.Function; + +class CommandRunner { + + private final BitwardenLibrary library; + + private final Pointer client; + + CommandRunner(BitwardenLibrary library, Pointer client) { + this.library = library; + this.client = client; + } + + T runCommand(Command command, Function deserializer) { + String response = null; + + try { + response = library.run_command(commandToString(command), client); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return deserializer.apply(response); + } + + private String commandToString(Command command) throws IOException { + // Removes null properties from the generated converter output to avoid command errors + String inputJson = Converter.CommandToJsonString(command); + + ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + + Object inputObject = mapper.readValue(inputJson, Object.class); + return mapper.writeValueAsString(inputObject); + } +} diff --git a/languages/java/src/main/java/com/bitwarden/sdk/ExampleProgram.java b/languages/java/src/main/java/com/bitwarden/sdk/ExampleProgram.java new file mode 100644 index 000000000..f3ad53036 --- /dev/null +++ b/languages/java/src/main/java/com/bitwarden/sdk/ExampleProgram.java @@ -0,0 +1,42 @@ +package com.bitwarden.sdk; + +import com.bitwarden.sdk.schema.*; + +import java.util.UUID; + +public class ExampleProgram { + + public static void main(String[] args) { + BitwardenSettings bitwardenSettings = new BitwardenSettings(); + bitwardenSettings.setApiUrl("https://api.bitwarden.com"); + bitwardenSettings.setIdentityUrl("https://identity.bitwarden.com"); + + try (BitwardenClient bitwardenClient = new BitwardenClient(bitwardenSettings)) { + APIKeyLoginResponse apiKeyLoginResponse = bitwardenClient.accessTokenLogin(""); + + UUID organizationId = UUID.fromString(""); + ProjectResponse projectResponse = bitwardenClient.projects().create(organizationId, "NewTestProject"); + UUID projectId = projectResponse.getID(); + ProjectsResponse projectsResponse = bitwardenClient.projects().list(organizationId); + projectResponse = bitwardenClient.projects().get(projectId); + projectResponse = bitwardenClient.projects().update(projectId, organizationId, "NewTestProject2"); + + String key = "key"; + String value = "value"; + String note = "note"; + SecretResponse secretResponse = bitwardenClient.secrets().create(key, value, note, organizationId, + new UUID[]{projectId}); + UUID secretId = secretResponse.getID(); + SecretIdentifiersResponse secretIdentifiersResponse = bitwardenClient.secrets().list(organizationId); + secretResponse = bitwardenClient.secrets().get(secretId); + key = "key2"; + value = "value2"; + note = "note2"; + secretResponse = bitwardenClient.secrets().update(secretId, key, value, note, organizationId, + new UUID[]{projectId}); + + SecretsDeleteResponse secretsDeleteResponse = bitwardenClient.secrets().delete(new UUID[]{secretId}); + ProjectsDeleteResponse projectsDeleteResponse = bitwardenClient.projects().delete(new UUID[]{projectId}); + } + } +} diff --git a/languages/java/src/main/java/com/bitwarden/sdk/ProjectsClient.java b/languages/java/src/main/java/com/bitwarden/sdk/ProjectsClient.java new file mode 100644 index 000000000..73b87440c --- /dev/null +++ b/languages/java/src/main/java/com/bitwarden/sdk/ProjectsClient.java @@ -0,0 +1,109 @@ +package com.bitwarden.sdk; + +import com.bitwarden.sdk.schema.*; + +import java.util.UUID; + +public class ProjectsClient { + + private final CommandRunner commandRunner; + + ProjectsClient(CommandRunner commandRunner) { + this.commandRunner = commandRunner; + } + + public ProjectResponse get(UUID id) { + Command command = new Command(); + ProjectsCommand projectsCommand = new ProjectsCommand(); + ProjectGetRequest projectGetRequest = new ProjectGetRequest(); + projectGetRequest.setID(id); + projectsCommand.setGet(projectGetRequest); + command.setProjects(projectsCommand); + + ResponseForProjectResponse response = commandRunner.runCommand(command, + BitwardenClient.throwingFunctionWrapper(Converter::ResponseForProjectResponseFromJsonString)); + + if (response == null || !response.getSuccess()) { + throw new BitwardenClientException(response != null ? response.getErrorMessage() : "Project not found"); + } + + return response.getData(); + } + + public ProjectResponse create(UUID organizationId, String name) { + Command command = new Command(); + ProjectsCommand projectsCommand = new ProjectsCommand(); + ProjectCreateRequest projectCreateRequest = new ProjectCreateRequest(); + projectCreateRequest.setOrganizationID(organizationId); + projectCreateRequest.setName(name); + projectsCommand.setCreate(projectCreateRequest); + command.setProjects(projectsCommand); + + ResponseForProjectResponse response = commandRunner.runCommand(command, + BitwardenClient.throwingFunctionWrapper(Converter::ResponseForProjectResponseFromJsonString)); + + if (response == null || !response.getSuccess()) { + throw new BitwardenClientException(response != null ? response.getErrorMessage() : "Project create failed"); + } + + return response.getData(); + } + + public ProjectResponse update(UUID id, UUID organizationId, String name) { + Command command = new Command(); + ProjectsCommand projectsCommand = new ProjectsCommand(); + ProjectPutRequest projectPutRequest = new ProjectPutRequest(); + projectPutRequest.setID(id); + projectPutRequest.setOrganizationID(organizationId); + projectPutRequest.setName(name); + projectsCommand.setUpdate(projectPutRequest); + command.setProjects(projectsCommand); + + ResponseForProjectResponse response = commandRunner.runCommand(command, + BitwardenClient.throwingFunctionWrapper(Converter::ResponseForProjectResponseFromJsonString)); + + if (response == null || !response.getSuccess()) { + throw new BitwardenClientException(response != null ? response.getErrorMessage() : "Project update failed"); + } + + return response.getData(); + } + + public ProjectsDeleteResponse delete(UUID[] ids) { + Command command = new Command(); + ProjectsCommand projectsCommand = new ProjectsCommand(); + ProjectsDeleteRequest projectsDeleteRequest = new ProjectsDeleteRequest(); + projectsDeleteRequest.setIDS(ids); + projectsCommand.setDelete(projectsDeleteRequest); + command.setProjects(projectsCommand); + + ResponseForProjectsDeleteResponse response = commandRunner.runCommand(command, + BitwardenClient.throwingFunctionWrapper(Converter::ResponseForProjectsDeleteResponseFromJsonString)); + + if (response == null || !response.getSuccess()) { + throw new BitwardenClientException(response != null ? + response.getErrorMessage() : "Projects update failed"); + } + + return response.getData(); + } + + public ProjectsResponse list(UUID organizationId) { + Command command = new Command(); + ProjectsCommand projectsCommand = new ProjectsCommand(); + ProjectsListRequest projectsListRequest = new ProjectsListRequest(); + projectsListRequest.setOrganizationID(organizationId); + projectsCommand.setList(projectsListRequest); + command.setProjects(projectsCommand); + + ResponseForProjectsResponse response = commandRunner.runCommand(command, + BitwardenClient.throwingFunctionWrapper(Converter::ResponseForProjectsResponseFromJsonString)); + + if (response == null || !response.getSuccess()) { + throw new BitwardenClientException(response != null ? + response.getErrorMessage() : "No projects for given organization"); + } + + return response.getData(); + } +} diff --git a/languages/java/src/main/java/com/bitwarden/sdk/SecretsClient.java b/languages/java/src/main/java/com/bitwarden/sdk/SecretsClient.java new file mode 100644 index 000000000..f1a97fdc7 --- /dev/null +++ b/languages/java/src/main/java/com/bitwarden/sdk/SecretsClient.java @@ -0,0 +1,115 @@ +package com.bitwarden.sdk; + +import com.bitwarden.sdk.schema.*; + +import java.util.UUID; + +public class SecretsClient { + + private final CommandRunner commandRunner; + + SecretsClient(CommandRunner commandRunner) { + this.commandRunner = commandRunner; + } + + public SecretResponse get(UUID id) { + Command command = new Command(); + SecretsCommand secretsCommand = new SecretsCommand(); + SecretGetRequest secretGetRequest = new SecretGetRequest(); + secretGetRequest.setID(id); + secretsCommand.setGet(secretGetRequest); + command.setSecrets(secretsCommand); + + ResponseForSecretResponse response = commandRunner.runCommand(command, + BitwardenClient.throwingFunctionWrapper(Converter::ResponseForSecretResponseFromJsonString)); + + if (response == null || !response.getSuccess()) { + throw new BitwardenClientException(response != null ? response.getErrorMessage() : "Secret not found"); + } + + return response.getData(); + } + + public SecretResponse create(String key, String value, String note, UUID organizationId, UUID[] projectIds) { + Command command = new Command(); + SecretsCommand secretsCommand = new SecretsCommand(); + SecretCreateRequest secretCreateRequest = new SecretCreateRequest(); + secretCreateRequest.setKey(key); + secretCreateRequest.setValue(value); + secretCreateRequest.setNote(note); + secretCreateRequest.setOrganizationID(organizationId); + secretCreateRequest.setProjectIDS(projectIds); + secretsCommand.setCreate(secretCreateRequest); + command.setSecrets(secretsCommand); + + ResponseForSecretResponse response = commandRunner.runCommand(command, + BitwardenClient.throwingFunctionWrapper(Converter::ResponseForSecretResponseFromJsonString)); + + if (response == null || !response.getSuccess()) { + throw new BitwardenClientException(response != null ? response.getErrorMessage() : "Secret create failed"); + } + + return response.getData(); + } + + public SecretResponse update(UUID id, String key, String value, String note, UUID organizationId, + UUID[] projectIds) { + Command command = new Command(); + SecretsCommand secretsCommand = new SecretsCommand(); + SecretPutRequest secretPutRequest = new SecretPutRequest(); + secretPutRequest.setID(id); + secretPutRequest.setKey(key); + secretPutRequest.setValue(value); + secretPutRequest.setNote(note); + secretPutRequest.setOrganizationID(organizationId); + secretPutRequest.setProjectIDS(projectIds); + secretsCommand.setUpdate(secretPutRequest); + command.setSecrets(secretsCommand); + + ResponseForSecretResponse response = commandRunner.runCommand(command, + BitwardenClient.throwingFunctionWrapper(Converter::ResponseForSecretResponseFromJsonString)); + + if (response == null || !response.getSuccess()) { + throw new BitwardenClientException(response != null ? response.getErrorMessage() : "Secret update failed"); + } + + return response.getData(); + } + + public SecretsDeleteResponse delete(UUID[] ids) { + Command command = new Command(); + SecretsCommand secretsCommand = new SecretsCommand(); + SecretsDeleteRequest secretsDeleteRequest = new SecretsDeleteRequest(); + secretsDeleteRequest.setIDS(ids); + secretsCommand.setDelete(secretsDeleteRequest); + command.setSecrets(secretsCommand); + + ResponseForSecretsDeleteResponse response = commandRunner.runCommand(command, + BitwardenClient.throwingFunctionWrapper(Converter::ResponseForSecretsDeleteResponseFromJsonString)); + + if (response == null || !response.getSuccess()) { + throw new BitwardenClientException(response != null ? response.getErrorMessage() : "Secrets delete failed"); + } + + return response.getData(); + } + + public SecretIdentifiersResponse list(UUID organizationId) { + Command command = new Command(); + SecretsCommand secretsCommand = new SecretsCommand(); + SecretIdentifiersRequest secretIdentifiersRequest = new SecretIdentifiersRequest(); + secretIdentifiersRequest.setOrganizationID(organizationId); + secretsCommand.setList(secretIdentifiersRequest); + command.setSecrets(secretsCommand); + + ResponseForSecretIdentifiersResponse response = commandRunner.runCommand(command, + BitwardenClient.throwingFunctionWrapper(Converter::ResponseForSecretIdentifiersResponseFromJsonString)); + + if (response == null || !response.getSuccess()) { + throw new BitwardenClientException(response != null ? + response.getErrorMessage() : "No secrets for given organization"); + } + + return response.getData(); + } +} diff --git a/languages/java/src/main/java/com/bitwarden/sdk/ThrowingFunction.java b/languages/java/src/main/java/com/bitwarden/sdk/ThrowingFunction.java new file mode 100644 index 000000000..cab5b1041 --- /dev/null +++ b/languages/java/src/main/java/com/bitwarden/sdk/ThrowingFunction.java @@ -0,0 +1,7 @@ +package com.bitwarden.sdk; + +@FunctionalInterface +public interface ThrowingFunction { + + R accept(T t) throws E; +} diff --git a/support/scripts/schemas.ts b/support/scripts/schemas.ts index 148b089e6..7181d6010 100644 --- a/support/scripts/schemas.ts +++ b/support/scripts/schemas.ts @@ -1,4 +1,10 @@ -import { quicktype, InputData, JSONSchemaInput, FetchingJSONSchemaStore } from "quicktype-core"; +import { + quicktype, + quicktypeMultiFile, + InputData, + JSONSchemaInput, + FetchingJSONSchemaStore, +} from "quicktype-core"; import fs from "fs"; import path from "path"; @@ -63,6 +69,23 @@ async function main() { }); writeToFile("./languages/csharp/Bitwarden.Sdk/schemas.cs", csharp.lines); + + const java = await quicktypeMultiFile({ + inputData, + lang: "java", + rendererOptions: { + package: "com.bitwarden.sdk.schema", + "java-version": "8", + }, + }); + + const javaDir = "./languages/java/src/main/java/com/bitwarden/sdk/schema/"; + if (!fs.existsSync(javaDir)) { + fs.mkdirSync(javaDir); + } + java.forEach((file, path) => { + writeToFile(javaDir + path, file.lines); + }); } main(); From 9f775961f2122555a5fc9e332573281d20a849f7 Mon Sep 17 00:00:00 2001 From: Vladimir Cvetic Date: Thu, 30 Nov 2023 16:25:03 +0100 Subject: [PATCH 042/157] Golang SDK (#218) ## Type of change ``` - [ ] Bug fix - [ x] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective The objective is to develop a Golang wrapper for the Bitwarden client that interacts with a native rust library. This is to ensure that a consistent interface is provided, and the details of the Bitwarden rust library can be abstracted away. ## Code changes BitwardenClient.go Defines the BitwardenClient struct with dependencies and submodules for handling projects and secrets. Implements the NewBitwardenClient function that creates a new client. Implements methods for access token login and cleanup. BitwardenLibrary_native.go Implements the BitwardenLibrary interface using cgo for interacting with the native C library. Provides methods for initialization, memory release, and running commands on the client. BitwardenLibrary_custom.go An alternative implementation of the BitwardenLibrary interface, possibly for testing or alternative backends. The code is currently the same as BitwardenLibrary_native.go. CommandRunner.go Implements the CommandRunner struct that takes care of running commands via the library interface. Centralizes command-running logic. Projects.go Implements a Projects struct with methods to create, list, get, update, and delete projects. Utilizes the CommandRunner interface to run these commands. Secrets.go Implements a Secrets struct with methods to create, list, get, update, and delete secrets. Utilizes the CommandRunner interface to run these commands. *Notes* - The code is designed to be modular, separating concerns into different components (like Projects, Secrets, and CommandRunner). - Interfaces are used where possible to make it easier to mock these components for testing. - The native C API is encapsulated neatly, making it easier to switch out or update the underlying library without affecting the rest of the code. --------- Co-authored-by: Boris Bujak Co-authored-by: Boris Bujak --- .github/workflows/generate_schemas.yml | 6 + .github/workflows/golang-release.yml | 73 +++++++++++ .gitignore | 1 + languages/go/README.md | 113 +++++++++++++++++ languages/go/bitwarden_client.go | 63 ++++++++++ languages/go/command_runner.go | 37 ++++++ languages/go/example/example.go | 86 +++++++++++++ languages/go/example/go.mod | 10 ++ languages/go/example/go.sum | 2 + languages/go/go.mod | 5 + languages/go/go.sum | 2 + .../internal/cinterface/bitwarden_library.go | 55 +++++++++ languages/go/project.go | 107 ++++++++++++++++ languages/go/secrets.go | 114 ++++++++++++++++++ languages/go/util.go | 33 +++++ support/scripts/schemas.ts | 11 ++ 16 files changed, 718 insertions(+) create mode 100644 .github/workflows/golang-release.yml create mode 100644 languages/go/README.md create mode 100644 languages/go/bitwarden_client.go create mode 100644 languages/go/command_runner.go create mode 100644 languages/go/example/example.go create mode 100644 languages/go/example/go.mod create mode 100644 languages/go/example/go.sum create mode 100644 languages/go/go.mod create mode 100644 languages/go/go.sum create mode 100644 languages/go/internal/cinterface/bitwarden_library.go create mode 100644 languages/go/project.go create mode 100644 languages/go/secrets.go create mode 100644 languages/go/util.go diff --git a/.github/workflows/generate_schemas.yml b/.github/workflows/generate_schemas.yml index 675f53c1e..7eba684c4 100644 --- a/.github/workflows/generate_schemas.yml +++ b/.github/workflows/generate_schemas.yml @@ -64,6 +64,12 @@ jobs: path: ${{ github.workspace }}/support/schemas/* if-no-files-found: error + - name: Upload Go schemas artifact + uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + with: + name: schemas.go + path: ${{ github.workspace }}/languages/go/schema.go + - name: Upload java schemas artifact uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 with: diff --git a/.github/workflows/golang-release.yml b/.github/workflows/golang-release.yml new file mode 100644 index 000000000..6f41bcd3e --- /dev/null +++ b/.github/workflows/golang-release.yml @@ -0,0 +1,73 @@ +name: Go Release + +on: + workflow_dispatch: + inputs: + version_number: + description: "New Version" + required: true + +env: + GO111MODULE: on + GO_VERSION: "^1.18" + +jobs: + build_rust: + uses: ./.github/workflows/build-rust-cross-platform.yml + + generate-schemas: + uses: ./.github/workflows/generate_schemas.yml + + build: + name: Build + needs: + - build_rust + - generate-schemas + runs-on: ubuntu-22.04 + steps: + - name: Checkout Repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Setup Go environment + uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Cache dependencies + uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Build + run: go build -v ./... + + - name: Test + run: go test -v ./... + + release: + name: Release + needs: build + runs-on: ubuntu-22.04 + steps: + - name: Checkout Repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Setup Go environment + uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Set release version + run: echo "VERSION=${{ github.event.inputs.version_number }}" >> $GITHUB_ENV + + - name: Install Goreleaser + run: go install github.com/goreleaser/goreleaser@v1.21.2 + + - name: Run Goreleaser + run: goreleaser release --rm-dist --skip-validate + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ env.VERSION }} diff --git a/.gitignore b/.gitignore index 38b074349..37d652453 100644 --- a/.gitignore +++ b/.gitignore @@ -49,4 +49,5 @@ crates/bitwarden-napi/src-ts/bitwarden_client/schemas.ts languages/csharp/Bitwarden.Sdk/schemas.cs languages/js_webassembly/bitwarden_client/schemas.ts languages/python/BitwardenClient/schemas.py +languages/go/schema.go languages/java/src/main/java/com/bitwarden/sdk/schema diff --git a/languages/go/README.md b/languages/go/README.md new file mode 100644 index 000000000..e57badf7e --- /dev/null +++ b/languages/go/README.md @@ -0,0 +1,113 @@ +# Bitwarden SDK in Go + +This SDK is designed to interact with Bitwarden services in Go. It includes implementations for +managing projects and secrets, as well as a client interface to facilitate operations like login. + +## Prerequisites + +- Go installed +- C environment to run CGO + +## Installation + +Download the SDK files and place them in your Go project directory. + +## Table of Contents + +- [Initialization](#initialization) +- [Login](#login) +- [Projects](#projects) +- [Secrets](#secrets) +- [Close Client](#close-client) + +--- + +### Initialization + +To initialize the client, you need to import the SDK and create a new `BitwardenClient` instance. + +```go +import "github.com/bitwarden/sdk/languages/go" + +bitwardenClient, _ := sdk.NewBitwardenClient(&apiURL, &identityURL) +``` + +--- + +### Login + +To login using an access token: + +```go +apiKeyLogin, err := bitwardenClient.AccessTokenLogin(accessToken) +``` + +--- + +### Projects + +#### Create a Project + +```go +project, err := client.Projects.Create("organization_id", "project_name") +``` + +#### List Projects + +```go +projects, err := client.Projects.List("organization_id") +``` + +#### Update a Project + +```go +project, err := client.Projects.Update("project_id", "organization_id", "new_project_name") +``` + +#### Delete Projects + +```go +project, err := client.Projects.Delete([]string{"project_id_1", "project_id_2"}) +``` + +--- + +### Secrets + +#### Create a Secret + +```go +secret, err := client.Secrets.Create("key", "value", "note", "organization_id", []string{"project_id"}) +``` + +#### List Secrets + +```go +secrets, err := client.Secrets.List("organization_id") +``` + +#### Update a Secret + +```go +secret, err := client.Secrets.Update("secret_id", "new_key", "new_value", "new_note", "organization_id", []string{"project_id"}) +``` + +#### Delete Secrets + +```go +secret, err := client.Secrets.Delete([]string{"secret_id_1", "secret_id_2"}) +``` + +--- + +### Close Client + +To free up resources: + +```go +defer bitwardenClient.Close() +``` + +--- + +For more detailed information, refer to the code comments and method signatures. diff --git a/languages/go/bitwarden_client.go b/languages/go/bitwarden_client.go new file mode 100644 index 000000000..5e1108ce1 --- /dev/null +++ b/languages/go/bitwarden_client.go @@ -0,0 +1,63 @@ +package sdk + +import ( + "encoding/json" + + "github.com/bitwarden/sdk/languages/go/internal/cinterface" +) + +type BitwardenClient struct { + client cinterface.ClientPointer + lib cinterface.BitwardenLibrary + commandRunner CommandRunnerInterface + Projects ProjectsInterface + Secrets SecretsInterface +} + +func NewBitwardenClient(apiURL *string, identityURL *string) (*BitwardenClient, error) { + deviceType := DeviceType("SDK") + userAgent := "Bitwarden GOLANG-SDK" + clientSettings := ClientSettings{ + APIURL: apiURL, + IdentityURL: identityURL, + UserAgent: &userAgent, + DeviceType: &deviceType, + } + + settingsJSON, err := json.Marshal(clientSettings) + if err != nil { + return nil, err + } + + lib := cinterface.NewBitwardenLibrary() + client, err := lib.Init(string(settingsJSON)) + if err != nil { + return nil, err + } + runner := NewCommandRunner(client, lib) + + return &BitwardenClient{ + lib: lib, + client: client, + commandRunner: runner, + Projects: NewProjects(runner), + Secrets: NewSecrets(runner), + }, nil +} + +func (c *BitwardenClient) AccessTokenLogin(accessToken string) error { + req := AccessTokenLoginRequest{AccessToken: accessToken} + command := Command{AccessTokenLogin: &req} + + responseStr, err := c.commandRunner.RunCommand(command) + if err != nil { + return err + } + + var response APIKeyLoginResponse + return checkSuccessAndError(responseStr, &response) +} + +func (c *BitwardenClient) Close() { + c.lib.FreeMem(c.client) +} diff --git a/languages/go/command_runner.go b/languages/go/command_runner.go new file mode 100644 index 000000000..3f79f7149 --- /dev/null +++ b/languages/go/command_runner.go @@ -0,0 +1,37 @@ +package sdk + +import ( + "encoding/json" + + "github.com/bitwarden/sdk/languages/go/internal/cinterface" +) + +type CommandRunnerInterface interface { + RunCommand(command Command) (string, error) +} + +type CommandRunner struct { + client cinterface.ClientPointer + lib cinterface.BitwardenLibrary +} + +func NewCommandRunner(client cinterface.ClientPointer, lib cinterface.BitwardenLibrary) *CommandRunner { + return &CommandRunner{ + client: client, + lib: lib, + } +} + +func (c *CommandRunner) RunCommand(command Command) (string, error) { + commandJSON, err := json.Marshal(command) + if err != nil { + return "", err + } + + responseStr, err := c.lib.RunCommand(string(commandJSON), c.client) + if err != nil { + return "", err + } + + return responseStr, nil +} diff --git a/languages/go/example/example.go b/languages/go/example/example.go new file mode 100644 index 000000000..5935d8002 --- /dev/null +++ b/languages/go/example/example.go @@ -0,0 +1,86 @@ +package main + +import ( + "fmt" + "os" + + sdk "github.com/bitwarden/sdk/languages/go" + "github.com/gofrs/uuid" +) + +func main() { + apiURL := os.Getenv("API_URL") + identityURL := os.Getenv("IDENTITY_URL") + + bitwardenClient, _ := sdk.NewBitwardenClient(&apiURL, &identityURL) + + accessToken := os.Getenv("ACCESS_TOKEN") + organizationIDStr := os.Getenv("ORGANIZATION_ID") + projectName := os.Getenv("PROJECT_NAME") + + if projectName == "" { + projectName = "NewTestProject" // default value + } + + err := bitwardenClient.AccessTokenLogin(accessToken) + if err != nil { + panic(err) + } + + organizationID, err := uuid.FromString(organizationIDStr) + if err != nil { + panic(err) + } + + project, err := bitwardenClient.Projects.Create(organizationID.String(), projectName) + if err != nil { + panic(err) + } + fmt.Println(project) + projectID := project.ID + fmt.Println(projectID) + + if _, err = bitwardenClient.Projects.List(organizationID.String()); err != nil { + panic(err) + } + + if _, err = bitwardenClient.Projects.Get(projectID); err != nil { + panic(err) + } + + if _, err = bitwardenClient.Projects.Update(projectID, organizationID.String(), projectName+"2"); err != nil { + panic(err) + } + + key := "key" + value := "value" + note := "note" + + secret, err := bitwardenClient.Secrets.Create(key, value, note, organizationID.String(), []string{projectID}) + if err != nil { + panic(err) + } + secretID := secret.ID + + if _, err = bitwardenClient.Secrets.List(organizationID.String()); err != nil { + panic(err) + } + + if _, err = bitwardenClient.Secrets.Get(secretID); err != nil { + panic(err) + } + + if _, err = bitwardenClient.Secrets.Update(secretID, key, value, note, organizationID.String(), []string{projectID}); err != nil { + panic(err) + } + + if _, err = bitwardenClient.Secrets.Delete([]string{secretID}); err != nil { + panic(err) + } + + if _, err = bitwardenClient.Projects.Delete([]string{projectID}); err != nil { + panic(err) + } + + defer bitwardenClient.Close() +} diff --git a/languages/go/example/go.mod b/languages/go/example/go.mod new file mode 100644 index 000000000..bbde28fd5 --- /dev/null +++ b/languages/go/example/go.mod @@ -0,0 +1,10 @@ +module example + +replace github.com/bitwarden/sdk/languages/go => ../ + +go 1.20 + +require ( + github.com/bitwarden/sdk/languages/go v0.0.0-00010101000000-000000000000 + github.com/gofrs/uuid v4.4.0+incompatible +) diff --git a/languages/go/example/go.sum b/languages/go/example/go.sum new file mode 100644 index 000000000..c0ad68738 --- /dev/null +++ b/languages/go/example/go.sum @@ -0,0 +1,2 @@ +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= diff --git a/languages/go/go.mod b/languages/go/go.mod new file mode 100644 index 000000000..a9e125453 --- /dev/null +++ b/languages/go/go.mod @@ -0,0 +1,5 @@ +module github.com/bitwarden/sdk/languages/go + +go 1.18 + +require github.com/gofrs/uuid v4.4.0+incompatible diff --git a/languages/go/go.sum b/languages/go/go.sum new file mode 100644 index 000000000..c0ad68738 --- /dev/null +++ b/languages/go/go.sum @@ -0,0 +1,2 @@ +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= diff --git a/languages/go/internal/cinterface/bitwarden_library.go b/languages/go/internal/cinterface/bitwarden_library.go new file mode 100644 index 000000000..42327adb2 --- /dev/null +++ b/languages/go/internal/cinterface/bitwarden_library.go @@ -0,0 +1,55 @@ +package cinterface + +import ( + "fmt" + "unsafe" +) + +/* +#cgo LDFLAGS: -lbitwarden_c +#cgo linux LDFLAGS: -L/usr/local/lib -L/usr/lib -L ./lib +#cgo darwin LDFLAGS: -L/usr/local/lib -L/usr/lib -L ./lib +#include +typedef void* ClientPtr; +extern char* run_command(const char *command, ClientPtr client); +extern ClientPtr init(const char *clientSettings); +extern void free_mem(ClientPtr client); +*/ +import "C" + +type ClientPointer struct { + Pointer C.ClientPtr +} + +type BitwardenLibrary interface { + Init(clientSettings string) (ClientPointer, error) + FreeMem(client ClientPointer) + RunCommand(command string, client ClientPointer) (string, error) +} + +type BitwardenLibraryImpl struct{} + +func NewBitwardenLibrary() BitwardenLibrary { + return &BitwardenLibraryImpl{} +} + +func (b *BitwardenLibraryImpl) Init(clientSettings string) (ClientPointer, error) { + ptr := C.init(C.CString(clientSettings)) + if ptr == nil { + return ClientPointer{}, fmt.Errorf("initialization failed") + } + return ClientPointer{Pointer: ptr}, nil +} + +func (b *BitwardenLibraryImpl) FreeMem(client ClientPointer) { + C.free_mem(client.Pointer) +} + +func (b *BitwardenLibraryImpl) RunCommand(command string, client ClientPointer) (string, error) { + cstr := C.run_command(C.CString(command), client.Pointer) + if cstr == nil { + return "", fmt.Errorf("run command failed") + } + defer C.free(unsafe.Pointer(cstr)) + return C.GoString(cstr), nil +} diff --git a/languages/go/project.go b/languages/go/project.go new file mode 100644 index 000000000..24a30a7ac --- /dev/null +++ b/languages/go/project.go @@ -0,0 +1,107 @@ +package sdk + +type ProjectsInterface interface { + Create(organizationID string, name string) (*ProjectResponse, error) + List(organizationID string) (*ProjectsResponse, error) + Get(projectID string) (*ProjectResponse, error) + Update(projectID string, organizationID string, name string) (*ProjectResponse, error) + Delete(projectIDs []string) (*ProjectsDeleteResponse, error) +} + +type Projects struct { + CommandRunner CommandRunnerInterface +} + +func NewProjects(commandRunner CommandRunnerInterface) *Projects { + return &Projects{CommandRunner: commandRunner} +} + +func (p *Projects) Get(id string) (*ProjectResponse, error) { + command := Command{ + Projects: &ProjectsCommand{ + Get: &ProjectGetRequest{ + ID: id, + }, + }, + } + var response ProjectResponse + if err := p.executeCommand(command, &response); err != nil { + return nil, err + } + return &response, nil +} + +func (p *Projects) Create(organizationID string, name string) (*ProjectResponse, error) { + command := Command{ + Projects: &ProjectsCommand{ + Create: &ProjectCreateRequest{ + OrganizationID: organizationID, + Name: name, + }, + }, + } + + var response ProjectResponse + if err := p.executeCommand(command, &response); err != nil { + return nil, err + } + return &response, nil +} + +func (p *Projects) List(organizationID string) (*ProjectsResponse, error) { + command := Command{ + Projects: &ProjectsCommand{ + List: &ProjectsListRequest{ + OrganizationID: organizationID, + }, + }, + } + + var response ProjectsResponse + if err := p.executeCommand(command, &response); err != nil { + return nil, err + } + return &response, nil +} + +func (p *Projects) Update(projectID, organizationID, name string) (*ProjectResponse, error) { + command := Command{ + Projects: &ProjectsCommand{ + Update: &ProjectPutRequest{ + ID: projectID, + OrganizationID: organizationID, + Name: name, + }, + }, + } + + var response ProjectResponse + if err := p.executeCommand(command, &response); err != nil { + return nil, err + } + return &response, nil +} + +func (p *Projects) Delete(projectIDs []string) (*ProjectsDeleteResponse, error) { + command := Command{ + Projects: &ProjectsCommand{ + Delete: &ProjectsDeleteRequest{ + IDS: projectIDs, + }, + }, + } + + var response ProjectsDeleteResponse + if err := p.executeCommand(command, &response); err != nil { + return nil, err + } + return &response, nil +} + +func (p *Projects) executeCommand(command Command, target interface{}) error { + responseStr, err := p.CommandRunner.RunCommand(command) + if err != nil { + return err + } + return checkSuccessAndError(responseStr, target) +} diff --git a/languages/go/secrets.go b/languages/go/secrets.go new file mode 100644 index 000000000..e6863c459 --- /dev/null +++ b/languages/go/secrets.go @@ -0,0 +1,114 @@ +package sdk + +type SecretsInterface interface { + Create(key, value, note string, organizationID string, projectIDs []string) (*SecretResponse, error) + List(organizationID string) (*SecretIdentifiersResponse, error) + Get(secretID string) (*SecretResponse, error) + Update(secretID string, key, value, note string, organizationID string, projectIDs []string) (*SecretResponse, error) + Delete(secretIDs []string) (*SecretsDeleteResponse, error) +} + +type Secrets struct { + CommandRunner CommandRunnerInterface +} + +func NewSecrets(commandRunner CommandRunnerInterface) *Secrets { + return &Secrets{CommandRunner: commandRunner} +} + +func (s *Secrets) executeCommand(command Command, target interface{}) error { + responseStr, err := s.CommandRunner.RunCommand(command) + if err != nil { + return err + } + return checkSuccessAndError(responseStr, target) +} + +func (s *Secrets) Create(key, value, note string, organizationID string, projectIDs []string) (*SecretResponse, error) { + command := Command{ + Secrets: &SecretsCommand{ + Create: &SecretCreateRequest{ + Key: key, + Value: value, + Note: note, + OrganizationID: organizationID, + ProjectIDS: projectIDs, + }, + }, + } + + var response SecretResponse + if err := s.executeCommand(command, &response); err != nil { + return nil, err + } + return &response, nil +} + +func (s *Secrets) List(organizationID string) (*SecretIdentifiersResponse, error) { + command := Command{ + Secrets: &SecretsCommand{ + List: &SecretIdentifiersRequest{ + OrganizationID: organizationID, + }, + }, + } + + var response SecretIdentifiersResponse + if err := s.executeCommand(command, &response); err != nil { + return nil, err + } + return &response, nil +} + +func (s *Secrets) Get(id string) (*SecretResponse, error) { + command := Command{ + Secrets: &SecretsCommand{ + Get: &SecretGetRequest{ + ID: id, + }, + }, + } + + var response SecretResponse + if err := s.executeCommand(command, &response); err != nil { + return nil, err + } + return &response, nil +} + +func (s *Secrets) Update(id string, key, value, note string, organizationID string, projectIDs []string) (*SecretResponse, error) { + command := Command{ + Secrets: &SecretsCommand{ + Update: &SecretPutRequest{ + ID: id, + Key: key, + Value: value, + Note: note, + OrganizationID: organizationID, + ProjectIDS: projectIDs, + }, + }, + } + + var response SecretResponse + if err := s.executeCommand(command, &response); err != nil { + return nil, err + } + return &response, nil +} + +func (s *Secrets) Delete(ids []string) (*SecretsDeleteResponse, error) { + command := Command{ + Secrets: &SecretsCommand{ + Delete: &SecretsDeleteRequest{ + IDS: ids, + }, + }, + } + + var response SecretsDeleteResponse + if err := s.executeCommand(command, &response); err != nil { + return nil, err + } + return &response, nil +} diff --git a/languages/go/util.go b/languages/go/util.go new file mode 100644 index 000000000..01ab3578d --- /dev/null +++ b/languages/go/util.go @@ -0,0 +1,33 @@ +package sdk + +import ( + "encoding/json" + "fmt" +) + +func checkSuccessAndError(responseStr string, v interface{}) error { + var wrapper struct { + Success bool `json:"success"` + ErrorMessage *string `json:"errorMessage"` + Data *json.RawMessage `json:"data"` + } + + err := json.Unmarshal([]byte(responseStr), &wrapper) + if err != nil { + return fmt.Errorf("failed to unmarshal wrapper response: %v", err) + } + + if !wrapper.Success { + if wrapper.ErrorMessage != nil { + return fmt.Errorf("API error: %s", *wrapper.ErrorMessage) + } + return fmt.Errorf("API error: unknown") + } + + err = json.Unmarshal(*wrapper.Data, &v) + if err != nil { + return fmt.Errorf("failed to unmarshal response: %v", err) + } + + return nil +} diff --git a/support/scripts/schemas.ts b/support/scripts/schemas.ts index 7181d6010..98c0c39cb 100644 --- a/support/scripts/schemas.ts +++ b/support/scripts/schemas.ts @@ -70,6 +70,17 @@ async function main() { writeToFile("./languages/csharp/Bitwarden.Sdk/schemas.cs", csharp.lines); + const go = await quicktype({ + inputData, + lang: "go", + rendererOptions: { + package: "sdk", + "just-types-and-package": true, + }, + }); + + writeToFile("./languages/go/schema.go", go.lines); + const java = await quicktypeMultiFile({ inputData, lang: "java", From 0f5f11bc5767b86baa514300fa17262a3b89abf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Slobodan=20Todosijevi=C4=87?= <68587964+slobokv83@users.noreply.github.com> Date: Thu, 30 Nov 2023 16:42:45 +0100 Subject: [PATCH 043/157] Cpp Wrapper for sdk (#259) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Type of change ``` - [ ] Bug fix - [x ] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Implemented C++ library that wraps native C library and exposed its commands through BitwardenClient class. ## Code changes - Implemented BitwardenLibrary for connection with `bitwarden_c` library and its three functions: `init`, `run_command`, and `free_mem` - Implemented CRUD operations for the Projects and Secrets for Bitwarden Secrets Manager using the API - Developed building mechanism that is building C++ dynamic library using `Cmake` - Added example in the `examples` directory - Added documentation in .md files for CRUD operations use, building the dynamic library, and for the client use --------- Co-authored-by: Todosijevic-Slobodan Co-authored-by: Daniel GarcΓ­a --- .gitignore | 2 + languages/cpp/CMakeBuild.md | 33 +++++ languages/cpp/CMakeLists.txt | 36 ++++++ languages/cpp/ExampleUse.md | 70 ++++++++++ languages/cpp/README.md | 97 ++++++++++++++ languages/cpp/examples/Wrapper.cpp | 73 +++++++++++ languages/cpp/include/BitwardenClient.h | 36 ++++++ languages/cpp/include/BitwardenLibrary.h | 27 ++++ languages/cpp/include/BitwardenSettings.h | 19 +++ languages/cpp/include/CommandRunner.h | 45 +++++++ languages/cpp/include/Projects.h | 19 +++ languages/cpp/include/Secrets.h | 20 +++ languages/cpp/src/BitwardenClient.cpp | 145 +++++++++++++++++++++ languages/cpp/src/BitwardenLibrary.cpp | 107 ++++++++++++++++ languages/cpp/src/CommandRunner.cpp | 49 +++++++ languages/cpp/src/Projects.cpp | 132 +++++++++++++++++++ languages/cpp/src/Secrets.cpp | 149 ++++++++++++++++++++++ support/scripts/schemas.ts | 16 +++ 18 files changed, 1075 insertions(+) create mode 100644 languages/cpp/CMakeBuild.md create mode 100644 languages/cpp/CMakeLists.txt create mode 100644 languages/cpp/ExampleUse.md create mode 100644 languages/cpp/README.md create mode 100644 languages/cpp/examples/Wrapper.cpp create mode 100644 languages/cpp/include/BitwardenClient.h create mode 100644 languages/cpp/include/BitwardenLibrary.h create mode 100644 languages/cpp/include/BitwardenSettings.h create mode 100644 languages/cpp/include/CommandRunner.h create mode 100644 languages/cpp/include/Projects.h create mode 100644 languages/cpp/include/Secrets.h create mode 100644 languages/cpp/src/BitwardenClient.cpp create mode 100644 languages/cpp/src/BitwardenLibrary.cpp create mode 100644 languages/cpp/src/CommandRunner.cpp create mode 100644 languages/cpp/src/Projects.cpp create mode 100644 languages/cpp/src/Secrets.cpp diff --git a/.gitignore b/.gitignore index 37d652453..007c3e839 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /target .DS_Store .pytest_cache +.vscode/c_cpp_properties.json # Build results [Dd]ebug/ @@ -49,5 +50,6 @@ crates/bitwarden-napi/src-ts/bitwarden_client/schemas.ts languages/csharp/Bitwarden.Sdk/schemas.cs languages/js_webassembly/bitwarden_client/schemas.ts languages/python/BitwardenClient/schemas.py +languages/cpp/include/schemas.hpp languages/go/schema.go languages/java/src/main/java/com/bitwarden/sdk/schema diff --git a/languages/cpp/CMakeBuild.md b/languages/cpp/CMakeBuild.md new file mode 100644 index 000000000..4c7c29814 --- /dev/null +++ b/languages/cpp/CMakeBuild.md @@ -0,0 +1,33 @@ +# CMAKE build + +## INTRODUCTION + +Cmake is used to build the c++ Bitwarden client library. Output should be placed in the build directory. The output contains two dynamic libraries: one that we are building `BitwardenClient` and another that the building library uses `bitwarden_c`. + +## PREREQUISITES + +- Cmake installed, minimum version 3.15 +- `schemas.hpp` generated into `include` directory +- installed `nlohmann-json` library +- installed `boost` library + +## BUILD commands + +One should be in the root directory of the c++ wrapper (the same level where is CMakeLists.txt placed). Paths of the three libraries should be placed inside the cmake build command: + +$ mkdir build +$ cd build +$ cmake .. -DNLOHMANN=/path/to/include/nlohmann -DBOOST=/path/to/include/boost -DTARGET=relative/path/to/libbitwarden_c +$ cmake --build . + + + +## Example + +macOS: + +$ mkdir build +$ cd build +$ cmake .. -DNLOHMANN=/opt/hombrew/include -DBOOST=/opt/homebrew/include -DTARGET=../../target/release/libbitwarden_c.dylib +$ cmake --build . + diff --git a/languages/cpp/CMakeLists.txt b/languages/cpp/CMakeLists.txt new file mode 100644 index 000000000..e513a32ed --- /dev/null +++ b/languages/cpp/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.15) +project(BitwardenClient) + +set(CMAKE_CXX_STANDARD 20) + +# Set placeholders to be passed from command line +set(NLOHMANN_JSON_INCLUDE_DIR_PLACEHOLDER ${NLOHMANN}) +set(BOOST_INCLUDE_DIR_PLACEHOLDER ${BOOST}) +set(TARGET_INCLUDE_DIR_PLACEHOLDER ${TARGET}) + +# Specify the locations of nlohmann.json and Boost libraries +find_path(NLOHMANN_JSON_INCLUDE_DIR nlohmann/json.hpp HINTS ${NLOHMANN_JSON_INCLUDE_DIR_PLACEHOLDER}) +find_path(BOOST_INCLUDE_DIR boost/optional.hpp HINTS ${BOOST_INCLUDE_DIR_PLACEHOLDER}) + +# Include directories for library +include_directories(include ${NLOHMANN_JSON_INCLUDE_DIR} ${BOOST_INCLUDE_DIR}) + +# Add library source files +file(GLOB SOURCES "src/*.cpp") + +# Add library source files along with the schemas.cpp file +add_library(BitwardenClient SHARED ${SOURCES} ${SCHEMAS_SOURCE}) + +# Set path for native library loading +set(LIB_BITWARDEN_C "${CMAKE_SOURCE_DIR}/${TARGET}") + +# Copy the library to the build directory before building +add_custom_command( + TARGET BitwardenClient PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${LIB_BITWARDEN_C} + $ +) + +# Link libraries +target_link_libraries(BitwardenClient PRIVATE ${LIB_BITWARDEN_C}) diff --git a/languages/cpp/ExampleUse.md b/languages/cpp/ExampleUse.md new file mode 100644 index 000000000..579cd0e67 --- /dev/null +++ b/languages/cpp/ExampleUse.md @@ -0,0 +1,70 @@ +# EXAMPLES + + +## PREREQUISITES + +### BITWARDEN Libraries +One should have two libraries at the same path: +- `BitwardeClient` +- `bitwarden_c` + +It should look like `libBitwardeClient.dylib` and `libbitwarden_c.dylib` for the macOS. + +For Linux: `libBitwardeClient.so` and `libbitwarden_c.so` +For Windows: `BitwardeClient.dll` and `bitwarden_c.dll` + +### INCLUDE directory + +`include` directory contains: +- `BitwardenLibrary.h` +- `BitwardenClient.h` +- `BitwardenSettings.h` +- `CommandRunner.h` +- `Projects.h` +- `Secrets.h` +- `schemas.hpp` + +### Other libraries +- `nlohmann-json` (https://github.com/nlohmann/json) +- `boost` (https://www.boost.org/) + + +### COMPILING + +One could use g++/clang++ for compiling. +Example of the folder structure (macOS): + +--root + --build + `libBitwardenClient.dylib` + `libbitwarden_c.dylib` + --include + --`BitwardenLibrary.h` + --`BitwardenClient.h` + --`BitwardenSettings.h` + --`CommandRunner.h` + --`Projects.h` + --`Secrets.h` + --`schemas.hpp` + --examples + --`Wrapper.cpp` + + +1. $ export ACCESS_TOKEN=<"access-token"> +2. $ export ORGANIZATION_ID=<"organization-id"> +3. $ export DYLD_LIBRARY_PATH=/path/to/your/library:$DYLD_LIBRARY_PATH + +The last step is neccessary to add the path for the dynamic library (macOS). +For the Linux one should use: +$ export LD_LIBRARY_PATH=/path/to/your/library:$LD_LIBRARY_PATH +For the Windows: +$ set PATH=%PATH%;C:\path\to\your\library + +4. $ cd examples +5. $ clang++ -std=c++20 -I../include -I/path/to/include/nlohmann -I/path/to/include/boost -L../build/ -o MyBitwardenApp Wrapper.cpp -lBitwardenClient -ldl + +for Windows `-ldl` should be excluded, + +The result is `MyBitwardenApp` in the `examples` directory, and one can run it from the `examples` directory: + +6. $ ./MyBitwardenApp diff --git a/languages/cpp/README.md b/languages/cpp/README.md new file mode 100644 index 000000000..23b59ac76 --- /dev/null +++ b/languages/cpp/README.md @@ -0,0 +1,97 @@ +# Bitwarden Secrets Manager SDK + +C++ bindings for interacting with the [Bitwarden Secrets Manager]. This is a beta release and might be missing some functionality. + +## Create access token + +Review the help documentation on [Access Tokens] + +## Usage code snippets + +### Client settings + +```c++ +// Optional - if not stressed, then default values are used +BitwardenSettings bitwardenSettings; +bitwardenSettings.set_api_url(""); +bitwardenSettings.set_identity_url(""); +``` + + +### Create new Bitwarden client + +```c++ +std::string accessToken = ""; +// Optional - argument in BitwardenClient +BitwardenClient bitwardenClient = BitwardenClient(bitwardenSettings); +bitwardenClient.accessTokenLogin(accessToken); +``` + +### Create new project + +```c++ +boost::uuids::uuid organizationUuid = boost::uuids::string_generator()(""); +ProjectResponse projectResponseCreate = bitwardenClient.createProject(organizationUuid, "TestProject"); +``` + +### List all projects + +```c++ +ProjectsResponse projectResponseList = bitwardenClient.listProjects(organizationUuid); +``` + +### Get project details + +```c++ +boost::uuids::uuid projectId = boost::uuids::string_generator()(projectResponseCreate.get_id()); +ProjectResponse projectResponseGet = bitwardenClient.getProject(projectId); +``` + +### Update project + +```c++ +boost::uuids::uuid projectId = boost::uuids::string_generator()(projectResponseCreate.get_id()); +ProjectResponse projectResponseUpdate = bitwardenClient.updateProject(projectId, organizationUuid, "TestProjectUpdated"); +``` + +### Delete projects + +```c++ +SecretsDeleteResponse secretsDeleteResponse = bitwardenClient.deleteSecrets({secretId}); +``` + +### Add new secret + +```c++ +std::string key = "key"; +std::string value = "value"; +std::string note = "note"; +SecretResponse secretResponseCreate = bitwardenClient.createSecret(key, value, note, organizationUuid, {projectId}); +``` + +### List secrets + +```c++ +SecretIdentifiersResponse secretIdentifiersResponse = bitwardenClient.listSecrets(organizationUuid); +``` + +### Get secret details + +``` +boost::uuids::uuid secretId = boost::uuids::string_generator()(secretResponseCreate.get_id()); +SecretResponse secretResponseGet = bitwardenClient.getSecret(secretId); +``` + +### Update secret +```c++ +SecretResponse secretResponseUpdate = bitwardenClient.updateSecret(secretId, "key2", "value2", "note2", organizationUuid, {projectId}); +``` + +# Delete secrets + +```c++ +SecretsDeleteResponse secretsDeleteResponse = bitwardenClient.deleteSecrets({secretId}); +``` + +[Access Tokens]: https://bitwarden.com/help/access-tokens/ +[Bitwarden Secrets Manager]: https://bitwarden.com/products/secrets-manager/ diff --git a/languages/cpp/examples/Wrapper.cpp b/languages/cpp/examples/Wrapper.cpp new file mode 100644 index 000000000..df4aa164c --- /dev/null +++ b/languages/cpp/examples/Wrapper.cpp @@ -0,0 +1,73 @@ +#include "BitwardenClient.h" +#include +#include + +int main() { + // Retrieve access token and organization ID from environment variables + const char* accessTokenEnv = std::getenv("ACCESS_TOKEN"); + const char* organizationIdEnv = std::getenv("ORGANIZATION_ID"); + + if (!accessTokenEnv || !organizationIdEnv) { + std::cerr << "Error: Environment variables ACCESS_TOKEN or ORGANIZATION_ID not set." << std::endl; + return 1; + } + + std::string accessToken = accessTokenEnv; + std::string organizationId = organizationIdEnv; + + + + // Optional - commented to use default values + // BitwardenSettings bitwardenSettings; + // bitwardenSettings.set_api_url(""); + // bitwardenSettings.set_identity_url(""); + + // Create a Bitwarden client instance + BitwardenClient bitwardenClient = BitwardenClient(); + // // Access token login + bitwardenClient.accessTokenLogin(accessToken); + // Organization ID + boost::uuids::uuid organizationUuid = boost::uuids::string_generator()(organizationId); + + // // Create a new project + ProjectResponse projectResponseCreate = bitwardenClient.createProject(organizationUuid, "NewTestProject"); + boost::uuids::uuid projectId = boost::uuids::string_generator()(projectResponseCreate.get_id()); + + // List projects + ProjectsResponse projectResponseList = bitwardenClient.listProjects(organizationUuid); + + // Get project details + ProjectResponse projectResponseGet = bitwardenClient.getProject(projectId); + + // Update project + ProjectResponse ProjectResponseUpdate = bitwardenClient.updateProject(projectId, organizationUuid, "NewTestProject2"); + + // Secrets + std::string key = "key"; + std::string value = "value"; + std::string note = "note"; + + // Create a new secret + SecretResponse secretResponseCreate = bitwardenClient.createSecret(key, value, note, organizationUuid, {projectId}); + boost::uuids::uuid secretId = boost::uuids::string_generator()(secretResponseCreate.get_id()); + + // List secrets + SecretIdentifiersResponse secretIdentifiersResponse = bitwardenClient.listSecrets(organizationUuid); + + // Get secret details + SecretResponse secretResponseGet = bitwardenClient.getSecret(secretId); + + // Update secret + key = "key2"; + value = "value2"; + note = "note2"; + SecretResponse responseForSecretResponseUpdate = bitwardenClient.updateSecret(secretId, key, value, note, organizationUuid, {projectId}); + + // Delete secrets + SecretsDeleteResponse secretsDeleteResponse = bitwardenClient.deleteSecrets({secretId}); + + // Delete projects + ProjectsDeleteResponse projectsDeleteResponse = bitwardenClient.deleteProjects({projectId}); + + return 0; +} diff --git a/languages/cpp/include/BitwardenClient.h b/languages/cpp/include/BitwardenClient.h new file mode 100644 index 000000000..a5cf72475 --- /dev/null +++ b/languages/cpp/include/BitwardenClient.h @@ -0,0 +1,36 @@ +#pragma once + +#include "CommandRunner.h" +#include "BitwardenSettings.h" +#include "Projects.h" +#include "Secrets.h" +#include +#include + +class BitwardenClient { +public: + BitwardenClient(const BitwardenSettings& bitwardenSettings = BitwardenSettings()); + ~BitwardenClient(); + + void accessTokenLogin(const std::string& accessToken); + ProjectResponse getProject(const boost::uuids::uuid& id); + ProjectResponse createProject(const boost::uuids::uuid& organizationId, const std::string& name); + ProjectResponse updateProject(const boost::uuids::uuid& id, const boost::uuids::uuid& organizationId, const std::string& name); + ProjectsDeleteResponse deleteProjects(const std::vector& ids); + ProjectsResponse listProjects(const boost::uuids::uuid &organizationId); + SecretResponse getSecret(const boost::uuids::uuid& id); + SecretResponse createSecret(const std::string& key, const std::string& value, const std::string& note, const boost::uuids::uuid& organizationId, const std::vector& projectIds); + SecretResponse updateSecret(const boost::uuids::uuid& id, const std::string& key, const std::string& value, const std::string& note, const boost::uuids::uuid& organizationId, const std::vector& projectIds); + SecretsDeleteResponse deleteSecrets(const std::vector& ids); + SecretIdentifiersResponse listSecrets(const boost::uuids::uuid& organizationId); + +private: + BitwardenLibrary* library; + void* client; + CommandRunner* commandRunner; + Projects projects; + Secrets secrets; + bool isClientOpen; + ClientSettings clientSettings; + +}; diff --git a/languages/cpp/include/BitwardenLibrary.h b/languages/cpp/include/BitwardenLibrary.h new file mode 100644 index 000000000..5fee78726 --- /dev/null +++ b/languages/cpp/include/BitwardenLibrary.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +class BitwardenLibrary { +public: + BitwardenLibrary(const std::string& providedLibraryPath); + ~BitwardenLibrary(); + + void* init(const char* clientSettingsJson); + void free_mem(void* client); + const char* run_command(const char* commandJson, void* client); + +private: +#ifdef _WIN32 + HMODULE libraryHandle; +#else + void* libraryHandle; +#endif +}; + diff --git a/languages/cpp/include/BitwardenSettings.h b/languages/cpp/include/BitwardenSettings.h new file mode 100644 index 000000000..4d075ed0a --- /dev/null +++ b/languages/cpp/include/BitwardenSettings.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +class BitwardenSettings { +public: + BitwardenSettings() = default; + ~BitwardenSettings() = default; + + const std::string& get_api_url() const { return api_url; } + void set_api_url(const std::string& value) { api_url = value; } + + const std::string& get_identity_url() const { return identity_url; } + void set_identity_url(const std::string& value) { identity_url = value; } + +private: + std::string api_url; + std::string identity_url; +}; diff --git a/languages/cpp/include/CommandRunner.h b/languages/cpp/include/CommandRunner.h new file mode 100644 index 000000000..9aa6cbe9c --- /dev/null +++ b/languages/cpp/include/CommandRunner.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include "BitwardenLibrary.h" +#include "schemas.hpp" +#include + +using namespace Bitwarden::Sdk; + +class CommandRunner { +public: + CommandRunner(BitwardenLibrary* library, void* client); + + template + R runCommand(const Command& command, Func deserializer); + + + +private: + BitwardenLibrary* library; + void* client; + + std::string commandToString(const Command& command); + nlohmann::json filterNullObjects(const nlohmann::json& input); +}; + +template +R CommandRunner::runCommand(const Command& command, Func deserializer) { + // Serialize the Command object to a JSON string + std::string jsonString = commandToString(command); + const char* jsonCStr = jsonString.c_str(); + const char* response = library->run_command(jsonCStr, client); + + // Deserialize the response using the provided deserializer function + T deserialized = deserializer(response); + + // Unwrap the response and throw an exception if it was not successful + if (!deserialized.get_success()) { + throw std::runtime_error(*deserialized.get_error_message()); + } + + return deserialized.get_data().get(); +} + diff --git a/languages/cpp/include/Projects.h b/languages/cpp/include/Projects.h new file mode 100644 index 000000000..9bef19b9c --- /dev/null +++ b/languages/cpp/include/Projects.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include "CommandRunner.h" + +class Projects { +public: + Projects(CommandRunner* commandRunner); + + ProjectResponse get(const boost::uuids::uuid& id); + ProjectResponse create(const boost::uuids::uuid& organizationId, const std::string& name); + ProjectResponse update(const boost::uuids::uuid& id, const boost::uuids::uuid& organizationId, const std::string& name); + ProjectsDeleteResponse deleteProjects(const std::vector& ids); + ProjectsResponse list(const boost::uuids::uuid& organizationId); + +private: + CommandRunner* commandRunner; +}; diff --git a/languages/cpp/include/Secrets.h b/languages/cpp/include/Secrets.h new file mode 100644 index 000000000..024ec3692 --- /dev/null +++ b/languages/cpp/include/Secrets.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include "CommandRunner.h" + +class Secrets { +public: + Secrets(CommandRunner* commandRunner); + + SecretResponse get(const boost::uuids::uuid& id); + SecretResponse create(const std::string& key, const std::string& value, const std::string& note, const boost::uuids::uuid& organizationId, const std::vector& projectIds); + SecretResponse update(const boost::uuids::uuid& id, const std::string& key, const std::string& value, const std::string& note, const boost::uuids::uuid& organizationId, const std::vector& projectIds); + SecretsDeleteResponse deleteSecrets(const std::vector& ids); + SecretIdentifiersResponse list(const boost::uuids::uuid& organizationId); + +private: + CommandRunner* commandRunner; +}; + diff --git a/languages/cpp/src/BitwardenClient.cpp b/languages/cpp/src/BitwardenClient.cpp new file mode 100644 index 000000000..fef9ea267 --- /dev/null +++ b/languages/cpp/src/BitwardenClient.cpp @@ -0,0 +1,145 @@ +#include "BitwardenClient.h" +#include +#include + +BitwardenClient::BitwardenClient(const BitwardenSettings& bitwardenSettings) + : library(nullptr), commandRunner(nullptr), isClientOpen(false), projects(nullptr), secrets(nullptr) { + + // Set default values for optional strings + boost::optional apiUrl = bitwardenSettings.get_api_url().empty() + ? boost::optional("https://api.bitwarden.com") + : boost::optional(bitwardenSettings.get_api_url()); + + boost::optional identityUrl = bitwardenSettings.get_identity_url().empty() + ? boost::optional("https://identity.bitwarden.com") + : boost::optional(bitwardenSettings.get_identity_url()); + + boost::optional user_agent = boost::optional("Bitwarden CPP-SDK"); + + // Set values in clientSettings + clientSettings.set_device_type(Bitwarden::Sdk::DeviceType::SDK); + clientSettings.set_user_agent(user_agent); + clientSettings.set_api_url(apiUrl); + clientSettings.set_identity_url(identityUrl); + + nlohmann::json jsonClientSettings; + Bitwarden::Sdk::to_json(jsonClientSettings, clientSettings); + + std::string jsonClientSettingsString = jsonClientSettings.dump(); + const char* jsonClientSettingsCStr = jsonClientSettingsString.c_str(); + + try { + library = new BitwardenLibrary("./"); + client = library->init(jsonClientSettingsCStr); + commandRunner = new CommandRunner(library, client); + projects = Projects(commandRunner); + secrets = Secrets(commandRunner); + isClientOpen = true; + } catch (const std::exception& ex) { + std::cerr << "Failed to initialize: " << ex.what() << std::endl; + throw ex; + } +} + +BitwardenClient::~BitwardenClient() { + if (library) { + delete commandRunner; + library->free_mem(client); + delete library; + isClientOpen = false; + } +} + +void BitwardenClient::accessTokenLogin(const std::string& accessToken) { + Command command; + AccessTokenLoginRequest accessTokenLoginRequest; + accessTokenLoginRequest.set_access_token(accessToken); + command.set_access_token_login(accessTokenLoginRequest); + + auto deserializer = [](const char* response) -> ResponseForApiKeyLoginResponse { + nlohmann::json jsonResponse = nlohmann::json::parse(response); + ResponseForApiKeyLoginResponse loginResponse; + Bitwarden::Sdk::from_json(jsonResponse, loginResponse); + return loginResponse; + }; + try { + commandRunner->runCommand(command, deserializer); + } catch (const std::exception& ex) { + std::cerr << "Error in accessTokenLogin: " << ex.what() << std::endl; + throw ex; + } +} + +ProjectResponse BitwardenClient::getProject(const boost::uuids::uuid& id){ + if (!isClientOpen) { + throw std::runtime_error("Client is not open."); + } + return projects.get(id); +} + +ProjectResponse BitwardenClient::createProject(const boost::uuids::uuid& organizationId, const std::string& name){ + if (!isClientOpen) { + throw std::runtime_error("Client is not open."); + } + return projects.create(organizationId, name); +} + +ProjectResponse BitwardenClient::updateProject(const boost::uuids::uuid& id, const boost::uuids::uuid& organizationId, const std::string& name){ + if (!isClientOpen) { + throw std::runtime_error("Client is not open."); + } + return projects.update(id, organizationId, name); +} + +ProjectsDeleteResponse BitwardenClient::deleteProjects(const std::vector& ids) { + if (!isClientOpen) { + throw std::runtime_error("Client is not open."); + } + return projects.deleteProjects(ids); + +} + +ProjectsResponse BitwardenClient::listProjects(const boost::uuids::uuid &organizationId) { + if (!isClientOpen) { + throw std::runtime_error("Client is not open."); + } + return projects.list(organizationId); + +} + +SecretResponse BitwardenClient::getSecret(const boost::uuids::uuid& id){ + if (!isClientOpen) { + throw std::runtime_error("Client is not open."); + } + return secrets.get(id); +} + +SecretResponse BitwardenClient::createSecret(const std::string& key, const std::string& value, const std::string& note, const boost::uuids::uuid& organizationId, const std::vector& projectIds){ + if (!isClientOpen) { + throw std::runtime_error("Client is not open."); + } + return secrets.create(key, value, note, organizationId, projectIds); +} + +SecretResponse BitwardenClient::updateSecret(const boost::uuids::uuid& id, const std::string& key, const std::string& value, const std::string& note, const boost::uuids::uuid& organizationId, const std::vector& projectIds){ + if (!isClientOpen) { + throw std::runtime_error("Client is not open."); + } + return secrets.update(id, key, value, note, organizationId, projectIds); +} + +SecretsDeleteResponse BitwardenClient::deleteSecrets(const std::vector& ids) { + if (!isClientOpen) { + throw std::runtime_error("Client is not open."); + } + return secrets.deleteSecrets(ids); + +} + +SecretIdentifiersResponse BitwardenClient::listSecrets(const boost::uuids::uuid &organizationId) { + if (!isClientOpen) { + throw std::runtime_error("Client is not open."); + } + return secrets.list(organizationId); + +} diff --git a/languages/cpp/src/BitwardenLibrary.cpp b/languages/cpp/src/BitwardenLibrary.cpp new file mode 100644 index 000000000..0af592786 --- /dev/null +++ b/languages/cpp/src/BitwardenLibrary.cpp @@ -0,0 +1,107 @@ +#include "BitwardenLibrary.h" +#include + +BitwardenLibrary::BitwardenLibrary(const std::string& providedLibraryPath) : libraryHandle(nullptr) { + std::string libraryExtension; + std::string libraryNameUnix = "libbitwarden_c"; + std::string libraryNameWin = "bitwarden_c"; +#if defined(_WIN32) + libraryExtension = ".dll"; +#elif defined(__linux__) + libraryExtension = ".so"; +#elif defined(__APPLE__) + libraryExtension = ".dylib"; +#else + // Unsupported platform + std::cerr << "Unsupported platform." << std::endl; + return; +#endif + + // Load the dynamic library +#ifdef _WIN32 + std::string libraryPath = providedLibraryPath + libraryNameWin + libraryExtension; + // Load the dynamic library on Windows + libraryHandle = LoadLibraryA(libraryPath.c_str()); + + if (!libraryHandle) { + std::cerr << "Failed to load the Bitwarden library." << std::endl; + } +#else + std::string libraryPath = providedLibraryPath + libraryNameUnix + libraryExtension; + // Load the dynamic library on Unix-based systems (Linux, macOS) + libraryHandle = dlopen(libraryPath.c_str(), RTLD_NOW); + + if (!libraryHandle) { + std::cerr << "Failed to load the Bitwarden library: " << dlerror() << std::endl; + } +#endif +} + +BitwardenLibrary::~BitwardenLibrary() { + if (libraryHandle) { +#ifdef _WIN32 + FreeLibrary(libraryHandle); +#else + dlclose(libraryHandle); +#endif + } +} + +void* BitwardenLibrary::init(const char* clientSettingsJson) { + typedef void* (*InitFunction)(const char*); + InitFunction initFunction = nullptr; + +#ifdef _WIN32 + // Get the address of the init function on Windows + initFunction = reinterpret_cast(GetProcAddress(libraryHandle, "init")); +#else + // Get the address of the init function on Unix-based systems + initFunction = reinterpret_cast(dlsym(libraryHandle, "init")); +#endif + + if (initFunction) { + return initFunction(clientSettingsJson); + } + + std::cerr << "Failed to load init function from the Bitwarden library: " << std::endl; + return nullptr; +} + +void BitwardenLibrary::free_mem(void* client) { + typedef void (*FreeMemFunction)(void*); + FreeMemFunction freeMemFunction = nullptr; + +#ifdef _WIN32 + // Get the address of the free_mem function on Windows + freeMemFunction = reinterpret_cast(GetProcAddress(libraryHandle, "free_mem")); +#else + // Get the address of the free_mem function on Unix-based systems + freeMemFunction = reinterpret_cast(dlsym(libraryHandle, "free_mem")); +#endif + + if (freeMemFunction) { + freeMemFunction(client); + } else { + std::cerr << "Failed to load free_mem function from the Bitwarden library." << std::endl; + } +} + +const char* BitwardenLibrary::run_command(const char* commandJson, void* client) { + typedef const char* (*RunCommandFunction)(const char*, void*); + RunCommandFunction runCommandFunction = nullptr; + +#ifdef _WIN32 + // Get the address of the run_command function on Windows + runCommandFunction = reinterpret_cast(GetProcAddress(libraryHandle, "run_command")); +#else + // Get the address of the run_command function on Unix-based systems + runCommandFunction = reinterpret_cast(dlsym(libraryHandle, "run_command")); +#endif + + if (runCommandFunction) { + return runCommandFunction(commandJson, client); + } + + std::cerr << "Failed to load run_command function from the Bitwarden library." << std::endl; + return nullptr; +} diff --git a/languages/cpp/src/CommandRunner.cpp b/languages/cpp/src/CommandRunner.cpp new file mode 100644 index 000000000..032347f34 --- /dev/null +++ b/languages/cpp/src/CommandRunner.cpp @@ -0,0 +1,49 @@ +#include "CommandRunner.h" +#include +#include +#include + + +CommandRunner::CommandRunner(BitwardenLibrary* library, void* client) : library(library), client(client) {} + +// Function to recursively filter out objects with all null values +nlohmann::json CommandRunner::filterNullObjects(const nlohmann::json& input) { + nlohmann::json result; + + for (auto it = input.begin(); it != input.end(); ++it) { + if (!it.value().is_null()) { + if (it.value().is_object()) { + // Recursively filter nested objects + json nestedFiltered = filterNullObjects(it.value()); + if (!nestedFiltered.empty()) { + result[it.key()] = nestedFiltered; + } + } else { + result[it.key()] = it.value(); + } + } + } + + return result; +} + +// Implement the commandToString function +std::string CommandRunner::commandToString(const Command& command) { + try { + // Create an nlohmann::json object from the Command object + nlohmann::json jsonCommand; + nlohmann::json filteredJsonCommand; + + Bitwarden::Sdk::to_json(jsonCommand, command); + + filteredJsonCommand = filterNullObjects(jsonCommand); + + // Convert the JSON to a string + std::string jsonCommandString = filteredJsonCommand.dump(); + + return jsonCommandString; + } catch (const std::exception& ex) { + std::cerr << "Error: " << ex.what() << std::endl; + throw ex; + } +} diff --git a/languages/cpp/src/Projects.cpp b/languages/cpp/src/Projects.cpp new file mode 100644 index 000000000..d0aa6ed49 --- /dev/null +++ b/languages/cpp/src/Projects.cpp @@ -0,0 +1,132 @@ +#include "Projects.h" +#include +#include +#include +#include +#include + +Projects::Projects(CommandRunner* commandRunner) : commandRunner(commandRunner) {} + +auto projectsDeserializer = [](const char* response) -> ResponseForProjectResponse { + nlohmann::json jsonResponse = nlohmann::json::parse(response); + ResponseForProjectResponse projectResponse; + Bitwarden::Sdk::from_json(jsonResponse, projectResponse); + return projectResponse; +}; + +auto deleteProjectsDeserializer = [](const char* response) -> ResponseForProjectsDeleteResponse { + nlohmann::json jsonResponse = nlohmann::json::parse(response); + ResponseForProjectsDeleteResponse deleteProjectsResponse; + Bitwarden::Sdk::from_json(jsonResponse, deleteProjectsResponse); + return deleteProjectsResponse; +}; + +auto projectListDeserializer = [](const char* response) -> ResponseForProjectsResponse { + nlohmann::json jsonResponse = nlohmann::json::parse(response); + ResponseForProjectsResponse listResponse; + Bitwarden::Sdk::from_json(jsonResponse, listResponse); + return listResponse; +}; + +ProjectResponse Projects::get(const boost::uuids::uuid& id) { + Command command; + ProjectsCommand projectsCommand; + ProjectGetRequest projectGetRequest; + + std::string idStr = boost::uuids::to_string(id); + projectGetRequest.set_id(idStr); + + projectsCommand.set_get(projectGetRequest); + command.set_projects(projectsCommand); + + try { + return commandRunner->runCommand(command, projectsDeserializer); + } catch (const std::exception& ex) { + std::cerr << "Error in getProject: " << ex.what() << std::endl; + throw ex; + } +} + +ProjectResponse Projects::create(const boost::uuids::uuid& organizationId, const std::string& name) { + Command command; + ProjectsCommand projectsCommand; + ProjectCreateRequest projectCreateRequest; + + std::string orgIdStr = boost::uuids::to_string(organizationId); + projectCreateRequest.set_organization_id(orgIdStr); + + projectCreateRequest.set_name(name); + projectsCommand.set_create(projectCreateRequest); + command.set_projects(projectsCommand); + + try { + return commandRunner->runCommand(command, projectsDeserializer); + } catch (const std::exception& ex) { + std::cerr << "Error in createProject: " << ex.what() << std::endl; + throw ex; + } +} + +ProjectResponse Projects::update(const boost::uuids::uuid& id, const boost::uuids::uuid& organizationId, const std::string& name) { + Command command; + ProjectsCommand projectsCommand; + ProjectPutRequest projectPutRequest; + + std::string idStr = boost::uuids::to_string(id); + projectPutRequest.set_id(idStr); + + std::string orgIdStr = boost::uuids::to_string(organizationId); + projectPutRequest.set_organization_id(orgIdStr); + + projectPutRequest.set_name(name); + projectsCommand.set_update(projectPutRequest); + command.set_projects(projectsCommand); + + try { + return commandRunner->runCommand(command, projectsDeserializer); + } catch (const std::exception& ex) { + std::cerr << "Error in updateProject: " << ex.what() << std::endl; + throw ex; + } +} + +ProjectsDeleteResponse Projects::deleteProjects(const std::vector& ids) { + Command command; + ProjectsCommand projectsCommand; + ProjectsDeleteRequest projectsDeleteRequest; + + std::vector idStrs; + for (const auto& id : ids) { + idStrs.push_back(boost::uuids::to_string(id)); + } + projectsDeleteRequest.set_ids(idStrs); + + projectsCommand.set_projects_command_delete(projectsDeleteRequest); + command.set_projects(projectsCommand); + + try { + return commandRunner->runCommand(command, deleteProjectsDeserializer); + } catch (const std::exception& ex) { + std::cerr << "Error in deleteProjects: " << ex.what() << std::endl; + throw ex; + } +} + +ProjectsResponse Projects::list(const boost::uuids::uuid& organizationId) { + Command command; + ProjectsCommand projectsCommand; + ProjectsListRequest projectsListRequest; + + std::string orgIdStr = boost::uuids::to_string(organizationId); + projectsListRequest.set_organization_id(orgIdStr); + + projectsCommand.set_list(projectsListRequest); + command.set_projects(projectsCommand); + + try { + return commandRunner->runCommand(command, projectListDeserializer); + } catch (const std::exception& ex) { + std::cerr << "Error in listProjects: " << ex.what() << std::endl; + throw ex; + } +} diff --git a/languages/cpp/src/Secrets.cpp b/languages/cpp/src/Secrets.cpp new file mode 100644 index 000000000..e153ea7f1 --- /dev/null +++ b/languages/cpp/src/Secrets.cpp @@ -0,0 +1,149 @@ +#include "Secrets.h" +#include +#include +#include +#include + +Secrets::Secrets(CommandRunner* commandRunner) : commandRunner(commandRunner) {} + +auto secretsDeserializer = [](const std::string& response) -> ResponseForSecretResponse { + nlohmann::json jsonResponse = nlohmann::json::parse(response); + ResponseForSecretResponse secretResponse; + Bitwarden::Sdk::from_json(jsonResponse, secretResponse); + return secretResponse; +}; + +auto deleteSecretsDeserializer = [](const std::string& response) -> ResponseForSecretsDeleteResponse { + nlohmann::json jsonResponse = nlohmann::json::parse(response); + ResponseForSecretsDeleteResponse deleteSecretsResponse; + Bitwarden::Sdk::from_json(jsonResponse, deleteSecretsResponse); + return deleteSecretsResponse; +}; + +auto secretListDeserializer = [](const std::string& response) -> ResponseForSecretIdentifiersResponse { + nlohmann::json jsonResponse = nlohmann::json::parse(response); + ResponseForSecretIdentifiersResponse listResponse; + Bitwarden::Sdk::from_json(jsonResponse, listResponse); + return listResponse; +}; + +SecretResponse Secrets::get(const boost::uuids::uuid& id) { + Command command; + SecretsCommand secretsCommand; + SecretGetRequest secretGetRequest; + + std::string idStr = boost::uuids::to_string(id); + secretGetRequest.set_id(idStr); + + secretsCommand.set_get(secretGetRequest); + command.set_secrets(secretsCommand); + + try { + return commandRunner->runCommand(command, secretsDeserializer); + } catch (const std::exception& ex) { + std::cerr << "Error in getSecret: " << ex.what() << std::endl; + throw ex; + } +} + +SecretResponse Secrets::create(const std::string& key, const std::string& value, const std::string& note, const boost::uuids::uuid& organizationId, const std::vector& projectIds) { + Command command; + SecretsCommand secretsCommand; + SecretCreateRequest secretCreateRequest; + + std::string orgIdStr = boost::uuids::to_string(organizationId); + secretCreateRequest.set_organization_id(orgIdStr); + + secretCreateRequest.set_key(key); + secretCreateRequest.set_value(value); + secretCreateRequest.set_note(note); + + std::vector projectIdsStr; + for (const auto& projectId : projectIds) { + projectIdsStr.push_back(boost::uuids::to_string(projectId)); + } + secretCreateRequest.set_project_ids(projectIdsStr); + + secretsCommand.set_create(secretCreateRequest); + command.set_secrets(secretsCommand); + + try { + return commandRunner->runCommand(command, secretsDeserializer); + } catch (const std::exception& ex) { + std::cerr << "Error in createSecret: " << ex.what() << std::endl; + throw ex; + } +} + +SecretResponse Secrets::update(const boost::uuids::uuid& id, const std::string& key, const std::string& value, const std::string& note, const boost::uuids::uuid& organizationId, const std::vector& projectIds) { + Command command; + SecretsCommand secretsCommand; + SecretPutRequest secretPutRequest; + + std::string idStr = boost::uuids::to_string(id); + secretPutRequest.set_id(idStr); + + std::string orgIdStr = boost::uuids::to_string(organizationId); + secretPutRequest.set_organization_id(orgIdStr); + + secretPutRequest.set_key(key); + secretPutRequest.set_value(value); + secretPutRequest.set_note(note); + + std::vector projectIdsStr; + for (const auto& projectId : projectIds) { + projectIdsStr.push_back(boost::uuids::to_string(projectId)); + } + secretPutRequest.set_project_ids(projectIdsStr); + + secretsCommand.set_update(secretPutRequest); + command.set_secrets(secretsCommand); + + try { + return commandRunner->runCommand(command, secretsDeserializer); + } catch (const std::exception& ex) { + std::cerr << "Error in updateSecret: " << ex.what() << std::endl; + throw ex; + } +} + +SecretsDeleteResponse Secrets::deleteSecrets(const std::vector& ids) { + Command command; + SecretsCommand secretsCommand; + SecretsDeleteRequest secretsDeleteRequest; + + std::vector idsStr; + for (const auto& id : ids) { + idsStr.push_back(boost::uuids::to_string(id)); + } + secretsDeleteRequest.set_ids(idsStr); + + secretsCommand.set_secrets_command_delete(secretsDeleteRequest); + command.set_secrets(secretsCommand); + + try { + return commandRunner->runCommand(command, deleteSecretsDeserializer); + } catch (const std::exception& ex) { + std::cerr << "Error in deleteSecrets: " << ex.what() << std::endl; + throw ex; + } +} + +SecretIdentifiersResponse Secrets::list(const boost::uuids::uuid& organizationId) { + Command command; + SecretsCommand secretsCommand; + SecretIdentifiersRequest secretIdentifiersRequest; + + std::string orgIdStr = boost::uuids::to_string(organizationId); + secretIdentifiersRequest.set_organization_id(orgIdStr); + + secretsCommand.set_list(secretIdentifiersRequest); + command.set_secrets(secretsCommand); + + try { + return commandRunner->runCommand(command, secretListDeserializer); + } catch (const std::exception& ex) { + std::cerr << "Error in listSecret: " << ex.what() << std::endl; + throw ex; + } +} diff --git a/support/scripts/schemas.ts b/support/scripts/schemas.ts index 98c0c39cb..2958efc18 100644 --- a/support/scripts/schemas.ts +++ b/support/scripts/schemas.ts @@ -70,6 +70,22 @@ async function main() { writeToFile("./languages/csharp/Bitwarden.Sdk/schemas.cs", csharp.lines); + const cpp = await quicktype({ + inputData, + lang: "cpp", + rendererOptions: { + namespace: "Bitwarden::Sdk", + "include-location": "global-include", + }, + }); + + cpp.lines.forEach((line, idx) => { + // Replace DOMAIN for URI_DOMAIN, because DOMAIN is an already defined macro + cpp.lines[idx] = line.replace(/DOMAIN/g, "URI_DOMAIN"); + }); + + writeToFile("./languages/cpp/include/schemas.hpp", cpp.lines); + const go = await quicktype({ inputData, lang: "go", From dd26683bdd9afedb76df4b9705ba3c982d8fbb5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Thu, 30 Nov 2023 17:29:27 +0100 Subject: [PATCH 044/157] Update uniffi to add comments support (#371) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective This upgrade adds code comments to the generated uniffi code --- Cargo.lock | 63 +++++++++++++++++++++--------- Cargo.toml | 10 ++--- crates/bitwarden-uniffi/Cargo.toml | 4 +- crates/bitwarden/Cargo.toml | 2 +- crates/uniffi-bindgen/Cargo.toml | 2 +- 5 files changed, 53 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2a4d3b6aa..50e263401 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2977,6 +2977,12 @@ version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + [[package]] name = "socket2" version = "0.4.10" @@ -3156,6 +3162,17 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.50" @@ -3435,6 +3452,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + [[package]] name = "unicode-normalization" version = "0.1.22" @@ -3458,8 +3481,8 @@ checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "uniffi" -version = "0.25.1" -source = "git+https://github.com/mozilla/uniffi-rs?rev=0a03b713306d6ce3de033157fc2ce92a238c2e24#0a03b713306d6ce3de033157fc2ce92a238c2e24" +version = "0.25.2" +source = "git+https://github.com/mozilla/uniffi-rs?rev=23711c8151bbb794369aa1f9d383db386792dff9#23711c8151bbb794369aa1f9d383db386792dff9" dependencies = [ "anyhow", "camino", @@ -3479,8 +3502,8 @@ dependencies = [ [[package]] name = "uniffi_bindgen" -version = "0.25.1" -source = "git+https://github.com/mozilla/uniffi-rs?rev=0a03b713306d6ce3de033157fc2ce92a238c2e24#0a03b713306d6ce3de033157fc2ce92a238c2e24" +version = "0.25.2" +source = "git+https://github.com/mozilla/uniffi-rs?rev=23711c8151bbb794369aa1f9d383db386792dff9#23711c8151bbb794369aa1f9d383db386792dff9" dependencies = [ "anyhow", "askama", @@ -3494,6 +3517,7 @@ dependencies = [ "once_cell", "paste", "serde", + "textwrap", "toml 0.5.11", "uniffi_meta", "uniffi_testing", @@ -3502,8 +3526,8 @@ dependencies = [ [[package]] name = "uniffi_build" -version = "0.25.1" -source = "git+https://github.com/mozilla/uniffi-rs?rev=0a03b713306d6ce3de033157fc2ce92a238c2e24#0a03b713306d6ce3de033157fc2ce92a238c2e24" +version = "0.25.2" +source = "git+https://github.com/mozilla/uniffi-rs?rev=23711c8151bbb794369aa1f9d383db386792dff9#23711c8151bbb794369aa1f9d383db386792dff9" dependencies = [ "anyhow", "camino", @@ -3512,8 +3536,8 @@ dependencies = [ [[package]] name = "uniffi_checksum_derive" -version = "0.25.1" -source = "git+https://github.com/mozilla/uniffi-rs?rev=0a03b713306d6ce3de033157fc2ce92a238c2e24#0a03b713306d6ce3de033157fc2ce92a238c2e24" +version = "0.25.2" +source = "git+https://github.com/mozilla/uniffi-rs?rev=23711c8151bbb794369aa1f9d383db386792dff9#23711c8151bbb794369aa1f9d383db386792dff9" dependencies = [ "quote", "syn 2.0.39", @@ -3521,8 +3545,8 @@ dependencies = [ [[package]] name = "uniffi_core" -version = "0.25.1" -source = "git+https://github.com/mozilla/uniffi-rs?rev=0a03b713306d6ce3de033157fc2ce92a238c2e24#0a03b713306d6ce3de033157fc2ce92a238c2e24" +version = "0.25.2" +source = "git+https://github.com/mozilla/uniffi-rs?rev=23711c8151bbb794369aa1f9d383db386792dff9#23711c8151bbb794369aa1f9d383db386792dff9" dependencies = [ "anyhow", "bytes", @@ -3536,8 +3560,8 @@ dependencies = [ [[package]] name = "uniffi_macros" -version = "0.25.1" -source = "git+https://github.com/mozilla/uniffi-rs?rev=0a03b713306d6ce3de033157fc2ce92a238c2e24#0a03b713306d6ce3de033157fc2ce92a238c2e24" +version = "0.25.2" +source = "git+https://github.com/mozilla/uniffi-rs?rev=23711c8151bbb794369aa1f9d383db386792dff9#23711c8151bbb794369aa1f9d383db386792dff9" dependencies = [ "bincode", "camino", @@ -3554,8 +3578,8 @@ dependencies = [ [[package]] name = "uniffi_meta" -version = "0.25.1" -source = "git+https://github.com/mozilla/uniffi-rs?rev=0a03b713306d6ce3de033157fc2ce92a238c2e24#0a03b713306d6ce3de033157fc2ce92a238c2e24" +version = "0.25.2" +source = "git+https://github.com/mozilla/uniffi-rs?rev=23711c8151bbb794369aa1f9d383db386792dff9#23711c8151bbb794369aa1f9d383db386792dff9" dependencies = [ "anyhow", "bytes", @@ -3565,8 +3589,8 @@ dependencies = [ [[package]] name = "uniffi_testing" -version = "0.25.1" -source = "git+https://github.com/mozilla/uniffi-rs?rev=0a03b713306d6ce3de033157fc2ce92a238c2e24#0a03b713306d6ce3de033157fc2ce92a238c2e24" +version = "0.25.2" +source = "git+https://github.com/mozilla/uniffi-rs?rev=23711c8151bbb794369aa1f9d383db386792dff9#23711c8151bbb794369aa1f9d383db386792dff9" dependencies = [ "anyhow", "camino", @@ -3577,10 +3601,11 @@ dependencies = [ [[package]] name = "uniffi_udl" -version = "0.25.1" -source = "git+https://github.com/mozilla/uniffi-rs?rev=0a03b713306d6ce3de033157fc2ce92a238c2e24#0a03b713306d6ce3de033157fc2ce92a238c2e24" +version = "0.25.2" +source = "git+https://github.com/mozilla/uniffi-rs?rev=23711c8151bbb794369aa1f9d383db386792dff9#23711c8151bbb794369aa1f9d383db386792dff9" dependencies = [ "anyhow", + "textwrap", "uniffi_meta", "uniffi_testing", "weedle2", @@ -3785,7 +3810,7 @@ dependencies = [ [[package]] name = "weedle2" version = "4.0.0" -source = "git+https://github.com/mozilla/uniffi-rs?rev=0a03b713306d6ce3de033157fc2ce92a238c2e24#0a03b713306d6ce3de033157fc2ce92a238c2e24" +source = "git+https://github.com/mozilla/uniffi-rs?rev=23711c8151bbb794369aa1f9d383db386792dff9#23711c8151bbb794369aa1f9d383db386792dff9" dependencies = [ "nom", ] diff --git a/Cargo.toml b/Cargo.toml index c7c2906dc..f841f07c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,8 +24,8 @@ codegen-units = 1 # Using git dependency temporarily to add support for immutable records in generated code [patch.crates-io] -uniffi = { git = "https://github.com/mozilla/uniffi-rs", rev = "0a03b713306d6ce3de033157fc2ce92a238c2e24" } -uniffi_build = { git = "https://github.com/mozilla/uniffi-rs", rev = "0a03b713306d6ce3de033157fc2ce92a238c2e24" } -uniffi_bindgen = { git = "https://github.com/mozilla/uniffi-rs", rev = "0a03b713306d6ce3de033157fc2ce92a238c2e24" } -uniffi_core = { git = "https://github.com/mozilla/uniffi-rs", rev = "0a03b713306d6ce3de033157fc2ce92a238c2e24" } -uniffi_macros = { git = "https://github.com/mozilla/uniffi-rs", rev = "0a03b713306d6ce3de033157fc2ce92a238c2e24" } +uniffi = { git = "https://github.com/mozilla/uniffi-rs", rev = "23711c8151bbb794369aa1f9d383db386792dff9" } +uniffi_build = { git = "https://github.com/mozilla/uniffi-rs", rev = "23711c8151bbb794369aa1f9d383db386792dff9" } +uniffi_bindgen = { git = "https://github.com/mozilla/uniffi-rs", rev = "23711c8151bbb794369aa1f9d383db386792dff9" } +uniffi_core = { git = "https://github.com/mozilla/uniffi-rs", rev = "23711c8151bbb794369aa1f9d383db386792dff9" } +uniffi_macros = { git = "https://github.com/mozilla/uniffi-rs", rev = "23711c8151bbb794369aa1f9d383db386792dff9" } diff --git a/crates/bitwarden-uniffi/Cargo.toml b/crates/bitwarden-uniffi/Cargo.toml index 405642d71..c1a47ea29 100644 --- a/crates/bitwarden-uniffi/Cargo.toml +++ b/crates/bitwarden-uniffi/Cargo.toml @@ -19,12 +19,12 @@ chrono = { version = ">=0.4.26, <0.5", features = [ ], default-features = false } env_logger = "0.10.1" schemars = { version = ">=0.8, <0.9", optional = true } -uniffi = "=0.25.1" +uniffi = "=0.25.2" bitwarden = { path = "../bitwarden", features = ["mobile", "internal"] } [build-dependencies] -uniffi = { version = "=0.25.1", features = ["build"] } +uniffi = { version = "=0.25.2", features = ["build"] } [target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies] openssl = { version = "0.10", features = ["vendored"] } diff --git a/crates/bitwarden/Cargo.toml b/crates/bitwarden/Cargo.toml index e52c03723..aea2cfe9e 100644 --- a/crates/bitwarden/Cargo.toml +++ b/crates/bitwarden/Cargo.toml @@ -54,7 +54,7 @@ sha1 = ">=0.10.5, <0.11" sha2 = ">=0.10.6, <0.11" subtle = ">=2.5.0, <3.0" thiserror = ">=1.0.40, <2.0" -uniffi = { version = "=0.25.1", optional = true } +uniffi = { version = "=0.25.2", optional = true } uuid = { version = ">=1.3.3, <2.0", features = ["serde"] } [dev-dependencies] diff --git a/crates/uniffi-bindgen/Cargo.toml b/crates/uniffi-bindgen/Cargo.toml index 825f923e3..cfbb5b554 100644 --- a/crates/uniffi-bindgen/Cargo.toml +++ b/crates/uniffi-bindgen/Cargo.toml @@ -10,4 +10,4 @@ name = "uniffi-bindgen" path = "uniffi-bindgen.rs" [dependencies] -uniffi = { version = "=0.25.1", features = ["cli"] } +uniffi = { version = "=0.25.2", features = ["cli"] } From 9bdce5dafe28f6afb9f0ad8f5f1a2a876befcbee Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 30 Nov 2023 18:05:12 +0100 Subject: [PATCH 045/157] Update swagger (#273) --- README.md | 28 +-- .../.openapi-generator/FILES | 11 +- crates/bitwarden-api-api/README.md | 18 +- .../src/apis/auth_requests_api.rs | 51 ++++++ .../bitwarden-api-api/src/apis/devices_api.rs | 151 +++++++++++----- crates/bitwarden-api-api/src/apis/mod.rs | 3 +- .../src/apis/organization_users_api.rs | 18 +- .../src/apis/organizations_api.rs | 170 ++++++++++++------ .../src/apis/service_accounts_api.rs | 7 +- .../src/models/attachment_response_model.rs | 2 +- .../src/models/billing_customer_discount.rs | 26 +++ .../models/cipher_details_response_model.rs | 3 + .../cipher_mini_details_response_model.rs | 3 + .../src/models/cipher_mini_response_model.rs | 3 + .../src/models/cipher_request_model.rs | 3 + .../src/models/cipher_response_model.rs | 3 + .../models/cipher_with_id_request_model.rs | 3 + .../device_keys_update_request_model.rs | 29 +++ .../src/models/device_response_model.rs | 15 +- .../src/models/event_type.rs | 6 + .../src/models/global_domains.rs | 2 +- crates/bitwarden-api-api/src/models/mod.rs | 22 ++- .../organization_public_key_response_model.rs | 26 +++ ...rganization_subscription_response_model.rs | 3 + ...reset_password_enrollment_request_model.rs | 15 -- .../other_device_keys_update_request_model.rs | 33 ++++ .../models/protected_device_response_model.rs | 44 +++++ ...secrets_manager_subscribe_request_model.rs | 29 +++ ..._account_secrets_details_response_model.rs | 41 +++++ ...ails_response_model_list_response_model.rs | 29 +++ .../src/models/subscription_response_model.rs | 3 + .../update_devices_trust_request_model.rs | 43 +++++ 32 files changed, 669 insertions(+), 174 deletions(-) create mode 100644 crates/bitwarden-api-api/src/models/billing_customer_discount.rs create mode 100644 crates/bitwarden-api-api/src/models/device_keys_update_request_model.rs create mode 100644 crates/bitwarden-api-api/src/models/organization_public_key_response_model.rs create mode 100644 crates/bitwarden-api-api/src/models/other_device_keys_update_request_model.rs create mode 100644 crates/bitwarden-api-api/src/models/protected_device_response_model.rs create mode 100644 crates/bitwarden-api-api/src/models/secrets_manager_subscribe_request_model.rs create mode 100644 crates/bitwarden-api-api/src/models/service_account_secrets_details_response_model.rs create mode 100644 crates/bitwarden-api-api/src/models/service_account_secrets_details_response_model_list_response_model.rs create mode 100644 crates/bitwarden-api-api/src/models/update_devices_trust_request_model.rs diff --git a/README.md b/README.md index 18613d4dc..2de94b09c 100644 --- a/README.md +++ b/README.md @@ -57,10 +57,10 @@ The first step is to generate the swagger documents from the server repository. ```bash # src/Api -dotnet swagger tofile --output ../../api.json .\bin\Debug\net6.0\Api.dll internal +dotnet swagger tofile --output ../../api.json ./bin/Debug/net6.0/Api.dll internal # src/Identity -dotnet swagger tofile --output ../../identity.json .\bin\Debug\net6.0\Identity.dll v1 +ASPNETCORE_ENVIRONMENT=development dotnet swagger tofile --output ../../identity.json ./bin/Debug/net6.0/Identity.dll v1 ``` ### OpenApi Generator @@ -68,20 +68,20 @@ dotnet swagger tofile --output ../../identity.json .\bin\Debug\net6.0\Identity.d Runs from the root of the SDK project. ```bash -npx openapi-generator-cli generate ` - -i ../server/api.json ` - -g rust ` - -o crates/bitwarden-api-api ` - --package-name bitwarden-api-api ` - -t ./support/openapi-template ` +npx openapi-generator-cli generate \ + -i ../server/api.json \ + -g rust \ + -o crates/bitwarden-api-api \ + --package-name bitwarden-api-api \ + -t ./support/openapi-template \ --additional-properties=packageVersion=1.0.0 -npx openapi-generator-cli generate ` - -i ../server/identity.json ` - -g rust ` - -o crates/bitwarden-api-identity ` - --package-name bitwarden-api-identity ` - -t ./support/openapi-template ` +npx openapi-generator-cli generate \ + -i ../server/identity.json \ + -g rust \ + -o crates/bitwarden-api-identity \ + --package-name bitwarden-api-identity \ + -t ./support/openapi-template \ --additional-properties=packageVersion=1.0.0 ``` diff --git a/crates/bitwarden-api-api/.openapi-generator/FILES b/crates/bitwarden-api-api/.openapi-generator/FILES index 4fbc2fbf9..586d4f2db 100644 --- a/crates/bitwarden-api-api/.openapi-generator/FILES +++ b/crates/bitwarden-api-api/.openapi-generator/FILES @@ -69,6 +69,7 @@ src/models/authenticator_attestation_raw_response.rs src/models/base_access_policy_response_model.rs src/models/base_secret_response_model.rs src/models/base_secret_response_model_list_response_model.rs +src/models/billing_customer_discount.rs src/models/billing_history_response_model.rs src/models/billing_invoice.rs src/models/billing_payment_response_model.rs @@ -122,6 +123,7 @@ src/models/collection_with_id_request_model.rs src/models/config_response_model.rs src/models/delete_recover_request_model.rs src/models/device_keys_request_model.rs +src/models/device_keys_update_request_model.rs src/models/device_request_model.rs src/models/device_response_model.rs src/models/device_response_model_list_response_model.rs @@ -201,10 +203,10 @@ src/models/organization_domain_response_model.rs src/models/organization_domain_response_model_list_response_model.rs src/models/organization_domain_sso_details_request_model.rs src/models/organization_domain_sso_details_response_model.rs -src/models/organization_enroll_secrets_manager_request_model.rs src/models/organization_keys_request_model.rs src/models/organization_keys_response_model.rs src/models/organization_license.rs +src/models/organization_public_key_response_model.rs src/models/organization_response_model.rs src/models/organization_seat_request_model.rs src/models/organization_sponsorship_create_request_model.rs @@ -241,6 +243,7 @@ src/models/organization_user_update_request_model.rs src/models/organization_user_user_details_response_model.rs src/models/organization_user_user_details_response_model_list_response_model.rs src/models/organization_verify_bank_request_model.rs +src/models/other_device_keys_update_request_model.rs src/models/password_hint_request_model.rs src/models/password_request_model.rs src/models/payment_method_type.rs @@ -272,6 +275,7 @@ src/models/project_create_request_model.rs src/models/project_response_model.rs src/models/project_response_model_list_response_model.rs src/models/project_update_request_model.rs +src/models/protected_device_response_model.rs src/models/provider_organization_add_request_model.rs src/models/provider_organization_create_request_model.rs src/models/provider_organization_organization_details_response_model.rs @@ -316,6 +320,7 @@ src/models/secret_update_request_model.rs src/models/secret_verification_request_model.rs src/models/secret_with_projects_inner_project.rs src/models/secret_with_projects_list_response_model.rs +src/models/secrets_manager_subscribe_request_model.rs src/models/secrets_manager_subscription_update_request_model.rs src/models/secrets_with_projects_inner_secret.rs src/models/secure_note_type.rs @@ -337,7 +342,8 @@ src/models/service_account_create_request_model.rs src/models/service_account_project_access_policy_response_model.rs src/models/service_account_project_access_policy_response_model_list_response_model.rs src/models/service_account_response_model.rs -src/models/service_account_response_model_list_response_model.rs +src/models/service_account_secrets_details_response_model.rs +src/models/service_account_secrets_details_response_model_list_response_model.rs src/models/service_account_update_request_model.rs src/models/set_key_connector_key_request_model.rs src/models/set_password_request_model.rs @@ -370,6 +376,7 @@ src/models/two_factor_web_authn_request_model.rs src/models/two_factor_web_authn_response_model.rs src/models/two_factor_yubi_key_response_model.rs src/models/update_avatar_request_model.rs +src/models/update_devices_trust_request_model.rs src/models/update_domains_request_model.rs src/models/update_key_request_model.rs src/models/update_profile_request_model.rs diff --git a/crates/bitwarden-api-api/README.md b/crates/bitwarden-api-api/README.md index 255041dc5..43cbc40e8 100644 --- a/crates/bitwarden-api-api/README.md +++ b/crates/bitwarden-api-api/README.md @@ -86,6 +86,7 @@ All URIs are relative to _http://localhost_ | _AccountsApi_ | [**accounts_verify_password_post**](docs/AccountsApi.md#accounts_verify_password_post) | **POST** /accounts/verify-password | | _AccountsBillingApi_ | [**accounts_billing_history_get**](docs/AccountsBillingApi.md#accounts_billing_history_get) | **GET** /accounts/billing/history | | _AccountsBillingApi_ | [**accounts_billing_payment_method_get**](docs/AccountsBillingApi.md#accounts_billing_payment_method_get) | **GET** /accounts/billing/payment-method | +| _AuthRequestsApi_ | [**auth_requests_admin_request_post**](docs/AuthRequestsApi.md#auth_requests_admin_request_post) | **POST** /auth-requests/admin-request | | _AuthRequestsApi_ | [**auth_requests_get**](docs/AuthRequestsApi.md#auth_requests_get) | **GET** /auth-requests | | _AuthRequestsApi_ | [**auth_requests_id_get**](docs/AuthRequestsApi.md#auth_requests_id_get) | **GET** /auth-requests/{id} | | _AuthRequestsApi_ | [**auth_requests_id_put**](docs/AuthRequestsApi.md#auth_requests_id_put) | **PUT** /auth-requests/{id} | @@ -162,7 +163,6 @@ All URIs are relative to _http://localhost_ | _CollectionsApi_ | [**organizations_org_id_collections_id_users_put**](docs/CollectionsApi.md#organizations_org_id_collections_id_users_put) | **PUT** /organizations/{orgId}/collections/{id}/users | | _CollectionsApi_ | [**organizations_org_id_collections_post**](docs/CollectionsApi.md#organizations_org_id_collections_post) | **POST** /organizations/{orgId}/collections | | _ConfigApi_ | [**config_get**](docs/ConfigApi.md#config_get) | **GET** /config | -| _DevicesApi_ | [**devices_exist_by_types_post**](docs/DevicesApi.md#devices_exist_by_types_post) | **POST** /devices/exist-by-types | | _DevicesApi_ | [**devices_get**](docs/DevicesApi.md#devices_get) | **GET** /devices | | _DevicesApi_ | [**devices_id_delete**](docs/DevicesApi.md#devices_id_delete) | **DELETE** /devices/{id} | | _DevicesApi_ | [**devices_id_delete_post**](docs/DevicesApi.md#devices_id_delete_post) | **POST** /devices/{id}/delete | @@ -176,9 +176,11 @@ All URIs are relative to _http://localhost_ | _DevicesApi_ | [**devices_identifier_identifier_token_put**](docs/DevicesApi.md#devices_identifier_identifier_token_put) | **PUT** /devices/identifier/{identifier}/token | | _DevicesApi_ | [**devices_identifier_keys_post**](docs/DevicesApi.md#devices_identifier_keys_post) | **POST** /devices/{identifier}/keys | | _DevicesApi_ | [**devices_identifier_keys_put**](docs/DevicesApi.md#devices_identifier_keys_put) | **PUT** /devices/{identifier}/keys | +| _DevicesApi_ | [**devices_identifier_retrieve_keys_post**](docs/DevicesApi.md#devices_identifier_retrieve_keys_post) | **POST** /devices/{identifier}/retrieve-keys | | _DevicesApi_ | [**devices_knowndevice_email_identifier_get**](docs/DevicesApi.md#devices_knowndevice_email_identifier_get) | **GET** /devices/knowndevice/{email}/{identifier} | | _DevicesApi_ | [**devices_knowndevice_get**](docs/DevicesApi.md#devices_knowndevice_get) | **GET** /devices/knowndevice | | _DevicesApi_ | [**devices_post**](docs/DevicesApi.md#devices_post) | **POST** /devices | +| _DevicesApi_ | [**devices_update_trust_post**](docs/DevicesApi.md#devices_update_trust_post) | **POST** /devices/update-trust | | _EmergencyAccessApi_ | [**emergency_access_granted_get**](docs/EmergencyAccessApi.md#emergency_access_granted_get) | **GET** /emergency-access/granted | | _EmergencyAccessApi_ | [**emergency_access_id_accept_post**](docs/EmergencyAccessApi.md#emergency_access_id_accept_post) | **POST** /emergency-access/{id}/accept | | _EmergencyAccessApi_ | [**emergency_access_id_approve_post**](docs/EmergencyAccessApi.md#emergency_access_id_approve_post) | **POST** /emergency-access/{id}/approve | @@ -304,7 +306,6 @@ All URIs are relative to _http://localhost_ | _OrganizationsApi_ | [**organizations_id_cancel_post**](docs/OrganizationsApi.md#organizations_id_cancel_post) | **POST** /organizations/{id}/cancel | | _OrganizationsApi_ | [**organizations_id_delete**](docs/OrganizationsApi.md#organizations_id_delete) | **DELETE** /organizations/{id} | | _OrganizationsApi_ | [**organizations_id_delete_post**](docs/OrganizationsApi.md#organizations_id_delete_post) | **POST** /organizations/{id}/delete | -| _OrganizationsApi_ | [**organizations_id_enroll_secrets_manager_post**](docs/OrganizationsApi.md#organizations_id_enroll_secrets_manager_post) | **POST** /organizations/{id}/enroll-secrets-manager | | _OrganizationsApi_ | [**organizations_id_get**](docs/OrganizationsApi.md#organizations_id_get) | **GET** /organizations/{id} | | _OrganizationsApi_ | [**organizations_id_import_post**](docs/OrganizationsApi.md#organizations_id_import_post) | **POST** /organizations/{id}/import | | _OrganizationsApi_ | [**organizations_id_keys_get**](docs/OrganizationsApi.md#organizations_id_keys_get) | **GET** /organizations/{id}/keys | @@ -313,6 +314,7 @@ All URIs are relative to _http://localhost_ | _OrganizationsApi_ | [**organizations_id_license_get**](docs/OrganizationsApi.md#organizations_id_license_get) | **GET** /organizations/{id}/license | | _OrganizationsApi_ | [**organizations_id_payment_post**](docs/OrganizationsApi.md#organizations_id_payment_post) | **POST** /organizations/{id}/payment | | _OrganizationsApi_ | [**organizations_id_post**](docs/OrganizationsApi.md#organizations_id_post) | **POST** /organizations/{id} | +| _OrganizationsApi_ | [**organizations_id_public_key_get**](docs/OrganizationsApi.md#organizations_id_public_key_get) | **GET** /organizations/{id}/public-key | | _OrganizationsApi_ | [**organizations_id_put**](docs/OrganizationsApi.md#organizations_id_put) | **PUT** /organizations/{id} | | _OrganizationsApi_ | [**organizations_id_reinstate_post**](docs/OrganizationsApi.md#organizations_id_reinstate_post) | **POST** /organizations/{id}/reinstate | | _OrganizationsApi_ | [**organizations_id_rotate_api_key_post**](docs/OrganizationsApi.md#organizations_id_rotate_api_key_post) | **POST** /organizations/{id}/rotate-api-key | @@ -321,6 +323,7 @@ All URIs are relative to _http://localhost_ | _OrganizationsApi_ | [**organizations_id_sso_get**](docs/OrganizationsApi.md#organizations_id_sso_get) | **GET** /organizations/{id}/sso | | _OrganizationsApi_ | [**organizations_id_sso_post**](docs/OrganizationsApi.md#organizations_id_sso_post) | **POST** /organizations/{id}/sso | | _OrganizationsApi_ | [**organizations_id_storage_post**](docs/OrganizationsApi.md#organizations_id_storage_post) | **POST** /organizations/{id}/storage | +| _OrganizationsApi_ | [**organizations_id_subscribe_secrets_manager_post**](docs/OrganizationsApi.md#organizations_id_subscribe_secrets_manager_post) | **POST** /organizations/{id}/subscribe-secrets-manager | | _OrganizationsApi_ | [**organizations_id_subscription_get**](docs/OrganizationsApi.md#organizations_id_subscription_get) | **GET** /organizations/{id}/subscription | | _OrganizationsApi_ | [**organizations_id_subscription_post**](docs/OrganizationsApi.md#organizations_id_subscription_post) | **POST** /organizations/{id}/subscription | | _OrganizationsApi_ | [**organizations_id_tax_get**](docs/OrganizationsApi.md#organizations_id_tax_get) | **GET** /organizations/{id}/tax | @@ -472,6 +475,7 @@ All URIs are relative to _http://localhost_ - [BaseAccessPolicyResponseModel](docs/BaseAccessPolicyResponseModel.md) - [BaseSecretResponseModel](docs/BaseSecretResponseModel.md) - [BaseSecretResponseModelListResponseModel](docs/BaseSecretResponseModelListResponseModel.md) +- [BillingCustomerDiscount](docs/BillingCustomerDiscount.md) - [BillingHistoryResponseModel](docs/BillingHistoryResponseModel.md) - [BillingInvoice](docs/BillingInvoice.md) - [BillingPaymentResponseModel](docs/BillingPaymentResponseModel.md) @@ -525,6 +529,7 @@ All URIs are relative to _http://localhost_ - [ConfigResponseModel](docs/ConfigResponseModel.md) - [DeleteRecoverRequestModel](docs/DeleteRecoverRequestModel.md) - [DeviceKeysRequestModel](docs/DeviceKeysRequestModel.md) +- [DeviceKeysUpdateRequestModel](docs/DeviceKeysUpdateRequestModel.md) - [DeviceRequestModel](docs/DeviceRequestModel.md) - [DeviceResponseModel](docs/DeviceResponseModel.md) - [DeviceResponseModelListResponseModel](docs/DeviceResponseModelListResponseModel.md) @@ -603,10 +608,10 @@ All URIs are relative to _http://localhost_ - [OrganizationDomainResponseModelListResponseModel](docs/OrganizationDomainResponseModelListResponseModel.md) - [OrganizationDomainSsoDetailsRequestModel](docs/OrganizationDomainSsoDetailsRequestModel.md) - [OrganizationDomainSsoDetailsResponseModel](docs/OrganizationDomainSsoDetailsResponseModel.md) -- [OrganizationEnrollSecretsManagerRequestModel](docs/OrganizationEnrollSecretsManagerRequestModel.md) - [OrganizationKeysRequestModel](docs/OrganizationKeysRequestModel.md) - [OrganizationKeysResponseModel](docs/OrganizationKeysResponseModel.md) - [OrganizationLicense](docs/OrganizationLicense.md) +- [OrganizationPublicKeyResponseModel](docs/OrganizationPublicKeyResponseModel.md) - [OrganizationResponseModel](docs/OrganizationResponseModel.md) - [OrganizationSeatRequestModel](docs/OrganizationSeatRequestModel.md) - [OrganizationSponsorshipCreateRequestModel](docs/OrganizationSponsorshipCreateRequestModel.md) @@ -644,6 +649,7 @@ All URIs are relative to _http://localhost_ - [OrganizationUserUserDetailsResponseModel](docs/OrganizationUserUserDetailsResponseModel.md) - [OrganizationUserUserDetailsResponseModelListResponseModel](docs/OrganizationUserUserDetailsResponseModelListResponseModel.md) - [OrganizationVerifyBankRequestModel](docs/OrganizationVerifyBankRequestModel.md) +- [OtherDeviceKeysUpdateRequestModel](docs/OtherDeviceKeysUpdateRequestModel.md) - [PasswordHintRequestModel](docs/PasswordHintRequestModel.md) - [PasswordRequestModel](docs/PasswordRequestModel.md) - [PaymentMethodType](docs/PaymentMethodType.md) @@ -675,6 +681,7 @@ All URIs are relative to _http://localhost_ - [ProjectResponseModel](docs/ProjectResponseModel.md) - [ProjectResponseModelListResponseModel](docs/ProjectResponseModelListResponseModel.md) - [ProjectUpdateRequestModel](docs/ProjectUpdateRequestModel.md) +- [ProtectedDeviceResponseModel](docs/ProtectedDeviceResponseModel.md) - [ProviderOrganizationAddRequestModel](docs/ProviderOrganizationAddRequestModel.md) - [ProviderOrganizationCreateRequestModel](docs/ProviderOrganizationCreateRequestModel.md) - [ProviderOrganizationOrganizationDetailsResponseModel](docs/ProviderOrganizationOrganizationDetailsResponseModel.md) @@ -719,6 +726,7 @@ All URIs are relative to _http://localhost_ - [SecretVerificationRequestModel](docs/SecretVerificationRequestModel.md) - [SecretWithProjectsInnerProject](docs/SecretWithProjectsInnerProject.md) - [SecretWithProjectsListResponseModel](docs/SecretWithProjectsListResponseModel.md) +- [SecretsManagerSubscribeRequestModel](docs/SecretsManagerSubscribeRequestModel.md) - [SecretsManagerSubscriptionUpdateRequestModel](docs/SecretsManagerSubscriptionUpdateRequestModel.md) - [SecretsWithProjectsInnerSecret](docs/SecretsWithProjectsInnerSecret.md) - [SecureNoteType](docs/SecureNoteType.md) @@ -740,7 +748,8 @@ All URIs are relative to _http://localhost_ - [ServiceAccountProjectAccessPolicyResponseModel](docs/ServiceAccountProjectAccessPolicyResponseModel.md) - [ServiceAccountProjectAccessPolicyResponseModelListResponseModel](docs/ServiceAccountProjectAccessPolicyResponseModelListResponseModel.md) - [ServiceAccountResponseModel](docs/ServiceAccountResponseModel.md) -- [ServiceAccountResponseModelListResponseModel](docs/ServiceAccountResponseModelListResponseModel.md) +- [ServiceAccountSecretsDetailsResponseModel](docs/ServiceAccountSecretsDetailsResponseModel.md) +- [ServiceAccountSecretsDetailsResponseModelListResponseModel](docs/ServiceAccountSecretsDetailsResponseModelListResponseModel.md) - [ServiceAccountUpdateRequestModel](docs/ServiceAccountUpdateRequestModel.md) - [SetKeyConnectorKeyRequestModel](docs/SetKeyConnectorKeyRequestModel.md) - [SetPasswordRequestModel](docs/SetPasswordRequestModel.md) @@ -773,6 +782,7 @@ All URIs are relative to _http://localhost_ - [TwoFactorWebAuthnResponseModel](docs/TwoFactorWebAuthnResponseModel.md) - [TwoFactorYubiKeyResponseModel](docs/TwoFactorYubiKeyResponseModel.md) - [UpdateAvatarRequestModel](docs/UpdateAvatarRequestModel.md) +- [UpdateDevicesTrustRequestModel](docs/UpdateDevicesTrustRequestModel.md) - [UpdateDomainsRequestModel](docs/UpdateDomainsRequestModel.md) - [UpdateKeyRequestModel](docs/UpdateKeyRequestModel.md) - [UpdateProfileRequestModel](docs/UpdateProfileRequestModel.md) diff --git a/crates/bitwarden-api-api/src/apis/auth_requests_api.rs b/crates/bitwarden-api-api/src/apis/auth_requests_api.rs index 476a94129..39c4dd7b4 100644 --- a/crates/bitwarden-api-api/src/apis/auth_requests_api.rs +++ b/crates/bitwarden-api-api/src/apis/auth_requests_api.rs @@ -13,6 +13,13 @@ use reqwest; use super::{configuration, Error}; use crate::apis::ResponseContent; +/// struct for typed errors of method [`auth_requests_admin_request_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum AuthRequestsAdminRequestPostError { + UnknownValue(serde_json::Value), +} + /// struct for typed errors of method [`auth_requests_get`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -48,6 +55,50 @@ pub enum AuthRequestsPostError { UnknownValue(serde_json::Value), } +pub async fn auth_requests_admin_request_post( + configuration: &configuration::Configuration, + auth_request_create_request_model: Option, +) -> Result> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/auth-requests/admin-request", + local_var_configuration.base_path + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::POST, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + if let Some(ref local_var_token) = local_var_configuration.oauth_access_token { + local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); + }; + local_var_req_builder = local_var_req_builder.json(&auth_request_create_request_model); + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} + pub async fn auth_requests_get( configuration: &configuration::Configuration, ) -> Result> { diff --git a/crates/bitwarden-api-api/src/apis/devices_api.rs b/crates/bitwarden-api-api/src/apis/devices_api.rs index e7be0685e..95564464a 100644 --- a/crates/bitwarden-api-api/src/apis/devices_api.rs +++ b/crates/bitwarden-api-api/src/apis/devices_api.rs @@ -13,13 +13,6 @@ use reqwest; use super::{configuration, Error}; use crate::apis::ResponseContent; -/// struct for typed errors of method [`devices_exist_by_types_post`] -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum DevicesExistByTypesPostError { - UnknownValue(serde_json::Value), -} - /// struct for typed errors of method [`devices_get`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -111,6 +104,13 @@ pub enum DevicesIdentifierKeysPutError { UnknownValue(serde_json::Value), } +/// struct for typed errors of method [`devices_identifier_retrieve_keys_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum DevicesIdentifierRetrieveKeysPostError { + UnknownValue(serde_json::Value), +} + /// struct for typed errors of method [`devices_knowndevice_email_identifier_get`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -132,48 +132,11 @@ pub enum DevicesPostError { UnknownValue(serde_json::Value), } -pub async fn devices_exist_by_types_post( - configuration: &configuration::Configuration, - device_type: Option>, -) -> Result> { - let local_var_configuration = configuration; - - let local_var_client = &local_var_configuration.client; - - let local_var_uri_str = format!( - "{}/devices/exist-by-types", - local_var_configuration.base_path - ); - let mut local_var_req_builder = - local_var_client.request(reqwest::Method::POST, local_var_uri_str.as_str()); - - if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { - local_var_req_builder = - local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); - } - if let Some(ref local_var_token) = local_var_configuration.oauth_access_token { - local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); - }; - local_var_req_builder = local_var_req_builder.json(&device_type); - - let local_var_req = local_var_req_builder.build()?; - let local_var_resp = local_var_client.execute(local_var_req).await?; - - let local_var_status = local_var_resp.status(); - let local_var_content = local_var_resp.text().await?; - - if !local_var_status.is_client_error() && !local_var_status.is_server_error() { - serde_json::from_str(&local_var_content).map_err(Error::from) - } else { - let local_var_entity: Option = - serde_json::from_str(&local_var_content).ok(); - let local_var_error = ResponseContent { - status: local_var_status, - content: local_var_content, - entity: local_var_entity, - }; - Err(Error::ResponseError(local_var_error)) - } +/// struct for typed errors of method [`devices_update_trust_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum DevicesUpdateTrustPostError { + UnknownValue(serde_json::Value), } pub async fn devices_get( @@ -755,6 +718,55 @@ pub async fn devices_identifier_keys_put( } } +pub async fn devices_identifier_retrieve_keys_post( + configuration: &configuration::Configuration, + identifier: &str, + secret_verification_request_model: Option, +) -> Result< + crate::models::ProtectedDeviceResponseModel, + Error, +> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/devices/{identifier}/retrieve-keys", + local_var_configuration.base_path, + identifier = crate::apis::urlencode(identifier.to_string()) + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::POST, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + if let Some(ref local_var_token) = local_var_configuration.oauth_access_token { + local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); + }; + local_var_req_builder = local_var_req_builder.json(&secret_verification_request_model); + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} + pub async fn devices_knowndevice_email_identifier_get( configuration: &configuration::Configuration, email: &str, @@ -890,3 +902,44 @@ pub async fn devices_post( Err(Error::ResponseError(local_var_error)) } } + +pub async fn devices_update_trust_post( + configuration: &configuration::Configuration, + update_devices_trust_request_model: Option, +) -> Result<(), Error> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!("{}/devices/update-trust", local_var_configuration.base_path); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::POST, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + if let Some(ref local_var_token) = local_var_configuration.oauth_access_token { + local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); + }; + local_var_req_builder = local_var_req_builder.json(&update_devices_trust_request_model); + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + Ok(()) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} diff --git a/crates/bitwarden-api-api/src/apis/mod.rs b/crates/bitwarden-api-api/src/apis/mod.rs index 623d5d0b6..492680582 100644 --- a/crates/bitwarden-api-api/src/apis/mod.rs +++ b/crates/bitwarden-api-api/src/apis/mod.rs @@ -1,5 +1,4 @@ -use std::error; -use std::fmt; +use std::{error, fmt}; #[derive(Debug, Clone)] pub struct ResponseContent { diff --git a/crates/bitwarden-api-api/src/apis/organization_users_api.rs b/crates/bitwarden-api-api/src/apis/organization_users_api.rs index f81290a86..5a8e99916 100644 --- a/crates/bitwarden-api-api/src/apis/organization_users_api.rs +++ b/crates/bitwarden-api-api/src/apis/organization_users_api.rs @@ -391,10 +391,7 @@ pub async fn organizations_org_id_users_enable_secrets_manager_patch( configuration: &configuration::Configuration, org_id: uuid::Uuid, organization_user_bulk_request_model: Option, -) -> Result< - crate::models::OrganizationUserBulkResponseModelListResponseModel, - Error, -> { +) -> Result<(), Error> { let local_var_configuration = configuration; let local_var_client = &local_var_configuration.client; @@ -423,7 +420,7 @@ pub async fn organizations_org_id_users_enable_secrets_manager_patch( let local_var_content = local_var_resp.text().await?; if !local_var_status.is_client_error() && !local_var_status.is_server_error() { - serde_json::from_str(&local_var_content).map_err(Error::from) + Ok(()) } else { let local_var_entity: Option = serde_json::from_str(&local_var_content).ok(); @@ -440,10 +437,7 @@ pub async fn organizations_org_id_users_enable_secrets_manager_put( configuration: &configuration::Configuration, org_id: uuid::Uuid, organization_user_bulk_request_model: Option, -) -> Result< - crate::models::OrganizationUserBulkResponseModelListResponseModel, - Error, -> { +) -> Result<(), Error> { let local_var_configuration = configuration; let local_var_client = &local_var_configuration.client; @@ -472,7 +466,7 @@ pub async fn organizations_org_id_users_enable_secrets_manager_put( let local_var_content = local_var_resp.text().await?; if !local_var_status.is_client_error() && !local_var_status.is_server_error() { - serde_json::from_str(&local_var_content).map_err(Error::from) + Ok(()) } else { let local_var_entity: Option = serde_json::from_str(&local_var_content).ok(); @@ -1761,8 +1755,8 @@ pub async fn organizations_org_id_users_revoke_put( pub async fn organizations_org_id_users_user_id_reset_password_enrollment_put( configuration: &configuration::Configuration, - org_id: &str, - user_id: &str, + org_id: uuid::Uuid, + user_id: uuid::Uuid, organization_user_reset_password_enrollment_request_model: Option< crate::models::OrganizationUserResetPasswordEnrollmentRequestModel, >, diff --git a/crates/bitwarden-api-api/src/apis/organizations_api.rs b/crates/bitwarden-api-api/src/apis/organizations_api.rs index 1cd531b2f..6377ef6bf 100644 --- a/crates/bitwarden-api-api/src/apis/organizations_api.rs +++ b/crates/bitwarden-api-api/src/apis/organizations_api.rs @@ -62,13 +62,6 @@ pub enum OrganizationsIdDeletePostError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organizations_id_enroll_secrets_manager_post`] -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum OrganizationsIdEnrollSecretsManagerPostError { - UnknownValue(serde_json::Value), -} - /// struct for typed errors of method [`organizations_id_get`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -125,6 +118,13 @@ pub enum OrganizationsIdPostError { UnknownValue(serde_json::Value), } +/// struct for typed errors of method [`organizations_id_public_key_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum OrganizationsIdPublicKeyGetError { + UnknownValue(serde_json::Value), +} + /// struct for typed errors of method [`organizations_id_put`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -181,6 +181,13 @@ pub enum OrganizationsIdStoragePostError { UnknownValue(serde_json::Value), } +/// struct for typed errors of method [`organizations_id_subscribe_secrets_manager_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum OrganizationsIdSubscribeSecretsManagerPostError { + UnknownValue(serde_json::Value), +} + /// struct for typed errors of method [`organizations_id_subscription_get`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -549,55 +556,6 @@ pub async fn organizations_id_delete_post( } } -pub async fn organizations_id_enroll_secrets_manager_post( - configuration: &configuration::Configuration, - id: uuid::Uuid, - organization_enroll_secrets_manager_request_model: Option< - crate::models::OrganizationEnrollSecretsManagerRequestModel, - >, -) -> Result<(), Error> { - let local_var_configuration = configuration; - - let local_var_client = &local_var_configuration.client; - - let local_var_uri_str = format!( - "{}/organizations/{id}/enroll-secrets-manager", - local_var_configuration.base_path, - id = crate::apis::urlencode(id.to_string()) - ); - let mut local_var_req_builder = - local_var_client.request(reqwest::Method::POST, local_var_uri_str.as_str()); - - if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { - local_var_req_builder = - local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); - } - if let Some(ref local_var_token) = local_var_configuration.oauth_access_token { - local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); - }; - local_var_req_builder = - local_var_req_builder.json(&organization_enroll_secrets_manager_request_model); - - let local_var_req = local_var_req_builder.build()?; - let local_var_resp = local_var_client.execute(local_var_req).await?; - - let local_var_status = local_var_resp.status(); - let local_var_content = local_var_resp.text().await?; - - if !local_var_status.is_client_error() && !local_var_status.is_server_error() { - Ok(()) - } else { - let local_var_entity: Option = - serde_json::from_str(&local_var_content).ok(); - let local_var_error = ResponseContent { - status: local_var_status, - content: local_var_content, - entity: local_var_entity, - }; - Err(Error::ResponseError(local_var_error)) - } -} - pub async fn organizations_id_get( configuration: &configuration::Configuration, id: &str, @@ -693,7 +651,7 @@ pub async fn organizations_id_import_post( pub async fn organizations_id_keys_get( configuration: &configuration::Configuration, id: &str, -) -> Result> { +) -> Result> { let local_var_configuration = configuration; let local_var_client = &local_var_configuration.client; @@ -965,6 +923,53 @@ pub async fn organizations_id_post( } } +pub async fn organizations_id_public_key_get( + configuration: &configuration::Configuration, + id: &str, +) -> Result< + crate::models::OrganizationPublicKeyResponseModel, + Error, +> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/organizations/{id}/public-key", + local_var_configuration.base_path, + id = crate::apis::urlencode(id.to_string()) + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + if let Some(ref local_var_token) = local_var_configuration.oauth_access_token { + local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); + }; + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} + pub async fn organizations_id_put( configuration: &configuration::Configuration, id: &str, @@ -1332,6 +1337,57 @@ pub async fn organizations_id_storage_post( } } +pub async fn organizations_id_subscribe_secrets_manager_post( + configuration: &configuration::Configuration, + id: uuid::Uuid, + secrets_manager_subscribe_request_model: Option< + crate::models::SecretsManagerSubscribeRequestModel, + >, +) -> Result< + crate::models::ProfileOrganizationResponseModel, + Error, +> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/organizations/{id}/subscribe-secrets-manager", + local_var_configuration.base_path, + id = crate::apis::urlencode(id.to_string()) + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::POST, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + if let Some(ref local_var_token) = local_var_configuration.oauth_access_token { + local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); + }; + local_var_req_builder = local_var_req_builder.json(&secrets_manager_subscribe_request_model); + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} + pub async fn organizations_id_subscription_get( configuration: &configuration::Configuration, id: &str, diff --git a/crates/bitwarden-api-api/src/apis/service_accounts_api.rs b/crates/bitwarden-api-api/src/apis/service_accounts_api.rs index 511fb3674..47045b475 100644 --- a/crates/bitwarden-api-api/src/apis/service_accounts_api.rs +++ b/crates/bitwarden-api-api/src/apis/service_accounts_api.rs @@ -72,8 +72,9 @@ pub enum ServiceAccountsIdPutError { pub async fn organizations_organization_id_service_accounts_get( configuration: &configuration::Configuration, organization_id: uuid::Uuid, + include_access_to_secrets: Option, ) -> Result< - crate::models::ServiceAccountResponseModelListResponseModel, + crate::models::ServiceAccountSecretsDetailsResponseModelListResponseModel, Error, > { let local_var_configuration = configuration; @@ -88,6 +89,10 @@ pub async fn organizations_organization_id_service_accounts_get( let mut local_var_req_builder = local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + if let Some(ref local_var_str) = include_access_to_secrets { + local_var_req_builder = + local_var_req_builder.query(&[("includeAccessToSecrets", &local_var_str.to_string())]); + } if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { local_var_req_builder = local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); diff --git a/crates/bitwarden-api-api/src/models/attachment_response_model.rs b/crates/bitwarden-api-api/src/models/attachment_response_model.rs index 86637a161..c9066488d 100644 --- a/crates/bitwarden-api-api/src/models/attachment_response_model.rs +++ b/crates/bitwarden-api-api/src/models/attachment_response_model.rs @@ -21,7 +21,7 @@ pub struct AttachmentResponseModel { #[serde(rename = "key", skip_serializing_if = "Option::is_none")] pub key: Option, #[serde(rename = "size", skip_serializing_if = "Option::is_none")] - pub size: Option, + pub size: Option, #[serde(rename = "sizeName", skip_serializing_if = "Option::is_none")] pub size_name: Option, } diff --git a/crates/bitwarden-api-api/src/models/billing_customer_discount.rs b/crates/bitwarden-api-api/src/models/billing_customer_discount.rs new file mode 100644 index 000000000..61f695d70 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/billing_customer_discount.rs @@ -0,0 +1,26 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +pub struct BillingCustomerDiscount { + #[serde(rename = "id", skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(rename = "active", skip_serializing_if = "Option::is_none")] + pub active: Option, +} + +impl BillingCustomerDiscount { + pub fn new() -> BillingCustomerDiscount { + BillingCustomerDiscount { + id: None, + active: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/cipher_details_response_model.rs b/crates/bitwarden-api-api/src/models/cipher_details_response_model.rs index 448712702..289169e41 100644 --- a/crates/bitwarden-api-api/src/models/cipher_details_response_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_details_response_model.rs @@ -51,6 +51,8 @@ pub struct CipherDetailsResponseModel { pub deleted_date: Option, #[serde(rename = "reprompt", skip_serializing_if = "Option::is_none")] pub reprompt: Option, + #[serde(rename = "key", skip_serializing_if = "Option::is_none")] + pub key: Option, #[serde(rename = "folderId", skip_serializing_if = "Option::is_none")] pub folder_id: Option, #[serde(rename = "favorite", skip_serializing_if = "Option::is_none")] @@ -85,6 +87,7 @@ impl CipherDetailsResponseModel { creation_date: None, deleted_date: None, reprompt: None, + key: None, folder_id: None, favorite: None, edit: None, diff --git a/crates/bitwarden-api-api/src/models/cipher_mini_details_response_model.rs b/crates/bitwarden-api-api/src/models/cipher_mini_details_response_model.rs index 28dafcdbb..1aa5b8e3b 100644 --- a/crates/bitwarden-api-api/src/models/cipher_mini_details_response_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_mini_details_response_model.rs @@ -51,6 +51,8 @@ pub struct CipherMiniDetailsResponseModel { pub deleted_date: Option, #[serde(rename = "reprompt", skip_serializing_if = "Option::is_none")] pub reprompt: Option, + #[serde(rename = "key", skip_serializing_if = "Option::is_none")] + pub key: Option, #[serde(rename = "collectionIds", skip_serializing_if = "Option::is_none")] pub collection_ids: Option>, } @@ -77,6 +79,7 @@ impl CipherMiniDetailsResponseModel { creation_date: None, deleted_date: None, reprompt: None, + key: None, collection_ids: None, } } diff --git a/crates/bitwarden-api-api/src/models/cipher_mini_response_model.rs b/crates/bitwarden-api-api/src/models/cipher_mini_response_model.rs index 8ea52926d..c96ddfd18 100644 --- a/crates/bitwarden-api-api/src/models/cipher_mini_response_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_mini_response_model.rs @@ -51,6 +51,8 @@ pub struct CipherMiniResponseModel { pub deleted_date: Option, #[serde(rename = "reprompt", skip_serializing_if = "Option::is_none")] pub reprompt: Option, + #[serde(rename = "key", skip_serializing_if = "Option::is_none")] + pub key: Option, } impl CipherMiniResponseModel { @@ -75,6 +77,7 @@ impl CipherMiniResponseModel { creation_date: None, deleted_date: None, reprompt: None, + key: None, } } } diff --git a/crates/bitwarden-api-api/src/models/cipher_request_model.rs b/crates/bitwarden-api-api/src/models/cipher_request_model.rs index 4ac3f62d7..2e445802b 100644 --- a/crates/bitwarden-api-api/src/models/cipher_request_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_request_model.rs @@ -20,6 +20,8 @@ pub struct CipherRequestModel { pub favorite: Option, #[serde(rename = "reprompt", skip_serializing_if = "Option::is_none")] pub reprompt: Option, + #[serde(rename = "key", skip_serializing_if = "Option::is_none")] + pub key: Option, #[serde(rename = "name")] pub name: String, #[serde(rename = "notes", skip_serializing_if = "Option::is_none")] @@ -56,6 +58,7 @@ impl CipherRequestModel { folder_id: None, favorite: None, reprompt: None, + key: None, name, notes: None, fields: None, diff --git a/crates/bitwarden-api-api/src/models/cipher_response_model.rs b/crates/bitwarden-api-api/src/models/cipher_response_model.rs index 62a930e1f..59831e6fb 100644 --- a/crates/bitwarden-api-api/src/models/cipher_response_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_response_model.rs @@ -51,6 +51,8 @@ pub struct CipherResponseModel { pub deleted_date: Option, #[serde(rename = "reprompt", skip_serializing_if = "Option::is_none")] pub reprompt: Option, + #[serde(rename = "key", skip_serializing_if = "Option::is_none")] + pub key: Option, #[serde(rename = "folderId", skip_serializing_if = "Option::is_none")] pub folder_id: Option, #[serde(rename = "favorite", skip_serializing_if = "Option::is_none")] @@ -83,6 +85,7 @@ impl CipherResponseModel { creation_date: None, deleted_date: None, reprompt: None, + key: None, folder_id: None, favorite: None, edit: None, diff --git a/crates/bitwarden-api-api/src/models/cipher_with_id_request_model.rs b/crates/bitwarden-api-api/src/models/cipher_with_id_request_model.rs index db4c3604f..093d2e5d9 100644 --- a/crates/bitwarden-api-api/src/models/cipher_with_id_request_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_with_id_request_model.rs @@ -20,6 +20,8 @@ pub struct CipherWithIdRequestModel { pub favorite: Option, #[serde(rename = "reprompt", skip_serializing_if = "Option::is_none")] pub reprompt: Option, + #[serde(rename = "key", skip_serializing_if = "Option::is_none")] + pub key: Option, #[serde(rename = "name")] pub name: String, #[serde(rename = "notes", skip_serializing_if = "Option::is_none")] @@ -58,6 +60,7 @@ impl CipherWithIdRequestModel { folder_id: None, favorite: None, reprompt: None, + key: None, name, notes: None, fields: None, diff --git a/crates/bitwarden-api-api/src/models/device_keys_update_request_model.rs b/crates/bitwarden-api-api/src/models/device_keys_update_request_model.rs new file mode 100644 index 000000000..65d82513c --- /dev/null +++ b/crates/bitwarden-api-api/src/models/device_keys_update_request_model.rs @@ -0,0 +1,29 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +pub struct DeviceKeysUpdateRequestModel { + #[serde(rename = "encryptedPublicKey")] + pub encrypted_public_key: String, + #[serde(rename = "encryptedUserKey")] + pub encrypted_user_key: String, +} + +impl DeviceKeysUpdateRequestModel { + pub fn new( + encrypted_public_key: String, + encrypted_user_key: String, + ) -> DeviceKeysUpdateRequestModel { + DeviceKeysUpdateRequestModel { + encrypted_public_key, + encrypted_user_key, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/device_response_model.rs b/crates/bitwarden-api-api/src/models/device_response_model.rs index 463d6f104..c6dc76d96 100644 --- a/crates/bitwarden-api-api/src/models/device_response_model.rs +++ b/crates/bitwarden-api-api/src/models/device_response_model.rs @@ -22,15 +22,8 @@ pub struct DeviceResponseModel { pub identifier: Option, #[serde(rename = "creationDate", skip_serializing_if = "Option::is_none")] pub creation_date: Option, - #[serde(rename = "encryptedUserKey", skip_serializing_if = "Option::is_none")] - pub encrypted_user_key: Option, - #[serde(rename = "encryptedPublicKey", skip_serializing_if = "Option::is_none")] - pub encrypted_public_key: Option, - #[serde( - rename = "encryptedPrivateKey", - skip_serializing_if = "Option::is_none" - )] - pub encrypted_private_key: Option, + #[serde(rename = "isTrusted", skip_serializing_if = "Option::is_none")] + pub is_trusted: Option, } impl DeviceResponseModel { @@ -42,9 +35,7 @@ impl DeviceResponseModel { r#type: None, identifier: None, creation_date: None, - encrypted_user_key: None, - encrypted_public_key: None, - encrypted_private_key: None, + is_trusted: None, } } } diff --git a/crates/bitwarden-api-api/src/models/event_type.rs b/crates/bitwarden-api-api/src/models/event_type.rs index 3988c656f..f0cea8ebc 100644 --- a/crates/bitwarden-api-api/src/models/event_type.rs +++ b/crates/bitwarden-api-api/src/models/event_type.rs @@ -24,6 +24,7 @@ pub enum EventType { Variant1007 = 1007, Variant1008 = 1008, Variant1009 = 1009, + Variant1010 = 1010, Variant1100 = 1100, Variant1101 = 1101, Variant1102 = 1102, @@ -61,6 +62,8 @@ pub enum EventType { Variant1510 = 1510, Variant1511 = 1511, Variant1512 = 1512, + Variant1513 = 1513, + Variant1514 = 1514, Variant1600 = 1600, Variant1601 = 1601, Variant1602 = 1602, @@ -99,6 +102,7 @@ impl ToString for EventType { Self::Variant1007 => String::from("1007"), Self::Variant1008 => String::from("1008"), Self::Variant1009 => String::from("1009"), + Self::Variant1010 => String::from("1010"), Self::Variant1100 => String::from("1100"), Self::Variant1101 => String::from("1101"), Self::Variant1102 => String::from("1102"), @@ -136,6 +140,8 @@ impl ToString for EventType { Self::Variant1510 => String::from("1510"), Self::Variant1511 => String::from("1511"), Self::Variant1512 => String::from("1512"), + Self::Variant1513 => String::from("1513"), + Self::Variant1514 => String::from("1514"), Self::Variant1600 => String::from("1600"), Self::Variant1601 => String::from("1601"), Self::Variant1602 => String::from("1602"), diff --git a/crates/bitwarden-api-api/src/models/global_domains.rs b/crates/bitwarden-api-api/src/models/global_domains.rs index f0582caa4..10a943482 100644 --- a/crates/bitwarden-api-api/src/models/global_domains.rs +++ b/crates/bitwarden-api-api/src/models/global_domains.rs @@ -11,7 +11,7 @@ #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] pub struct GlobalDomains { #[serde(rename = "type", skip_serializing_if = "Option::is_none")] - pub r#type: Option, + pub r#type: Option, #[serde(rename = "domains", skip_serializing_if = "Option::is_none")] pub domains: Option>, #[serde(rename = "excluded", skip_serializing_if = "Option::is_none")] diff --git a/crates/bitwarden-api-api/src/models/mod.rs b/crates/bitwarden-api-api/src/models/mod.rs index 9d2aa9ae8..db4702827 100644 --- a/crates/bitwarden-api-api/src/models/mod.rs +++ b/crates/bitwarden-api-api/src/models/mod.rs @@ -42,6 +42,8 @@ pub mod base_secret_response_model; pub use self::base_secret_response_model::BaseSecretResponseModel; pub mod base_secret_response_model_list_response_model; pub use self::base_secret_response_model_list_response_model::BaseSecretResponseModelListResponseModel; +pub mod billing_customer_discount; +pub use self::billing_customer_discount::BillingCustomerDiscount; pub mod billing_history_response_model; pub use self::billing_history_response_model::BillingHistoryResponseModel; pub mod billing_invoice; @@ -148,6 +150,8 @@ pub mod delete_recover_request_model; pub use self::delete_recover_request_model::DeleteRecoverRequestModel; pub mod device_keys_request_model; pub use self::device_keys_request_model::DeviceKeysRequestModel; +pub mod device_keys_update_request_model; +pub use self::device_keys_update_request_model::DeviceKeysUpdateRequestModel; pub mod device_request_model; pub use self::device_request_model::DeviceRequestModel; pub mod device_response_model; @@ -304,14 +308,14 @@ pub mod organization_domain_sso_details_request_model; pub use self::organization_domain_sso_details_request_model::OrganizationDomainSsoDetailsRequestModel; pub mod organization_domain_sso_details_response_model; pub use self::organization_domain_sso_details_response_model::OrganizationDomainSsoDetailsResponseModel; -pub mod organization_enroll_secrets_manager_request_model; -pub use self::organization_enroll_secrets_manager_request_model::OrganizationEnrollSecretsManagerRequestModel; pub mod organization_keys_request_model; pub use self::organization_keys_request_model::OrganizationKeysRequestModel; pub mod organization_keys_response_model; pub use self::organization_keys_response_model::OrganizationKeysResponseModel; pub mod organization_license; pub use self::organization_license::OrganizationLicense; +pub mod organization_public_key_response_model; +pub use self::organization_public_key_response_model::OrganizationPublicKeyResponseModel; pub mod organization_response_model; pub use self::organization_response_model::OrganizationResponseModel; pub mod organization_seat_request_model; @@ -386,6 +390,8 @@ pub mod organization_user_user_details_response_model_list_response_model; pub use self::organization_user_user_details_response_model_list_response_model::OrganizationUserUserDetailsResponseModelListResponseModel; pub mod organization_verify_bank_request_model; pub use self::organization_verify_bank_request_model::OrganizationVerifyBankRequestModel; +pub mod other_device_keys_update_request_model; +pub use self::other_device_keys_update_request_model::OtherDeviceKeysUpdateRequestModel; pub mod password_hint_request_model; pub use self::password_hint_request_model::PasswordHintRequestModel; pub mod password_request_model; @@ -448,6 +454,8 @@ pub mod project_response_model_list_response_model; pub use self::project_response_model_list_response_model::ProjectResponseModelListResponseModel; pub mod project_update_request_model; pub use self::project_update_request_model::ProjectUpdateRequestModel; +pub mod protected_device_response_model; +pub use self::protected_device_response_model::ProtectedDeviceResponseModel; pub mod provider_organization_add_request_model; pub use self::provider_organization_add_request_model::ProviderOrganizationAddRequestModel; pub mod provider_organization_create_request_model; @@ -536,6 +544,8 @@ pub mod secret_with_projects_inner_project; pub use self::secret_with_projects_inner_project::SecretWithProjectsInnerProject; pub mod secret_with_projects_list_response_model; pub use self::secret_with_projects_list_response_model::SecretWithProjectsListResponseModel; +pub mod secrets_manager_subscribe_request_model; +pub use self::secrets_manager_subscribe_request_model::SecretsManagerSubscribeRequestModel; pub mod secrets_manager_subscription_update_request_model; pub use self::secrets_manager_subscription_update_request_model::SecretsManagerSubscriptionUpdateRequestModel; pub mod secrets_with_projects_inner_secret; @@ -578,8 +588,10 @@ pub mod service_account_project_access_policy_response_model_list_response_model pub use self::service_account_project_access_policy_response_model_list_response_model::ServiceAccountProjectAccessPolicyResponseModelListResponseModel; pub mod service_account_response_model; pub use self::service_account_response_model::ServiceAccountResponseModel; -pub mod service_account_response_model_list_response_model; -pub use self::service_account_response_model_list_response_model::ServiceAccountResponseModelListResponseModel; +pub mod service_account_secrets_details_response_model; +pub use self::service_account_secrets_details_response_model::ServiceAccountSecretsDetailsResponseModel; +pub mod service_account_secrets_details_response_model_list_response_model; +pub use self::service_account_secrets_details_response_model_list_response_model::ServiceAccountSecretsDetailsResponseModelListResponseModel; pub mod service_account_update_request_model; pub use self::service_account_update_request_model::ServiceAccountUpdateRequestModel; pub mod set_key_connector_key_request_model; @@ -644,6 +656,8 @@ pub mod two_factor_yubi_key_response_model; pub use self::two_factor_yubi_key_response_model::TwoFactorYubiKeyResponseModel; pub mod update_avatar_request_model; pub use self::update_avatar_request_model::UpdateAvatarRequestModel; +pub mod update_devices_trust_request_model; +pub use self::update_devices_trust_request_model::UpdateDevicesTrustRequestModel; pub mod update_domains_request_model; pub use self::update_domains_request_model::UpdateDomainsRequestModel; pub mod update_key_request_model; diff --git a/crates/bitwarden-api-api/src/models/organization_public_key_response_model.rs b/crates/bitwarden-api-api/src/models/organization_public_key_response_model.rs new file mode 100644 index 000000000..c5849d3e6 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/organization_public_key_response_model.rs @@ -0,0 +1,26 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +pub struct OrganizationPublicKeyResponseModel { + #[serde(rename = "object", skip_serializing_if = "Option::is_none")] + pub object: Option, + #[serde(rename = "publicKey", skip_serializing_if = "Option::is_none")] + pub public_key: Option, +} + +impl OrganizationPublicKeyResponseModel { + pub fn new() -> OrganizationPublicKeyResponseModel { + OrganizationPublicKeyResponseModel { + object: None, + public_key: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/organization_subscription_response_model.rs b/crates/bitwarden-api-api/src/models/organization_subscription_response_model.rs index c3789f17a..b08184d43 100644 --- a/crates/bitwarden-api-api/src/models/organization_subscription_response_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_subscription_response_model.rs @@ -102,6 +102,8 @@ pub struct OrganizationSubscriptionResponseModel { pub storage_name: Option, #[serde(rename = "storageGb", skip_serializing_if = "Option::is_none")] pub storage_gb: Option, + #[serde(rename = "discount", skip_serializing_if = "Option::is_none")] + pub discount: Option>, #[serde(rename = "subscription", skip_serializing_if = "Option::is_none")] pub subscription: Option>, #[serde(rename = "upcomingInvoice", skip_serializing_if = "Option::is_none")] @@ -162,6 +164,7 @@ impl OrganizationSubscriptionResponseModel { max_autoscale_sm_service_accounts: None, storage_name: None, storage_gb: None, + discount: None, subscription: None, upcoming_invoice: None, expiration_without_grace_period: None, diff --git a/crates/bitwarden-api-api/src/models/organization_user_reset_password_enrollment_request_model.rs b/crates/bitwarden-api-api/src/models/organization_user_reset_password_enrollment_request_model.rs index c21020f96..7287c58a3 100644 --- a/crates/bitwarden-api-api/src/models/organization_user_reset_password_enrollment_request_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_user_reset_password_enrollment_request_model.rs @@ -10,17 +10,6 @@ #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] pub struct OrganizationUserResetPasswordEnrollmentRequestModel { - #[serde(rename = "masterPasswordHash", skip_serializing_if = "Option::is_none")] - pub master_password_hash: Option, - #[serde(rename = "otp", skip_serializing_if = "Option::is_none")] - pub otp: Option, - #[serde( - rename = "authRequestAccessCode", - skip_serializing_if = "Option::is_none" - )] - pub auth_request_access_code: Option, - #[serde(rename = "secret", skip_serializing_if = "Option::is_none")] - pub secret: Option, #[serde(rename = "resetPasswordKey", skip_serializing_if = "Option::is_none")] pub reset_password_key: Option, } @@ -28,10 +17,6 @@ pub struct OrganizationUserResetPasswordEnrollmentRequestModel { impl OrganizationUserResetPasswordEnrollmentRequestModel { pub fn new() -> OrganizationUserResetPasswordEnrollmentRequestModel { OrganizationUserResetPasswordEnrollmentRequestModel { - master_password_hash: None, - otp: None, - auth_request_access_code: None, - secret: None, reset_password_key: None, } } diff --git a/crates/bitwarden-api-api/src/models/other_device_keys_update_request_model.rs b/crates/bitwarden-api-api/src/models/other_device_keys_update_request_model.rs new file mode 100644 index 000000000..c163a4343 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/other_device_keys_update_request_model.rs @@ -0,0 +1,33 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +pub struct OtherDeviceKeysUpdateRequestModel { + #[serde(rename = "encryptedPublicKey")] + pub encrypted_public_key: String, + #[serde(rename = "encryptedUserKey")] + pub encrypted_user_key: String, + #[serde(rename = "deviceId")] + pub device_id: uuid::Uuid, +} + +impl OtherDeviceKeysUpdateRequestModel { + pub fn new( + encrypted_public_key: String, + encrypted_user_key: String, + device_id: uuid::Uuid, + ) -> OtherDeviceKeysUpdateRequestModel { + OtherDeviceKeysUpdateRequestModel { + encrypted_public_key, + encrypted_user_key, + device_id, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/protected_device_response_model.rs b/crates/bitwarden-api-api/src/models/protected_device_response_model.rs new file mode 100644 index 000000000..f34e1128d --- /dev/null +++ b/crates/bitwarden-api-api/src/models/protected_device_response_model.rs @@ -0,0 +1,44 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +pub struct ProtectedDeviceResponseModel { + #[serde(rename = "object", skip_serializing_if = "Option::is_none")] + pub object: Option, + #[serde(rename = "id", skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(rename = "name", skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(rename = "type", skip_serializing_if = "Option::is_none")] + pub r#type: Option, + #[serde(rename = "identifier", skip_serializing_if = "Option::is_none")] + pub identifier: Option, + #[serde(rename = "creationDate", skip_serializing_if = "Option::is_none")] + pub creation_date: Option, + #[serde(rename = "encryptedUserKey", skip_serializing_if = "Option::is_none")] + pub encrypted_user_key: Option, + #[serde(rename = "encryptedPublicKey", skip_serializing_if = "Option::is_none")] + pub encrypted_public_key: Option, +} + +impl ProtectedDeviceResponseModel { + pub fn new() -> ProtectedDeviceResponseModel { + ProtectedDeviceResponseModel { + object: None, + id: None, + name: None, + r#type: None, + identifier: None, + creation_date: None, + encrypted_user_key: None, + encrypted_public_key: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/secrets_manager_subscribe_request_model.rs b/crates/bitwarden-api-api/src/models/secrets_manager_subscribe_request_model.rs new file mode 100644 index 000000000..2510d8f2c --- /dev/null +++ b/crates/bitwarden-api-api/src/models/secrets_manager_subscribe_request_model.rs @@ -0,0 +1,29 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +pub struct SecretsManagerSubscribeRequestModel { + #[serde(rename = "additionalSmSeats")] + pub additional_sm_seats: i32, + #[serde(rename = "additionalServiceAccounts")] + pub additional_service_accounts: i32, +} + +impl SecretsManagerSubscribeRequestModel { + pub fn new( + additional_sm_seats: i32, + additional_service_accounts: i32, + ) -> SecretsManagerSubscribeRequestModel { + SecretsManagerSubscribeRequestModel { + additional_sm_seats, + additional_service_accounts, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/service_account_secrets_details_response_model.rs b/crates/bitwarden-api-api/src/models/service_account_secrets_details_response_model.rs new file mode 100644 index 000000000..5be4d8bb1 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/service_account_secrets_details_response_model.rs @@ -0,0 +1,41 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +pub struct ServiceAccountSecretsDetailsResponseModel { + #[serde(rename = "object", skip_serializing_if = "Option::is_none")] + pub object: Option, + #[serde(rename = "id", skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(rename = "organizationId", skip_serializing_if = "Option::is_none")] + pub organization_id: Option, + #[serde(rename = "name", skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(rename = "creationDate", skip_serializing_if = "Option::is_none")] + pub creation_date: Option, + #[serde(rename = "revisionDate", skip_serializing_if = "Option::is_none")] + pub revision_date: Option, + #[serde(rename = "accessToSecrets", skip_serializing_if = "Option::is_none")] + pub access_to_secrets: Option, +} + +impl ServiceAccountSecretsDetailsResponseModel { + pub fn new() -> ServiceAccountSecretsDetailsResponseModel { + ServiceAccountSecretsDetailsResponseModel { + object: None, + id: None, + organization_id: None, + name: None, + creation_date: None, + revision_date: None, + access_to_secrets: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/service_account_secrets_details_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/service_account_secrets_details_response_model_list_response_model.rs new file mode 100644 index 000000000..5edfbefba --- /dev/null +++ b/crates/bitwarden-api-api/src/models/service_account_secrets_details_response_model_list_response_model.rs @@ -0,0 +1,29 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +pub struct ServiceAccountSecretsDetailsResponseModelListResponseModel { + #[serde(rename = "object", skip_serializing_if = "Option::is_none")] + pub object: Option, + #[serde(rename = "data", skip_serializing_if = "Option::is_none")] + pub data: Option>, + #[serde(rename = "continuationToken", skip_serializing_if = "Option::is_none")] + pub continuation_token: Option, +} + +impl ServiceAccountSecretsDetailsResponseModelListResponseModel { + pub fn new() -> ServiceAccountSecretsDetailsResponseModelListResponseModel { + ServiceAccountSecretsDetailsResponseModelListResponseModel { + object: None, + data: None, + continuation_token: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/subscription_response_model.rs b/crates/bitwarden-api-api/src/models/subscription_response_model.rs index 2ebba2a44..c21cbdbea 100644 --- a/crates/bitwarden-api-api/src/models/subscription_response_model.rs +++ b/crates/bitwarden-api-api/src/models/subscription_response_model.rs @@ -22,6 +22,8 @@ pub struct SubscriptionResponseModel { pub upcoming_invoice: Option>, #[serde(rename = "subscription", skip_serializing_if = "Option::is_none")] pub subscription: Option>, + #[serde(rename = "discount", skip_serializing_if = "Option::is_none")] + pub discount: Option>, #[serde(rename = "license", skip_serializing_if = "Option::is_none")] pub license: Option>, #[serde(rename = "expiration", skip_serializing_if = "Option::is_none")] @@ -39,6 +41,7 @@ impl SubscriptionResponseModel { max_storage_gb: None, upcoming_invoice: None, subscription: None, + discount: None, license: None, expiration: None, using_in_app_purchase: None, diff --git a/crates/bitwarden-api-api/src/models/update_devices_trust_request_model.rs b/crates/bitwarden-api-api/src/models/update_devices_trust_request_model.rs new file mode 100644 index 000000000..e4d381d51 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/update_devices_trust_request_model.rs @@ -0,0 +1,43 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +pub struct UpdateDevicesTrustRequestModel { + #[serde(rename = "masterPasswordHash", skip_serializing_if = "Option::is_none")] + pub master_password_hash: Option, + #[serde(rename = "otp", skip_serializing_if = "Option::is_none")] + pub otp: Option, + #[serde( + rename = "authRequestAccessCode", + skip_serializing_if = "Option::is_none" + )] + pub auth_request_access_code: Option, + #[serde(rename = "secret", skip_serializing_if = "Option::is_none")] + pub secret: Option, + #[serde(rename = "currentDevice")] + pub current_device: Box, + #[serde(rename = "otherDevices", skip_serializing_if = "Option::is_none")] + pub other_devices: Option>, +} + +impl UpdateDevicesTrustRequestModel { + pub fn new( + current_device: crate::models::DeviceKeysUpdateRequestModel, + ) -> UpdateDevicesTrustRequestModel { + UpdateDevicesTrustRequestModel { + master_password_hash: None, + otp: None, + auth_request_access_code: None, + secret: None, + current_device: Box::new(current_device), + other_devices: None, + } + } +} From b51a5d6b522906d277895ee3eb8e893e80744d1d Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 30 Nov 2023 19:00:54 +0100 Subject: [PATCH 046/157] [PM-4272] Expose fingerprint to uniffi (#372) --- crates/bitwarden-uniffi/src/docs.rs | 4 +++ crates/bitwarden-uniffi/src/lib.rs | 6 ++++ crates/bitwarden-uniffi/src/platform/mod.rs | 16 +++++++++ crates/bitwarden/src/client/client.rs | 2 +- .../src/platform/generate_fingerprint.rs | 1 + languages/kotlin/doc.md | 33 +++++++++++++++++++ support/docs/docs.ts | 1 + 7 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 crates/bitwarden-uniffi/src/platform/mod.rs diff --git a/crates/bitwarden-uniffi/src/docs.rs b/crates/bitwarden-uniffi/src/docs.rs index 45fabb0b7..d66c78002 100644 --- a/crates/bitwarden-uniffi/src/docs.rs +++ b/crates/bitwarden-uniffi/src/docs.rs @@ -2,6 +2,7 @@ use bitwarden::{ auth::password::MasterPasswordPolicyOptions, client::kdf::Kdf, mobile::crypto::{InitOrgCryptoRequest, InitUserCryptoRequest}, + platform::FingerprintRequest, tool::{ExportFormat, PassphraseGeneratorRequest, PasswordGeneratorRequest}, vault::{ Cipher, CipherView, Collection, Folder, FolderView, Send, SendListView, SendView, @@ -34,6 +35,9 @@ pub enum DocRef { // Exporters ExportFormat(ExportFormat), + // Platform + FingerprintRequest(FingerprintRequest), + // Auth MasterPasswordPolicyOptions(MasterPasswordPolicyOptions), diff --git a/crates/bitwarden-uniffi/src/lib.rs b/crates/bitwarden-uniffi/src/lib.rs index 5035f22b9..13253d9a6 100644 --- a/crates/bitwarden-uniffi/src/lib.rs +++ b/crates/bitwarden-uniffi/src/lib.rs @@ -9,6 +9,7 @@ use bitwarden::client::client_settings::ClientSettings; pub mod auth; pub mod crypto; mod error; +pub mod platform; pub mod tool; mod uniffi_support; pub mod vault; @@ -18,6 +19,7 @@ pub mod docs; use crypto::ClientCrypto; use error::Result; +use platform::ClientPlatform; use tool::ClientGenerators; use vault::ClientVault; @@ -42,6 +44,10 @@ impl Client { Arc::new(ClientVault(self)) } + pub fn platform(self: Arc) -> Arc { + Arc::new(ClientPlatform(self)) + } + /// Generator operations pub fn generators(self: Arc) -> Arc { Arc::new(ClientGenerators(self)) diff --git a/crates/bitwarden-uniffi/src/platform/mod.rs b/crates/bitwarden-uniffi/src/platform/mod.rs new file mode 100644 index 000000000..f4eaaa095 --- /dev/null +++ b/crates/bitwarden-uniffi/src/platform/mod.rs @@ -0,0 +1,16 @@ +use std::sync::Arc; + +use bitwarden::platform::FingerprintRequest; + +use crate::{error::Result, Client}; + +#[derive(uniffi::Object)] +pub struct ClientPlatform(pub(crate) Arc); + +#[uniffi::export] +impl ClientPlatform { + /// Fingerprint + pub async fn fingerprint(&self, req: FingerprintRequest) -> Result { + Ok(self.0 .0.read().await.fingerprint(&req)?.fingerprint) + } +} diff --git a/crates/bitwarden/src/client/client.rs b/crates/bitwarden/src/client/client.rs index 960ca9bda..5133a5c01 100644 --- a/crates/bitwarden/src/client/client.rs +++ b/crates/bitwarden/src/client/client.rs @@ -262,7 +262,7 @@ impl Client { } #[cfg(feature = "internal")] - pub fn fingerprint(&mut self, input: &FingerprintRequest) -> Result { + pub fn fingerprint(&self, input: &FingerprintRequest) -> Result { generate_fingerprint(input) } } diff --git a/crates/bitwarden/src/platform/generate_fingerprint.rs b/crates/bitwarden/src/platform/generate_fingerprint.rs index 800d6e847..e9562ab34 100644 --- a/crates/bitwarden/src/platform/generate_fingerprint.rs +++ b/crates/bitwarden/src/platform/generate_fingerprint.rs @@ -7,6 +7,7 @@ use crate::{crypto::fingerprint, error::Result, util::BASE64_ENGINE}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct FingerprintRequest { /// The input material, used in the fingerprint generation process. pub fingerprint_material: String, diff --git a/languages/kotlin/doc.md b/languages/kotlin/doc.md index fd65c7b71..652e09813 100644 --- a/languages/kotlin/doc.md +++ b/languages/kotlin/doc.md @@ -327,6 +327,19 @@ Decrypt password history **Output**: std::result::Result +## ClientPlatform + +### `fingerprint` + +Fingerprint + +**Arguments**: + +- self: +- req: [FingerprintRequest](#fingerprintrequest) + +**Output**: std::result::Result + ## ClientSends ### `encrypt` @@ -818,6 +831,26 @@ implementations. +## `FingerprintRequest` + + + + + + + + + + + + + + + + + +
KeyTypeDescription
fingerprintMaterialstringThe input material, used in the fingerprint generation process.
publicKeystringThe user's public key encoded with base64.
+ ## `Folder` diff --git a/support/docs/docs.ts b/support/docs/docs.ts index 14603dd57..b547b4502 100644 --- a/support/docs/docs.ts +++ b/support/docs/docs.ts @@ -28,6 +28,7 @@ const rootElements = [ "ClientFolders", "ClientGenerators", "ClientPasswordHistory", + "ClientPlatform", "ClientSends", "ClientVault", ]; From b6c6532c441990e1c87bd9817449b1c128154377 Mon Sep 17 00:00:00 2001 From: Milos Trifunovic Date: Thu, 30 Nov 2023 19:36:04 +0100 Subject: [PATCH 047/157] Ruby SDK (#245) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Type of change ``` - [ ] Bug fix - [ x] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Ruby wrapper for Bitwarden SDK. ## Code changes First version which introduces basic functionality to interact with projects and clients. --------- Co-authored-by: Milos Trifunovic Co-authored-by: Miloő Trifunović <84377717+milost77@users.noreply.github.com> Co-authored-by: Oscar Hinton Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com> Co-authored-by: Daniel García --- .github/workflows/generate_schemas.yml | 7 + .github/workflows/publish-ruby.yml | 88 +++++++++++++ languages/ruby/.gitignore | 3 + languages/ruby/CHANGELOG.md | 5 + languages/ruby/README.md | 95 ++++++++++++++ languages/ruby/bitwarden_sdk/Rakefile | 8 ++ .../ruby/bitwarden_sdk/bitwarden-sdk.gemspec | 47 +++++++ .../ruby/bitwarden_sdk/lib/bitwarden-sdk.rb | 56 ++++++++ .../ruby/bitwarden_sdk/lib/bitwarden_error.rb | 9 ++ .../ruby/bitwarden_sdk/lib/bitwarden_lib.rb | 35 +++++ .../ruby/bitwarden_sdk/lib/command_runner.rb | 15 +++ .../lib/extended_schemas/schemas.rb | 64 +++++++++ languages/ruby/bitwarden_sdk/lib/projects.rb | 116 ++++++++++++++++ languages/ruby/bitwarden_sdk/lib/secrets.rb | 124 ++++++++++++++++++ languages/ruby/bitwarden_sdk/lib/version.rb | 5 + .../ruby/bitwarden_sdk/sig/bitwarden-sdk.rbs | 13 ++ .../bitwarden_sdk/sig/bitwarden_settings.rbs | 8 ++ .../ruby/bitwarden_sdk/sig/command_runner.rbs | 4 + .../bitwarden_sdk/sig/projects_client.rbs | 17 +++ languages/ruby/bitwarden_sdk/sig/sdk.rbs | 3 + .../ruby/bitwarden_sdk/sig/secrets_client.rbs | 18 +++ languages/ruby/examples/example.rb | 67 ++++++++++ sig/bitwarden_sdk/bitwarden_client.rbs | 5 + support/scripts/schemas.ts | 10 ++ 24 files changed, 822 insertions(+) create mode 100644 .github/workflows/publish-ruby.yml create mode 100644 languages/ruby/.gitignore create mode 100644 languages/ruby/CHANGELOG.md create mode 100644 languages/ruby/README.md create mode 100644 languages/ruby/bitwarden_sdk/Rakefile create mode 100644 languages/ruby/bitwarden_sdk/bitwarden-sdk.gemspec create mode 100644 languages/ruby/bitwarden_sdk/lib/bitwarden-sdk.rb create mode 100644 languages/ruby/bitwarden_sdk/lib/bitwarden_error.rb create mode 100644 languages/ruby/bitwarden_sdk/lib/bitwarden_lib.rb create mode 100644 languages/ruby/bitwarden_sdk/lib/command_runner.rb create mode 100644 languages/ruby/bitwarden_sdk/lib/extended_schemas/schemas.rb create mode 100644 languages/ruby/bitwarden_sdk/lib/projects.rb create mode 100644 languages/ruby/bitwarden_sdk/lib/secrets.rb create mode 100644 languages/ruby/bitwarden_sdk/lib/version.rb create mode 100644 languages/ruby/bitwarden_sdk/sig/bitwarden-sdk.rbs create mode 100644 languages/ruby/bitwarden_sdk/sig/bitwarden_settings.rbs create mode 100644 languages/ruby/bitwarden_sdk/sig/command_runner.rbs create mode 100644 languages/ruby/bitwarden_sdk/sig/projects_client.rbs create mode 100644 languages/ruby/bitwarden_sdk/sig/sdk.rbs create mode 100644 languages/ruby/bitwarden_sdk/sig/secrets_client.rbs create mode 100644 languages/ruby/examples/example.rb create mode 100644 sig/bitwarden_sdk/bitwarden_client.rbs diff --git a/.github/workflows/generate_schemas.yml b/.github/workflows/generate_schemas.yml index 7eba684c4..b0517ea36 100644 --- a/.github/workflows/generate_schemas.yml +++ b/.github/workflows/generate_schemas.yml @@ -57,6 +57,13 @@ jobs: path: ${{ github.workspace }}/languages/python/BitwardenClient/schemas.py if-no-files-found: error + - name: Upload ruby schemas artifact + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + with: + name: schemas.rb + path: ${{ github.workspace }}/languages/ruby/bitwarden_sdk/lib/schemas.rb + if-no-files-found: error + - name: Upload json schemas artifact uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 with: diff --git a/.github/workflows/publish-ruby.yml b/.github/workflows/publish-ruby.yml new file mode 100644 index 000000000..da641dce5 --- /dev/null +++ b/.github/workflows/publish-ruby.yml @@ -0,0 +1,88 @@ +name: Publish Ruby SDK + +on: + pull_request: + branches: + - master + +jobs: + generate_schemas: + uses: ./.github/workflows/generate_schemas.yml + + build_rust: + uses: ./.github/workflows/build-rust-cross-platform.yml + + build_ruby: + name: Build Ruby + runs-on: ubuntu-22.04 + needs: + - generate_schemas + - build_rust + steps: + - name: Checkout Repository + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + + - name: Set up Ruby + uses: ruby/setup-ruby@54a18e26dbbb1eabc604f317ade9a5788dddef81 # v1.159.0 + with: + ruby-version: 3.2 + + - name: Download Ruby schemas artifact + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + with: + name: schemas.rb + path: languages/ruby/bitwarden_sdk/lib + + - name: Download x86_64-apple-darwin files + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + with: + name: libbitwarden_c_files-x86_64-apple-darwin + path: temp/macos-x64 + + - name: Download aarch64-apple-darwin files + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + with: + name: libbitwarden_c_files-aarch64-apple-darwin + path: temp/macos-arm64 + + - name: Download x86_64-unknown-linux-gnu files + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + with: + name: libbitwarden_c_files-x86_64-unknown-linux-gnu + path: temp/ubuntu-x64 + + - name: Download x86_64-pc-windows-msvc files + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + with: + name: libbitwarden_c_files-x86_64-pc-windows-msvc + path: temp/windows-x64 + + - name: Copy lib files + run: | + mkdir -p languages/ruby/bitwarden_sdk/lib/macos-arm64 + mkdir -p languages/ruby/bitwarden_sdk/lib/ubuntu-x64 + mkdir -p languages/ruby/bitwarden_sdk/lib/macos-x64 + mkdir -p languages/ruby/bitwarden_sdk/lib/windows-x64 + + platforms=("macos-arm64" "ubuntu-x64" "macos-x64" "windows-x64") + files=("libbitwarden_c.dylib" "libbitwarden_c.so" "libbitwarden_c.dylib" "bitwarden_c.dll") + + for ((i=0; i<${#platforms[@]}; i++)); do + cp "temp/${platforms[$i]}/${files[$i]}" "languages/ruby/bitwarden_sdk/lib/${platforms[$i]}/${files[$i]}" + done + shell: bash + + - name: Build gem + run: gem build bitwarden-sdk.gemspec + working-directory: languages/ruby/bitwarden_sdk + + - name: Push gem to Rubygems + run: | + mkdir -p $HOME/.gem + touch $HOME/.gem/credentials + chmod 0600 $HOME/.gem/credentials + printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials + gem push *.gem + env: + GEM_HOST_API_KEY: ${{ secrets.GEM_HOST_API_KEY }} + working-directory: languages/ruby/bitwarden_sdk diff --git a/languages/ruby/.gitignore b/languages/ruby/.gitignore new file mode 100644 index 000000000..f0e7bd4c1 --- /dev/null +++ b/languages/ruby/.gitignore @@ -0,0 +1,3 @@ +*.lock +*.gem +bitwarden_sdk/lib/schemas.rb diff --git a/languages/ruby/CHANGELOG.md b/languages/ruby/CHANGELOG.md new file mode 100644 index 000000000..91c5ee3c2 --- /dev/null +++ b/languages/ruby/CHANGELOG.md @@ -0,0 +1,5 @@ +## [Unreleased] + +## [0.1.0] - 2023-09-19 + +- Initial release diff --git a/languages/ruby/README.md b/languages/ruby/README.md new file mode 100644 index 000000000..d02d5c500 --- /dev/null +++ b/languages/ruby/README.md @@ -0,0 +1,95 @@ +# Bitwarden Secrets Manager SDK + +Ruby bindings for interacting with the [Bitwarden Secrets Manager]. This is a beta release and might be missing some functionality. + +## Installation + +Requirements: Ruby >= 3.0 + +Install gem: `gem install bitwarden-sdk` + +Import it: require 'bitwarden-sdk' + + +## Usage + +To interact with client first you need to obtain access token from Bitwarden. +Client will be initialized with default client settings if they are not provided +via env variables. + +```ruby +require 'bitwarden-sdk' + +# then you can initialize BitwardenSettings: +bitwarden_settings = BitwardenSDK::BitwardenSettings.new( + 'https://api.bitwarden.com', + 'https://identity.bitwarden.com' +) + +# By passing these setting you can initialize BitwardenClient + +bw_client = BitwardenSDK::BitwardenClient.new(bitwarden_settings) +response = bw_client.access_token_login(token) +puts response +``` + +After successful authorization you can interact with client to manage your projects and secrets. +```ruby + +# CREATE project +project_name = 'Test project 1' +response = bw_client.project_client.create_project(project_name, organization_id) +puts response +project_id = response['id'] + +# GET project +response = bw_client.project_client.get(project_id) +puts response + +# LIST projects +response = bw_client.project_client.list_projects(organization_id) +puts response + +# UPDATE projects +name = 'Updated test project 1' +response = bw_client.project_client.update_project(project_id, name, organization_id) +puts response + +# DELETE project +response = bw_client.project_client.delete_projects([project_id]) +puts response +``` + +Similarly, you interact with secrets: +```ruby +# CREATE secret +key = 'AWS-SES' +note = 'Private account' +value = '8t27.dfj;' +response = bw_client.secrets_client.create(key, note, organization_id, [project_id], value) +puts response +secret_id = response['id'] + +# GET secret +response = bw_client.secrets_client.get(secret_id) +puts response + +# GET secret by ids +response = bw_client.secrets_client.get_by_ids([secret_id]) +puts response + +# LIST secrets +response = bw_client.secrets_client.list(organization_id) +puts response + +# UPDATE secret +note = 'updated password' +value = '7I.ert10AjK' +response = bw_client.secrets_client.update(secret_id, key, note,organization_id, [project_id], value) +puts response + +# DELETE secret +response = bw_client.secrets_client.delete_secret([secret_id]) +puts response +``` +[Bitwarden Secrets Manager]: https://bitwarden.com/products/secrets-manager/ diff --git a/languages/ruby/bitwarden_sdk/Rakefile b/languages/ruby/bitwarden_sdk/Rakefile new file mode 100644 index 000000000..192414345 --- /dev/null +++ b/languages/ruby/bitwarden_sdk/Rakefile @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require "bundler/gem_tasks" +require "rubocop/rake_task" + +RuboCop::RakeTask.new + +task default: :rubocop diff --git a/languages/ruby/bitwarden_sdk/bitwarden-sdk.gemspec b/languages/ruby/bitwarden_sdk/bitwarden-sdk.gemspec new file mode 100644 index 000000000..12a4f5fdc --- /dev/null +++ b/languages/ruby/bitwarden_sdk/bitwarden-sdk.gemspec @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require_relative 'lib/version' + +Gem::Specification.new do |spec| + spec.name = 'bitwarden-sdk' + spec.version = BitwardenSDK::VERSION + spec.authors = ['Bitwarden Inc.'] + spec.email = ['hello@bitwarden_sdk.com'] + + spec.summary = 'Bitwarden Secrets Manager SDK.' + spec.description = 'Ruby wrapper for Bitwarden secrets manager SDK.' + spec.homepage = 'https://bitwarden.com/products/secrets-manager/' + spec.required_ruby_version = '>= 3.0.0' + + spec.metadata['homepage_uri'] = spec.homepage + spec.metadata['source_code_uri'] = 'https://github.com/bitwarden/sdk' + spec.metadata['changelog_uri'] = 'https://github.com/bitwarden/sdk/blob/master/languages/ruby/CHANGELOG.md' + + # Specify which files should be added to the gem when it is released. + # The `git ls-files -z` loads the files in the RubyGem that have been added into git. + spec.files = Dir.chdir(__dir__) do + `git ls-files -z`.split("\x0").reject do |f| + (File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor]) + end + end + + spec.files += Dir.glob('lib/ubuntu-x64/**/*') + spec.files += Dir.glob('lib/macos-x64/**/*') + spec.files += Dir.glob('lib/windows-x64/**/*') + spec.files += Dir.glob('lib/macos-arm64/**/*') + spec.files += Dir.glob('lib/schemas.rb') + + spec.bindir = 'exe' + spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } + spec.require_paths = ['lib'] + + # Uncomment to register a new dependency of your gem + # spec.add_dependency "example-gem", "~> 1.0" + spec.add_dependency 'dry-struct', '~> 1.6' + spec.add_dependency 'dry-types', '~> 1.7' + spec.add_dependency 'ffi', '~> 1.15' + spec.add_dependency 'json', '~> 2.6' + spec.add_dependency 'rake', '~> 13.0' + spec.add_dependency 'rubocop', '~> 1.21' + +end diff --git a/languages/ruby/bitwarden_sdk/lib/bitwarden-sdk.rb b/languages/ruby/bitwarden_sdk/lib/bitwarden-sdk.rb new file mode 100644 index 000000000..c2ce7e576 --- /dev/null +++ b/languages/ruby/bitwarden_sdk/lib/bitwarden-sdk.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require 'json' +require 'dry-types' + +require_relative 'schemas' +require_relative 'extended_schemas/schemas' +require_relative 'command_runner' +require_relative 'bitwarden_lib' +require_relative 'bitwarden_error' +require_relative 'projects' +require_relative 'secrets' + +module BitwardenSDK + class BitwardenSettings + attr_accessor :api_url, :identity_url + + def initialize(api_url, identity_url) + # if api_url.nil? || identity_url.nil? + # raise ArgumentError, "api_url and identity_url cannot be nil" + # end + + @api_url = api_url + @identity_url = identity_url + end + end + + class BitwardenClient + attr_reader :bitwarden, :project_client, :secrets_client + + def initialize(bitwarden_settings) + client_settings = ClientSettings.new( + api_url: bitwarden_settings.api_url, + identity_url: bitwarden_settings.identity_url, + user_agent: 'Bitwarden RUBY-SDK', + device_type: nil + ) + + @bitwarden = BitwardenLib + @handle = @bitwarden.init(client_settings.to_json) + @command_runner = CommandRunner.new(@bitwarden, @handle) + @project_client = ProjectsClient.new(@command_runner) + @secrets_client = SecretsClient.new(@command_runner) + end + + def access_token_login(access_token) + access_token_request = AccessTokenLoginRequest.new(access_token: access_token) + @command_runner.run(SelectiveCommand.new(access_token_login: access_token_request)) + nil + end + + def free_mem + @bitwarden.free_mem(@handle) + end + end +end diff --git a/languages/ruby/bitwarden_sdk/lib/bitwarden_error.rb b/languages/ruby/bitwarden_sdk/lib/bitwarden_error.rb new file mode 100644 index 000000000..ec9adf932 --- /dev/null +++ b/languages/ruby/bitwarden_sdk/lib/bitwarden_error.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module BitwardenSDK + class BitwardenError < StandardError + def initialize(message = 'Error getting response') + super(message) + end + end +end diff --git a/languages/ruby/bitwarden_sdk/lib/bitwarden_lib.rb b/languages/ruby/bitwarden_sdk/lib/bitwarden_lib.rb new file mode 100644 index 000000000..e09541788 --- /dev/null +++ b/languages/ruby/bitwarden_sdk/lib/bitwarden_lib.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'ffi' + +module BitwardenSDK + module BitwardenLib + extend FFI::Library + + def self.mac_with_intel? + `uname -m`.strip == 'x86_64' + end + + ffi_lib case RUBY_PLATFORM + when /darwin/ + local_file = if mac_with_intel? + File.expand_path('macos-x64/libbitwarden_c.dylib', __dir__) + else + File.expand_path('macos-arm64/libbitwarden_c.dylib', __dir__) + end + File.exist?(local_file) ? local_file : File.expand_path('../../../../target/debug/libbitwarden_c.dylib', __dir__) + when /linux/ + local_file = File.expand_path('ubuntu-x64/libbitwarden_c.so', __dir__) + File.exist?(local_file) ? local_file : File.expand_path('../../../../target/debug/libbitwarden_c.so', __dir__) + when /mswin|mingw/ + local_file = File.expand_path('windows-x64/bitwarden_c.dll', __dir__) + File.exist?(local_file) ? local_file : File.expand_path('../../../../target/debug/bitwarden_c.dll', __dir__) + else + raise "Unsupported platform: #{RUBY_PLATFORM}" + end + + attach_function :init, [:string], :pointer + attach_function :run_command, %i[string pointer], :string + attach_function :free_mem, [:pointer], :void + end +end diff --git a/languages/ruby/bitwarden_sdk/lib/command_runner.rb b/languages/ruby/bitwarden_sdk/lib/command_runner.rb new file mode 100644 index 000000000..7da2269b6 --- /dev/null +++ b/languages/ruby/bitwarden_sdk/lib/command_runner.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module BitwardenSDK + class CommandRunner + def initialize(bitwarden_sdk, handle) + @bitwarden_sdk = bitwarden_sdk + @handle = handle + end + + # @param [Dry-Struct] cmd + def run(cmd) + @bitwarden_sdk.run_command(cmd.to_json, @handle) + end + end +end diff --git a/languages/ruby/bitwarden_sdk/lib/extended_schemas/schemas.rb b/languages/ruby/bitwarden_sdk/lib/extended_schemas/schemas.rb new file mode 100644 index 000000000..be7ca2cbc --- /dev/null +++ b/languages/ruby/bitwarden_sdk/lib/extended_schemas/schemas.rb @@ -0,0 +1,64 @@ + +module BitwardenSDK + class SelectiveCommand < Command + attribute :password_login, PasswordLoginRequest.optional.default(nil) + attribute :api_key_login, APIKeyLoginRequest.optional.default(nil) + attribute :access_token_login, AccessTokenLoginRequest.optional.default(nil) + attribute :get_user_api_key, SecretVerificationRequest.optional.default(nil) + attribute :fingerprint, FingerprintRequest.optional.default(nil) + attribute :sync, SyncRequest.optional.default(nil) + attribute :secrets, SecretsCommand.optional.default(nil) + attribute :projects, ProjectsCommand.optional.default(nil) + + def to_dynamic + { + "passwordLogin" => password_login&.to_dynamic, + "apiKeyLogin" => api_key_login&.to_dynamic, + "accessTokenLogin" => access_token_login&.to_dynamic, + "getUserApiKey" => get_user_api_key&.to_dynamic, + "fingerprint" => fingerprint&.to_dynamic, + "sync" => sync&.to_dynamic, + "secrets" => secrets&.to_dynamic, + "projects" => projects&.to_dynamic, + }.compact + end + end + + class SelectiveProjectsCommand < ProjectsCommand + attribute :get, ProjectGetRequest.optional.default(nil) + attribute :create, ProjectCreateRequest.optional.default(nil) + attribute :list, ProjectsListRequest.optional.default(nil) + attribute :update, ProjectPutRequest.optional.default(nil) + attribute :delete, ProjectsDeleteRequest.optional.default(nil) + + def to_dynamic + { + "get" => get&.to_dynamic, + "create" => create&.to_dynamic, + "list" => list&.to_dynamic, + "update" => update&.to_dynamic, + "delete" => delete&.to_dynamic, + }.compact + end + end + + class SelectiveSecretsCommand < SecretsCommand + attribute :get, SecretGetRequest.optional.default(nil) + attribute :get_by_ids, SecretsGetRequest.optional.default(nil) + attribute :create, SecretCreateRequest.optional.default(nil) + attribute :list, SecretIdentifiersRequest.optional.default(nil) + attribute :update, SecretPutRequest.optional.default(nil) + attribute :delete, SecretsDeleteRequest.optional.default(nil) + + def to_dynamic + { + "get" => get&.to_dynamic, + "getByIds" => get_by_ids&.to_dynamic, + "create" => create&.to_dynamic, + "list" => list&.to_dynamic, + "update" => update&.to_dynamic, + "delete" => delete&.to_dynamic, + }.compact + end + end +end diff --git a/languages/ruby/bitwarden_sdk/lib/projects.rb b/languages/ruby/bitwarden_sdk/lib/projects.rb new file mode 100644 index 000000000..4363fcb9e --- /dev/null +++ b/languages/ruby/bitwarden_sdk/lib/projects.rb @@ -0,0 +1,116 @@ +# frozen_string_literal: true + +require_relative 'bitwarden_error' + +module BitwardenSDK + class ProjectsClient + def initialize(command_runner) + @command_runner = command_runner + end + + def create_project(project_name, organization_id) + project_create_request = ProjectCreateRequest.new( + project_create_request_name: project_name, + organization_id: organization_id + ) + command = create_command( + create: project_create_request + ) + response = parse_response(command) + + projects_response = ResponseForProjectResponse.from_json!(response).to_dynamic + + if projects_response.key?('success') && projects_response['success'] == true && + projects_response.key?('data') + return projects_response['data'] + end + + error_response(projects_response) + end + + def get(project_id) + project_get_request = ProjectGetRequest.new(id: project_id) + command = create_command(get: project_get_request) + response = parse_response(command) + + projects_response = ResponseForProjectResponse.from_json!(response).to_dynamic + + if projects_response.key?('success') && projects_response['success'] == true && + projects_response.key?('data') + return projects_response['data'] + end + + error_response(projects_response) + end + + def list_projects(organization_id) + project_list_request = ProjectsListRequest.new(organization_id: organization_id) + command = create_command(list: project_list_request) + response = parse_response(command) + + projects_response = ResponseForProjectsResponse.from_json!(response).to_dynamic + + if projects_response.key?('success') && projects_response['success'] == true && + projects_response.key?('data') && projects_response['data'].key?('data') + return projects_response['data']['data'] + end + + error_response(projects_response) + end + + def update_project(id, project_put_request_name, organization_id) + project_put_request = ProjectPutRequest.new( + id: id, + project_put_request_name: project_put_request_name, + organization_id: organization_id + ) + command = create_command( + update: project_put_request + ) + response = parse_response(command) + + projects_response = ResponseForProjectResponse.from_json!(response).to_dynamic + + if projects_response.key?('success') && projects_response['success'] == true && + projects_response.key?('data') + return projects_response['data'] + end + + error_response(projects_response) + end + + def delete_projects(ids) + project_delete_request = ProjectsDeleteRequest.new(ids: ids) + command = create_command(delete: project_delete_request) + response = parse_response(command) + + projects_response = ResponseForProjectsDeleteResponse.from_json!(response).to_dynamic + + if projects_response.key?('success') && projects_response['success'] == true && + projects_response.key?('data') && projects_response['data'].key?('data') + return projects_response['data']['data'] + end + + error_response(projects_response) + end + + private + + def error_response(response) + raise BitwardenError, response['errorMessage'] if response.key?('errorMessage') + + raise BitwardenError, 'Error while getting response' + end + + def create_command(commands) + SelectiveCommand.new(projects: SelectiveProjectsCommand.new(commands)) + end + + def parse_response(command) + response = @command_runner.run(command) + raise BitwardenError, 'Error getting response' if response.nil? + + response + end + end +end diff --git a/languages/ruby/bitwarden_sdk/lib/secrets.rb b/languages/ruby/bitwarden_sdk/lib/secrets.rb new file mode 100644 index 000000000..0eca98993 --- /dev/null +++ b/languages/ruby/bitwarden_sdk/lib/secrets.rb @@ -0,0 +1,124 @@ +# frozen_string_literal: true + +require 'json' + +module BitwardenSDK + class SecretsClient + def initialize(command_runner) + @command_runner = command_runner + end + + def get(id) + command = create_command(get: SecretGetRequest.new(id: id)) + response = run_command(command) + + secrets_response = ResponseForSecretResponse.from_json!(response).to_dynamic + + if secrets_response.key?('success') && secrets_response['success'] == true && + secrets_response.key?('data') + return secrets_response['data'] + end + + error_response(secrets_response) + end + + def get_by_ids(ids) + command = create_command(get_by_ids: SecretsGetRequest.new(ids: ids)) + response = run_command(command) + + secrets_response = ResponseForSecretIdentifiersResponse.from_json!(response).to_dynamic + + if secrets_response.key?('success') && secrets_response['success'] == true && + secrets_response.key?('data') && secrets_response['data'].key?('data') + return secrets_response['data']['data'] + end + + error_response(secrets_response) + end + + def create(key, note, organization_id, project_ids, value) + command = create_command( + create: SecretCreateRequest.new( + key: key, note: note, organization_id: organization_id, project_ids: project_ids, value: value + ) + ) + response = run_command(command) + + secrets_response = ResponseForSecretResponse.from_json!(response).to_dynamic + + if secrets_response.key?('success') && secrets_response['success'] == true && + secrets_response.key?('data') + return secrets_response['data'] + end + + error_response(secrets_response) + end + + def list(organization_id) + command = create_command(list: SecretIdentifiersRequest.new(organization_id: organization_id)) + response = run_command(command) + + secrets_response = ResponseForSecretIdentifiersResponse.from_json!(response).to_dynamic + + if secrets_response.key?('success') && secrets_response['success'] == true && + secrets_response.key?('data') && secrets_response['data'].key?('data') + return secrets_response['data']['data'] + end + + error_response(secrets_response) + end + + def update(id, key, note, organization_id, project_ids, value) + command = create_command( + update: SecretPutRequest.new( + id: id, key: key, note: note, organization_id: organization_id, project_ids: project_ids, value: value + ) + ) + response = run_command(command) + + secrets_response = ResponseForSecretResponse.from_json!(response).to_dynamic + + if secrets_response.key?('success') && secrets_response['success'] == true && + secrets_response.key?('data') + return secrets_response['data'] + end + + error_response(secrets_response) + end + + def delete_secret(ids) + command = create_command(delete: SecretsDeleteRequest.new(ids: ids)) + response = run_command(command) + + secrets_response = ResponseForSecretsDeleteResponse.from_json!(response).to_dynamic + + if secrets_response.key?('success') && secrets_response['success'] == true && + secrets_response.key?('data') && secrets_response['data'].key?('data') + return secrets_response['data']['data'] + end + + error_response(secrets_response) + end + + private + + def error_response(response) + if response['errorMessage'] + raise BitwardenError, response['errorMessage'] if response.key?('errorMessage') + else + raise BitwardenError, 'Error while getting response' + end + end + + def create_command(commands) + SelectiveCommand.new(secrets: SelectiveSecretsCommand.new(commands)) + end + + def run_command(command) + response = @command_runner.run(command) + raise BitwardenError, 'Error getting response' if response.nil? + + response + end + end +end diff --git a/languages/ruby/bitwarden_sdk/lib/version.rb b/languages/ruby/bitwarden_sdk/lib/version.rb new file mode 100644 index 000000000..1fd8c02c1 --- /dev/null +++ b/languages/ruby/bitwarden_sdk/lib/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module BitwardenSDK + VERSION = '0.0.0' +end diff --git a/languages/ruby/bitwarden_sdk/sig/bitwarden-sdk.rbs b/languages/ruby/bitwarden_sdk/sig/bitwarden-sdk.rbs new file mode 100644 index 000000000..3f6a73f6a --- /dev/null +++ b/languages/ruby/bitwarden_sdk/sig/bitwarden-sdk.rbs @@ -0,0 +1,13 @@ +require_relative '../lib/schemas' + +class BitwardenClient + @command_runner: CommandRunner + + attr_reader bitwarden: Module + attr_reader project_client: ProjectsClient + attr_reader secrets_client: SecretsClient + + def initialize: (BitwardenSettings) -> void + def access_token_login: (String) -> JSON + def free_mem: () -> nil +end diff --git a/languages/ruby/bitwarden_sdk/sig/bitwarden_settings.rbs b/languages/ruby/bitwarden_sdk/sig/bitwarden_settings.rbs new file mode 100644 index 000000000..154ee16e5 --- /dev/null +++ b/languages/ruby/bitwarden_sdk/sig/bitwarden_settings.rbs @@ -0,0 +1,8 @@ +require_relative '../lib/schemas' + +class BitwardenSettings + attr_accessor api_url: String + attr_accessor identity_url: String + + def initialize: (String, String) -> void +end diff --git a/languages/ruby/bitwarden_sdk/sig/command_runner.rbs b/languages/ruby/bitwarden_sdk/sig/command_runner.rbs new file mode 100644 index 000000000..7a7a17dd9 --- /dev/null +++ b/languages/ruby/bitwarden_sdk/sig/command_runner.rbs @@ -0,0 +1,4 @@ +class CommandRunner + @bitwarden_sdk: Module + def run: -> String +end diff --git a/languages/ruby/bitwarden_sdk/sig/projects_client.rbs b/languages/ruby/bitwarden_sdk/sig/projects_client.rbs new file mode 100644 index 000000000..00c9e578d --- /dev/null +++ b/languages/ruby/bitwarden_sdk/sig/projects_client.rbs @@ -0,0 +1,17 @@ +require_once '../lib/extended_schemas/schemas.rbs' +require_once '../schemas.rbs' + +class ProjectsClient + @command_runner: CommandRunner + def initialize: (command_runner: CommandRunner) -> void + def create_project: (project_name: String, organization_id: String) -> ProjectsResponse + def get: (project_id: String) -> ProjectsResponse + def list_projects: (organization_id: String) -> Array(DatumElement) + def update_project: (id: String, project_put_request_name: String, organization_id: String) -> ProjectsResponse + def delete_projects: (ids: Array[String]) -> Array(ProjectDeleteResponse) + + private + + def create_command: (SelectiveProjectsCommand) -> SelectiveCommand + def parse_response: (ResponseForProjectResponse) -> ResponseForProjectResponse +end diff --git a/languages/ruby/bitwarden_sdk/sig/sdk.rbs b/languages/ruby/bitwarden_sdk/sig/sdk.rbs new file mode 100644 index 000000000..260fa1420 --- /dev/null +++ b/languages/ruby/bitwarden_sdk/sig/sdk.rbs @@ -0,0 +1,3 @@ +module BitwardenSDK + VERSION: String +end diff --git a/languages/ruby/bitwarden_sdk/sig/secrets_client.rbs b/languages/ruby/bitwarden_sdk/sig/secrets_client.rbs new file mode 100644 index 000000000..ccebcecd8 --- /dev/null +++ b/languages/ruby/bitwarden_sdk/sig/secrets_client.rbs @@ -0,0 +1,18 @@ +require_once '../lib/extended_schemas/schemas.rbs' +require_once '../schemas.rbs' + +class SecretsClient + # @command_runner: CommandRunner + def initialize: (command_runner: CommandRunner) -> void + def get: (id: String) -> SecretResponse + def get_by_ids: (ids: Array[String]) -> Array(SecretIdentifierResponse) + def create: (key: String, note: String, organization_id: String, project_ids: Array[String], value: String) -> SecretResponse + def list: (organization_id: String) -> Array(SecretIdentifierResponse) + def update: (id: String, key: String, note: String, organization_id: String, project_ids: Array[String], value: String) -> SecretResponse + def delete_secret: (ids: Array[String]) -> Array(SecretDeleteResponse) + + private + + def create_command: (SelectiveSecretsCommand) -> SelectiveCommand + def parse_response: (SelectiveSecretCommand) -> ResponseForSecretResponse +end diff --git a/languages/ruby/examples/example.rb b/languages/ruby/examples/example.rb new file mode 100644 index 000000000..c0deeb437 --- /dev/null +++ b/languages/ruby/examples/example.rb @@ -0,0 +1,67 @@ +# NOTE - for example purpose only - import gem instead +require 'bitwarden-sdk' + +token = '' +organization_id = '' + +bitwarden_settings = BitwardenSDK::BitwardenSettings.new( + 'https://api.bitwarden.com', + 'https://identity.bitwarden.com/connect/token' +) + +bw_client = BitwardenSDK::BitwardenClient.new(bitwarden_settings) +response = bw_client.access_token_login(token) +puts response + +# CREATE project +project_name = 'Test project 1' +response = bw_client.project_client.create_project(project_name, organization_id) +puts response +project_id = response['id'] + +# GET project +response = bw_client.project_client.get(project_id) +puts response + +# LIST projects +response = bw_client.project_client.list_projects(organization_id) +puts response + +# UPDATE projects +name = 'Updated test project 1' +response = bw_client.project_client.update_project(project_id, name, organization_id) +puts response + +# CREATE secret +key = 'AWS-SES' +note = 'Private account' +value = '8t27.dfj;' +response = bw_client.secrets_client.create(key, note, organization_id, [project_id], value) +puts response +secret_id = response['id'] + +# GET secret +response = bw_client.secrets_client.get(secret_id) +puts response + +# GET secret by ids +response = bw_client.secrets_client.get_by_ids([secret_id]) +puts response + +# LIST secrets +response = bw_client.secrets_client.list(organization_id) +puts response + +# UPDATE secret +note = 'updated password' +value = '7I.ert10AjK' +response = bw_client.secrets_client.update(secret_id, key, note,organization_id, [project_id], value) +puts response + +# DELETE secret +response = bw_client.secrets_client.delete_secret([secret_id]) +puts response + +# DELETE project +response = bw_client.project_client.delete_projects([project_id]) +puts response diff --git a/sig/bitwarden_sdk/bitwarden_client.rbs b/sig/bitwarden_sdk/bitwarden_client.rbs new file mode 100644 index 000000000..1ff97eb92 --- /dev/null +++ b/sig/bitwarden_sdk/bitwarden_client.rbs @@ -0,0 +1,5 @@ +module BitwardenSDK + class BitwardenClient + attr_reader project_client: ProjectsClient + end +end diff --git a/support/scripts/schemas.ts b/support/scripts/schemas.ts index 2958efc18..9e25bedc5 100644 --- a/support/scripts/schemas.ts +++ b/support/scripts/schemas.ts @@ -58,6 +58,16 @@ async function main() { writeToFile("./languages/python/BitwardenClient/schemas.py", python.lines); + const ruby = await quicktype({ + inputData, + lang: "ruby", + rendererOptions: { + "ruby-version": "3.0", + }, + }); + + writeToFile("./languages/ruby/bitwarden_sdk/lib/schemas.rb", ruby.lines); + const csharp = await quicktype({ inputData, lang: "csharp", From e219efa40d9f9d04a8f69e55dd6409b6ff43f069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20Trifunovi=C4=87?= <84377717+milost77@users.noreply.github.com> Date: Fri, 1 Dec 2023 13:35:44 +0100 Subject: [PATCH 048/157] PHP SDK implementation (#316) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Type of change Implemented PHP library that wraps native C library and exposed its commands through BitwardenClient class. ``` - [ ] Bug fix - [ x] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective To provide PHP interface for bitwarden c library files by which you can use PHP code to work with Bitwarden API. It implements CRUD requests for projects and secrets. ## Code changes TODO: Updating package repository - will hosted on Packagist. --------- Co-authored-by: Daniel García --- .github/workflows/publish-php.yml | 75 ++++++ languages/php/.gitignore | 2 + languages/php/README.md | 100 +++++++ languages/php/composer.json | 22 ++ languages/php/composer.lock | 247 ++++++++++++++++++ languages/php/example.php | 43 +++ languages/php/src/BitwardenClient.php | 64 +++++ languages/php/src/BitwardenLib.php | 79 ++++++ languages/php/src/BitwardenSettings.php | 26 ++ languages/php/src/CommandRunner.php | 37 +++ languages/php/src/ProjectsClient.php | 81 ++++++ languages/php/src/SecretsClient.php | 98 +++++++ .../src/schemas/AccessTokenLoginRequest.php | 39 +++ .../src/schemas/BitwardenClassStructure.php | 11 + .../schemas/BitwardenClassStructureTrait.php | 189 ++++++++++++++ languages/php/src/schemas/ClientSettings.php | 133 ++++++++++ languages/php/src/schemas/Command.php | 44 ++++ .../php/src/schemas/ProjectCreateRequest.php | 43 +++ .../php/src/schemas/ProjectGetRequest.php | 37 +++ .../php/src/schemas/ProjectPutRequest.php | 50 ++++ languages/php/src/schemas/ProjectsCommand.php | 55 ++++ .../php/src/schemas/ProjectsDeleteRequest.php | 39 +++ .../php/src/schemas/ProjectsListRequest.php | 38 +++ .../php/src/schemas/SecretCreateRequest.php | 58 ++++ .../php/src/schemas/SecretGetRequest.php | 38 +++ .../src/schemas/SecretIdentifiersRequest.php | 38 +++ .../php/src/schemas/SecretPutRequest.php | 64 +++++ .../src/schemas/SecretVerificationRequest.php | 35 +++ languages/php/src/schemas/SecretsCommand.php | 56 ++++ .../php/src/schemas/SecretsDeleteRequest.php | 39 +++ .../php/src/schemas/SecretsGetRequest.php | 39 +++ 31 files changed, 1919 insertions(+) create mode 100644 .github/workflows/publish-php.yml create mode 100644 languages/php/.gitignore create mode 100644 languages/php/README.md create mode 100644 languages/php/composer.json create mode 100644 languages/php/composer.lock create mode 100644 languages/php/example.php create mode 100644 languages/php/src/BitwardenClient.php create mode 100644 languages/php/src/BitwardenLib.php create mode 100644 languages/php/src/BitwardenSettings.php create mode 100644 languages/php/src/CommandRunner.php create mode 100644 languages/php/src/ProjectsClient.php create mode 100644 languages/php/src/SecretsClient.php create mode 100644 languages/php/src/schemas/AccessTokenLoginRequest.php create mode 100644 languages/php/src/schemas/BitwardenClassStructure.php create mode 100644 languages/php/src/schemas/BitwardenClassStructureTrait.php create mode 100644 languages/php/src/schemas/ClientSettings.php create mode 100644 languages/php/src/schemas/Command.php create mode 100644 languages/php/src/schemas/ProjectCreateRequest.php create mode 100644 languages/php/src/schemas/ProjectGetRequest.php create mode 100644 languages/php/src/schemas/ProjectPutRequest.php create mode 100644 languages/php/src/schemas/ProjectsCommand.php create mode 100644 languages/php/src/schemas/ProjectsDeleteRequest.php create mode 100644 languages/php/src/schemas/ProjectsListRequest.php create mode 100644 languages/php/src/schemas/SecretCreateRequest.php create mode 100644 languages/php/src/schemas/SecretGetRequest.php create mode 100644 languages/php/src/schemas/SecretIdentifiersRequest.php create mode 100644 languages/php/src/schemas/SecretPutRequest.php create mode 100644 languages/php/src/schemas/SecretVerificationRequest.php create mode 100644 languages/php/src/schemas/SecretsCommand.php create mode 100644 languages/php/src/schemas/SecretsDeleteRequest.php create mode 100644 languages/php/src/schemas/SecretsGetRequest.php diff --git a/.github/workflows/publish-php.yml b/.github/workflows/publish-php.yml new file mode 100644 index 000000000..b443f2057 --- /dev/null +++ b/.github/workflows/publish-php.yml @@ -0,0 +1,75 @@ +name: Publish PHP SDK + +on: + pull_request: + branches: + - master + +jobs: + build_rust: + uses: ./.github/workflows/build-rust-cross-platform.yml + + setup_php: + name: Setup PHP + runs-on: ubuntu-22.04 + needs: + - build_rust + + steps: + - name: Checkout Repository + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + + - name: Setup PHP with PECL extension + uses: shivammathur/setup-php@7fdd3ece872ec7ec4c098ae5ab7637d5e0a96067 # 2.26.0 + with: + php-version: "8.0" + tools: composer + extensions: ext-ffi + + - name: Composer check + run: | + composer install + composer validate + working-directory: languages/php/ + + - name: Download x86_64-apple-darwin files + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + with: + name: libbitwarden_c_files-x86_64-apple-darwin + path: temp/macos-x64 + + - name: Download aarch64-apple-darwin files + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + with: + name: libbitwarden_c_files-aarch64-apple-darwin + path: temp/macos-arm64 + + - name: Download x86_64-unknown-linux-gnu files + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + with: + name: libbitwarden_c_files-x86_64-unknown-linux-gnu + path: temp/ubuntu-x64 + + - name: Download x86_64-pc-windows-msvc files + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + with: + name: libbitwarden_c_files-x86_64-pc-windows-msvc + path: temp/windows-x64 + + - name: Copy lib files + run: | + mkdir -p languages/php/src/lib/macos-arm64 + mkdir -p languages/php/src/lib/ubuntu-x64 + mkdir -p languages/php/src/lib/macos-x64 + mkdir -p languages/php/src/lib/windows-x64 + + platforms=("macos-arm64" "ubuntu-x64" "macos-x64" "windows-x64") + files=("libbitwarden_c.dylib" "libbitwarden_c.so" "libbitwarden_c.dylib" "bitwarden_c.dll") + + for ((i=0; i<${#platforms[@]}; i++)); do + cp "temp/${platforms[$i]}/${files[$i]}" "languages/php/src/lib/${platforms[$i]}/${files[$i]}" + done + + - name: Publish version + run: curl -XPOST -H'content-type:application/json' 'https://packagist.org/api/update-package?username=malirobot&apiToken=${{secrets.PACKAGIST_KEY}}' -d'{"repository":{"url":"https://packagist.org/packages/bitwarden/sdk"}}' + working-directory: languages/php/ diff --git a/languages/php/.gitignore b/languages/php/.gitignore new file mode 100644 index 000000000..b2a69e9a0 --- /dev/null +++ b/languages/php/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +vendor diff --git a/languages/php/README.md b/languages/php/README.md new file mode 100644 index 000000000..9e4a9385d --- /dev/null +++ b/languages/php/README.md @@ -0,0 +1,100 @@ +# Bitwarden Secrets Manager SDK wrapper for PHP + +PHP bindings for interacting with the [Bitwarden Secrets Manager]. This is a beta release and might be missing some functionality. +Supported are CRUD operations on project and secret entities. + +## Installation + +Requirements: +- PHP >= 8.0 +- Composer +- Bitwarden C libraries which you can generate using BitwardenSDK and following instructions in its readme (requires Rust). https://github.com/bitwarden/sdk +If you are not using the standalone version of this library, file will be placed in `target/debug` folder if you are using from BitwardenSDK repository. +- Access token for the Bitwarden account + + +## Usage + +To interact with the client first you need to obtain the access token from Bitwarden. +You can then initialize BitwardenSettings passing $api_url and $identity_url if needed. These parameteres are +optional and if they are not defined, BitwardenSettings instance will try to get these values from ENV, and +if they are not defined there as well, it will use defaults: `https://api.bitwarden.com` as api_url and +`https://identity.bitwarden.com` as identity_url. You can also pass device type as argument but that is entirely +optional. + +Passing BitwardenSettings instance to BitwardenClient will initialize it. Before using the client you must +be authorized by calling the access_token_login method passing your Bitwarden access token to it. + + +```php +$access_token = ''; +$api_url = ""; +$identity_url = ""; +$bitwarden_settings = new \Bitwarden\Sdk\BitwardenSettings($api_url, $identity_url); + +$bitwarden_client = new \Bitwarden\Sdk\BitwardenClient($bitwarden_settings); +$bitwarden_client->access_token_login($access_token); +``` + +After successful authorization you can interact with client to manage your projects and secrets. +```php +$organization_id = ""; + +$bitwarden_client = new \Bitwarden\Sdk\BitwardenClient($bitwarden_settings); +$res = $bitwarden_client->access_token_login($access_token); + +// create project +$name = "PHP project" +$res = $bitwarden_client->projects->create($name, $organization_id); +$project_id = $res->id; + +// get project +$res = $bitwarden_client->projects->get($project_id); + +// list projects +$res = $bitwarden_client->projects->list($organization_id); + +// update project +$name = "Updated PHP project" +$res = $bitwarden_client->projects->put($project_id, $name, $organization_id); + +// get secret +$res = $bitwarden_client->secrets->get($secret_id); + +// list secrets +$res = $bitwarden_client->secrets->list($organization_id); + +// delete project +$res = $bitwarden_client->projects->delete([$project_id]); + +``` + +Similarly, you interact with secrets: +```php +$organization_id = ""; + +// create secret +$key = "AWS secret key"; +$note = "Private account"; +$secret = "76asaj,Is_)" +$res = $bitwarden_client->secrets->create($key, $note, $organization_id, [$project_id], $secret); +$secret_id = $res->id; + +// get secret +$res = $bitwarden_sdk->secrets->get($secret_id); + +// list secrets +$res = $bitwarden_client->secrets->list($organization_id); + +// update secret +$note = "Updated account"; +$key = "AWS private updated" +$secret = "7uYTE,:Aer" +$res = $bitwarden_client->secrets->update($secret_id, $key, $note, $organization_id, [$project_id], $secret); + +// delete secret +$res = $bitwarden_sdk->secrets->delete([$secret_id]); +``` + + +[Bitwarden Secrets Manager]: https://bitwarden.com/products/secrets-manager/ diff --git a/languages/php/composer.json b/languages/php/composer.json new file mode 100644 index 000000000..18b333eac --- /dev/null +++ b/languages/php/composer.json @@ -0,0 +1,22 @@ +{ + "name": "bitwarden/sdk", + "description": "PHP bindings for interacting with the Bitwarden Secrets Manager. This is a beta release and might be missing some functionality.", + "type": "library", + "keywords": ["bitwarden","sdk","password-manager"], + "homepage": "https://github.com/bitwarden/sdk", + "require": { + "php": "^8.0", + "swaggest/json-schema": "^0.12.42", + "ext-ffi": "*" + }, + "autoload": { + "psr-4": { + "Bitwarden\\Sdk\\": "src/" + } + }, + "authors": [ + { + "name": "Bitwarden Inc." + } + ] +} diff --git a/languages/php/composer.lock b/languages/php/composer.lock new file mode 100644 index 000000000..fc6b42c4f --- /dev/null +++ b/languages/php/composer.lock @@ -0,0 +1,247 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "7081b1bfe099982a63ad06d5ab9fa66d", + "packages": [ + { + "name": "phplang/scope-exit", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/phplang/scope-exit.git", + "reference": "239b73abe89f9414aa85a7ca075ec9445629192b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phplang/scope-exit/zipball/239b73abe89f9414aa85a7ca075ec9445629192b", + "reference": "239b73abe89f9414aa85a7ca075ec9445629192b", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpLang\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD" + ], + "authors": [ + { + "name": "Sara Golemon", + "email": "pollita@php.net", + "homepage": "https://twitter.com/SaraMG", + "role": "Developer" + } + ], + "description": "Emulation of SCOPE_EXIT construct from C++", + "homepage": "https://github.com/phplang/scope-exit", + "keywords": [ + "cleanup", + "exit", + "scope" + ], + "support": { + "issues": "https://github.com/phplang/scope-exit/issues", + "source": "https://github.com/phplang/scope-exit/tree/master" + }, + "time": "2016-09-17T00:15:18+00:00" + }, + { + "name": "swaggest/json-diff", + "version": "v3.10.4", + "source": { + "type": "git", + "url": "https://github.com/swaggest/json-diff.git", + "reference": "f4e511708060ff7511a3743fab4aa484a062bcfb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swaggest/json-diff/zipball/f4e511708060ff7511a3743fab4aa484a062bcfb", + "reference": "f4e511708060ff7511a3743fab4aa484a062bcfb", + "shasum": "" + }, + "require": { + "ext-json": "*" + }, + "require-dev": { + "phperf/phpunit": "4.8.37" + }, + "type": "library", + "autoload": { + "psr-4": { + "Swaggest\\JsonDiff\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Viacheslav Poturaev", + "email": "vearutop@gmail.com" + } + ], + "description": "JSON diff/rearrange/patch/pointer library for PHP", + "support": { + "issues": "https://github.com/swaggest/json-diff/issues", + "source": "https://github.com/swaggest/json-diff/tree/v3.10.4" + }, + "time": "2022-11-09T13:21:05+00:00" + }, + { + "name": "swaggest/json-schema", + "version": "v0.12.42", + "source": { + "type": "git", + "url": "https://github.com/swaggest/php-json-schema.git", + "reference": "d23adb53808b8e2da36f75bc0188546e4cbe3b45" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swaggest/php-json-schema/zipball/d23adb53808b8e2da36f75bc0188546e4cbe3b45", + "reference": "d23adb53808b8e2da36f75bc0188546e4cbe3b45", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=5.4", + "phplang/scope-exit": "^1.0", + "swaggest/json-diff": "^3.8.2", + "symfony/polyfill-mbstring": "^1.19" + }, + "require-dev": { + "phperf/phpunit": "4.8.37" + }, + "suggest": { + "ext-mbstring": "For better performance" + }, + "type": "library", + "autoload": { + "psr-4": { + "Swaggest\\JsonSchema\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Viacheslav Poturaev", + "email": "vearutop@gmail.com" + } + ], + "description": "High definition PHP structures with JSON-schema based validation", + "support": { + "email": "vearutop@gmail.com", + "issues": "https://github.com/swaggest/php-json-schema/issues", + "source": "https://github.com/swaggest/php-json-schema/tree/v0.12.42" + }, + "time": "2023-09-12T14:43:42+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "42292d99c55abe617799667f454222c54c60e229" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", + "reference": "42292d99c55abe617799667f454222c54c60e229", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-28T09:04:16+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.0", + "ext-ffi": "*" + }, + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/languages/php/example.php b/languages/php/example.php new file mode 100644 index 000000000..0fdb6930a --- /dev/null +++ b/languages/php/example.php @@ -0,0 +1,43 @@ +'; +$organization_id = ""; + +$client_settings = new \Bitwarden\Sdk\BitwardenSettings(); + +$bitwarden_client = new \Bitwarden\Sdk\BitwardenClient($client_settings); +$bitwarden_client->access_token_login($access_token); + +// create project +$res = $bitwarden_client->projects->create('php project', $organization_id); +$project_id = $res->id; + +// get project +$res = $bitwarden_client->projects->get($project_id); + +// list projects +$res = $bitwarden_client->projects->list($organization_id); + +// update project +$res = $bitwarden_client->projects->put($project_id, 'php test awesome', $organization_id); + +// create secret +$res = $bitwarden_client->secrets->create("New Key", "hello world", $organization_id, [$project_id], "123"); +$secret_id = $res->id; + +// get secret +$res = $bitwarden_client->secrets->get($secret_id); + +// list secrets +$res = $bitwarden_client->secrets->list($organization_id); + +// update secret +$res = $bitwarden_client->secrets->update($secret_id, "hello world 2", "hello", $organization_id, [$project_id], "123"); + +// delete secret +$res = $bitwarden_client->secrets->delete([$secret_id]); + +// delete project +$res = $bitwarden_client->projects->delete([$project_id]); diff --git a/languages/php/src/BitwardenClient.php b/languages/php/src/BitwardenClient.php new file mode 100644 index 000000000..79fccdf9c --- /dev/null +++ b/languages/php/src/BitwardenClient.php @@ -0,0 +1,64 @@ +clientSettings = new ClientSettings(); + $this->clientSettings->apiUrl = $bitwardenSettings->get_api_url(); + $this->clientSettings->identityUrl = $bitwardenSettings->get_identity_url(); + $this->clientSettings->userAgent = "Bitwarden PHP-SDK"; + + $this->bitwarden_lib = new BitwardenLib(); + $this->handle = $this->bitwarden_lib->init($this->clientSettings); + + $this->commandRunner = new CommandRunner($this->bitwarden_lib, $this->handle); + $this->projects = new ProjectsClient($this->commandRunner); + $this->secrets = new SecretsClient($this->commandRunner); + } + + /** + * @throws \Exception + */ + public function access_token_login(string $access_token) + { + $access_token_request = new AccessTokenLoginRequest(); + $access_token_request->accessToken = $access_token; + $command = new Command(); + $command->accessTokenLogin = $access_token_request->jsonSerialize(); + $result = $this->commandRunner->run($command); + if (!isset($result->authenticated)) { + throw new \Exception("Authorization error"); + } + + if ($result->authenticated == False) { + throw new \Exception("Unauthorized"); + } + } + + public function __destruct() + { + $this->bitwarden_lib->free_mem(); + } +} diff --git a/languages/php/src/BitwardenLib.php b/languages/php/src/BitwardenLib.php new file mode 100644 index 000000000..3eb3ed5f4 --- /dev/null +++ b/languages/php/src/BitwardenLib.php @@ -0,0 +1,79 @@ +ffi = FFI::cdef(' + void* init(const char* param); + char* run_command(void* c_str_ptr, void* client_ptr); + void free_mem(void* client_ptr);', + $lib_file + ); + } + + public function init(ClientSettings $client_settings): FFI\CData + { + $this->handle = $this->ffi->init(json_encode($client_settings->jsonSerialize())); + return $this->handle; + } + + public function run_command(Command $command): \stdClass + { + $encoded_json = json_encode($command->jsonSerialize()); + try { + $result = $this->ffi->run_command($encoded_json, $this->handle); + return json_decode(FFI::string($result)); + } catch (\FFI\Exception $e) { + throw new \RuntimeException('Error occurred during FFI operation: ' . $e->getMessage()); + } + } + + public function free_mem(): void + { + $this->ffi->free_mem($this->handle); + } +} diff --git a/languages/php/src/BitwardenSettings.php b/languages/php/src/BitwardenSettings.php new file mode 100644 index 000000000..b3d62bc2e --- /dev/null +++ b/languages/php/src/BitwardenSettings.php @@ -0,0 +1,26 @@ +api_url = $api_url; + $this->identity_url = $identity_url; + } + + public function get_api_url(): ?string + { + return $this->api_url; + } + + public function get_identity_url(): ?string + { + return $this->identity_url; + } +} diff --git a/languages/php/src/CommandRunner.php b/languages/php/src/CommandRunner.php new file mode 100644 index 000000000..9eec68b2d --- /dev/null +++ b/languages/php/src/CommandRunner.php @@ -0,0 +1,37 @@ +bitwardenLib = $bitwardenLib; + $this->handle = $handle; + } + + /** + * @throws \Exception + */ + public function run(Command $command): \stdClass + { + $result = $this->bitwardenLib->run_command($command); + if ($result->success == true) { + return $result->data; + } + + if (isset($result->errorMessage)) + { + throw new \Exception($result->errorMessage); + } + throw new \Exception("Unknown error occurred"); + } +} diff --git a/languages/php/src/ProjectsClient.php b/languages/php/src/ProjectsClient.php new file mode 100644 index 000000000..6b6f9fb6a --- /dev/null +++ b/languages/php/src/ProjectsClient.php @@ -0,0 +1,81 @@ +commandRunner = $commandRunner; + } + + public function get(string $project_id): \stdClass + { + $project_get_request = new ProjectGetRequest(); + $project_get_request->id = $project_id; + $project_get_request->validate(); + $project_command = new ProjectsCommand(); + $project_command->get = $project_get_request->jsonSerialize(); + return $this->run_project_command($project_command); + } + + public function list(string $organization_id): \stdClass + { + $project_list_request = new ProjectsListRequest(); + $project_list_request->organizationId = $organization_id; + $project_list_request->validate(); + $project_command = new ProjectsCommand(); + $project_command->list = $project_list_request->jsonSerialize(); + return $this->run_project_command($project_command); + } + + public function create(string $project_name, string $organization_id): \stdClass + { + $project_create_request = new ProjectCreateRequest(); + $project_create_request->name = $project_name; + $project_create_request->organizationId = $organization_id; + $project_create_request->validate(); + $project_command = new ProjectsCommand(); + $project_command->create = $project_create_request->jsonSerialize(); + return $this->run_project_command($project_command); + } + + public function put(string $project_id, string $project_name, string $organization_id): \stdClass + { + $project_put_request = new ProjectPutRequest(); + $project_put_request->organizationId = $organization_id; + $project_put_request->name = $project_name; + $project_put_request->id = $project_id; + $project_put_request->validate(); + $project_command = new ProjectsCommand(); + $project_command->update = $project_put_request->jsonSerialize(); + return $this->run_project_command($project_command); + } + + public function delete(array $ids): \stdClass + { + $projects_delete_request = new ProjectsDeleteRequest(); + $projects_delete_request->ids = $ids; + $projects_delete_request->validate(); + $project_command = new ProjectsCommand(); + $project_command->delete = $projects_delete_request->jsonSerialize(); + return $this->run_project_command($project_command); + } + + public function run_project_command($projectCommand): \stdClass + { + $command = new Command(); + $command->projects = $projectCommand; + return $this->commandRunner->run($command); + } +} diff --git a/languages/php/src/SecretsClient.php b/languages/php/src/SecretsClient.php new file mode 100644 index 000000000..d5c0b0cef --- /dev/null +++ b/languages/php/src/SecretsClient.php @@ -0,0 +1,98 @@ +commandRunner = $commandRunner; + } + + public function get(string $secret_id): \stdClass + { + $secret_get_request = new SecretGetRequest(); + $secret_get_request->id = $secret_id; + $secret_get_request->validate(); + $secret_command = new SecretsCommand(); + $secret_command->get = $secret_get_request->jsonSerialize(); + return $this->run_secret_command($secret_command); + } + + public function get_by_ids(array $secret_ids): \stdClass + { + $project_get_by_ids_request = new SecretsGetRequest(); + $project_get_by_ids_request->ids = $secret_ids; + $project_get_by_ids_request->validate(); + $secrets_command = new SecretsCommand(); + $secrets_command->get_by_ids = $project_get_by_ids_request->jsonSerialize(); + return $this->run_secret_command($secrets_command); + } + + public function list(string $organization_id): \stdClass + { + $secrets_list_request = new SecretIdentifiersRequest(); + $secrets_list_request->organizationId = $organization_id; + $secrets_list_request->validate(); + $secrets_command = new SecretsCommand(); + $secrets_command->list = $secrets_list_request->jsonSerialize(); + return $this->run_secret_command($secrets_command); + } + + public function create(string $key, string $note, string $organization_id, array $project_ids, string $value): \stdClass + { + $secrets_create_request = new SecretCreateRequest(); + $secrets_create_request->organizationId = $organization_id; + $secrets_create_request->projectIds = $project_ids; + $secrets_create_request->key = $key; + $secrets_create_request->note = $note; + $secrets_create_request->value = $value; + $secrets_create_request->validate(); + $secrets_command = new SecretsCommand(); + $secrets_command->create = $secrets_create_request->jsonSerialize(); + return $this->run_secret_command($secrets_command); + } + + public function update(string $id, string $key, string $note, string $organization_id, array $project_ids, string $value): \stdClass + { + $secrets_put_request = new SecretPutRequest(); + $secrets_put_request->id = $id; + $secrets_put_request->organizationId = $organization_id; + $secrets_put_request->projectIds = $project_ids; + $secrets_put_request->key = $key; + $secrets_put_request->note = $note; + $secrets_put_request->value = $value; + $secrets_put_request->validate(); + $secrets_command = new SecretsCommand(); + $secrets_command->update = $secrets_put_request->jsonSerialize(); + return $this->run_secret_command($secrets_command); + } + + public function delete(array $secrets_ids): \stdClass + { + $secrets_delete_request = new SecretsDeleteRequest(); + $secrets_delete_request->ids = $secrets_ids; + $secrets_delete_request->validate(); + $secrets_command = new SecretsCommand(); + $secrets_command->delete = $secrets_delete_request->jsonSerialize(); + return $this->run_secret_command($secrets_command); + } + + public function run_secret_command($secretsCommand): \stdClass + { + $command = new Command(); + $command->secrets = $secretsCommand; + return $this->commandRunner->run($command); + } +} diff --git a/languages/php/src/schemas/AccessTokenLoginRequest.php b/languages/php/src/schemas/AccessTokenLoginRequest.php new file mode 100644 index 000000000..a08805f92 --- /dev/null +++ b/languages/php/src/schemas/AccessTokenLoginRequest.php @@ -0,0 +1,39 @@ +accessToken = Schema::string(); + $properties->accessToken->description = "Bitwarden service API access token"; + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->description = "Login to Bitwarden with access token"; + $ownerSchema->required = array( + self::names()->accessToken, + ); + $ownerSchema->setFromRef('#/definitions/AccessTokenLoginRequest'); + } +} diff --git a/languages/php/src/schemas/BitwardenClassStructure.php b/languages/php/src/schemas/BitwardenClassStructure.php new file mode 100644 index 000000000..fd50354d4 --- /dev/null +++ b/languages/php/src/schemas/BitwardenClassStructure.php @@ -0,0 +1,11 @@ +properties = $properties; + $schema->objectItemClass = $className; + $schemaWrapper = new Wrapper($schema); + static::setUpProperties($properties, $schema); + if (null === $schema->getFromRefs()) { + $schema->setFromRef('#/definitions/' . $className); + } + if ($properties->isEmpty()) { + $schema->properties = null; + } + $properties->lock(); + } + + return $schemaWrapper; + } + + /** + * @return Properties|static|null + */ + public static function properties() + { + return static::schema()->getProperties(); + } + + /** + * @param mixed $data + * @param Context $options + * @return static|mixed + * @throws \Swaggest\JsonSchema\Exception + * @throws \Swaggest\JsonSchema\InvalidValue + */ + public static function import($data, Context $options = null) + { + return static::schema()->in($data, $options); + } + + /** + * @param mixed $data + * @param Context $options + * @return mixed + * @throws \Swaggest\JsonSchema\InvalidValue + * @throws \Exception + */ + public static function export($data, Context $options = null) + { + return static::schema()->out($data, $options); + } + + /** + * @param ObjectItemContract $objectItem + * @return static + */ + public static function pick(ObjectItemContract $objectItem) + { + $className = get_called_class(); + return $objectItem->getNestedObject($className); + } + + /** + * @return static + */ + public static function create() + { + return new static; + } + + protected $__validateOnSet = true; // todo skip validation during import + + /** + * @return \stdClass + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + $result = new \stdClass(); + $schema = static::schema(); + $properties = $schema->getProperties(); + $processed = array(); + if (null !== $properties) { + foreach ($properties->getDataKeyMap() as $propertyName => $dataName) { + $value = $this->$propertyName ?? null; + + // Value is exported if exists. + if (null !== $value || array_key_exists($propertyName, $this->__arrayOfData)) { + $result->$dataName = $value; + $processed[$propertyName] = true; + continue; + } + + // Non-existent value is only exported if belongs to nullable property (having 'null' in type array). + $property = $schema->getProperty($propertyName); + if ($property instanceof Schema) { + $types = $property->type; + if ($types === Schema::NULL || (is_array($types) && in_array(Schema::NULL, $types))) { + $result->$dataName = $value; + } + } + } + } + foreach ($schema->getNestedPropertyNames() as $name) { + /** @var ObjectItem $nested */ + $nested = $this->$name; + if (null !== $nested) { + foreach ((array)$nested->jsonSerialize() as $key => $value) { + $result->$key = $value; + } + } + } + + if (!empty($this->__arrayOfData)) { + foreach ($this->__arrayOfData as $name => $value) { + if (!isset($processed[$name])) { + $result->$name = $this->{$name}; + } + } + } + + return $result; + } + + /** + * @return static|NameMirror + */ + public static function names(Properties $properties = null, $mapping = Schema::DEFAULT_MAPPING) + { + if ($properties !== null) { + return new NameMirror($properties->getDataKeyMap($mapping)); + } + + static $nameflector = null; + if (null === $nameflector) { + $nameflector = new NameMirror(); + } + return $nameflector; + } + + public function __set($name, $column) // todo nested schemas + { + if ($this->__validateOnSet) { + if ($property = static::schema()->getProperty($name)) { + $property->out($column); + } + } + $this->__arrayOfData[$name] = $column; + return $this; + } + + public static function className() + { + return get_called_class(); + } + + /** + * @throws \Exception + * @throws \Swaggest\JsonSchema\InvalidValue + */ + public function validate() + { + static::schema()->out($this); + } +} + diff --git a/languages/php/src/schemas/ClientSettings.php b/languages/php/src/schemas/ClientSettings.php new file mode 100644 index 000000000..c27cc3322 --- /dev/null +++ b/languages/php/src/schemas/ClientSettings.php @@ -0,0 +1,133 @@ +identityUrl = Schema::string(); + $properties->identityUrl->description = "The identity url of the targeted Bitwarden instance. Defaults to `https://identity.bitwarden.com`"; + $properties->identityUrl->default = "https://identity.bitwarden.com"; + $properties->apiUrl = Schema::string(); + $properties->apiUrl->description = "The api url of the targeted Bitwarden instance. Defaults to `https://api.bitwarden.com`"; + $properties->apiUrl->default = "https://api.bitwarden.com"; + $properties->userAgent = Schema::string(); + $properties->userAgent->description = "The user_agent to sent to Bitwarden. Defaults to `Bitwarden Rust-SDK`"; + $properties->userAgent->default = "Bitwarden Rust-SDK"; + $properties->deviceType = new Schema(); + $propertiesDeviceTypeAllOf0 = Schema::string(); + $propertiesDeviceTypeAllOf0->enum = array( + self::ANDROID, + self::I_OS, + self::CHROME_EXTENSION, + self::FIREFOX_EXTENSION, + self::OPERA_EXTENSION, + self::EDGE_EXTENSION, + self::WINDOWS_DESKTOP, + self::MAC_OS_DESKTOP, + self::LINUX_DESKTOP, + self::CHROME_BROWSER, + self::FIREFOX_BROWSER, + self::OPERA_BROWSER, + self::EDGE_BROWSER, + self::IE_BROWSER, + self::UNKNOWN_BROWSER, + self::ANDROID_AMAZON, + self::UWP, + self::SAFARI_BROWSER, + self::VIVALDI_BROWSER, + self::VIVALDI_EXTENSION, + self::SAFARI_EXTENSION, + self::SDK, + ); + $propertiesDeviceTypeAllOf0->setFromRef('#/definitions/DeviceType'); + $properties->deviceType->allOf[0] = $propertiesDeviceTypeAllOf0; + $properties->deviceType->description = "Device type to send to Bitwarden. Defaults to SDK"; + $properties->deviceType->default = "SDK"; + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->schema = "http://json-schema.org/draft-07/schema#"; + $ownerSchema->title = "ClientSettings"; + $ownerSchema->description = "Basic client behavior settings. These settings specify the various targets and behavior of the Bitwarden Client. They are optional and uneditable once the client is initialized.\n\nDefaults to\n\n``` # use bitwarden::client::client_settings::{ClientSettings, DeviceType}; # use assert_matches::assert_matches; let settings = ClientSettings { identity_url: \"https://identity.bitwarden.com\".to_string(), api_url: \"https://api.bitwarden.com\".to_string(), user_agent: \"Bitwarden Rust-SDK\".to_string(), device_type: DeviceType::SDK, }; let default = ClientSettings::default(); assert_matches!(settings, default); ```\n\nTargets `localhost:8080` for debug builds."; + } +} diff --git a/languages/php/src/schemas/Command.php b/languages/php/src/schemas/Command.php new file mode 100644 index 000000000..cbd649c2f --- /dev/null +++ b/languages/php/src/schemas/Command.php @@ -0,0 +1,44 @@ +projects = ProjectsCommand::schema(); + $properties->secrets = SecretsCommand::schema(); + $properties->accessTokenLogin = AccessTokenLoginRequest::schema(); + + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + + $ownerSchema->oneOf = array( + self::names()->projects, + self::names()->secrets, + self::names()->accessTokenLogin, + ); + } +} diff --git a/languages/php/src/schemas/ProjectCreateRequest.php b/languages/php/src/schemas/ProjectCreateRequest.php new file mode 100644 index 000000000..6a4e0f082 --- /dev/null +++ b/languages/php/src/schemas/ProjectCreateRequest.php @@ -0,0 +1,43 @@ +organizationId = Schema::string(); + $properties->organizationId->description = "Organization where the project will be created"; + $properties->organizationId->format = "uuid"; + $properties->name = Schema::string(); + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->required = array( + self::names()->name, + self::names()->organizationId, + ); + $ownerSchema->setFromRef('#/definitions/ProjectCreateRequest'); + } +} diff --git a/languages/php/src/schemas/ProjectGetRequest.php b/languages/php/src/schemas/ProjectGetRequest.php new file mode 100644 index 000000000..972bf18ec --- /dev/null +++ b/languages/php/src/schemas/ProjectGetRequest.php @@ -0,0 +1,37 @@ +id = Schema::string(); + $properties->id->description = "ID of the project to retrieve"; + $properties->id->format = "uuid"; + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->required = array( + self::names()->id, + ); + $ownerSchema->setFromRef('#/definitions/ProjectGetRequest'); + } +} diff --git a/languages/php/src/schemas/ProjectPutRequest.php b/languages/php/src/schemas/ProjectPutRequest.php new file mode 100644 index 000000000..96b9705e7 --- /dev/null +++ b/languages/php/src/schemas/ProjectPutRequest.php @@ -0,0 +1,50 @@ +id = Schema::string(); + $properties->id->description = "ID of the project to modify"; + $properties->id->format = "uuid"; + $properties->organizationId = Schema::string(); + $properties->organizationId->description = "Organization ID of the project to modify"; + $properties->organizationId->format = "uuid"; + $properties->name = Schema::string(); + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->required = array( + self::names()->id, + self::names()->name, + self::names()->organizationId, + ); + $ownerSchema->setFromRef('#/definitions/ProjectPutRequest'); + } +} diff --git a/languages/php/src/schemas/ProjectsCommand.php b/languages/php/src/schemas/ProjectsCommand.php new file mode 100644 index 000000000..22645db3c --- /dev/null +++ b/languages/php/src/schemas/ProjectsCommand.php @@ -0,0 +1,55 @@ + Requires Authentication > Requires using an Access Token for login or calling Sync at least once Deletes all the projects whose IDs match the provided ones + * + * Returns: [ProjectsDeleteResponse](bitwarden::secrets_manager::projects::ProjectsDeleteResponse) + */ +class ProjectsCommand extends BitwardenClassStructure +{ + public ?\stdClass $delete; + + public ?\stdClass $get; + + public ?\stdClass $list; + + public ?\stdClass $create; + + public ?\stdClass $update; + + + /** + * @param Properties|static $properties + * @param Schema $ownerSchema + */ + public static function setUpProperties($properties, Schema $ownerSchema) + { + $properties->delete = ProjectsDeleteRequest::schema() ? ProjectsDeleteRequest::schema() : null; + $properties->get = ProjectGetRequest::schema() ? ProjectGetRequest::schema() : null; + $properties->list = ProjectsListRequest::schema() ? ProjectsListRequest::schema() : null; + $properties->update = ProjectPutRequest::schema() ? ProjectPutRequest::schema() : null; + $properties->create = ProjectCreateRequest::schema() ? ProjectCreateRequest::schema() : null; + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->description = "> Requires Authentication > Requires using an Access Token for login or calling Sync at least once Deletes all the projects whose IDs match the provided ones\n\nReturns: [ProjectsDeleteResponse](bitwarden::secrets_manager::projects::ProjectsDeleteResponse)"; + + $ownerSchema->oneOf = array( + self::names()->create, + self::names()->delete, + self::names()->get, + self::names()->list, + self::names()->update, + ); + } +} diff --git a/languages/php/src/schemas/ProjectsDeleteRequest.php b/languages/php/src/schemas/ProjectsDeleteRequest.php new file mode 100644 index 000000000..87a7cfad7 --- /dev/null +++ b/languages/php/src/schemas/ProjectsDeleteRequest.php @@ -0,0 +1,39 @@ +ids = Schema::arr(); + $properties->ids->items = Schema::string(); + $properties->ids->items->format = "uuid"; + $properties->ids->description = "IDs of the projects to delete"; + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->required = array( + self::names()->ids, + ); + $ownerSchema->setFromRef('#/definitions/ProjectsDeleteRequest'); + } +} diff --git a/languages/php/src/schemas/ProjectsListRequest.php b/languages/php/src/schemas/ProjectsListRequest.php new file mode 100644 index 000000000..cc1a9474f --- /dev/null +++ b/languages/php/src/schemas/ProjectsListRequest.php @@ -0,0 +1,38 @@ +organizationId = Schema::string(); + $properties->organizationId->description = "Organization to retrieve all the projects from"; + $properties->organizationId->format = "uuid"; + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->required = array( + self::names()->organizationId, + ); + $ownerSchema->setFromRef('#/definitions/ProjectsListRequest'); + } +} diff --git a/languages/php/src/schemas/SecretCreateRequest.php b/languages/php/src/schemas/SecretCreateRequest.php new file mode 100644 index 000000000..d34b36e98 --- /dev/null +++ b/languages/php/src/schemas/SecretCreateRequest.php @@ -0,0 +1,58 @@ +organizationId = Schema::string(); + $properties->organizationId->description = "Organization where the secret will be created"; + $properties->organizationId->format = "uuid"; + $properties->key = Schema::string(); + $properties->value = Schema::string(); + $properties->note = Schema::string(); + $properties->projectIds = (new Schema())->setType([Schema::_ARRAY, Schema::NULL]); + $properties->projectIds->items = Schema::string(); + $properties->projectIds->items->format = "uuid"; + $properties->projectIds->description = "IDs of the projects that this secret will belong to"; + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->required = array( + self::names()->key, + self::names()->note, + self::names()->organizationId, + self::names()->value, + ); + $ownerSchema->setFromRef('#/definitions/SecretCreateRequest'); + } +} diff --git a/languages/php/src/schemas/SecretGetRequest.php b/languages/php/src/schemas/SecretGetRequest.php new file mode 100644 index 000000000..f31f7cad3 --- /dev/null +++ b/languages/php/src/schemas/SecretGetRequest.php @@ -0,0 +1,38 @@ +id = Schema::string(); + $properties->id->description = "ID of the secret to retrieve"; + $properties->id->format = "uuid"; + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->required = array( + self::names()->id, + ); + $ownerSchema->setFromRef('#/definitions/SecretGetRequest'); + } +} diff --git a/languages/php/src/schemas/SecretIdentifiersRequest.php b/languages/php/src/schemas/SecretIdentifiersRequest.php new file mode 100644 index 000000000..b4e75b801 --- /dev/null +++ b/languages/php/src/schemas/SecretIdentifiersRequest.php @@ -0,0 +1,38 @@ +organizationId = Schema::string(); + $properties->organizationId->description = "Organization to retrieve all the secrets from"; + $properties->organizationId->format = "uuid"; + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->required = array( + self::names()->organizationId, + ); + $ownerSchema->setFromRef('#/definitions/SecretIdentifiersRequest'); + } +} diff --git a/languages/php/src/schemas/SecretPutRequest.php b/languages/php/src/schemas/SecretPutRequest.php new file mode 100644 index 000000000..d890a909d --- /dev/null +++ b/languages/php/src/schemas/SecretPutRequest.php @@ -0,0 +1,64 @@ +id = Schema::string(); + $properties->id->description = "ID of the secret to modify"; + $properties->id->format = "uuid"; + $properties->organizationId = Schema::string(); + $properties->organizationId->description = "Organization ID of the secret to modify"; + $properties->organizationId->format = "uuid"; + $properties->key = Schema::string(); + $properties->value = Schema::string(); + $properties->note = Schema::string(); + $properties->projectIds = (new Schema())->setType([Schema::_ARRAY, Schema::NULL]); + $properties->projectIds->items = Schema::string(); + $properties->projectIds->items->format = "uuid"; + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->required = array( + self::names()->id, + self::names()->key, + self::names()->note, + self::names()->organizationId, + self::names()->value, + ); + $ownerSchema->setFromRef('#/definitions/SecretPutRequest'); + } +} diff --git a/languages/php/src/schemas/SecretVerificationRequest.php b/languages/php/src/schemas/SecretVerificationRequest.php new file mode 100644 index 000000000..95cfd1e15 --- /dev/null +++ b/languages/php/src/schemas/SecretVerificationRequest.php @@ -0,0 +1,35 @@ +masterPassword = (new Schema())->setType([Schema::STRING, Schema::NULL]); + $properties->masterPassword->description = "The user's master password to use for user verification. If supplied, this will be used for verification purposes."; + $properties->otp = (new Schema())->setType([Schema::STRING, Schema::NULL]); + $properties->otp->description = "Alternate user verification method through OTP. This is provided for users who have no master password due to use of Customer Managed Encryption. Must be present and valid if master_password is absent."; + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->setFromRef('#/definitions/SecretVerificationRequest'); + } +} diff --git a/languages/php/src/schemas/SecretsCommand.php b/languages/php/src/schemas/SecretsCommand.php new file mode 100644 index 000000000..1ed8c97c5 --- /dev/null +++ b/languages/php/src/schemas/SecretsCommand.php @@ -0,0 +1,56 @@ + Requires Authentication > Requires using an Access Token for login or calling Sync at least once Deletes all the secrets whose IDs match the provided ones + * + * Returns: [SecretsDeleteResponse](bitwarden::secrets_manager::secrets::SecretsDeleteResponse) + */ +class SecretsCommand extends BitwardenClassStructure +{ + public ?\stdClass $delete; + + public ?\stdClass $get; + + public ?\stdClass $getByIds; + + public ?\stdClass $list; + + public ?\stdClass $create; + + public ?\stdClass $put; + + /** + * @param Properties|static $properties + * @param Schema $ownerSchema + */ + public static function setUpProperties($properties, Schema $ownerSchema) + { + $properties->delete = SecretsDeleteRequest::schema() ? SecretsDeleteRequest::schema() : null; + $properties->getByIds = SecretsGetRequest::schema() ? SecretGetRequest::schema() : null; + $properties->create = SecretCreateRequest::schema() ? SecretCreateRequest::schema() : null; + $properties->put = SecretPutRequest::schema() ? SecretPutRequest::schema() : null; + $properties->list = SecretIdentifiersRequest::schema() ? SecretIdentifiersRequest::schema() : null; + $properties->get = SecretsGetRequest::schema() ? SecretGetRequest::schema() : null; + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->description = "> Requires Authentication > Requires using an Access Token for login or calling Sync at least once Deletes all the secrets whose IDs match the provided ones\n\nReturns: [SecretsDeleteResponse](bitwarden::secrets_manager::secrets::SecretsDeleteResponse)"; + $ownerSchema->oneOf = array( + self::names()->create, + self::names()->put, + self::names()->list, + self::names()->getByIds, + self::names()->delete, + ); + } +} diff --git a/languages/php/src/schemas/SecretsDeleteRequest.php b/languages/php/src/schemas/SecretsDeleteRequest.php new file mode 100644 index 000000000..35138fcb1 --- /dev/null +++ b/languages/php/src/schemas/SecretsDeleteRequest.php @@ -0,0 +1,39 @@ +ids = Schema::arr(); + $properties->ids->items = Schema::string(); + $properties->ids->items->format = "uuid"; + $properties->ids->description = "IDs of the secrets to delete"; + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->required = array( + self::names()->ids, + ); + $ownerSchema->setFromRef('#/definitions/SecretsDeleteRequest'); + } +} diff --git a/languages/php/src/schemas/SecretsGetRequest.php b/languages/php/src/schemas/SecretsGetRequest.php new file mode 100644 index 000000000..4758dabf4 --- /dev/null +++ b/languages/php/src/schemas/SecretsGetRequest.php @@ -0,0 +1,39 @@ +ids = Schema::arr(); + $properties->ids->items = Schema::string(); + $properties->ids->items->format = "uuid"; + $properties->ids->description = "IDs of the secrets to retrieve"; + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->required = array( + self::names()->ids, + ); + $ownerSchema->setFromRef('#/definitions/SecretsGetRequest'); + } +} From 12fa28a5b94dc7274b9f158ceca1f233aa334205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Fri, 1 Dec 2023 14:12:29 +0100 Subject: [PATCH 049/157] Unpin wasm-bindgen (#373) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective wasm-bindgen was pinned because version 0.2.88 had some accidental backwards incompatible breaking changes. Those are fixed in 0.2.89 so we can remove the pin now --- Cargo.lock | 20 ++++++++++---------- crates/bitwarden-wasm/Cargo.toml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 50e263401..ad20c5f96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3707,9 +3707,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" dependencies = [ "cfg-if", "serde", @@ -3719,9 +3719,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" dependencies = [ "bumpalo", "log", @@ -3746,9 +3746,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3756,9 +3756,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", @@ -3769,9 +3769,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] name = "wasm-bindgen-test" diff --git a/crates/bitwarden-wasm/Cargo.toml b/crates/bitwarden-wasm/Cargo.toml index 7ad0d1c3e..9a14d26b7 100644 --- a/crates/bitwarden-wasm/Cargo.toml +++ b/crates/bitwarden-wasm/Cargo.toml @@ -13,7 +13,7 @@ console_log = { version = "1.0.0", features = ["color"] } js-sys = "0.3.63" log = "0.4.20" serde = { version = "1.0.193", features = ["derive"] } -wasm-bindgen = { version = "=0.2.87", features = ["serde-serialize"] } +wasm-bindgen = { version = "0.2.89", features = ["serde-serialize"] } wasm-bindgen-futures = "0.4.36" bitwarden-json = { path = "../bitwarden-json", features = ["secrets"] } From 20c38c91d168ceadf807a6c24570cc997c954047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Fri, 1 Dec 2023 16:45:50 +0100 Subject: [PATCH 050/157] Reduce the number of CI jobs that are run on PRs (#375) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [x] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective With the recent addition of all the SDK language bindings, we've added a lot of CI jobs on PRs that are unnecessary and slow down builds. By switching them to run only on merge we should save some CI time. Ultimately, we'd want some way to make it so that the `Build Rust Cross Platform` get only called once and reused for every language SDK, but for now this should help. --- .github/workflows/build-dotnet.yml | 2 +- .github/workflows/build-java.yml | 2 +- .github/workflows/publish-php.yml | 2 +- .github/workflows/publish-ruby.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-dotnet.yml b/.github/workflows/build-dotnet.yml index 272fec363..1889d5656 100644 --- a/.github/workflows/build-dotnet.yml +++ b/.github/workflows/build-dotnet.yml @@ -1,7 +1,7 @@ name: Build .NET SDK on: - pull_request: + push: branches: - master diff --git a/.github/workflows/build-java.yml b/.github/workflows/build-java.yml index e500424dd..aa99523a7 100644 --- a/.github/workflows/build-java.yml +++ b/.github/workflows/build-java.yml @@ -1,7 +1,7 @@ name: Build Java SDK on: - pull_request: + push: branches: - master diff --git a/.github/workflows/publish-php.yml b/.github/workflows/publish-php.yml index b443f2057..bc1478bc5 100644 --- a/.github/workflows/publish-php.yml +++ b/.github/workflows/publish-php.yml @@ -1,7 +1,7 @@ name: Publish PHP SDK on: - pull_request: + push: branches: - master diff --git a/.github/workflows/publish-ruby.yml b/.github/workflows/publish-ruby.yml index da641dce5..b4e022243 100644 --- a/.github/workflows/publish-ruby.yml +++ b/.github/workflows/publish-ruby.yml @@ -1,7 +1,7 @@ name: Publish Ruby SDK on: - pull_request: + push: branches: - master From 267d79fe6055db6f911317c4605d3812f1adcb49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Fri, 1 Dec 2023 17:17:45 +0100 Subject: [PATCH 051/157] Add binary files to gitignore (#376) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective These files have to be copied in specific places when building the SDK bindings, and having them tracked by git is quite annoying. --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index 007c3e839..18d2d6639 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,13 @@ bld/ [Bb]in/ [Oo]bj/ +# Binary files +*.dylib +*.a +*.so +*.dll +*.class + # Editor directories and files .idea xcuserdata/ From 87327115365d0e75ac32e5714c12566dae647cfd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 00:12:38 +0100 Subject: [PATCH 052/157] [deps]: Update Rust crate wiremock to 0.5.22 (#379) 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 | Type | Update | Change | |---|---|---|---| | [wiremock](https://togithub.com/LukeMathWalker/wiremock-rs) | dev-dependencies | patch | `0.5.21` -> `0.5.22` | --- ### Release Notes
LukeMathWalker/wiremock-rs (wiremock) ### [`v0.5.22`](https://togithub.com/LukeMathWalker/wiremock-rs/compare/v0.5.21...v0.5.22) [Compare Source](https://togithub.com/LukeMathWalker/wiremock-rs/compare/v0.5.21...v0.5.22)
--- ### Configuration πŸ“… **Schedule**: Branch creation - "every weekend" (UTC), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. β™» **Rebasing**: Whenever PR is behind base branch, 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, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/bitwarden/sdk). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- crates/bitwarden/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ad20c5f96..49abafb26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4017,9 +4017,9 @@ dependencies = [ [[package]] name = "wiremock" -version = "0.5.21" +version = "0.5.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "079aee011e8a8e625d16df9e785de30a6b77f80a6126092d76a57375f96448da" +checksum = "13a3a53eaf34f390dd30d7b1b078287dd05df2aa2e21a589ccb80f5c7253c2e9" dependencies = [ "assert-json-diff", "async-trait", diff --git a/crates/bitwarden/Cargo.toml b/crates/bitwarden/Cargo.toml index aea2cfe9e..308f98850 100644 --- a/crates/bitwarden/Cargo.toml +++ b/crates/bitwarden/Cargo.toml @@ -60,4 +60,4 @@ uuid = { version = ">=1.3.3, <2.0", features = ["serde"] } [dev-dependencies] rand_chacha = "0.3.1" tokio = { version = "1.34.0", features = ["rt", "macros"] } -wiremock = "0.5.21" +wiremock = "0.5.22" From ac167df7ecbf39e90e5df8fcd8b809c697ebb961 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 00:13:46 +0100 Subject: [PATCH 053/157] [deps]: Update Rust crate clap to 4.4.10 (#378) --- Cargo.lock | 8 ++++---- crates/bitwarden-cli/Cargo.toml | 2 +- crates/bw/Cargo.toml | 2 +- crates/bws/Cargo.toml | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 49abafb26..cc507ac33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -657,9 +657,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.8" +version = "4.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" +checksum = "41fffed7514f420abec6d183b1d3acfd9099c79c3a10a06ade4f8203f1411272" dependencies = [ "clap_builder", "clap_derive", @@ -667,9 +667,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.8" +version = "4.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" +checksum = "63361bae7eef3771745f02d8d892bec2fee5f6e34af316ba556e7f97a7069ff1" dependencies = [ "anstream", "anstyle", diff --git a/crates/bitwarden-cli/Cargo.toml b/crates/bitwarden-cli/Cargo.toml index 0966f7885..86ca2fac1 100644 --- a/crates/bitwarden-cli/Cargo.toml +++ b/crates/bitwarden-cli/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" rust-version = "1.57" [dependencies] -clap = { version = "4.4.8", features = ["derive"] } +clap = { version = "4.4.10", features = ["derive"] } color-eyre = "0.6" inquire = "0.6.2" supports-color = "2.1.0" diff --git a/crates/bw/Cargo.toml b/crates/bw/Cargo.toml index 9ceb2b153..706628f53 100644 --- a/crates/bw/Cargo.toml +++ b/crates/bw/Cargo.toml @@ -13,7 +13,7 @@ Bitwarden Password Manager CLI keywords = ["bitwarden", "password-manager", "cli"] [dependencies] -clap = { version = "4.4.8", features = ["derive", "env"] } +clap = { version = "4.4.10", features = ["derive", "env"] } color-eyre = "0.6" env_logger = "0.10.1" inquire = "0.6.2" diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 95dbef003..cef520b1a 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -20,7 +20,7 @@ chrono = { version = "0.4.31", features = [ "clock", "std", ], default-features = false } -clap = { version = "4.4.8", features = ["derive", "env", "string"] } +clap = { version = "4.4.10", features = ["derive", "env", "string"] } clap_complete = "4.4.4" color-eyre = "0.6" comfy-table = "^7.1.0" From 4dfc1150e7a82b68b271e625eeca8f8237e750a5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 00:15:07 +0100 Subject: [PATCH 054/157] [deps]: Update System.Text.Json to v7.0.4 (#380) --- languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj b/languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj index c110785a9..951b893ab 100644 --- a/languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj +++ b/languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj @@ -24,7 +24,7 @@ - + From ac340edb88d7813caecd2c552fef2b85204e19da Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 00:15:50 +0100 Subject: [PATCH 055/157] [deps]: Update quicktype-core to v23.0.80 (#382) --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6ee8982ec..71dd5d501 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@openapitools/openapi-generator-cli": "2.7.0", "handlebars": "^4.7.8", "prettier": "3.1.0", - "quicktype-core": "23.0.79", + "quicktype-core": "23.0.80", "rimraf": "5.0.5", "ts-node": "10.9.1", "typescript": "5.2.2" @@ -1494,9 +1494,9 @@ } }, "node_modules/quicktype-core": { - "version": "23.0.79", - "resolved": "https://registry.npmjs.org/quicktype-core/-/quicktype-core-23.0.79.tgz", - "integrity": "sha512-Auzy8AhorFt6YGeB53/dzUSINmKKassAyCsr2wpNgG9XoC3i6oUoLuySNUzYIkyCFCGmKdBRBQeyAqPOVteoYw==", + "version": "23.0.80", + "resolved": "https://registry.npmjs.org/quicktype-core/-/quicktype-core-23.0.80.tgz", + "integrity": "sha512-dd+aJRzAl3MzkaXJMjUu0j60y82gwX/RRr3EvW/aScQKycvkgwliNDN2tIiLB06EKBzjgC9mtlMqKyRg2rYKhQ==", "dev": true, "dependencies": { "@glideapps/ts-necessities": "2.1.3", diff --git a/package.json b/package.json index 97f08c571..3c475897d 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "@openapitools/openapi-generator-cli": "2.7.0", "handlebars": "^4.7.8", "prettier": "3.1.0", - "quicktype-core": "23.0.79", + "quicktype-core": "23.0.80", "rimraf": "5.0.5", "ts-node": "10.9.1", "typescript": "5.2.2" From c4ee7bcf568d265947e1469f76dc8e8fc0a5393e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 10:53:52 +0100 Subject: [PATCH 056/157] [deps]: Update Rust crate async-lock to 3.2.0 (#389) 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 | Type | Update | Change | |---|---|---|---| | [async-lock](https://togithub.com/smol-rs/async-lock) | dependencies | minor | `3.1.2` -> `3.2.0` | --- ### Release Notes
smol-rs/async-lock (async-lock) ### [`v3.2.0`](https://togithub.com/smol-rs/async-lock/blob/HEAD/CHANGELOG.md#Version-320) [Compare Source](https://togithub.com/smol-rs/async-lock/compare/v3.1.2...v3.2.0) - Add missing methods for blocking on locking with types wrapped in `Arc` ([#​71](https://togithub.com/smol-rs/async-lock/issues/71)).
--- ### Configuration πŸ“… **Schedule**: Branch creation - "every weekend" (UTC), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. β™» **Rebasing**: Whenever PR is behind base branch, 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, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/bitwarden/sdk). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- crates/bitwarden-uniffi/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cc507ac33..5aa51175d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -203,9 +203,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.1.2" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea8b3453dd7cc96711834b75400d671b73e3656975fa68d9f277163b7f7e316" +checksum = "7125e42787d53db9dd54261812ef17e937c95a51e4d291373b670342fa44310c" dependencies = [ "event-listener 4.0.0", "event-listener-strategy", diff --git a/crates/bitwarden-uniffi/Cargo.toml b/crates/bitwarden-uniffi/Cargo.toml index c1a47ea29..ecde30a4b 100644 --- a/crates/bitwarden-uniffi/Cargo.toml +++ b/crates/bitwarden-uniffi/Cargo.toml @@ -12,7 +12,7 @@ crate-type = ["lib", "staticlib", "cdylib"] bench = false [dependencies] -async-lock = "3.1.2" +async-lock = "3.2.0" chrono = { version = ">=0.4.26, <0.5", features = [ "serde", "std", From 9244d76b7e910d72f19f780d6e9189d58e268838 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 10:57:27 +0100 Subject: [PATCH 057/157] [deps]: Lock file maintenance (#391) 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: | Update | Change | |---|---| | lockFileMaintenance | All locks refreshed | πŸ”§ This Pull Request updates lock files to use the latest dependency versions. --- ### Configuration πŸ“… **Schedule**: Branch creation - "before 4am on Monday" (UTC), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. β™» **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. πŸ‘» **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://togithub.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/bitwarden/sdk). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 177 ++++++++++++++------- crates/bitwarden-napi/package-lock.json | 12 +- languages/js_webassembly/package-lock.json | 72 ++++----- package-lock.json | 30 ++-- 4 files changed, 179 insertions(+), 112 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5aa51175d..d1c2dc9c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -763,9 +763,9 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400" +checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" dependencies = [ "crossbeam-utils", ] @@ -830,9 +830,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -840,9 +840,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" @@ -961,9 +961,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" dependencies = [ "powerfmt", ] @@ -1049,12 +1049,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1142,18 +1142,18 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "fs-err" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5fd9bcbe8b1087cbd395b51498c01bc997cef73e778a80b77a811af5e2d29f" +checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" dependencies = [ "autocfg", ] @@ -1317,9 +1317,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "glob" @@ -1329,15 +1329,15 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" dependencies = [ "aho-corasick", "bstr", - "fnv", "log", - "regex", + "regex-automata 0.4.3", + "regex-syntax 0.8.2", ] [[package]] @@ -1378,9 +1378,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "heck" @@ -1544,9 +1544,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -1576,7 +1576,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown 0.14.2", + "hashbrown 0.14.3", ] [[package]] @@ -1666,9 +1666,9 @@ checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" dependencies = [ "wasm-bindgen", ] @@ -1726,9 +1726,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "lock_api" @@ -2058,9 +2058,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.59" +version = "0.10.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33" +checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800" dependencies = [ "bitflags 2.4.1", "cfg-if", @@ -2099,9 +2099,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.95" +version = "0.9.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9" +checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f" dependencies = [ "cc", "libc", @@ -2203,9 +2203,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" @@ -2280,9 +2280,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] @@ -2599,9 +2599,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.3" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ef35bf3e7fe15a53c4ab08a998e42271eab13eb0db224126bc7bc4c4bad96d" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" dependencies = [ "const-oid", "digest", @@ -2625,15 +2625,15 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.25" +version = "0.38.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" +checksum = "9470c4bf8246c8daf25f9598dca807fb6510347b1e1cfa55749113850c79d88a" dependencies = [ "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -3011,9 +3011,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spki" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", @@ -3625,9 +3625,9 @@ checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" [[package]] name = "url" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -3734,9 +3734,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.37" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" dependencies = [ "cfg-if", "js-sys", @@ -3775,9 +3775,9 @@ checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] name = "wasm-bindgen-test" -version = "0.3.37" +version = "0.3.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e6e302a7ea94f83a6d09e78e7dc7d9ca7b186bc2829c24a22d0753efd680671" +checksum = "2cf9242c0d27999b831eae4767b2a146feb0b27d332d553e605864acd2afd403" dependencies = [ "console_error_panic_hook", "js-sys", @@ -3789,19 +3789,20 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.37" +version = "0.3.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecb993dd8c836930ed130e020e77d9b2e65dd0fbab1b67c790b0f5d80b11a575" +checksum = "794645f5408c9a039fd09f4d113cdfb2e7eba5ff1956b07bcf701cf4b394fe89" dependencies = [ "proc-macro2", "quote", + "syn 2.0.39", ] [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" dependencies = [ "js-sys", "wasm-bindgen", @@ -3882,6 +3883,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -3912,6 +3922,21 @@ dependencies = [ "windows_x86_64_msvc 0.48.5", ] +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -3924,6 +3949,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -3936,6 +3967,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -3948,6 +3985,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -3960,6 +4003,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -3972,6 +4021,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -3984,6 +4039,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -3996,6 +4057,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winnow" version = "0.5.19" diff --git a/crates/bitwarden-napi/package-lock.json b/crates/bitwarden-napi/package-lock.json index 3ccf3a7cb..a53303523 100644 --- a/crates/bitwarden-napi/package-lock.json +++ b/crates/bitwarden-napi/package-lock.json @@ -95,9 +95,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.9.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.2.tgz", - "integrity": "sha512-WHZXKFCEyIUJzAwh3NyyTHYSR35SevJ6mZ1nWwJafKtiQbqRTIKSRcw3Ma3acqgsent3RRDqeVwpHntMk+9irg==", + "version": "20.10.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.3.tgz", + "integrity": "sha512-XJavIpZqiXID5Yxnxv3RUDKTN5b81ddNC3ecsA0SoFXz/QU8OGBwZGMomiq0zw+uuqbL/krztv/DINAQ/EV4gg==", "dev": true, "peer": true, "dependencies": { @@ -196,9 +196,9 @@ } }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", + "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", "dev": true, "bin": { "tsc": "bin/tsc", diff --git a/languages/js_webassembly/package-lock.json b/languages/js_webassembly/package-lock.json index d5345c00a..c4a2126e6 100644 --- a/languages/js_webassembly/package-lock.json +++ b/languages/js_webassembly/package-lock.json @@ -116,9 +116,9 @@ } }, "node_modules/@types/connect-history-api-fallback": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.3.tgz", - "integrity": "sha512-6mfQ6iNvhSKCZJoY6sIG3m0pKkdUcweVNOLuBBKvoWGzl2yRxOJcYOTRyLKt3nxXvBLJWa6QkW//tgbIwJehmA==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", "dev": true, "dependencies": { "@types/express-serve-static-core": "*", @@ -126,9 +126,9 @@ } }, "node_modules/@types/eslint": { - "version": "8.44.7", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.7.tgz", - "integrity": "sha512-f5ORu2hcBbKei97U73mf+l9t4zTGl74IqZ0GQk4oVea/VS8tQZYkUveSYojk+frraAVYId0V2WC9O4PTNru2FQ==", + "version": "8.44.8", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.8.tgz", + "integrity": "sha512-4K8GavROwhrYl2QXDXm0Rv9epkA8GBFu0EI+XrrnnuCl7u8CWBRusX7fXJfanhZTDWSAL24gDI/UqXyUM0Injw==", "dev": true, "dependencies": { "@types/estree": "*", @@ -209,18 +209,18 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.9.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.2.tgz", - "integrity": "sha512-WHZXKFCEyIUJzAwh3NyyTHYSR35SevJ6mZ1nWwJafKtiQbqRTIKSRcw3Ma3acqgsent3RRDqeVwpHntMk+9irg==", + "version": "20.10.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.3.tgz", + "integrity": "sha512-XJavIpZqiXID5Yxnxv3RUDKTN5b81ddNC3ecsA0SoFXz/QU8OGBwZGMomiq0zw+uuqbL/krztv/DINAQ/EV4gg==", "dev": true, "dependencies": { "undici-types": "~5.26.4" } }, "node_modules/@types/node-forge": { - "version": "1.3.9", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.9.tgz", - "integrity": "sha512-meK88cx/sTalPSLSoCzkiUB4VPIFHmxtXm5FaaqRDqBX2i/Sy8bJ4odsan0b20RBjPh06dAQ+OTTdnyQyhJZyQ==", + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.10.tgz", + "integrity": "sha512-y6PJDYN4xYBxwd22l+OVH35N+1fCYWiuC3aiP2SlXVE6Lo7SS+rSx9r89hLxrP4pn6n1lBGhHJ12pj3F3Mpttw==", "dev": true, "dependencies": { "@types/node": "*" @@ -284,9 +284,9 @@ } }, "node_modules/@types/ws": { - "version": "8.5.9", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.9.tgz", - "integrity": "sha512-jbdrY0a8lxfdTp/+r7Z4CkycbOFN8WX+IOchLJr3juT/xzbJ8URyTVSJ/hvNdadTgM1mnedb47n+Y31GsFnQlg==", + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", "dev": true, "dependencies": { "@types/node": "*" @@ -765,9 +765,9 @@ } }, "node_modules/browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", + "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", "dev": true, "funding": [ { @@ -784,9 +784,9 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", + "caniuse-lite": "^1.0.30001565", + "electron-to-chromium": "^1.4.601", + "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, "bin": { @@ -836,9 +836,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001563", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001563.tgz", - "integrity": "sha512-na2WUmOxnwIZtwnFI2CZ/3er0wdNzU7hN+cPYz/z2ajHThnkWjNBOpEPP4n+4r2WPM847JaMotaJE3bnfzjyKw==", + "version": "1.0.30001566", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001566.tgz", + "integrity": "sha512-ggIhCsTxmITBAMmK8yZjEhCO5/47jKXPu6Dha/wuCS4JePVL+3uiDEBuhu2aIoT+bqTOR8L76Ip1ARL9xYsEJA==", "dev": true, "funding": [ { @@ -917,9 +917,9 @@ } }, "node_modules/clean-css": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.2.tgz", - "integrity": "sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", "dev": true, "dependencies": { "source-map": "~0.6.0" @@ -1278,9 +1278,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.588", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.588.tgz", - "integrity": "sha512-soytjxwbgcCu7nh5Pf4S2/4wa6UIu+A3p03U2yVr53qGxi1/VTR3ENI+p50v+UxqqZAfl48j3z55ud7VHIOr9w==", + "version": "1.4.601", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.601.tgz", + "integrity": "sha512-SpwUMDWe9tQu8JX5QCO1+p/hChAi9AE9UpoC3rcHVc+gdCGlbT3SGb5I1klgb952HRIyvt9wZhSz9bNBYz9swA==", "dev": true }, "node_modules/encodeurl": { @@ -2544,9 +2544,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, "node_modules/normalize-path": { @@ -3661,9 +3661,9 @@ } }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", + "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", "dev": true, "peer": true, "bin": { diff --git a/package-lock.json b/package-lock.json index 71dd5d501..d9f57608b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,9 +19,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", - "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.5.tgz", + "integrity": "sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==", "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" @@ -363,9 +363,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.9.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.2.tgz", - "integrity": "sha512-WHZXKFCEyIUJzAwh3NyyTHYSR35SevJ6mZ1nWwJafKtiQbqRTIKSRcw3Ma3acqgsent3RRDqeVwpHntMk+9irg==", + "version": "20.10.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.3.tgz", + "integrity": "sha512-XJavIpZqiXID5Yxnxv3RUDKTN5b81ddNC3ecsA0SoFXz/QU8OGBwZGMomiq0zw+uuqbL/krztv/DINAQ/EV4gg==", "dev": true, "peer": true, "dependencies": { @@ -373,9 +373,9 @@ } }, "node_modules/@types/urijs": { - "version": "1.19.23", - "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.23.tgz", - "integrity": "sha512-3Zbk6RzmIpvKTNEHO2RcPOGHM++BQEITMqBRR1Ju32WbruhV/pygYgxiP3xA0b1B88zjzs0Izzjxsbj768+IjA==", + "version": "1.19.25", + "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.25.tgz", + "integrity": "sha512-XOfUup9r3Y06nFAZh3WvO0rBU4OtlfPB/vgxpjg+NRdGU6CN6djdc6OEiH+PcqHCY6eFLo9Ista73uarf4gnBg==", "dev": true }, "node_modules/abort-controller": { @@ -584,9 +584,9 @@ } }, "node_modules/cli-spinners": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.1.tgz", - "integrity": "sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", "dev": true, "engines": { "node": ">=6" @@ -1252,9 +1252,9 @@ } }, "node_modules/lru-cache": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.3.tgz", - "integrity": "sha512-B7gr+F6MkqB3uzINHXNctGieGsRTMwIBgxkp0yq/5BwcuDzD4A8wQpHQW6vDAm1uKSLQghmRdD9sKqf2vJ1cEg==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", + "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", "dev": true, "engines": { "node": "14 || >=16.14" From d1ddaa7916ce29dbda28d116bb9fa485ba65a337 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 11:35:07 +0100 Subject: [PATCH 058/157] [deps]: Update typescript to v5.3.2 (#385) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index d9f57608b..bb67d4e04 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "quicktype-core": "23.0.80", "rimraf": "5.0.5", "ts-node": "10.9.1", - "typescript": "5.2.2" + "typescript": "5.3.2" } }, "node_modules/@babel/runtime": { @@ -1936,9 +1936,9 @@ } }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", + "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", "dev": true, "bin": { "tsc": "bin/tsc", diff --git a/package.json b/package.json index 3c475897d..363d8b34c 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,6 @@ "quicktype-core": "23.0.80", "rimraf": "5.0.5", "ts-node": "10.9.1", - "typescript": "5.2.2" + "typescript": "5.3.2" } } From 3d6b4a48c668346938425041273a8b27068578fa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 11:35:29 +0100 Subject: [PATCH 059/157] [deps]: Update rust-wasm-bindgen monorepo (#383) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bitwarden-wasm/Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bitwarden-wasm/Cargo.toml b/crates/bitwarden-wasm/Cargo.toml index 9a14d26b7..dc5bf9ba5 100644 --- a/crates/bitwarden-wasm/Cargo.toml +++ b/crates/bitwarden-wasm/Cargo.toml @@ -10,13 +10,13 @@ crate-type = ["cdylib", "rlib"] [dependencies] console_error_panic_hook = "0.1.7" console_log = { version = "1.0.0", features = ["color"] } -js-sys = "0.3.63" +js-sys = "0.3.66" log = "0.4.20" serde = { version = "1.0.193", features = ["derive"] } wasm-bindgen = { version = "0.2.89", features = ["serde-serialize"] } -wasm-bindgen-futures = "0.4.36" +wasm-bindgen-futures = "0.4.39" bitwarden-json = { path = "../bitwarden-json", features = ["secrets"] } [dev-dependencies] -wasm-bindgen-test = "0.3.36" +wasm-bindgen-test = "0.3.39" From 03495907dd6358253db830ca83e7e7edca1db1ff Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 12:09:22 +0100 Subject: [PATCH 060/157] [deps]: Lock file maintenance (#394) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> From 48aeb58eb63050c658259bfd188dd5fae9da9488 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 4 Dec 2023 18:01:57 +0100 Subject: [PATCH 061/157] Initial TOTP implementation (#392) --- Cargo.lock | 7 + crates/bitwarden-uniffi/src/vault/mod.rs | 16 +- crates/bitwarden/Cargo.toml | 2 + .../bitwarden/src/mobile/vault/client_totp.rs | 9 +- crates/bitwarden/src/vault/mod.rs | 3 +- crates/bitwarden/src/vault/totp.rs | 252 +++++++++++++++++- 6 files changed, 273 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d1c2dc9c0..3a67b163f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -332,6 +332,7 @@ dependencies = [ "bitwarden-api-identity", "cbc", "chrono", + "data-encoding", "getrandom 0.2.11", "hkdf", "hmac", @@ -929,6 +930,12 @@ dependencies = [ "syn 2.0.39", ] +[[package]] +name = "data-encoding" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" + [[package]] name = "deadpool" version = "0.9.5" diff --git a/crates/bitwarden-uniffi/src/vault/mod.rs b/crates/bitwarden-uniffi/src/vault/mod.rs index 79b3e5835..bd5f5332c 100644 --- a/crates/bitwarden-uniffi/src/vault/mod.rs +++ b/crates/bitwarden-uniffi/src/vault/mod.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use bitwarden::vault::TotpResponse; use chrono::{DateTime, Utc}; -use crate::Client; +use crate::{error::Result, Client}; pub mod ciphers; pub mod collections; @@ -47,13 +47,11 @@ impl ClientVault { /// - A base32 encoded string /// - OTP Auth URI /// - Steam URI - pub async fn generate_totp(&self, key: String, time: Option>) -> TotpResponse { - self.0 - .0 - .read() - .await - .vault() - .generate_totp(key, time) - .await + pub async fn generate_totp( + &self, + key: String, + time: Option>, + ) -> Result { + Ok(self.0 .0.read().await.vault().generate_totp(key, time)?) } } diff --git a/crates/bitwarden/Cargo.toml b/crates/bitwarden/Cargo.toml index 308f98850..a9e432c27 100644 --- a/crates/bitwarden/Cargo.toml +++ b/crates/bitwarden/Cargo.toml @@ -30,9 +30,11 @@ bitwarden-api-api = { path = "../bitwarden-api-api", version = "=0.2.2" } bitwarden-api-identity = { path = "../bitwarden-api-identity", version = "=0.2.2" } cbc = { version = ">=0.1.2, <0.2", features = ["alloc"] } chrono = { version = ">=0.4.26, <0.5", features = [ + "clock", "serde", "std", ], default-features = false } +data-encoding = ">=2.5.0, <3.0" # We don't use this directly (it's used by rand), but we need it here to enable WASM support getrandom = { version = ">=0.2.9, <0.3", features = ["js"] } hkdf = ">=0.12.3, <0.13" diff --git a/crates/bitwarden/src/mobile/vault/client_totp.rs b/crates/bitwarden/src/mobile/vault/client_totp.rs index 97eb243af..75bfd204c 100644 --- a/crates/bitwarden/src/mobile/vault/client_totp.rs +++ b/crates/bitwarden/src/mobile/vault/client_totp.rs @@ -1,5 +1,6 @@ use chrono::{DateTime, Utc}; +use crate::error::Result; use crate::vault::{generate_totp, TotpResponse}; use super::client_vault::ClientVault; @@ -12,7 +13,11 @@ impl<'a> ClientVault<'a> { /// - OTP Auth URI /// - Steam URI /// - pub async fn generate_totp(&'a self, key: String, time: Option>) -> TotpResponse { - generate_totp(key, time).await + pub fn generate_totp( + &'a self, + key: String, + time: Option>, + ) -> Result { + generate_totp(key, time) } } diff --git a/crates/bitwarden/src/vault/mod.rs b/crates/bitwarden/src/vault/mod.rs index 12910283c..ca9b12c1c 100644 --- a/crates/bitwarden/src/vault/mod.rs +++ b/crates/bitwarden/src/vault/mod.rs @@ -10,4 +10,5 @@ pub use collection::{Collection, CollectionView}; pub use folder::{Folder, FolderView}; pub use password_history::{PasswordHistory, PasswordHistoryView}; pub use send::{Send, SendListView, SendView}; -pub use totp::{generate_totp, TotpResponse}; +pub(crate) use totp::generate_totp; +pub use totp::TotpResponse; diff --git a/crates/bitwarden/src/vault/totp.rs b/crates/bitwarden/src/vault/totp.rs index 7e701f92e..e080a85a9 100644 --- a/crates/bitwarden/src/vault/totp.rs +++ b/crates/bitwarden/src/vault/totp.rs @@ -1,7 +1,23 @@ +use std::{collections::HashMap, str::FromStr}; + +use crate::error::{Error, Result}; use chrono::{DateTime, Utc}; +use data_encoding::BASE32; +use hmac::{Hmac, Mac}; +use reqwest::Url; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +type HmacSha1 = Hmac; +type HmacSha256 = Hmac; +type HmacSha512 = Hmac; + +const STEAM_CHARS: &str = "23456789BCDFGHJKMNPQRTVWXY"; + +const DEFAULT_ALGORITHM: Algorithm = Algorithm::Sha1; +const DEFAULT_DIGITS: u32 = 6; +const DEFAULT_PERIOD: u32 = 30; + #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] @@ -22,9 +38,237 @@ pub struct TotpResponse { /// - Steam URI /// /// Supports providing an optional time, and defaults to current system time if none is provided. -pub async fn generate_totp(_key: String, _time: Option>) -> TotpResponse { - TotpResponse { - code: "000 000".to_string(), - period: 30, +/// +/// Arguments: +/// - `key` - The key to generate the TOTP code from +/// - `time` - The time in UTC to generate the TOTP code for, defaults to current system time +pub(crate) fn generate_totp(key: String, time: Option>) -> Result { + let params: Totp = key.parse()?; + + let time = time.unwrap_or_else(Utc::now); + + let otp = params.derive_otp(time.timestamp()); + + Ok(TotpResponse { + code: otp, + period: params.period, + }) +} + +#[derive(Clone, Copy, Debug)] +enum Algorithm { + Sha1, + Sha256, + Sha512, + Steam, +} + +impl Algorithm { + // Derive the HMAC hash for the given algorithm + fn derive_hash(&self, key: &[u8], time: &[u8]) -> Vec { + fn compute_digest(digest: D, time: &[u8]) -> Vec { + digest.chain_update(time).finalize().into_bytes().to_vec() + } + + match self { + Algorithm::Sha1 => compute_digest( + HmacSha1::new_from_slice(key).expect("hmac new_from_slice should not fail"), + time, + ), + Algorithm::Sha256 => compute_digest( + HmacSha256::new_from_slice(key).expect("hmac new_from_slice should not fail"), + time, + ), + Algorithm::Sha512 => compute_digest( + HmacSha512::new_from_slice(key).expect("hmac new_from_slice should not fail"), + time, + ), + Algorithm::Steam => compute_digest( + HmacSha1::new_from_slice(key).expect("hmac new_from_slice should not fail"), + time, + ), + } + } +} + +#[derive(Debug)] +struct Totp { + algorithm: Algorithm, + digits: u32, + period: u32, + secret: Vec, +} + +impl Totp { + fn derive_otp(&self, time: i64) -> String { + let time = time / self.period as i64; + + let hash = self + .algorithm + .derive_hash(&self.secret, time.to_be_bytes().as_ref()); + let binary = derive_binary(hash); + + if let Algorithm::Steam = self.algorithm { + derive_steam_otp(binary, self.digits) + } else { + let otp = binary % 10_u32.pow(self.digits); + format!("{1:00$}", self.digits as usize, otp) + } + } +} + +impl FromStr for Totp { + type Err = Error; + + /// Parses the provided key and returns the corresponding `Totp`. + /// + /// Key can be either: + /// - A base32 encoded string + /// - OTP Auth URI + /// - Steam URI + fn from_str(key: &str) -> Result { + fn decode_secret(secret: &str) -> Result> { + BASE32 + .decode(secret.as_bytes()) + .map_err(|_| Error::Internal("Unable to decode secret")) + } + + let params = if key.starts_with("otpauth://") { + let url = Url::parse(key).map_err(|_| Error::Internal("Unable to parse URL"))?; + let parts: HashMap<_, _> = url.query_pairs().collect(); + + Totp { + algorithm: parts + .get("algorithm") + .and_then(|v| match v.to_uppercase().as_ref() { + "SHA1" => Some(Algorithm::Sha1), + "SHA256" => Some(Algorithm::Sha256), + "SHA512" => Some(Algorithm::Sha512), + _ => None, + }) + .unwrap_or(DEFAULT_ALGORITHM), + digits: parts + .get("digits") + .and_then(|v| v.parse().ok()) + .map(|v: u32| v.clamp(0, 10)) + .unwrap_or(DEFAULT_DIGITS), + period: parts + .get("period") + .and_then(|v| v.parse().ok()) + .map(|v: u32| v.max(1)) + .unwrap_or(DEFAULT_PERIOD), + secret: decode_secret( + &parts + .get("secret") + .map(|v| v.to_string()) + .ok_or(Error::Internal("Missing secret in otpauth URI"))?, + )?, + } + } else if let Some(secret) = key.strip_prefix("steam://") { + Totp { + algorithm: Algorithm::Steam, + digits: 5, + period: DEFAULT_PERIOD, + secret: decode_secret(secret)?, + } + } else { + Totp { + algorithm: DEFAULT_ALGORITHM, + digits: DEFAULT_DIGITS, + period: DEFAULT_PERIOD, + secret: decode_secret(key)?, + } + }; + + Ok(params) + } +} + +/// Derive the Steam OTP from the hash with the given number of digits. +fn derive_steam_otp(binary: u32, digits: u32) -> String { + let mut full_code = binary & 0x7fffffff; + + (0..digits) + .map(|_| { + let index = full_code as usize % STEAM_CHARS.len(); + let char = STEAM_CHARS + .chars() + .nth(index) + .expect("Should always be within range"); + full_code /= STEAM_CHARS.len() as u32; + char + }) + .collect() +} + +/// Derive the OTP from the hash with the given number of digits. +fn derive_binary(hash: Vec) -> u32 { + let offset = (hash.last().unwrap_or(&0) & 15) as usize; + + ((hash[offset] & 127) as u32) << 24 + | (hash[offset + 1] as u32) << 16 + | (hash[offset + 2] as u32) << 8 + | hash[offset + 3] as u32 +} + +#[cfg(test)] +mod tests { + use super::*; + use chrono::Utc; + + #[test] + fn test_generate_totp() { + let key = "WQIQ25BRKZYCJVYP".to_string(); + let time = Some( + DateTime::parse_from_rfc3339("2023-01-01T00:00:00.000Z") + .unwrap() + .with_timezone(&Utc), + ); + let response = generate_totp(key, time).unwrap(); + + assert_eq!(response.code, "194506".to_string()); + assert_eq!(response.period, 30); + } + + #[test] + fn test_generate_otpauth() { + let key = "otpauth://totp/test-account?secret=WQIQ25BRKZYCJVYP".to_string(); + let time = Some( + DateTime::parse_from_rfc3339("2023-01-01T00:00:00.000Z") + .unwrap() + .with_timezone(&Utc), + ); + let response = generate_totp(key, time).unwrap(); + + assert_eq!(response.code, "194506".to_string()); + assert_eq!(response.period, 30); + } + + #[test] + fn test_generate_otpauth_period() { + let key = "otpauth://totp/test-account?secret=WQIQ25BRKZYCJVYP&period=60".to_string(); + let time = Some( + DateTime::parse_from_rfc3339("2023-01-01T00:00:00.000Z") + .unwrap() + .with_timezone(&Utc), + ); + let response = generate_totp(key, time).unwrap(); + + assert_eq!(response.code, "730364".to_string()); + assert_eq!(response.period, 60); + } + + #[test] + fn test_generate_steam() { + let key = "steam://HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ".to_string(); + let time = Some( + DateTime::parse_from_rfc3339("2023-01-01T00:00:00.000Z") + .unwrap() + .with_timezone(&Utc), + ); + let response = generate_totp(key, time).unwrap(); + + assert_eq!(response.code, "7W6CJ".to_string()); + assert_eq!(response.period, 30); } } From 7fc3b8436612b6218ca039ff530e12e7d7232a23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Mon, 4 Dec 2023 19:19:11 +0100 Subject: [PATCH 062/157] [PM-3435] Passphrase generator (#262) ## Type of change ``` - [ ] Bug fix - [x] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Added passphrase generator, including `bw` cli support --------- Co-authored-by: Hinton --- .../src/tool/generators/client_generator.rs | 26 +- crates/bitwarden/src/tool/generators/mod.rs | 4 +- .../src/tool/generators/passphrase.rs | 236 ++++++++++++++++++ .../bitwarden/src/tool/generators/password.rs | 17 -- crates/bw/src/main.rs | 32 ++- languages/kotlin/doc.md | 16 +- 6 files changed, 299 insertions(+), 32 deletions(-) create mode 100644 crates/bitwarden/src/tool/generators/passphrase.rs diff --git a/crates/bitwarden/src/tool/generators/client_generator.rs b/crates/bitwarden/src/tool/generators/client_generator.rs index 0384eec6a..8af8ecd19 100644 --- a/crates/bitwarden/src/tool/generators/client_generator.rs +++ b/crates/bitwarden/src/tool/generators/client_generator.rs @@ -1,8 +1,7 @@ use crate::{ error::Result, - tool::generators::password::{ - passphrase, password, PassphraseGeneratorRequest, PasswordGeneratorRequest, - }, + tool::generators::passphrase::{passphrase, PassphraseGeneratorRequest}, + tool::generators::password::{password, PasswordGeneratorRequest}, Client, }; @@ -15,6 +14,27 @@ impl<'a> ClientGenerator<'a> { password(input) } + /// Generates a random passphrase. + /// A passphrase is a combination of random words separated by a character. + /// An example of passphrase is `correct horse battery staple`. + /// + /// The number of words and their case, the word separator, and the inclusion of + /// a number in the passphrase can be customized using the `input` parameter. + /// + /// # Examples + /// + /// ``` + /// use bitwarden::{Client, tool::PassphraseGeneratorRequest, error::Result}; + /// async fn test() -> Result<()> { + /// let input = PassphraseGeneratorRequest { + /// num_words: 4, + /// ..Default::default() + /// }; + /// let passphrase = Client::new(None).generator().passphrase(input).await.unwrap(); + /// println!("{}", passphrase); + /// Ok(()) + /// } + /// ``` pub async fn passphrase(&self, input: PassphraseGeneratorRequest) -> Result { passphrase(input) } diff --git a/crates/bitwarden/src/tool/generators/mod.rs b/crates/bitwarden/src/tool/generators/mod.rs index 4d991b765..12c526930 100644 --- a/crates/bitwarden/src/tool/generators/mod.rs +++ b/crates/bitwarden/src/tool/generators/mod.rs @@ -1,5 +1,7 @@ mod client_generator; +mod passphrase; mod password; pub use client_generator::ClientGenerator; -pub use password::{PassphraseGeneratorRequest, PasswordGeneratorRequest}; +pub use passphrase::PassphraseGeneratorRequest; +pub use password::PasswordGeneratorRequest; diff --git a/crates/bitwarden/src/tool/generators/passphrase.rs b/crates/bitwarden/src/tool/generators/passphrase.rs new file mode 100644 index 000000000..e62fa8f9a --- /dev/null +++ b/crates/bitwarden/src/tool/generators/passphrase.rs @@ -0,0 +1,236 @@ +use crate::{ + error::{Error, Result}, + wordlist::EFF_LONG_WORD_LIST, +}; +use rand::{seq::SliceRandom, Rng, RngCore}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// Passphrase generator request options. +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "mobile", derive(uniffi::Record))] +pub struct PassphraseGeneratorRequest { + /// Number of words in the generated passphrase. + /// This value must be between 3 and 20. + pub num_words: u8, + /// Character separator between words in the generated passphrase. The value cannot be empty. + pub word_separator: String, + /// When set to true, capitalize the first letter of each word in the generated passphrase. + pub capitalize: bool, + /// When set to true, include a number at the end of one of the words in the generated passphrase. + pub include_number: bool, +} + +impl Default for PassphraseGeneratorRequest { + fn default() -> Self { + Self { + num_words: 3, + word_separator: ' '.to_string(), + capitalize: false, + include_number: false, + } + } +} + +const MINIMUM_PASSPHRASE_NUM_WORDS: u8 = 3; +const MAXIMUM_PASSPHRASE_NUM_WORDS: u8 = 20; + +/// Represents a set of valid options to generate a passhprase with. +/// To get an instance of it, use [`PassphraseGeneratorRequest::validate_options`](PassphraseGeneratorRequest::validate_options) +struct ValidPassphraseGeneratorOptions { + pub(super) num_words: u8, + pub(super) word_separator: String, + pub(super) capitalize: bool, + pub(super) include_number: bool, +} + +impl PassphraseGeneratorRequest { + /// Validates the request and returns an immutable struct with valid options to use with the passphrase generator. + fn validate_options(self) -> Result { + // TODO: Add password generator policy checks + + if !(MINIMUM_PASSPHRASE_NUM_WORDS..=MAXIMUM_PASSPHRASE_NUM_WORDS).contains(&self.num_words) + { + return Err(Error::Internal("'num_words' must be between 3 and 20")); + } + + if self.word_separator.chars().next().is_none() { + return Err(Error::Internal("'word_separator' cannot be empty")); + }; + + Ok(ValidPassphraseGeneratorOptions { + num_words: self.num_words, + word_separator: self.word_separator, + capitalize: self.capitalize, + include_number: self.include_number, + }) + } +} + +/// Implementation of the random passphrase generator. This is not accessible to the public API. +/// See [`ClientGenerator::passphrase`](crate::ClientGenerator::passphrase) for the API function. +/// +/// # Arguments: +/// * `options`: Valid parameters used to generate the passphrase. To create it, use +/// [`PassphraseGeneratorRequest::validate_options`](PassphraseGeneratorRequest::validate_options). +pub(super) fn passphrase(request: PassphraseGeneratorRequest) -> Result { + let options = request.validate_options()?; + Ok(passphrase_with_rng(rand::thread_rng(), options)) +} + +fn passphrase_with_rng(mut rng: impl RngCore, options: ValidPassphraseGeneratorOptions) -> String { + let mut passphrase_words = gen_words(&mut rng, options.num_words); + if options.include_number { + include_number_in_words(&mut rng, &mut passphrase_words); + } + if options.capitalize { + capitalize_words(&mut passphrase_words); + } + passphrase_words.join(&options.word_separator) +} + +fn gen_words(mut rng: impl RngCore, num_words: u8) -> Vec { + (0..num_words) + .map(|_| { + EFF_LONG_WORD_LIST + .choose(&mut rng) + .expect("slice is not empty") + .to_string() + }) + .collect() +} + +fn include_number_in_words(mut rng: impl RngCore, words: &mut [String]) { + let number_idx = rng.gen_range(0..words.len()); + words[number_idx].push_str(&rng.gen_range(0..=9).to_string()); +} + +fn capitalize_words(words: &mut [String]) { + words + .iter_mut() + .for_each(|w| *w = capitalize_first_letter(w)); +} + +fn capitalize_first_letter(s: &str) -> String { + // Unicode case conversion can change the length of the string, so we can't capitalize in place. + // Instead we extract the first character and convert it to uppercase. This returns + // an iterator which we collect into a string, and then append the rest of the input. + let mut c = s.chars(); + match c.next() { + None => String::new(), + Some(f) => f.to_uppercase().collect::() + c.as_str(), + } +} + +#[cfg(test)] +mod tests { + use rand::SeedableRng; + + use super::*; + + #[test] + fn test_gen_words() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + assert_eq!( + &gen_words(&mut rng, 4), + &["subsystem", "undertook", "silenced", "dinginess"] + ); + assert_eq!(&gen_words(&mut rng, 1), &["numbing"]); + assert_eq!(&gen_words(&mut rng, 2), &["catnip", "jokester"]); + } + + #[test] + fn test_capitalize() { + assert_eq!(capitalize_first_letter("hello"), "Hello"); + assert_eq!(capitalize_first_letter("1ello"), "1ello"); + assert_eq!(capitalize_first_letter("Hello"), "Hello"); + assert_eq!(capitalize_first_letter("h"), "H"); + assert_eq!(capitalize_first_letter(""), ""); + + // Also supports non-ascii, though the EFF list doesn't have any + assert_eq!(capitalize_first_letter("Ñéíóú"), "Áéíóú"); + } + + #[test] + fn test_capitalize_words() { + let mut words = vec!["hello".into(), "world".into()]; + capitalize_words(&mut words); + assert_eq!(words, &["Hello", "World"]); + } + + #[test] + fn test_include_number() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + + let mut words = vec!["hello".into(), "world".into()]; + include_number_in_words(&mut rng, &mut words); + assert_eq!(words, &["hello", "world7"]); + + let mut words = vec!["This".into(), "is".into(), "a".into(), "test".into()]; + include_number_in_words(&mut rng, &mut words); + assert_eq!(words, &["This", "is", "a1", "test"]); + } + + #[test] + fn test_separator() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + + let input = PassphraseGeneratorRequest { + num_words: 4, + word_separator: "πŸ‘¨πŸ»β€β€οΈβ€πŸ’‹β€πŸ‘¨πŸ»".into(), // This emoji is 35 bytes long, but represented as a single character + capitalize: false, + include_number: true, + } + .validate_options() + .unwrap(); + assert_eq!( + passphrase_with_rng(&mut rng, input), + "subsystem4πŸ‘¨πŸ»β€β€οΈβ€πŸ’‹β€πŸ‘¨πŸ»undertookπŸ‘¨πŸ»β€β€οΈβ€πŸ’‹β€πŸ‘¨πŸ»silencedπŸ‘¨πŸ»β€β€οΈβ€πŸ’‹β€πŸ‘¨πŸ»dinginess" + ); + } + + #[test] + fn test_passphrase() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + + let input = PassphraseGeneratorRequest { + num_words: 4, + word_separator: "-".into(), + capitalize: true, + include_number: true, + } + .validate_options() + .unwrap(); + assert_eq!( + passphrase_with_rng(&mut rng, input), + "Subsystem4-Undertook-Silenced-Dinginess" + ); + + let input = PassphraseGeneratorRequest { + num_words: 3, + word_separator: " ".into(), + capitalize: false, + include_number: true, + } + .validate_options() + .unwrap(); + assert_eq!( + passphrase_with_rng(&mut rng, input), + "drew7 hankering cabana" + ); + + let input = PassphraseGeneratorRequest { + num_words: 5, + word_separator: ";".into(), + capitalize: false, + include_number: false, + } + .validate_options() + .unwrap(); + assert_eq!( + passphrase_with_rng(&mut rng, input), + "duller;backlight;factual;husked;remover" + ); + } +} diff --git a/crates/bitwarden/src/tool/generators/password.rs b/crates/bitwarden/src/tool/generators/password.rs index 0a3874082..237394f56 100644 --- a/crates/bitwarden/src/tool/generators/password.rs +++ b/crates/bitwarden/src/tool/generators/password.rs @@ -27,23 +27,6 @@ pub struct PasswordGeneratorRequest { pub min_special: Option, } -/// Passphrase generator request. -/// -/// The default separator is `-` and default number of words is 3. -#[derive(Serialize, Deserialize, Debug, JsonSchema, Default)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -#[cfg_attr(feature = "mobile", derive(uniffi::Record))] -pub struct PassphraseGeneratorRequest { - pub num_words: Option, - pub word_separator: Option, - pub capitalize: Option, - pub include_number: Option, -} - pub(super) fn password(_input: PasswordGeneratorRequest) -> Result { Ok("pa11w0rd".to_string()) } - -pub(super) fn passphrase(_input: PassphraseGeneratorRequest) -> Result { - Ok("correct-horse-battery-staple".to_string()) -} diff --git a/crates/bw/src/main.rs b/crates/bw/src/main.rs index 609650ab1..c4664c7f4 100644 --- a/crates/bw/src/main.rs +++ b/crates/bw/src/main.rs @@ -1,5 +1,7 @@ use bitwarden::{ - auth::RegisterRequest, client::client_settings::ClientSettings, tool::PasswordGeneratorRequest, + auth::RegisterRequest, + client::client_settings::ClientSettings, + tool::{PassphraseGeneratorRequest, PasswordGeneratorRequest}, }; use bitwarden_cli::{install_color_eyre, text_prompt_when_none, Color}; use clap::{command, Args, CommandFactory, Parser, Subcommand}; @@ -87,7 +89,7 @@ enum ItemCommands { #[derive(Subcommand, Clone)] enum GeneratorCommands { Password(PasswordGeneratorArgs), - Passphrase {}, + Passphrase(PassphraseGeneratorArgs), } #[derive(Args, Clone)] @@ -113,6 +115,18 @@ struct PasswordGeneratorArgs { length: u8, } +#[derive(Args, Clone)] +struct PassphraseGeneratorArgs { + #[arg(long, default_value = "3", help = "Number of words in the passphrase")] + words: u8, + #[arg(long, default_value = " ", help = "Separator between words")] + separator: char, + #[arg(long, action, help = "Capitalize the first letter of each word")] + capitalize: bool, + #[arg(long, action, help = "Include a number in one of the words")] + include_number: bool, +} + #[tokio::main(flavor = "current_thread")] async fn main() -> Result<()> { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); @@ -206,7 +220,19 @@ async fn process_commands() -> Result<()> { println!("{}", password); } - GeneratorCommands::Passphrase {} => todo!(), + GeneratorCommands::Passphrase(args) => { + let passphrase = client + .generator() + .passphrase(PassphraseGeneratorRequest { + num_words: args.words, + word_separator: args.separator.to_string(), + capitalize: args.capitalize, + include_number: args.include_number, + }) + .await?; + + println!("{}", passphrase); + } }, }; diff --git a/languages/kotlin/doc.md b/languages/kotlin/doc.md index 652e09813..a84397719 100644 --- a/languages/kotlin/doc.md +++ b/languages/kotlin/doc.md @@ -1120,23 +1120,23 @@ implementations.
- - + + - - + + - - + + - - + +
numWordsinteger,nullintegerNumber of words in the generated passphrase. This value must be between 3 and 20.
wordSeparatorstring,nullstringCharacter separator between words in the generated passphrase. If the value is set, it cannot be empty.
capitalizeboolean,nullbooleanWhen set to true, capitalize the first letter of each word in the generated passphrase.
includeNumberboolean,nullbooleanWhen set to true, include a number at the end of one of the words in the generated passphrase.
From f684cc9ae7a17b535ca3cf9c8d4bb3a1f04ace16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Mon, 4 Dec 2023 20:14:41 +0100 Subject: [PATCH 063/157] Generate better schema types (#341) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective The `quicktype` schema generation seems to have trouble with types in schema files being referenced from other types in separate schema files, which created duplicate classes, with a lot of strange names. Instead of generating one individual file for each response, we create a big struct with all the options. This seems to solve the issue of references between schema files, and so no duplicate classes are created. Note that the way that the file is loaded in `quicktype` is different. By default, `quicktype` generates JSON conversion helper functions only for the root type, which would be the big struct `SchemaTypes`. By importing the file with `#/definitions/` appended, we only bring the sub schemas as root types, and ignore `SchemaTypes`, which we don't care about. Examples of the schema changes, using C# as an example: - `ProjectsResponse` previously contained an array of `DatumElement`, which were identical to `ProjectResponse`. Now it properly contains an array of `ProjectResponse`. This is also applicable to `SecretsResponse` which contained an array of `DatumClass` - Previously it would generate multiple variants for `Argon2ID`, named: `FluffyArgon2Id` and `PurpleArgon2Id`, now there is only one `Argon2Id`. This is applicable to the two factor response types, which change in the same way This is working correctly with the Java, Go, C++, PHP and Ruby bindings. --- crates/sdk-schemas/src/main.rs | 60 +++++++++++++++++----------------- support/scripts/schemas.ts | 22 +++++-------- 2 files changed, 38 insertions(+), 44 deletions(-) diff --git a/crates/sdk-schemas/src/main.rs b/crates/sdk-schemas/src/main.rs index ef7a2db83..0cfc045a0 100644 --- a/crates/sdk-schemas/src/main.rs +++ b/crates/sdk-schemas/src/main.rs @@ -1,8 +1,7 @@ use std::{fs::File, io::Write}; use anyhow::Result; -use itertools::Itertools; -use schemars::{schema::RootSchema, schema_for}; +use schemars::{schema::RootSchema, schema_for, JsonSchema}; /// Creates a json schema file for any type passed in using Schemars. The filename and path of the generated /// schema file is derived from the namespace passed into the macro or supplied as the first argument. @@ -45,6 +44,8 @@ use schemars::{schema::RootSchema, schema_for}; /// will generate `Response.json` at `{{pwd}}/path/to/folder/Response.json` macro_rules! write_schema_for { ($type:ty) => { + use itertools::Itertools; + let schema = schema_for!($type); let type_name = stringify!($type); @@ -65,12 +66,6 @@ macro_rules! write_schema_for { }; } -macro_rules! write_schema_for_response { - ( $($type:ty),+ $(,)? ) => { - $( write_schema_for!("response", bitwarden_json::response::Response<$type>); )+ - }; -} - fn write_schema(schema: RootSchema, dir_path: String, type_name: String) -> Result<()> { let file_name = type_name .split("::") @@ -88,34 +83,39 @@ fn write_schema(schema: RootSchema, dir_path: String, type_name: String) -> Resu Ok(()) } -fn main() -> Result<()> { +use bitwarden_json::response::Response; + +#[allow(dead_code)] +#[derive(JsonSchema)] +struct SchemaTypes { // Input types for new Client - write_schema_for!(bitwarden::client::client_settings::ClientSettings); + client_settings: bitwarden::client::client_settings::ClientSettings, + // Input types for Client::run_command - write_schema_for!(bitwarden_json::command::Command); + input_command: bitwarden_json::command::Command, // Output types for Client::run_command - // Only add structs which are direct results of SDK commands. - write_schema_for_response! { - bitwarden::auth::login::ApiKeyLoginResponse, - bitwarden::auth::login::PasswordLoginResponse, - bitwarden::auth::login::AccessTokenLoginResponse, - bitwarden::secrets_manager::secrets::SecretIdentifiersResponse, - bitwarden::secrets_manager::secrets::SecretResponse, - bitwarden::secrets_manager::secrets::SecretsResponse, - bitwarden::secrets_manager::secrets::SecretsDeleteResponse, - bitwarden::secrets_manager::projects::ProjectResponse, - bitwarden::secrets_manager::projects::ProjectsResponse, - bitwarden::secrets_manager::projects::ProjectsDeleteResponse, - }; + api_key_login: Response, + password_login: Response, + access_token_login: Response, + secret_identifiers: Response, + secret: Response, + secrets: Response, + secrets_delete: Response, + project: Response, + projects: Response, + projects_delete: Response, - // Same as above, but for the internal feature #[cfg(feature = "internal")] - write_schema_for_response! { - bitwarden::platform::FingerprintResponse, - bitwarden::platform::SyncResponse, - bitwarden::platform::UserApiKeyResponse, - }; + fingerprint: Response, + #[cfg(feature = "internal")] + sync: Response, + #[cfg(feature = "internal")] + user_api_key: Response, +} + +fn main() -> Result<()> { + write_schema_for!("schema_types", SchemaTypes); #[cfg(feature = "internal")] write_schema_for!(bitwarden_uniffi::docs::DocRef); diff --git a/support/scripts/schemas.ts b/support/scripts/schemas.ts index 9e25bedc5..602a68bbb 100644 --- a/support/scripts/schemas.ts +++ b/support/scripts/schemas.ts @@ -22,22 +22,16 @@ async function* walk(dir: string): AsyncIterable { async function main() { const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore()); - - const filenames: string[] = []; - for await (const p of walk("./support/schemas")) { - filenames.push(p); - } - - filenames.sort(); - - for (const f of filenames) { - const buffer = fs.readFileSync(f); - const relative = path.relative(path.join(process.cwd(), "support/schemas"), f); - await schemaInput.addSource({ name: relative, schema: buffer.toString() }); - } - const inputData = new InputData(); inputData.addInput(schemaInput); + inputData.addSource( + "schema", + { + name: "SchemaTypes", + uris: ["support/schemas/schema_types/SchemaTypes.json#/definitions/"], + }, + () => new JSONSchemaInput(new FetchingJSONSchemaStore()), + ); const ts = await quicktype({ inputData, From c006cbb2a04bce4afe90c12719986c68386aa4cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Tue, 5 Dec 2023 14:53:03 +0100 Subject: [PATCH 064/157] [PM-3434] Password generator (#261) ## Type of change ``` - [ ] Bug fix - [x] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Implement password generation The `min_` fields were set to bool incorrectly I assume, so I changed them to u8. At the moment if all the minimums turn out larger than the length, I just expand the length, but that seems wrong. I feel like it would be best to return an error in that case instead of changing any values behind the user, thoughts? --- .../src/tool/generators/client_generator.rs | 21 + .../bitwarden/src/tool/generators/password.rs | 396 +++++++++++++++++- crates/bw/src/main.rs | 2 +- 3 files changed, 401 insertions(+), 18 deletions(-) diff --git a/crates/bitwarden/src/tool/generators/client_generator.rs b/crates/bitwarden/src/tool/generators/client_generator.rs index 8af8ecd19..4675ad99d 100644 --- a/crates/bitwarden/src/tool/generators/client_generator.rs +++ b/crates/bitwarden/src/tool/generators/client_generator.rs @@ -10,6 +10,27 @@ pub struct ClientGenerator<'a> { } impl<'a> ClientGenerator<'a> { + /// Generates a random password. + /// + /// The character sets and password length can be customized using the `input` parameter. + /// + /// # Examples + /// + /// ``` + /// use bitwarden::{Client, tool::PasswordGeneratorRequest, error::Result}; + /// async fn test() -> Result<()> { + /// let input = PasswordGeneratorRequest { + /// lowercase: true, + /// uppercase: true, + /// numbers: true, + /// length: 20, + /// ..Default::default() + /// }; + /// let password = Client::new(None).generator().password(input).await.unwrap(); + /// println!("{}", password); + /// Ok(()) + /// } + /// ``` pub async fn password(&self, input: PasswordGeneratorRequest) -> Result { password(input) } diff --git a/crates/bitwarden/src/tool/generators/password.rs b/crates/bitwarden/src/tool/generators/password.rs index 237394f56..cf986495c 100644 --- a/crates/bitwarden/src/tool/generators/password.rs +++ b/crates/bitwarden/src/tool/generators/password.rs @@ -1,32 +1,394 @@ -use crate::error::Result; +use std::collections::BTreeSet; + +use rand::{distributions::Distribution, seq::SliceRandom, RngCore}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -/// Password generator request. If all options are false, the default is to -/// generate a password with: -/// - lowercase -/// - uppercase -/// - numbers -/// -/// The default length is 16. -#[derive(Serialize, Deserialize, Debug, JsonSchema, Default)] +use crate::error::{Error, Result}; + +/// Password generator request options. +#[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct PasswordGeneratorRequest { + /// Include lowercase characters (a-z). pub lowercase: bool, + /// Include uppercase characters (A-Z). pub uppercase: bool, + /// Include numbers (0-9). pub numbers: bool, + /// Include special characters: ! @ # $ % ^ & * pub special: bool, - pub length: Option, + /// The length of the generated password. + /// Note that the password length must be greater than the sum of all the minimums. + pub length: u8, + + /// When set to true, the generated password will not contain ambiguous characters. + /// The ambiguous characters are: I, O, l, 0, 1 + pub avoid_ambiguous: bool, // TODO: Should we rename this to include_all_characters? + + /// The minimum number of lowercase characters in the generated password. + /// When set, the value must be between 1 and 9. This value is ignored is lowercase is false + pub min_lowercase: Option, + /// The minimum number of uppercase characters in the generated password. + /// When set, the value must be between 1 and 9. This value is ignored is uppercase is false + pub min_uppercase: Option, + /// The minimum number of numbers in the generated password. + /// When set, the value must be between 1 and 9. This value is ignored is numbers is false + pub min_number: Option, + /// The minimum number of special characters in the generated password. + /// When set, the value must be between 1 and 9. This value is ignored is special is false + pub min_special: Option, +} + +const DEFAULT_PASSWORD_LENGTH: u8 = 16; + +impl Default for PasswordGeneratorRequest { + fn default() -> Self { + Self { + lowercase: true, + uppercase: true, + numbers: true, + special: false, + length: DEFAULT_PASSWORD_LENGTH, + avoid_ambiguous: false, + min_lowercase: None, + min_uppercase: None, + min_number: None, + min_special: None, + } + } +} + +const UPPER_CHARS_AMBIGUOUS: &[char] = &['I', 'O']; +const LOWER_CHARS_AMBIGUOUS: &[char] = &['l']; +const NUMBER_CHARS_AMBIGUOUS: &[char] = &['0', '1']; +const SPECIAL_CHARS: &[char] = &['!', '@', '#', '$', '%', '^', '&', '*']; + +/// A set of characters used to generate a password. This set is backed by a BTreeSet +/// to have consistent ordering between runs. This is not important during normal execution, +/// but it's necessary for the tests to be repeatable. +/// To create an instance, use [`CharSet::default()`](CharSet::default) +#[derive(Clone, Default)] +struct CharSet(BTreeSet); +impl CharSet { + /// Includes the given characters in the set. Any duplicate items will be ignored + pub fn include(self, other: impl IntoIterator) -> Self { + self.include_if(true, other) + } + + /// Includes the given characters in the set if the predicate is true. Any duplicate items will be ignored + pub fn include_if(mut self, predicate: bool, other: impl IntoIterator) -> Self { + if predicate { + self.0.extend(other); + } + self + } + + /// Excludes the given characters from the set. Any missing items will be ignored + pub fn exclude_if<'a>( + self, + predicate: bool, + other: impl IntoIterator, + ) -> Self { + if predicate { + let other: BTreeSet<_> = other.into_iter().copied().collect(); + Self(self.0.difference(&other).copied().collect()) + } else { + self + } + } +} +impl<'a> IntoIterator for &'a CharSet { + type Item = char; + type IntoIter = std::iter::Copied>; + fn into_iter(self) -> Self::IntoIter { + self.0.iter().copied() + } +} +impl Distribution for CharSet { + fn sample(&self, rng: &mut R) -> char { + let idx = rng.gen_range(0..self.0.len()); + *self.0.iter().nth(idx).expect("Valid index") + } +} + +/// Represents a set of valid options to generate a password with. +/// To get an instance of it, use [`PasswordGeneratorRequest::validate_options`](PasswordGeneratorRequest::validate_options) +struct PasswordGeneratorOptions { + pub(super) lower: (CharSet, usize), + pub(super) upper: (CharSet, usize), + pub(super) number: (CharSet, usize), + pub(super) special: (CharSet, usize), + pub(super) all: (CharSet, usize), + + pub(super) length: usize, +} + +impl PasswordGeneratorRequest { + /// Validates the request and returns an immutable struct with valid options to use with the password generator. + fn validate_options(self) -> Result { + // TODO: Add password generator policy checks + + // We always have to have at least one character set enabled + if !self.lowercase && !self.uppercase && !self.numbers && !self.special { + return Err(Error::Internal( + "At least one character set must be enabled", + )); + } + + if self.length < 4 { + return Err(Error::Internal( + "A password must be at least 4 characters long", + )); + } + + // Make sure the minimum values are zero when the character + // set is disabled, and at least one when it's enabled + fn get_minimum(min: Option, enabled: bool) -> usize { + if enabled { + usize::max(min.unwrap_or(1) as usize, 1) + } else { + 0 + } + } + + let length = self.length as usize; + let min_lowercase = get_minimum(self.min_lowercase, self.lowercase); + let min_uppercase = get_minimum(self.min_uppercase, self.uppercase); + let min_number = get_minimum(self.min_number, self.numbers); + let min_special = get_minimum(self.min_special, self.special); + + // Check that the minimum lengths aren't larger than the password length + let minimum_length = min_lowercase + min_uppercase + min_number + min_special; + if minimum_length > length { + return Err(Error::Internal( + "Password length can't be less than the sum of the minimums", + )); + } + + let lower = ( + CharSet::default() + .include_if(self.lowercase, 'a'..='z') + .exclude_if(self.avoid_ambiguous, LOWER_CHARS_AMBIGUOUS), + min_lowercase, + ); + + let upper = ( + CharSet::default() + .include_if(self.uppercase, 'A'..='Z') + .exclude_if(self.avoid_ambiguous, UPPER_CHARS_AMBIGUOUS), + min_uppercase, + ); + + let number = ( + CharSet::default() + .include_if(self.numbers, '0'..='9') + .exclude_if(self.avoid_ambiguous, NUMBER_CHARS_AMBIGUOUS), + min_number, + ); + + let special = ( + CharSet::default().include_if(self.special, SPECIAL_CHARS.iter().copied()), + min_special, + ); + + let all = ( + CharSet::default() + .include(&lower.0) + .include(&upper.0) + .include(&number.0) + .include(&special.0), + length - minimum_length, + ); + + Ok(PasswordGeneratorOptions { + lower, + upper, + number, + special, + all, + length, + }) + } +} + +/// Implementation of the random password generator. This is not accessible to the public API. +/// See [`ClientGenerator::password`](crate::ClientGenerator::password) for the API function. +pub(super) fn password(input: PasswordGeneratorRequest) -> Result { + let options = input.validate_options()?; + Ok(password_with_rng(rand::thread_rng(), options)) +} + +fn password_with_rng(mut rng: impl RngCore, options: PasswordGeneratorOptions) -> String { + let mut buf: Vec = Vec::with_capacity(options.length); + + let opts = [ + &options.all, + &options.upper, + &options.lower, + &options.number, + &options.special, + ]; + for (set, qty) in opts { + buf.extend(set.sample_iter(&mut rng).take(*qty)); + } + + buf.shuffle(&mut rng); - pub avoid_ambiguous: Option, // TODO: Should we rename this to include_all_characters? - pub min_lowercase: Option, - pub min_uppercase: Option, - pub min_number: Option, - pub min_special: Option, + buf.iter().collect() } -pub(super) fn password(_input: PasswordGeneratorRequest) -> Result { - Ok("pa11w0rd".to_string()) +#[cfg(test)] +mod test { + use std::collections::BTreeSet; + + use rand::SeedableRng; + + use super::*; + + // We convert the slices to BTreeSets to be able to use `is_subset` + fn ref_to_set<'a>(chars: impl IntoIterator) -> BTreeSet { + chars.into_iter().copied().collect() + } + fn to_set(chars: impl IntoIterator) -> BTreeSet { + chars.into_iter().collect() + } + + #[test] + fn test_password_gen_all_charsets_enabled() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + + let options = PasswordGeneratorRequest { + lowercase: true, + uppercase: true, + numbers: true, + special: true, + avoid_ambiguous: false, + ..Default::default() + } + .validate_options() + .unwrap(); + + assert_eq!(to_set(&options.lower.0), to_set('a'..='z')); + assert_eq!(to_set(&options.upper.0), to_set('A'..='Z')); + assert_eq!(to_set(&options.number.0), to_set('0'..='9')); + assert_eq!(to_set(&options.special.0), ref_to_set(SPECIAL_CHARS)); + + let pass = password_with_rng(&mut rng, options); + assert_eq!(pass, "Z!^B5r%hUa23dFM@"); + } + + #[test] + fn test_password_gen_only_letters_enabled() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + + let options = PasswordGeneratorRequest { + lowercase: true, + uppercase: true, + numbers: false, + special: false, + avoid_ambiguous: false, + ..Default::default() + } + .validate_options() + .unwrap(); + + assert_eq!(to_set(&options.lower.0), to_set('a'..='z')); + assert_eq!(to_set(&options.upper.0), to_set('A'..='Z')); + assert_eq!(to_set(&options.number.0), to_set([])); + assert_eq!(to_set(&options.special.0), to_set([])); + + let pass = password_with_rng(&mut rng, options); + assert_eq!(pass, "NQiFrGufQMiNUAmj"); + } + + #[test] + fn test_password_gen_only_numbers_and_lower_enabled_no_ambiguous() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + + let options = PasswordGeneratorRequest { + lowercase: true, + uppercase: false, + numbers: true, + special: false, + avoid_ambiguous: true, + ..Default::default() + } + .validate_options() + .unwrap(); + + assert!(to_set(&options.lower.0).is_subset(&to_set('a'..='z'))); + assert!(to_set(&options.lower.0).is_disjoint(&ref_to_set(LOWER_CHARS_AMBIGUOUS))); + + assert!(to_set(&options.number.0).is_subset(&to_set('0'..='9'))); + assert!(to_set(&options.number.0).is_disjoint(&ref_to_set(NUMBER_CHARS_AMBIGUOUS))); + + assert_eq!(to_set(&options.upper.0), to_set([])); + assert_eq!(to_set(&options.special.0), to_set([])); + + let pass = password_with_rng(&mut rng, options); + assert_eq!(pass, "mnjabfz5ct272prf"); + } + + #[test] + fn test_password_gen_only_upper_and_special_enabled_no_ambiguous() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + + let options = PasswordGeneratorRequest { + lowercase: false, + uppercase: true, + numbers: false, + special: true, + avoid_ambiguous: true, + ..Default::default() + } + .validate_options() + .unwrap(); + + assert!(to_set(&options.upper.0).is_subset(&to_set('A'..='Z'))); + assert!(to_set(&options.upper.0).is_disjoint(&ref_to_set(UPPER_CHARS_AMBIGUOUS))); + + assert_eq!(to_set(&options.special.0), ref_to_set(SPECIAL_CHARS)); + + assert_eq!(to_set(&options.lower.0), to_set([])); + assert_eq!(to_set(&options.number.0), to_set([])); + + let pass = password_with_rng(&mut rng, options); + assert_eq!(pass, "B*GBQANS%UZPQD!K"); + } + + #[test] + fn test_password_gen_minimum_limits() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + + let options = PasswordGeneratorRequest { + lowercase: true, + uppercase: true, + numbers: true, + special: true, + avoid_ambiguous: false, + length: 24, + min_lowercase: Some(5), + min_uppercase: Some(5), + min_number: Some(5), + min_special: Some(5), + } + .validate_options() + .unwrap(); + + assert_eq!(to_set(&options.lower.0), to_set('a'..='z')); + assert_eq!(to_set(&options.upper.0), to_set('A'..='Z')); + assert_eq!(to_set(&options.number.0), to_set('0'..='9')); + assert_eq!(to_set(&options.special.0), ref_to_set(SPECIAL_CHARS)); + + assert_eq!(options.lower.1, 5); + assert_eq!(options.upper.1, 5); + assert_eq!(options.number.1, 5); + assert_eq!(options.special.1, 5); + + let pass = password_with_rng(&mut rng, options); + assert_eq!(pass, "236q5!a#R%PG5rI%k1!*@uRt"); + } } diff --git a/crates/bw/src/main.rs b/crates/bw/src/main.rs index c4664c7f4..236aef22d 100644 --- a/crates/bw/src/main.rs +++ b/crates/bw/src/main.rs @@ -213,7 +213,7 @@ async fn process_commands() -> Result<()> { uppercase: args.uppercase, numbers: args.numbers, special: args.special, - length: Some(args.length), + length: args.length, ..Default::default() }) .await?; From f8ba13a9a6a9aedfe6da3dedf40d38bd44b64789 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 09:44:14 -0500 Subject: [PATCH 065/157] [deps]: Update actions/setup-dotnet action to v4 (#395) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-dotnet.yml | 2 +- .github/workflows/publish-dotnet.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-dotnet.yml b/.github/workflows/build-dotnet.yml index 1889d5656..7ebb03526 100644 --- a/.github/workflows/build-dotnet.yml +++ b/.github/workflows/build-dotnet.yml @@ -30,7 +30,7 @@ jobs: path: languages/csharp/Bitwarden.Sdk - name: Set up .NET Core - uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 + uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0 with: global-json-file: languages/csharp/global.json diff --git a/.github/workflows/publish-dotnet.yml b/.github/workflows/publish-dotnet.yml index c86b90dd1..b52656de2 100644 --- a/.github/workflows/publish-dotnet.yml +++ b/.github/workflows/publish-dotnet.yml @@ -32,7 +32,7 @@ jobs: path: languages/csharp/Bitwarden.Sdk - name: Set up .NET Core - uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 + uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0 with: global-json-file: languages/csharp/global.json From e5a35bc5127d250aa0a7ecdd8e2a99dc2ed4db8a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 09:47:27 -0500 Subject: [PATCH 066/157] [deps]: Update actions/setup-java action to v4 (#390) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-android.yml | 2 +- .github/workflows/build-java.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index d04d15a45..e3abd0430 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -80,7 +80,7 @@ jobs: key: cargo-combine-cache - name: Setup Java - uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0 + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0 with: distribution: temurin java-version: 17 diff --git a/.github/workflows/build-java.yml b/.github/workflows/build-java.yml index aa99523a7..653b5337b 100644 --- a/.github/workflows/build-java.yml +++ b/.github/workflows/build-java.yml @@ -30,7 +30,7 @@ jobs: path: languages/java/src/main/java/bit/sdk/schema/ - name: Setup Java - uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0 + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0 with: distribution: temurin java-version: 17 From 2e09ae0f9cd8f1621be6b06b1aba68c93d8e73ca Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 09:48:54 -0500 Subject: [PATCH 067/157] [deps]: Update actions/checkout action to v4 (#387) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/publish-php.yml | 2 +- .github/workflows/publish-ruby.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-php.yml b/.github/workflows/publish-php.yml index bc1478bc5..e425e26eb 100644 --- a/.github/workflows/publish-php.yml +++ b/.github/workflows/publish-php.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout Repository - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup PHP with PECL extension uses: shivammathur/setup-php@7fdd3ece872ec7ec4c098ae5ab7637d5e0a96067 # 2.26.0 diff --git a/.github/workflows/publish-ruby.yml b/.github/workflows/publish-ruby.yml index b4e022243..abd472c64 100644 --- a/.github/workflows/publish-ruby.yml +++ b/.github/workflows/publish-ruby.yml @@ -20,7 +20,7 @@ jobs: - build_rust steps: - name: Checkout Repository - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Set up Ruby uses: ruby/setup-ruby@54a18e26dbbb1eabc604f317ade9a5788dddef81 # v1.159.0 From f43b6970561b76c8c179cb4f8cf753da10c10f40 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 09:50:10 -0500 Subject: [PATCH 068/157] [deps]: Update gh minor (#381) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-java.yml | 4 ++-- .github/workflows/generate_schemas.yml | 2 +- .github/workflows/publish-php.yml | 2 +- .github/workflows/publish-ruby.yml | 2 +- .github/workflows/publish-rust-crates.yml | 2 +- .github/workflows/release-cli.yml | 2 +- .github/workflows/release-napi.yml | 2 +- .github/workflows/version-bump.yml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-java.yml b/.github/workflows/build-java.yml index 653b5337b..002164647 100644 --- a/.github/workflows/build-java.yml +++ b/.github/workflows/build-java.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Checkout Repository - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Download Java schemas artifact uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 @@ -60,7 +60,7 @@ jobs: path: languages/java/src/main/resources/windows-x64 - name: Publish Maven - uses: gradle/gradle-build-action@b5126f31dbc19dd434c3269bf8c28c315e121da2 # v2.8.1 + uses: gradle/gradle-build-action@87a9a15658c426a54dd469d4fc7dc1a73ca9d4a6 # v2.10.0 with: arguments: publish build-root-directory: languages/java diff --git a/.github/workflows/generate_schemas.yml b/.github/workflows/generate_schemas.yml index b0517ea36..1d69b1190 100644 --- a/.github/workflows/generate_schemas.yml +++ b/.github/workflows/generate_schemas.yml @@ -72,7 +72,7 @@ jobs: if-no-files-found: error - name: Upload Go schemas artifact - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 with: name: schemas.go path: ${{ github.workspace }}/languages/go/schema.go diff --git a/.github/workflows/publish-php.yml b/.github/workflows/publish-php.yml index e425e26eb..b033319ec 100644 --- a/.github/workflows/publish-php.yml +++ b/.github/workflows/publish-php.yml @@ -20,7 +20,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup PHP with PECL extension - uses: shivammathur/setup-php@7fdd3ece872ec7ec4c098ae5ab7637d5e0a96067 # 2.26.0 + uses: shivammathur/setup-php@e6f75134d35752277f093989e72e140eaa222f35 # 2.28.0 with: php-version: "8.0" tools: composer diff --git a/.github/workflows/publish-ruby.yml b/.github/workflows/publish-ruby.yml index abd472c64..fa46af292 100644 --- a/.github/workflows/publish-ruby.yml +++ b/.github/workflows/publish-ruby.yml @@ -23,7 +23,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Set up Ruby - uses: ruby/setup-ruby@54a18e26dbbb1eabc604f317ade9a5788dddef81 # v1.159.0 + uses: ruby/setup-ruby@8575951200e472d5f2d95c625da0c7bec8217c42 # v1.161.0 with: ruby-version: 3.2 diff --git a/.github/workflows/publish-rust-crates.yml b/.github/workflows/publish-rust-crates.yml index b5a03ad47..010238f40 100644 --- a/.github/workflows/publish-rust-crates.yml +++ b/.github/workflows/publish-rust-crates.yml @@ -103,7 +103,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Login to Azure - uses: Azure/login@4c88f01b0e3a5600e08a37889921afd060f75cf0 # v1.5.0 + uses: Azure/login@de95379fe4dadc2defb305917eaa7e5dde727294 # v1.5.1 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index 9ef782b7f..aad7a0a46 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -128,7 +128,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Login to Azure - uses: Azure/login@4c88f01b0e3a5600e08a37889921afd060f75cf0 # v1.5.0 + uses: Azure/login@de95379fe4dadc2defb305917eaa7e5dde727294 # v1.5.1 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} diff --git a/.github/workflows/release-napi.yml b/.github/workflows/release-napi.yml index 1e7b2e022..3171cfb33 100644 --- a/.github/workflows/release-napi.yml +++ b/.github/workflows/release-napi.yml @@ -126,7 +126,7 @@ jobs: run: npm run tsc - name: Login to Azure - uses: Azure/login@4c88f01b0e3a5600e08a37889921afd060f75cf0 # v1.5.0 + uses: Azure/login@de95379fe4dadc2defb305917eaa7e5dde727294 # v1.5.1 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index 9fb441fa9..5e403ac5b 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -43,7 +43,7 @@ jobs: run: cargo install cargo-edit - name: Login to Azure - Prod Subscription - uses: Azure/login@4c88f01b0e3a5600e08a37889921afd060f75cf0 # v1.5.0 + uses: Azure/login@de95379fe4dadc2defb305917eaa7e5dde727294 # v1.5.1 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} From 6ffef566754a3c75e4384fc4e7efe989abbd76eb Mon Sep 17 00:00:00 2001 From: Colton Hurst Date: Tue, 5 Dec 2023 10:22:35 -0500 Subject: [PATCH 069/157] SM-1023: Remove Instant from Client in SDK (#397) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Refactor out the `Instant` type in Client and use unix timestamps. This is to support this PR: https://github.com/bitwarden/sdk/pull/388 where we need to serialize token expiry times. It also brings benefits to other areas like WASM, where `Instant` doesn't exist. ## Code changes - **crates/bitwarden/src/auth/renew.rs:** Update the `renew_token` function to use timestamps instead of the `Instant` type - **crates/bitwarden/src/client/client.rs:** Update `Client` to store the unix timestamp for token expiration ## Before you submit - Please add **unit tests** where it makes sense to do so (encouraged but not required) --- crates/bitwarden/src/auth/renew.rs | 6 +++--- crates/bitwarden/src/client/client.rs | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/crates/bitwarden/src/auth/renew.rs b/crates/bitwarden/src/auth/renew.rs index 3b2dbef7d..a8bfac51f 100644 --- a/crates/bitwarden/src/auth/renew.rs +++ b/crates/bitwarden/src/auth/renew.rs @@ -1,4 +1,4 @@ -use std::time::{Duration, Instant}; +use chrono::Utc; #[cfg(feature = "internal")] use crate::{auth::api::request::ApiTokenRequest, client::UserLoginMethod}; @@ -9,10 +9,10 @@ use crate::{ }; pub(crate) async fn renew_token(client: &mut Client) -> Result<()> { - const TOKEN_RENEW_MARGIN: Duration = Duration::from_secs(5 * 60); + const TOKEN_RENEW_MARGIN_SECONDS: i64 = 5 * 60; if let (Some(expires), Some(login_method)) = (&client.token_expires_in, &client.login_method) { - if expires > &(Instant::now() + TOKEN_RENEW_MARGIN) { + if Utc::now().timestamp() < expires - TOKEN_RENEW_MARGIN_SECONDS { return Ok(()); } diff --git a/crates/bitwarden/src/client/client.rs b/crates/bitwarden/src/client/client.rs index 5133a5c01..6d1503312 100644 --- a/crates/bitwarden/src/client/client.rs +++ b/crates/bitwarden/src/client/client.rs @@ -1,5 +1,4 @@ -use std::time::{Duration, Instant}; - +use chrono::Utc; use reqwest::header::{self}; use uuid::Uuid; @@ -69,7 +68,7 @@ pub(crate) enum ServiceAccountLoginMethod { pub struct Client { token: Option, pub(crate) refresh_token: Option, - pub(crate) token_expires_in: Option, + pub(crate) token_expires_in: Option, pub(crate) login_method: Option, /// Use Client::get_api_configurations() to access this. @@ -190,7 +189,7 @@ impl Client { ) { self.token = Some(token.clone()); self.refresh_token = refresh_token; - self.token_expires_in = Some(Instant::now() + Duration::from_secs(expires_in)); + self.token_expires_in = Some(Utc::now().timestamp() + expires_in as i64); self.login_method = Some(login_method); self.__api_configurations.identity.oauth_access_token = Some(token.clone()); self.__api_configurations.api.oauth_access_token = Some(token); From 327cbb8f85b0b4f406bdf2ba680f1aa8fda8b6a2 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 5 Dec 2023 16:46:58 +0100 Subject: [PATCH 070/157] Fix regex (#396) --- .github/workflows/delete-old-packages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/delete-old-packages.yml b/.github/workflows/delete-old-packages.yml index 323a9a0a2..50c3a8fef 100644 --- a/.github/workflows/delete-old-packages.yml +++ b/.github/workflows/delete-old-packages.yml @@ -22,4 +22,4 @@ jobs: min-versions-to-keep: 25 # Ignore versions only containing version numbers - ignore-versions: '^\\d*\\.\\d*\\.\\d*(-SNAPSHOT)?$' + ignore-versions: '^\d*\.\d*\.\d*(-SNAPSHOT)?$' From 7ececc54b7888e0a12f26de93772b460aa97cfd2 Mon Sep 17 00:00:00 2001 From: tangowithfoxtrot <5676771+tangowithfoxtrot@users.noreply.github.com> Date: Wed, 6 Dec 2023 10:03:48 -0800 Subject: [PATCH 071/157] rename Python package to `bitwarden_sdk` (#399) ## Type of change - [ ] Bug fix - [ ] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [x] Build/deploy pipeline (DevOps) - [x] Other ## Objective Rename the Python package. This should be merged before #369. ## Code changes - **`languages/python/setup.py`:** Rename package - **`languages/python/bitwarden_sdk`:** Rename directory to match package name - **`.gitignore`:** Changed path to `schemas.py` - **`.github/workflows/generate_schemas.yml`:** Changed path to `schemas.py` ## Before you submit - Please add **unit tests** where it makes sense to do so (encouraged but not required) --- .github/workflows/generate_schemas.yml | 2 +- .gitignore | 2 +- .../python/{BitwardenClient => bitwarden_sdk}/__init__.py | 0 .../{BitwardenClient => bitwarden_sdk}/bitwarden_client.py | 0 languages/python/example.py | 4 ++-- languages/python/setup.py | 4 ++-- 6 files changed, 6 insertions(+), 6 deletions(-) rename languages/python/{BitwardenClient => bitwarden_sdk}/__init__.py (100%) rename languages/python/{BitwardenClient => bitwarden_sdk}/bitwarden_client.py (100%) diff --git a/.github/workflows/generate_schemas.yml b/.github/workflows/generate_schemas.yml index 1d69b1190..29798032e 100644 --- a/.github/workflows/generate_schemas.yml +++ b/.github/workflows/generate_schemas.yml @@ -54,7 +54,7 @@ jobs: uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 with: name: schemas.py - path: ${{ github.workspace }}/languages/python/BitwardenClient/schemas.py + path: ${{ github.workspace }}/languages/python/bitwarden_sdk/schemas.py if-no-files-found: error - name: Upload ruby schemas artifact diff --git a/.gitignore b/.gitignore index 18d2d6639..6ddcd3205 100644 --- a/.gitignore +++ b/.gitignore @@ -56,7 +56,7 @@ support/schemas crates/bitwarden-napi/src-ts/bitwarden_client/schemas.ts languages/csharp/Bitwarden.Sdk/schemas.cs languages/js_webassembly/bitwarden_client/schemas.ts -languages/python/BitwardenClient/schemas.py +languages/python/bitwarden_sdk/schemas.py languages/cpp/include/schemas.hpp languages/go/schema.go languages/java/src/main/java/com/bitwarden/sdk/schema diff --git a/languages/python/BitwardenClient/__init__.py b/languages/python/bitwarden_sdk/__init__.py similarity index 100% rename from languages/python/BitwardenClient/__init__.py rename to languages/python/bitwarden_sdk/__init__.py diff --git a/languages/python/BitwardenClient/bitwarden_client.py b/languages/python/bitwarden_sdk/bitwarden_client.py similarity index 100% rename from languages/python/BitwardenClient/bitwarden_client.py rename to languages/python/bitwarden_sdk/bitwarden_client.py diff --git a/languages/python/example.py b/languages/python/example.py index 2ee1cb280..b3f2ab006 100644 --- a/languages/python/example.py +++ b/languages/python/example.py @@ -1,8 +1,8 @@ import json import logging import sys -from BitwardenClient.bitwarden_client import BitwardenClient -from BitwardenClient.schemas import client_settings_from_dict, DeviceType +from bitwarden_sdk.bitwarden_client import BitwardenClient +from bitwarden_sdk.schemas import client_settings_from_dict, DeviceType # Create the BitwardenClient, which is used to interact with the SDK client = BitwardenClient(client_settings_from_dict({ diff --git a/languages/python/setup.py b/languages/python/setup.py index 7976ebcba..b243a4fe8 100644 --- a/languages/python/setup.py +++ b/languages/python/setup.py @@ -2,11 +2,11 @@ from setuptools_rust import Binding, RustExtension setup( - name="BitwardenClient", + name="bitwarden_sdk", description="A Bitwarden Client for python", version="0.1", rust_extensions=[RustExtension( "bitwarden_py", path="../../crates/bitwarden-py/Cargo.toml", binding=Binding.PyO3)], - packages=['bitwardenclient'], + packages=['bitwarden_sdk'], zip_safe=False, ) From 030e8424ce75ae0969851b244cde5ea599e932bc Mon Sep 17 00:00:00 2001 From: tangowithfoxtrot <5676771+tangowithfoxtrot@users.noreply.github.com> Date: Wed, 6 Dec 2023 13:20:37 -0800 Subject: [PATCH 072/157] Fix broken schema generation (#403) ## Type of change - [x] Bug fix - [ ] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ## Objective Fix broken schema generation workflow from #399. ## Code changes - **./support/scripts/schemas.ts:** Update the Python schemas dir ## Before you submit - Please add **unit tests** where it makes sense to do so (encouraged but not required) --- support/scripts/schemas.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/support/scripts/schemas.ts b/support/scripts/schemas.ts index 602a68bbb..0ca6f023b 100644 --- a/support/scripts/schemas.ts +++ b/support/scripts/schemas.ts @@ -50,7 +50,7 @@ async function main() { }, }); - writeToFile("./languages/python/BitwardenClient/schemas.py", python.lines); + writeToFile("./languages/python/bitwarden_sdk/schemas.py", python.lines); const ruby = await quicktype({ inputData, From f9f1e5a5681a5bbf9b15584ec3632c258555cf6d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 11:07:23 +0100 Subject: [PATCH 073/157] [deps]: Update typescript to v5.3.3 (#384) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bitwarden-napi/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bitwarden-napi/package-lock.json b/crates/bitwarden-napi/package-lock.json index a53303523..df8824a29 100644 --- a/crates/bitwarden-napi/package-lock.json +++ b/crates/bitwarden-napi/package-lock.json @@ -196,9 +196,9 @@ } }, "node_modules/typescript": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", - "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true, "bin": { "tsc": "bin/tsc", From 660211082ef415486c5f78b7ae77ab678cc326d9 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 7 Dec 2023 14:37:28 +0100 Subject: [PATCH 074/157] Mock exports (#404) --- crates/bitwarden/src/tool/exporters/client_exporter.rs | 1 + crates/bitwarden/src/tool/exporters/mod.rs | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/bitwarden/src/tool/exporters/client_exporter.rs b/crates/bitwarden/src/tool/exporters/client_exporter.rs index f14cbc846..9e0dfd5fc 100644 --- a/crates/bitwarden/src/tool/exporters/client_exporter.rs +++ b/crates/bitwarden/src/tool/exporters/client_exporter.rs @@ -10,6 +10,7 @@ pub struct ClientExporters<'a> { } impl<'a> ClientExporters<'a> { + /// **Draft:** Export the vault as a CSV, JSON, or encrypted JSON file. pub async fn export_vault( &self, folders: Vec, diff --git a/crates/bitwarden/src/tool/exporters/mod.rs b/crates/bitwarden/src/tool/exporters/mod.rs index 1170dbd38..d03ddeb77 100644 --- a/crates/bitwarden/src/tool/exporters/mod.rs +++ b/crates/bitwarden/src/tool/exporters/mod.rs @@ -20,9 +20,14 @@ pub enum ExportFormat { pub(super) fn export_vault( _folders: Vec, _ciphers: Vec, - _format: ExportFormat, + format: ExportFormat, ) -> Result { - todo!(); + Ok(match format { + ExportFormat::Csv => "Csv".to_owned(), + ExportFormat::Json => "Json".to_owned(), + ExportFormat::AccountEncryptedJson => "AccountEncryptedJson".to_owned(), + ExportFormat::EncryptedJson { .. } => "EncryptedJson".to_owned(), + }) } pub(super) fn export_organization_vault( From 557fb14b9e783eaa3bc2db2d34056fa4979cdc4b Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 7 Dec 2023 15:38:40 +0100 Subject: [PATCH 075/157] [PM-4930] Implement password validation (#401) --- crates/bitwarden-uniffi/src/auth/mod.rs | 20 ++++- crates/bitwarden-uniffi/src/docs.rs | 2 + crates/bitwarden/src/auth/client_auth.rs | 8 +- crates/bitwarden/src/auth/login/mod.rs | 13 +-- crates/bitwarden/src/auth/login/password.rs | 15 ++-- crates/bitwarden/src/auth/login/two_factor.rs | 11 ++- crates/bitwarden/src/auth/mod.rs | 44 ++++++++++ crates/bitwarden/src/auth/password.rs | 65 +++++++++++++++ crates/bitwarden/src/crypto/master_key.rs | 8 +- crates/bitwarden/src/crypto/mod.rs | 4 +- crates/bitwarden/src/mobile/client_kdf.rs | 7 +- crates/bitwarden/src/mobile/kdf.rs | 3 +- languages/kotlin/doc.md | 83 ++++++++++--------- 13 files changed, 215 insertions(+), 68 deletions(-) diff --git a/crates/bitwarden-uniffi/src/auth/mod.rs b/crates/bitwarden-uniffi/src/auth/mod.rs index a5107768e..cbd2b8e90 100644 --- a/crates/bitwarden-uniffi/src/auth/mod.rs +++ b/crates/bitwarden-uniffi/src/auth/mod.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use bitwarden::{ auth::{password::MasterPasswordPolicyOptions, RegisterKeyResponse}, client::kdf::Kdf, + crypto::HashPurpose, }; use crate::{error::Result, Client}; @@ -50,6 +51,7 @@ impl ClientAuth { email: String, password: String, kdf_params: Kdf, + purpose: HashPurpose, ) -> Result { Ok(self .0 @@ -57,7 +59,7 @@ impl ClientAuth { .read() .await .kdf() - .hash_password(email, password, kdf_params) + .hash_password(email, password, kdf_params, purpose) .await?) } @@ -76,4 +78,20 @@ impl ClientAuth { .auth() .make_register_keys(email, password, kdf)?) } + + /// Validate the user password + /// + /// To retrieve the user's password hash, use [`ClientAuth::hash_password`] with + /// `HashPurpose::LocalAuthentication` during login and persist it. If the login method has no + /// password, use the email OTP. + pub async fn validate_password(&self, password: String, password_hash: String) -> Result { + Ok(self + .0 + .0 + .write() + .await + .auth() + .validate_password(password, password_hash.to_string()) + .await?) + } } diff --git a/crates/bitwarden-uniffi/src/docs.rs b/crates/bitwarden-uniffi/src/docs.rs index d66c78002..e7acbdcae 100644 --- a/crates/bitwarden-uniffi/src/docs.rs +++ b/crates/bitwarden-uniffi/src/docs.rs @@ -1,6 +1,7 @@ use bitwarden::{ auth::password::MasterPasswordPolicyOptions, client::kdf::Kdf, + crypto::HashPurpose, mobile::crypto::{InitOrgCryptoRequest, InitUserCryptoRequest}, platform::FingerprintRequest, tool::{ExportFormat, PassphraseGeneratorRequest, PasswordGeneratorRequest}, @@ -27,6 +28,7 @@ pub enum DocRef { // Crypto InitUserCryptoRequest(InitUserCryptoRequest), InitOrgCryptoRequest(InitOrgCryptoRequest), + HashPurpose(HashPurpose), // Generators PasswordGeneratorRequest(PasswordGeneratorRequest), diff --git a/crates/bitwarden/src/auth/client_auth.rs b/crates/bitwarden/src/auth/client_auth.rs index aa1615c3b..3c096835a 100644 --- a/crates/bitwarden/src/auth/client_auth.rs +++ b/crates/bitwarden/src/auth/client_auth.rs @@ -9,7 +9,9 @@ use crate::{ ApiKeyLoginResponse, PasswordLoginRequest, PasswordLoginResponse, TwoFactorEmailRequest, }, - password::{password_strength, satisfies_policy, MasterPasswordPolicyOptions}, + password::{ + password_strength, satisfies_policy, validate_password, MasterPasswordPolicyOptions, + }, register::{make_register_keys, register}, RegisterKeyResponse, RegisterRequest, }, @@ -90,6 +92,10 @@ impl<'a> ClientAuth<'a> { pub async fn send_two_factor_email(&mut self, tf: &TwoFactorEmailRequest) -> Result<()> { send_two_factor_email(self.client, tf).await } + + pub async fn validate_password(&self, password: String, password_hash: String) -> Result { + validate_password(self.client, password, password_hash).await + } } impl<'a> Client { diff --git a/crates/bitwarden/src/auth/login/mod.rs b/crates/bitwarden/src/auth/login/mod.rs index 4ec9d1b49..d2f66b569 100644 --- a/crates/bitwarden/src/auth/login/mod.rs +++ b/crates/bitwarden/src/auth/login/mod.rs @@ -1,9 +1,6 @@ #[cfg(feature = "internal")] use { - crate::{ - client::{kdf::Kdf, Client}, - error::Result, - }, + crate::{client::Client, error::Result}, bitwarden_api_identity::{ apis::accounts_api::accounts_prelogin_post, models::{PreloginRequestModel, PreloginResponseModel}, @@ -39,14 +36,6 @@ pub(super) use access_token::login_access_token; #[cfg(feature = "secrets")] pub use access_token::{AccessTokenLoginRequest, AccessTokenLoginResponse}; -#[cfg(feature = "internal")] -async fn determine_password_hash(email: &str, kdf: &Kdf, password: &str) -> Result { - use crate::crypto::{HashPurpose, MasterKey}; - - let master_key = MasterKey::derive(password.as_bytes(), email.as_bytes(), kdf)?; - master_key.derive_master_key_hash(password.as_bytes(), HashPurpose::ServerAuthorization) -} - #[cfg(feature = "internal")] pub(crate) async fn request_prelogin( client: &mut Client, diff --git a/crates/bitwarden/src/auth/login/password.rs b/crates/bitwarden/src/auth/login/password.rs index a320131ea..08d7d2054 100644 --- a/crates/bitwarden/src/auth/login/password.rs +++ b/crates/bitwarden/src/auth/login/password.rs @@ -5,10 +5,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "internal")] use crate::{ - auth::{ - api::request::PasswordTokenRequest, - login::{determine_password_hash, TwoFactorRequest}, - }, + auth::{api::request::PasswordTokenRequest, login::TwoFactorRequest}, client::{kdf::Kdf, LoginMethod}, crypto::EncString, Client, @@ -26,12 +23,18 @@ pub(crate) async fn login_password( client: &mut Client, input: &PasswordLoginRequest, ) -> Result { - use crate::client::UserLoginMethod; + use crate::{auth::determine_password_hash, client::UserLoginMethod, crypto::HashPurpose}; info!("password logging in"); debug!("{:#?}, {:#?}", client, input); - let password_hash = determine_password_hash(&input.email, &input.kdf, &input.password).await?; + let password_hash = determine_password_hash( + &input.email, + &input.kdf, + &input.password, + HashPurpose::ServerAuthorization, + ) + .await?; let response = request_identity_tokens(client, input, &password_hash).await?; if let IdentityTokenResponse::Authenticated(r) = &response { diff --git a/crates/bitwarden/src/auth/login/two_factor.rs b/crates/bitwarden/src/auth/login/two_factor.rs index d8ede2473..cf3cb7907 100644 --- a/crates/bitwarden/src/auth/login/two_factor.rs +++ b/crates/bitwarden/src/auth/login/two_factor.rs @@ -3,8 +3,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; -use super::determine_password_hash; -use crate::{error::Result, Client}; +use crate::{auth::determine_password_hash, crypto::HashPurpose, error::Result, Client}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -22,7 +21,13 @@ pub(crate) async fn send_two_factor_email( // TODO: This should be resolved from the client let kdf = client.auth().prelogin(input.email.clone()).await?; - let password_hash = determine_password_hash(&input.email, &kdf, &input.password).await?; + let password_hash = determine_password_hash( + &input.email, + &kdf, + &input.password, + HashPurpose::ServerAuthorization, + ) + .await?; let config = client.get_api_configurations().await; bitwarden_api_api::apis::two_factor_api::two_factor_send_email_login_post( diff --git a/crates/bitwarden/src/auth/mod.rs b/crates/bitwarden/src/auth/mod.rs index 023f3270b..49be82d1c 100644 --- a/crates/bitwarden/src/auth/mod.rs +++ b/crates/bitwarden/src/auth/mod.rs @@ -11,3 +11,47 @@ pub use jwt_token::JWTToken; mod register; #[cfg(feature = "internal")] pub use register::{RegisterKeyResponse, RegisterRequest}; + +#[cfg(feature = "internal")] +use crate::{ + client::kdf::Kdf, + crypto::{HashPurpose, MasterKey}, + error::Result, +}; + +#[cfg(feature = "internal")] +async fn determine_password_hash( + email: &str, + kdf: &Kdf, + password: &str, + purpose: HashPurpose, +) -> Result { + let master_key = MasterKey::derive(password.as_bytes(), email.as_bytes(), kdf)?; + master_key.derive_master_key_hash(password.as_bytes(), purpose) +} + +#[cfg(test)] +mod tests { + use std::num::NonZeroU32; + + use super::*; + + #[cfg(feature = "internal")] + #[tokio::test] + async fn test_determine_password_hash() { + use super::determine_password_hash; + + let password = "password123"; + let email = "test@bitwarden.com"; + let kdf = Kdf::PBKDF2 { + iterations: NonZeroU32::new(100_000).unwrap(), + }; + let purpose = HashPurpose::LocalAuthorization; + + let result = determine_password_hash(email, &kdf, password, purpose) + .await + .unwrap(); + + assert_eq!(result, "7kTqkF1pY/3JeOu73N9kR99fDDe9O1JOZaVc7KH3lsU="); + } +} diff --git a/crates/bitwarden/src/auth/password.rs b/crates/bitwarden/src/auth/password.rs index a38c75ab6..eec68861a 100644 --- a/crates/bitwarden/src/auth/password.rs +++ b/crates/bitwarden/src/auth/password.rs @@ -1,5 +1,13 @@ use schemars::JsonSchema; +use super::determine_password_hash; +use crate::{ + client::{LoginMethod, UserLoginMethod}, + crypto::HashPurpose, + error::{Error, Result}, + Client, +}; + pub(super) fn password_strength( _password: String, _email: String, @@ -16,6 +24,33 @@ pub(super) fn satisfies_policy( true } +/// Validate if the provided password matches the password hash stored in the client. +pub(super) async fn validate_password( + client: &Client, + password: String, + password_hash: String, +) -> Result { + let login_method = client + .login_method + .as_ref() + .ok_or(Error::NotAuthenticated)?; + + if let LoginMethod::User(login_method) = login_method { + match login_method { + UserLoginMethod::Username { email, kdf, .. } + | UserLoginMethod::ApiKey { email, kdf, .. } => { + let hash = + determine_password_hash(email, kdf, &password, HashPurpose::LocalAuthorization) + .await?; + + Ok(hash == password_hash) + } + } + } else { + Err(Error::NotAuthenticated) + } +} + #[derive(Debug, JsonSchema)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] #[allow(dead_code)] @@ -32,3 +67,33 @@ pub struct MasterPasswordPolicyOptions { /// the user will be forced to update their password. enforce_on_login: bool, } + +#[cfg(test)] + +mod tests { + + #[cfg(feature = "mobile")] + #[tokio::test] + async fn test_validate_password() { + use std::num::NonZeroU32; + + use super::validate_password; + use crate::client::{kdf::Kdf, Client, LoginMethod, UserLoginMethod}; + + let mut client = Client::new(None); + client.set_login_method(LoginMethod::User(UserLoginMethod::Username { + email: "test@bitwarden.com".to_string(), + kdf: Kdf::PBKDF2 { + iterations: NonZeroU32::new(100_000).unwrap(), + }, + client_id: "1".to_string(), + })); + + let password = "password123".to_string(); + let password_hash = "7kTqkF1pY/3JeOu73N9kR99fDDe9O1JOZaVc7KH3lsU=".to_string(); + + let result = validate_password(&client, password, password_hash).await; + + assert!(result.unwrap()); + } +} diff --git a/crates/bitwarden/src/crypto/master_key.rs b/crates/bitwarden/src/crypto/master_key.rs index fdcf93dde..88e5b9327 100644 --- a/crates/bitwarden/src/crypto/master_key.rs +++ b/crates/bitwarden/src/crypto/master_key.rs @@ -1,6 +1,7 @@ use aes::cipher::{generic_array::GenericArray, typenum::U32}; use base64::Engine; use rand::Rng; +use schemars::JsonSchema; use sha2::Digest; use super::{ @@ -9,10 +10,11 @@ use super::{ }; use crate::{client::kdf::Kdf, error::Result, util::BASE64_ENGINE}; -#[derive(Copy, Clone)] -pub(crate) enum HashPurpose { +#[derive(Copy, Clone, JsonSchema)] +#[cfg_attr(feature = "mobile", derive(uniffi::Enum))] +pub enum HashPurpose { ServerAuthorization = 1, - // LocalAuthorization = 2, + LocalAuthorization = 2, } /// A Master Key. diff --git a/crates/bitwarden/src/crypto/mod.rs b/crates/bitwarden/src/crypto/mod.rs index b030a5107..fc79ad8f5 100644 --- a/crates/bitwarden/src/crypto/mod.rs +++ b/crates/bitwarden/src/crypto/mod.rs @@ -42,7 +42,9 @@ pub(crate) use shareable_key::derive_shareable_key; #[cfg(feature = "internal")] mod master_key; #[cfg(feature = "internal")] -pub(crate) use master_key::{HashPurpose, MasterKey}; +pub use master_key::HashPurpose; +#[cfg(feature = "internal")] +pub(crate) use master_key::MasterKey; #[cfg(feature = "internal")] mod user_key; #[cfg(feature = "internal")] diff --git a/crates/bitwarden/src/mobile/client_kdf.rs b/crates/bitwarden/src/mobile/client_kdf.rs index 320a03571..6b895f78a 100644 --- a/crates/bitwarden/src/mobile/client_kdf.rs +++ b/crates/bitwarden/src/mobile/client_kdf.rs @@ -1,4 +1,6 @@ -use crate::{client::kdf::Kdf, error::Result, mobile::kdf::hash_password, Client}; +use crate::{ + client::kdf::Kdf, crypto::HashPurpose, error::Result, mobile::kdf::hash_password, Client, +}; pub struct ClientKdf<'a> { pub(crate) client: &'a crate::Client, @@ -10,8 +12,9 @@ impl<'a> ClientKdf<'a> { email: String, password: String, kdf_params: Kdf, + purpose: HashPurpose, ) -> Result { - hash_password(self.client, email, password, kdf_params).await + hash_password(self.client, email, password, kdf_params, purpose).await } } diff --git a/crates/bitwarden/src/mobile/kdf.rs b/crates/bitwarden/src/mobile/kdf.rs index a34b339bb..de2fcc79f 100644 --- a/crates/bitwarden/src/mobile/kdf.rs +++ b/crates/bitwarden/src/mobile/kdf.rs @@ -10,8 +10,9 @@ pub async fn hash_password( email: String, password: String, kdf_params: Kdf, + purpose: HashPurpose, ) -> Result { let master_key = MasterKey::derive(password.as_bytes(), email.as_bytes(), &kdf_params)?; - master_key.derive_master_key_hash(password.as_bytes(), HashPurpose::ServerAuthorization) + master_key.derive_master_key_hash(password.as_bytes(), purpose) } diff --git a/languages/kotlin/doc.md b/languages/kotlin/doc.md index a84397719..f9f47db64 100644 --- a/languages/kotlin/doc.md +++ b/languages/kotlin/doc.md @@ -105,6 +105,7 @@ Hash the user password - email: String - password: String - kdf_params: [Kdf](#kdf) +- purpose: [HashPurpose](#hashpurpose) **Output**: std::result::Result @@ -121,6 +122,22 @@ Generate keys needed for registration process **Output**: std::result::Result +### `validate_password` + +Validate the user password + +To retrieve the user's password hash, use [`ClientAuth::hash_password`] with +`HashPurpose::LocalAuthentication` during login and persist it. If the login method has no +password, use the email OTP. + +**Arguments**: + +- self: +- password: String +- password_hash: String + +**Output**: std::result::Result<,BitwardenError> + ## ClientCiphers ### `encrypt` @@ -493,7 +510,7 @@ The key can be either: - key: String - time: Option -**Output**: [TotpResponse](#totpresponse) +**Output**: std::result::Result # References @@ -901,6 +918,16 @@ implementations. +## `HashPurpose` + + + + + + + +
KeyTypeDescription
+ ## `InitOrgCryptoRequest` @@ -1126,7 +1153,7 @@ implementations. - + @@ -1151,52 +1178,52 @@ implementations. - + - + - + - + - - + + - - + + - - + + - - + + - - + + - - + +
wordSeparator stringCharacter separator between words in the generated passphrase. If the value is set, it cannot be empty.Character separator between words in the generated passphrase. The value cannot be empty.
capitalize
lowercase booleanInclude lowercase characters (a-z).
uppercase booleanInclude uppercase characters (A-Z).
numbers booleanInclude numbers (0-9).
special booleanInclude special characters: ! @ # $ % ^ & *
lengthinteger,nullintegerThe length of the generated password. Note that the password length must be greater than the sum of all the minimums.
avoidAmbiguousboolean,nullbooleanWhen set to true, the generated password will not contain ambiguous characters. The ambiguous characters are: I, O, l, 0, 1
minLowercaseboolean,nullinteger,nullThe minimum number of lowercase characters in the generated password. When set, the value must be between 1 and 9. This value is ignored is lowercase is false
minUppercaseboolean,nullinteger,nullThe minimum number of uppercase characters in the generated password. When set, the value must be between 1 and 9. This value is ignored is uppercase is false
minNumberboolean,nullinteger,nullThe minimum number of numbers in the generated password. When set, the value must be between 1 and 9. This value is ignored is numbers is false
minSpecialboolean,nullinteger,nullThe minimum number of special characters in the generated password. When set, the value must be between 1 and 9. This value is ignored is special is false
@@ -1399,23 +1426,3 @@ implementations. - -## `TotpResponse` - - - - - - - - - - - - - - - - - -
KeyTypeDescription
codestringGenerated TOTP code
periodintegerTime period
From a4bf99b3ab50fbb02758b24a01229854d7633667 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 7 Dec 2023 17:54:44 +0100 Subject: [PATCH 076/157] Rename service_account_id to access_token_id (#405) --- .../src/auth/api/request/access_token_request.rs | 4 ++-- crates/bitwarden/src/auth/login/access_token.rs | 4 ++-- crates/bitwarden/src/auth/renew.rs | 4 ++-- crates/bitwarden/src/client/access_token.rs | 10 +++++----- crates/bitwarden/src/client/client.rs | 2 +- crates/bws/src/main.rs | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/bitwarden/src/auth/api/request/access_token_request.rs b/crates/bitwarden/src/auth/api/request/access_token_request.rs index b5035269b..9f41d6b40 100644 --- a/crates/bitwarden/src/auth/api/request/access_token_request.rs +++ b/crates/bitwarden/src/auth/api/request/access_token_request.rs @@ -13,10 +13,10 @@ pub struct AccessTokenRequest { } impl AccessTokenRequest { - pub fn new(service_account_id: Uuid, client_secret: &String) -> Self { + pub fn new(access_token_id: Uuid, client_secret: &String) -> Self { let obj = Self { scope: "api.secrets".to_string(), - client_id: service_account_id.to_string(), + client_id: access_token_id.to_string(), client_secret: client_secret.to_string(), grant_type: "client_credentials".to_string(), }; diff --git a/crates/bitwarden/src/auth/login/access_token.rs b/crates/bitwarden/src/auth/login/access_token.rs index 1ec8c8b34..8d5996119 100644 --- a/crates/bitwarden/src/auth/login/access_token.rs +++ b/crates/bitwarden/src/auth/login/access_token.rs @@ -59,7 +59,7 @@ pub(crate) async fn login_access_token( r.refresh_token.clone(), r.expires_in, LoginMethod::ServiceAccount(ServiceAccountLoginMethod::AccessToken { - service_account_id: access_token.service_account_id, + access_token_id: access_token.access_token_id, client_secret: access_token.client_secret, organization_id, }), @@ -76,7 +76,7 @@ async fn request_access_token( input: &AccessToken, ) -> Result { let config = client.get_api_configurations().await; - AccessTokenRequest::new(input.service_account_id, &input.client_secret) + AccessTokenRequest::new(input.access_token_id, &input.client_secret) .send(config) .await } diff --git a/crates/bitwarden/src/auth/renew.rs b/crates/bitwarden/src/auth/renew.rs index a8bfac51f..7973947ab 100644 --- a/crates/bitwarden/src/auth/renew.rs +++ b/crates/bitwarden/src/auth/renew.rs @@ -44,11 +44,11 @@ pub(crate) async fn renew_token(client: &mut Client) -> Result<()> { }, LoginMethod::ServiceAccount(s) => match s { ServiceAccountLoginMethod::AccessToken { - service_account_id, + access_token_id, client_secret, .. } => { - AccessTokenRequest::new(*service_account_id, client_secret) + AccessTokenRequest::new(*access_token_id, client_secret) .send(&client.__api_configurations) .await? } diff --git a/crates/bitwarden/src/client/access_token.rs b/crates/bitwarden/src/client/access_token.rs index b68d78572..db6b78f20 100644 --- a/crates/bitwarden/src/client/access_token.rs +++ b/crates/bitwarden/src/client/access_token.rs @@ -10,7 +10,7 @@ use crate::{ }; pub struct AccessToken { - pub service_account_id: Uuid, + pub access_token_id: Uuid, pub client_secret: String, pub encryption_key: SymmetricCryptoKey, } @@ -22,7 +22,7 @@ impl FromStr for AccessToken { let (first_part, encryption_key) = key.split_once(':').ok_or(AccessTokenInvalidError::NoKey)?; - let [version, service_account_id, client_secret]: [&str; 3] = first_part + let [version, access_token_id, client_secret]: [&str; 3] = first_part .split('.') .collect::>() .try_into() @@ -32,7 +32,7 @@ impl FromStr for AccessToken { return Err(AccessTokenInvalidError::WrongVersion.into()); } - let Ok(service_account_id) = service_account_id.parse() else { + let Ok(access_token_id) = access_token_id.parse() else { return Err(AccessTokenInvalidError::InvalidUuid.into()); }; @@ -49,7 +49,7 @@ impl FromStr for AccessToken { derive_shareable_key(encryption_key, "accesstoken", Some("sm-access-token")); Ok(AccessToken { - service_account_id, + access_token_id, client_secret: client_secret.to_owned(), encryption_key, }) @@ -69,7 +69,7 @@ mod tests { let token = AccessToken::from_str(access_token).unwrap(); assert_eq!( - &token.service_account_id.to_string(), + &token.access_token_id.to_string(), "ec2c1d46-6a4b-4751-a310-af9601317f2d" ); assert_eq!(token.client_secret, "C2IgxjjLF7qSshsbwe8JGcbM075YXw"); diff --git a/crates/bitwarden/src/client/client.rs b/crates/bitwarden/src/client/client.rs index 6d1503312..2b6b76a2f 100644 --- a/crates/bitwarden/src/client/client.rs +++ b/crates/bitwarden/src/client/client.rs @@ -58,7 +58,7 @@ pub(crate) enum UserLoginMethod { #[derive(Debug, Clone)] pub(crate) enum ServiceAccountLoginMethod { AccessToken { - service_account_id: Uuid, + access_token_id: Uuid, client_secret: String, organization_id: Uuid, }, diff --git a/crates/bws/src/main.rs b/crates/bws/src/main.rs index 6f27a0e4d..22e3b5dcd 100644 --- a/crates/bws/src/main.rs +++ b/crates/bws/src/main.rs @@ -272,7 +272,7 @@ async fn process_commands() -> Result<()> { profile } else if let Some(access_token) = cli.access_token { AccessToken::from_str(&access_token)? - .service_account_id + .access_token_id .to_string() } else { String::from("default") @@ -619,7 +619,7 @@ fn get_config_profile( profile.to_owned() } else { AccessToken::from_str(access_token)? - .service_account_id + .access_token_id .to_string() }; From 5cf658daf4057da469f4035bc5dd5269270e2a7d Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 7 Dec 2023 17:54:58 +0100 Subject: [PATCH 077/157] Parse exp from JWT (#406) --- crates/bitwarden/src/auth/jwt_token.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/bitwarden/src/auth/jwt_token.rs b/crates/bitwarden/src/auth/jwt_token.rs index 14cbaf84f..9097b84f2 100644 --- a/crates/bitwarden/src/auth/jwt_token.rs +++ b/crates/bitwarden/src/auth/jwt_token.rs @@ -13,6 +13,7 @@ use crate::{error::Result, util::BASE64_ENGINE}; /// TODO: We need to expand this to support user based JWT tokens. #[derive(serde::Deserialize)] pub struct JWTToken { + pub exp: u64, pub sub: String, pub email: Option, pub organization: Option, @@ -62,6 +63,7 @@ mod tests { f3LEddAPV8cAFza4DjA8pZJLFrMyRvMXcL_PjKF8qPVzqVWh03lfJ4clOIxR2gOuWIc902Y5E"; let token: JWTToken = jwt.parse().unwrap(); + assert_eq!(token.exp, 1675107177); assert_eq!(token.sub, "e25d37f3-b603-40de-84ba-af96012f5a42"); assert_eq!(token.email.as_deref(), Some("test@bitwarden.com")); assert_eq!(token.organization.as_deref(), None); From e6977ae187f0cfe90c5be4cc6a79a2375be2b1bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Fri, 8 Dec 2023 13:37:06 +0100 Subject: [PATCH 078/157] Add missing hashPurpose to mobile demo apps (#407) ## Type of change ``` - [x] Bug fix - [ ] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Noticed the mobile demo apps weren't updated with the new hashPurpose parameter, also ran formatting over the files --- .../com/bitwarden/myapplication/MainActivity.kt | 10 ++++++---- languages/swift/iOS/App/ContentView.swift | 14 +++++++------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt b/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt index 3c03c6bca..b41b12b8c 100644 --- a/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt +++ b/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt @@ -24,6 +24,7 @@ import androidx.compose.ui.unit.dp import androidx.fragment.app.FragmentActivity import com.bitwarden.core.DateTime import com.bitwarden.core.Folder +import com.bitwarden.core.HashPurpose import com.bitwarden.core.InitOrgCryptoRequest import com.bitwarden.core.InitUserCryptoMethod import com.bitwarden.core.InitUserCryptoRequest @@ -58,9 +59,9 @@ import javax.net.ssl.X509TrustManager /** * IMPORTANT: This file is provided only for the purpose of demostrating the use of the SDK functionality. - * It hasn't gone through a throrough security review and should not be considered production ready. It also doesn't - * handle a lot of errors and edge cases that a production application would need to deal with. - * Developers are encouraged to review and improve the code as needed to meet their security requirements. + * It hasn't gone through a throrough security review and should not be considered production ready. It also doesn't + * handle a lot of errors and edge cases that a production application would need to deal with. + * Developers are encouraged to review and improve the code as needed to meet their security requirements. * Additionally, we recommend to consult with security experts and conduct thorough testing before using the code in production. */ @@ -181,7 +182,8 @@ class MainActivity : FragmentActivity() { prelogin_body.kdfParallelism!! ) } - val masterPasswordHash = client.auth().hashPassword(EMAIL, PASSWORD, kdf) + val masterPasswordHash = + client.auth().hashPassword(EMAIL, PASSWORD, kdf, HashPurpose.SERVER_AUTHORIZATION) ///////////////////////////// Login ///////////////////////////// diff --git a/languages/swift/iOS/App/ContentView.swift b/languages/swift/iOS/App/ContentView.swift index 5df7dc3fd..c2b488b8f 100644 --- a/languages/swift/iOS/App/ContentView.swift +++ b/languages/swift/iOS/App/ContentView.swift @@ -10,9 +10,9 @@ import SwiftUI /** * IMPORTANT: This file is provided only for the purpose of demostrating the use of the SDK functionality. - * It hasn't gone through a throrough security review and should not be considered production ready. It also doesn't - * handle a lot of errors and edge cases that a production application would need to deal with. - * Developers are encouraged to review and improve the code as needed to meet their security requirements. + * It hasn't gone through a throrough security review and should not be considered production ready. It also doesn't + * handle a lot of errors and edge cases that a production application would need to deal with. + * Developers are encouraged to review and improve the code as needed to meet their security requirements. * Additionally, we recommend to consult with security experts and conduct thorough testing before using the code in production. */ @@ -49,8 +49,8 @@ struct ContentView: View { Button(action: { Task { do { - try await clientExamplePassword(clientAuth: client.auth(), clientCrypto: client.crypto(), setupBiometrics: setupBiometrics) - try await decryptVault(clientCrypto: client.crypto(), clientVault: client.vault()) + try await clientExamplePassword(clientAuth: client.auth(), clientCrypto: client.crypto(), setupBiometrics: setupBiometrics) + try await decryptVault(clientCrypto: client.crypto(), clientVault: client.vault()) } catch { print("ERROR:", error) } @@ -64,7 +64,7 @@ struct ContentView: View { Button(action: { Task { do { - try await clientExampleBiometrics(clientCrypto: client.crypto()) + try await clientExampleBiometrics(clientCrypto: client.crypto()) try await decryptVault(clientCrypto: client.crypto(), clientVault: client.vault()) } catch { print("ERROR:", error) @@ -117,7 +117,7 @@ struct ContentView: View { } let passwordHash = try await clientAuth.hashPassword( - email: EMAIL, password: PASSWORD, kdfParams: kdf) + email: EMAIL, password: PASSWORD, kdfParams: kdf, purpose: .serverAuthorization) ///////////////////////////// Login ///////////////////////////// From 94710e002713c5baf0fc890a9d974d02ef12ec05 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 11:07:24 +0100 Subject: [PATCH 079/157] [deps]: Update prettier to v3.1.1 (#418) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index bb67d4e04..faafd1f1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "devDependencies": { "@openapitools/openapi-generator-cli": "2.7.0", "handlebars": "^4.7.8", - "prettier": "3.1.0", + "prettier": "3.1.1", "quicktype-core": "23.0.80", "rimraf": "5.0.5", "ts-node": "10.9.1", @@ -1470,9 +1470,9 @@ } }, "node_modules/prettier": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz", - "integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz", + "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" diff --git a/package.json b/package.json index 363d8b34c..0332edade 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "devDependencies": { "@openapitools/openapi-generator-cli": "2.7.0", "handlebars": "^4.7.8", - "prettier": "3.1.0", + "prettier": "3.1.1", "quicktype-core": "23.0.80", "rimraf": "5.0.5", "ts-node": "10.9.1", From 34422641d04d168a1600c65ccc319e037cee3720 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 11:07:52 +0100 Subject: [PATCH 080/157] [deps]: Update Rust crate tokio to 1.35.0 (#416) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- crates/bitwarden-py/Cargo.toml | 2 +- crates/bitwarden/Cargo.toml | 2 +- crates/bw/Cargo.toml | 2 +- crates/bws/Cargo.toml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3a67b163f..1e351679e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3256,9 +3256,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.34.0" +version = "1.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" +checksum = "841d45b238a16291a4e1584e61820b8ae57d696cc5015c459c229ccc6990cc1c" dependencies = [ "backtrace", "bytes", diff --git a/crates/bitwarden-py/Cargo.toml b/crates/bitwarden-py/Cargo.toml index 94e9e6a43..3a7c5ca58 100644 --- a/crates/bitwarden-py/Cargo.toml +++ b/crates/bitwarden-py/Cargo.toml @@ -18,7 +18,7 @@ bitwarden-json = { path = "../bitwarden-json", features = ["secrets"] } pyo3-build-config = { version = "0.20.0" } [target.'cfg(not(target_arch="wasm32"))'.dependencies] -tokio = { version = "1.34.0", features = ["rt-multi-thread", "macros"] } +tokio = { version = "1.35.0", features = ["rt-multi-thread", "macros"] } pyo3-asyncio = { version = "0.20.0", features = [ "attributes", "tokio-runtime", diff --git a/crates/bitwarden/Cargo.toml b/crates/bitwarden/Cargo.toml index a9e432c27..01d6a0c11 100644 --- a/crates/bitwarden/Cargo.toml +++ b/crates/bitwarden/Cargo.toml @@ -61,5 +61,5 @@ uuid = { version = ">=1.3.3, <2.0", features = ["serde"] } [dev-dependencies] rand_chacha = "0.3.1" -tokio = { version = "1.34.0", features = ["rt", "macros"] } +tokio = { version = "1.35.0", features = ["rt", "macros"] } wiremock = "0.5.22" diff --git a/crates/bw/Cargo.toml b/crates/bw/Cargo.toml index 706628f53..5f05fcfa3 100644 --- a/crates/bw/Cargo.toml +++ b/crates/bw/Cargo.toml @@ -18,7 +18,7 @@ color-eyre = "0.6" env_logger = "0.10.1" inquire = "0.6.2" log = "0.4.20" -tokio = { version = "1.34.0", features = ["rt-multi-thread", "macros"] } +tokio = { version = "1.35.0", features = ["rt-multi-thread", "macros"] } bitwarden = { path = "../bitwarden", version = "0.3.1", features = [ "internal", diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index cef520b1a..300830e64 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -36,7 +36,7 @@ serde_json = "^1.0.108" serde_yaml = "0.9" supports-color = "2.1.0" thiserror = "1.0.50" -tokio = { version = "1.34.0", features = ["rt-multi-thread", "macros"] } +tokio = { version = "1.35.0", features = ["rt-multi-thread", "macros"] } toml = "0.8.8" uuid = { version = "^1.6.1", features = ["serde"] } From 0a31e05d8d0d7d4832b96f51fe9b3d41fcbc62f6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 11:20:38 +0100 Subject: [PATCH 081/157] [deps]: Update Rust crate clap to 4.4.11 (#410) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- crates/bitwarden-cli/Cargo.toml | 2 +- crates/bw/Cargo.toml | 2 +- crates/bws/Cargo.toml | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1e351679e..7c3426929 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -658,9 +658,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.10" +version = "4.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fffed7514f420abec6d183b1d3acfd9099c79c3a10a06ade4f8203f1411272" +checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" dependencies = [ "clap_builder", "clap_derive", @@ -668,9 +668,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.9" +version = "4.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63361bae7eef3771745f02d8d892bec2fee5f6e34af316ba556e7f97a7069ff1" +checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" dependencies = [ "anstream", "anstyle", diff --git a/crates/bitwarden-cli/Cargo.toml b/crates/bitwarden-cli/Cargo.toml index 86ca2fac1..2591035c2 100644 --- a/crates/bitwarden-cli/Cargo.toml +++ b/crates/bitwarden-cli/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" rust-version = "1.57" [dependencies] -clap = { version = "4.4.10", features = ["derive"] } +clap = { version = "4.4.11", features = ["derive"] } color-eyre = "0.6" inquire = "0.6.2" supports-color = "2.1.0" diff --git a/crates/bw/Cargo.toml b/crates/bw/Cargo.toml index 5f05fcfa3..53712100e 100644 --- a/crates/bw/Cargo.toml +++ b/crates/bw/Cargo.toml @@ -13,7 +13,7 @@ Bitwarden Password Manager CLI keywords = ["bitwarden", "password-manager", "cli"] [dependencies] -clap = { version = "4.4.10", features = ["derive", "env"] } +clap = { version = "4.4.11", features = ["derive", "env"] } color-eyre = "0.6" env_logger = "0.10.1" inquire = "0.6.2" diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 300830e64..ba59ece0e 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -20,7 +20,7 @@ chrono = { version = "0.4.31", features = [ "clock", "std", ], default-features = false } -clap = { version = "4.4.10", features = ["derive", "env", "string"] } +clap = { version = "4.4.11", features = ["derive", "env", "string"] } clap_complete = "4.4.4" color-eyre = "0.6" comfy-table = "^7.1.0" From e1bf48b26afb82be56422988c3465826ecfa92c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Mon, 11 Dec 2023 11:22:38 +0100 Subject: [PATCH 082/157] Use tokio executor for uniffi async (#408) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective When testing the username generator using uniffi, I noticed that uniffi was using it's own async executor by default, while reqwest was expecting tokio, which caused errors. I've changed all uniffi exported async functions to use tokio by default, to avoid these issues. --- Cargo.lock | 14 ++++++++++++++ crates/bitwarden-uniffi/src/auth/mod.rs | 2 +- crates/bitwarden-uniffi/src/crypto.rs | 2 +- crates/bitwarden-uniffi/src/platform/mod.rs | 2 +- crates/bitwarden-uniffi/src/tool/mod.rs | 4 ++-- crates/bitwarden-uniffi/src/vault/ciphers.rs | 2 +- crates/bitwarden-uniffi/src/vault/collections.rs | 2 +- crates/bitwarden-uniffi/src/vault/folders.rs | 2 +- crates/bitwarden-uniffi/src/vault/mod.rs | 2 +- .../bitwarden-uniffi/src/vault/password_history.rs | 2 +- crates/bitwarden-uniffi/src/vault/sends.rs | 2 +- crates/bitwarden/Cargo.toml | 2 +- 12 files changed, 26 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7c3426929..e7ba84c53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -201,6 +201,19 @@ dependencies = [ "futures-core", ] +[[package]] +name = "async-compat" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f68a707c1feb095d8c07f8a65b9f506b117d30af431cab89374357de7c11461b" +dependencies = [ + "futures-core", + "futures-io", + "once_cell", + "pin-project-lite", + "tokio", +] + [[package]] name = "async-lock" version = "3.2.0" @@ -3556,6 +3569,7 @@ version = "0.25.2" source = "git+https://github.com/mozilla/uniffi-rs?rev=23711c8151bbb794369aa1f9d383db386792dff9#23711c8151bbb794369aa1f9d383db386792dff9" dependencies = [ "anyhow", + "async-compat", "bytes", "camino", "log", diff --git a/crates/bitwarden-uniffi/src/auth/mod.rs b/crates/bitwarden-uniffi/src/auth/mod.rs index cbd2b8e90..ded3f776a 100644 --- a/crates/bitwarden-uniffi/src/auth/mod.rs +++ b/crates/bitwarden-uniffi/src/auth/mod.rs @@ -11,7 +11,7 @@ use crate::{error::Result, Client}; #[derive(uniffi::Object)] pub struct ClientAuth(pub(crate) Arc); -#[uniffi::export] +#[uniffi::export(async_runtime = "tokio")] impl ClientAuth { /// **API Draft:** Calculate Password Strength pub async fn password_strength( diff --git a/crates/bitwarden-uniffi/src/crypto.rs b/crates/bitwarden-uniffi/src/crypto.rs index 804202e2f..995750b7f 100644 --- a/crates/bitwarden-uniffi/src/crypto.rs +++ b/crates/bitwarden-uniffi/src/crypto.rs @@ -7,7 +7,7 @@ use crate::{error::Result, Client}; #[derive(uniffi::Object)] pub struct ClientCrypto(pub(crate) Arc); -#[uniffi::export] +#[uniffi::export(async_runtime = "tokio")] impl ClientCrypto { /// Initialization method for the user crypto. Needs to be called before any other crypto operations. pub async fn initialize_user_crypto(&self, req: InitUserCryptoRequest) -> Result<()> { diff --git a/crates/bitwarden-uniffi/src/platform/mod.rs b/crates/bitwarden-uniffi/src/platform/mod.rs index f4eaaa095..f03ed01e5 100644 --- a/crates/bitwarden-uniffi/src/platform/mod.rs +++ b/crates/bitwarden-uniffi/src/platform/mod.rs @@ -7,7 +7,7 @@ use crate::{error::Result, Client}; #[derive(uniffi::Object)] pub struct ClientPlatform(pub(crate) Arc); -#[uniffi::export] +#[uniffi::export(async_runtime = "tokio")] impl ClientPlatform { /// Fingerprint pub async fn fingerprint(&self, req: FingerprintRequest) -> Result { diff --git a/crates/bitwarden-uniffi/src/tool/mod.rs b/crates/bitwarden-uniffi/src/tool/mod.rs index 3243cceb6..6622a1b2d 100644 --- a/crates/bitwarden-uniffi/src/tool/mod.rs +++ b/crates/bitwarden-uniffi/src/tool/mod.rs @@ -10,7 +10,7 @@ use crate::{error::Result, Client}; #[derive(uniffi::Object)] pub struct ClientGenerators(pub(crate) Arc); -#[uniffi::export] +#[uniffi::export(async_runtime = "tokio")] impl ClientGenerators { /// **API Draft:** Generate Password pub async fn password(&self, settings: PasswordGeneratorRequest) -> Result { @@ -40,7 +40,7 @@ impl ClientGenerators { #[derive(uniffi::Object)] pub struct ClientExporters(pub(crate) Arc); -#[uniffi::export] +#[uniffi::export(async_runtime = "tokio")] impl ClientExporters { /// **API Draft:** Export user vault pub async fn export_vault( diff --git a/crates/bitwarden-uniffi/src/vault/ciphers.rs b/crates/bitwarden-uniffi/src/vault/ciphers.rs index 3122fe2c1..eb8543947 100644 --- a/crates/bitwarden-uniffi/src/vault/ciphers.rs +++ b/crates/bitwarden-uniffi/src/vault/ciphers.rs @@ -7,7 +7,7 @@ use crate::{Client, Result}; #[derive(uniffi::Object)] pub struct ClientCiphers(pub Arc); -#[uniffi::export] +#[uniffi::export(async_runtime = "tokio")] impl ClientCiphers { /// Encrypt cipher pub async fn encrypt(&self, cipher_view: CipherView) -> Result { diff --git a/crates/bitwarden-uniffi/src/vault/collections.rs b/crates/bitwarden-uniffi/src/vault/collections.rs index 0a1ab8976..53a7500ec 100644 --- a/crates/bitwarden-uniffi/src/vault/collections.rs +++ b/crates/bitwarden-uniffi/src/vault/collections.rs @@ -7,7 +7,7 @@ use crate::{Client, Result}; #[derive(uniffi::Object)] pub struct ClientCollections(pub Arc); -#[uniffi::export] +#[uniffi::export(async_runtime = "tokio")] impl ClientCollections { /// Decrypt collection pub async fn decrypt(&self, collection: Collection) -> Result { diff --git a/crates/bitwarden-uniffi/src/vault/folders.rs b/crates/bitwarden-uniffi/src/vault/folders.rs index d9f955b97..7cbbdc0ae 100644 --- a/crates/bitwarden-uniffi/src/vault/folders.rs +++ b/crates/bitwarden-uniffi/src/vault/folders.rs @@ -7,7 +7,7 @@ use crate::{Client, Result}; #[derive(uniffi::Object)] pub struct ClientFolders(pub Arc); -#[uniffi::export] +#[uniffi::export(async_runtime = "tokio")] impl ClientFolders { /// Encrypt folder pub async fn encrypt(&self, folder: FolderView) -> Result { diff --git a/crates/bitwarden-uniffi/src/vault/mod.rs b/crates/bitwarden-uniffi/src/vault/mod.rs index bd5f5332c..435e7f355 100644 --- a/crates/bitwarden-uniffi/src/vault/mod.rs +++ b/crates/bitwarden-uniffi/src/vault/mod.rs @@ -14,7 +14,7 @@ pub mod sends; #[derive(uniffi::Object)] pub struct ClientVault(pub(crate) Arc); -#[uniffi::export] +#[uniffi::export(async_runtime = "tokio")] impl ClientVault { /// Folder operations pub fn folders(self: Arc) -> Arc { diff --git a/crates/bitwarden-uniffi/src/vault/password_history.rs b/crates/bitwarden-uniffi/src/vault/password_history.rs index 9042aa8ad..470e28822 100644 --- a/crates/bitwarden-uniffi/src/vault/password_history.rs +++ b/crates/bitwarden-uniffi/src/vault/password_history.rs @@ -7,7 +7,7 @@ use crate::{Client, Result}; #[derive(uniffi::Object)] pub struct ClientPasswordHistory(pub Arc); -#[uniffi::export] +#[uniffi::export(async_runtime = "tokio")] impl ClientPasswordHistory { /// Encrypt password history pub async fn encrypt(&self, password_history: PasswordHistoryView) -> Result { diff --git a/crates/bitwarden-uniffi/src/vault/sends.rs b/crates/bitwarden-uniffi/src/vault/sends.rs index 6e2f1b879..e306168b8 100644 --- a/crates/bitwarden-uniffi/src/vault/sends.rs +++ b/crates/bitwarden-uniffi/src/vault/sends.rs @@ -7,7 +7,7 @@ use crate::{Client, Result}; #[derive(uniffi::Object)] pub struct ClientSends(pub Arc); -#[uniffi::export] +#[uniffi::export(async_runtime = "tokio")] impl ClientSends { /// Encrypt send pub async fn encrypt(&self, send: SendView) -> Result { diff --git a/crates/bitwarden/Cargo.toml b/crates/bitwarden/Cargo.toml index 01d6a0c11..4d92e4270 100644 --- a/crates/bitwarden/Cargo.toml +++ b/crates/bitwarden/Cargo.toml @@ -56,7 +56,7 @@ sha1 = ">=0.10.5, <0.11" sha2 = ">=0.10.6, <0.11" subtle = ">=2.5.0, <3.0" thiserror = ">=1.0.40, <2.0" -uniffi = { version = "=0.25.2", optional = true } +uniffi = { version = "=0.25.2", optional = true, features = ["tokio"] } uuid = { version = ">=1.3.3, <2.0", features = ["serde"] } [dev-dependencies] From ebe2acb36b7aa8c4c558721ca546e1b202a57a2e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 16:41:46 +0100 Subject: [PATCH 083/157] [deps]: Update @napi-rs/cli to v2.17.0 (#415) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bitwarden-napi/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bitwarden-napi/package-lock.json b/crates/bitwarden-napi/package-lock.json index df8824a29..00e591aaf 100644 --- a/crates/bitwarden-napi/package-lock.json +++ b/crates/bitwarden-napi/package-lock.json @@ -55,9 +55,9 @@ } }, "node_modules/@napi-rs/cli": { - "version": "2.16.5", - "resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.16.5.tgz", - "integrity": "sha512-mFEzwrg4IOLngGd2/P6yeqIWgwQNn59Z08n1rndu6kLDq1gg954NH9cM1O9Da0RJuybt46p43lqgSsnAY2mxqA==", + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.17.0.tgz", + "integrity": "sha512-/M7MZ3dIqyFs6c0Bxtk+SOobPq6vYWYqBLYCOKx3dYWqoyJNBEgmDKUTrxIZu9eHw9Ill3WyEoHPqS9P99cd8A==", "dev": true, "bin": { "napi": "scripts/index.js" From 784c0ef7ad838cb28b7d2b4f03dbbb54c0c6914c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 13:11:44 -0500 Subject: [PATCH 084/157] [deps]: Update actions/setup-go action to v5 (#417) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/golang-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/golang-release.yml b/.github/workflows/golang-release.yml index 6f41bcd3e..f42d428f9 100644 --- a/.github/workflows/golang-release.yml +++ b/.github/workflows/golang-release.yml @@ -29,7 +29,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup Go environment - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 + uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ env.GO_VERSION }} @@ -56,7 +56,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup Go environment - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 + uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ env.GO_VERSION }} From 18036ea12d5fbb900b776265ae01cc06a57cde35 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 13:12:07 -0500 Subject: [PATCH 085/157] [deps]: Update dtolnay/rust-toolchain digest to be73d79 (#409) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-android.yml | 4 ++-- .github/workflows/build-cli.yml | 4 ++-- .github/workflows/build-napi.yml | 2 +- .github/workflows/build-rust-crates.yml | 4 ++-- .github/workflows/build-rust-cross-platform.yml | 2 +- .github/workflows/direct-minimal-versions.yml | 2 +- .github/workflows/generate_schemas.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/publish-rust-crates.yml | 2 +- .github/workflows/release-cli.yml | 2 +- .github/workflows/rust-test.yml | 4 ++-- .github/workflows/version-bump.yml | 2 +- 12 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index e3abd0430..22eda7f88 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -28,7 +28,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@be73d7920c329f220ce78e0234b8f96b7ae60248 # stable with: toolchain: 1.67.0 # https://github.com/cross-rs/cross/issues/1222 @@ -70,7 +70,7 @@ jobs: fetch-depth: 0 - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@be73d7920c329f220ce78e0234b8f96b7ae60248 # stable with: toolchain: stable diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index 9aa39ab51..4239a9a0a 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -63,7 +63,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@be73d7920c329f220ce78e0234b8f96b7ae60248 # stable with: toolchain: stable targets: ${{ matrix.settings.target }} @@ -158,7 +158,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@be73d7920c329f220ce78e0234b8f96b7ae60248 # stable with: toolchain: stable diff --git a/.github/workflows/build-napi.yml b/.github/workflows/build-napi.yml index 16f87a177..d5bd935e9 100644 --- a/.github/workflows/build-napi.yml +++ b/.github/workflows/build-napi.yml @@ -61,7 +61,7 @@ jobs: cache-dependency-path: crates/bitwarden-napi/package-lock.json - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@be73d7920c329f220ce78e0234b8f96b7ae60248 # stable with: toolchain: stable targets: ${{ matrix.settings.target }} diff --git a/.github/workflows/build-rust-crates.yml b/.github/workflows/build-rust-crates.yml index 203276556..7504887f0 100644 --- a/.github/workflows/build-rust-crates.yml +++ b/.github/workflows/build-rust-crates.yml @@ -37,7 +37,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@be73d7920c329f220ce78e0234b8f96b7ae60248 # stable with: toolchain: stable targets: ${{ matrix.settings.target }} @@ -67,7 +67,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@be73d7920c329f220ce78e0234b8f96b7ae60248 # stable with: toolchain: stable targets: ${{ matrix.settings.target }} diff --git a/.github/workflows/build-rust-cross-platform.yml b/.github/workflows/build-rust-cross-platform.yml index 7cfa9b078..39039359f 100644 --- a/.github/workflows/build-rust-cross-platform.yml +++ b/.github/workflows/build-rust-cross-platform.yml @@ -25,7 +25,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@be73d7920c329f220ce78e0234b8f96b7ae60248 # stable with: toolchain: stable diff --git a/.github/workflows/direct-minimal-versions.yml b/.github/workflows/direct-minimal-versions.yml index b2085892a..f164a2433 100644 --- a/.github/workflows/direct-minimal-versions.yml +++ b/.github/workflows/direct-minimal-versions.yml @@ -39,7 +39,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@be73d7920c329f220ce78e0234b8f96b7ae60248 # stable with: toolchain: nightly targets: ${{ matrix.settings.target }} diff --git a/.github/workflows/generate_schemas.yml b/.github/workflows/generate_schemas.yml index 29798032e..efd7dd68d 100644 --- a/.github/workflows/generate_schemas.yml +++ b/.github/workflows/generate_schemas.yml @@ -16,7 +16,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@be73d7920c329f220ce78e0234b8f96b7ae60248 # stable with: toolchain: stable diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index cb1fb2b67..04b28ed25 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,7 +20,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@be73d7920c329f220ce78e0234b8f96b7ae60248 # stable with: toolchain: stable diff --git a/.github/workflows/publish-rust-crates.yml b/.github/workflows/publish-rust-crates.yml index 010238f40..aef94b37e 100644 --- a/.github/workflows/publish-rust-crates.yml +++ b/.github/workflows/publish-rust-crates.yml @@ -115,7 +115,7 @@ jobs: secrets: "cratesio-api-token" - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@be73d7920c329f220ce78e0234b8f96b7ae60248 # stable with: toolchain: stable diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index aad7a0a46..04863f34e 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -140,7 +140,7 @@ jobs: secrets: "cratesio-api-token" - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@be73d7920c329f220ce78e0234b8f96b7ae60248 # stable with: toolchain: stable diff --git a/.github/workflows/rust-test.yml b/.github/workflows/rust-test.yml index d4177bea1..2ea0ad5eb 100644 --- a/.github/workflows/rust-test.yml +++ b/.github/workflows/rust-test.yml @@ -39,7 +39,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@be73d7920c329f220ce78e0234b8f96b7ae60248 # stable with: toolchain: stable @@ -59,7 +59,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@be73d7920c329f220ce78e0234b8f96b7ae60248 # stable with: toolchain: stable targets: wasm32-unknown-unknown diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index 5e403ac5b..9881b9b0e 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -32,7 +32,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@be73d7920c329f220ce78e0234b8f96b7ae60248 # stable with: toolchain: stable From e270190d5a1727b640cf763b6bc2df1aab416082 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Mon, 11 Dec 2023 21:19:11 +0100 Subject: [PATCH 086/157] Run clippy linting on tests too (#420) --- .github/workflows/lint.yml | 2 +- .../api/response/identity_payload_response.rs | 19 +------------------ .../api/response/identity_refresh_response.rs | 18 +----------------- .../api/response/identity_token_response.rs | 2 +- crates/bitwarden/src/vault/send.rs | 2 +- 5 files changed, 5 insertions(+), 38 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 04b28ed25..d6e57740e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -49,6 +49,6 @@ jobs: RUSTDOCFLAGS: "-D warnings" - name: Cargo clippy - run: cargo clippy --all-features + run: cargo clippy --all-features --tests env: RUSTFLAGS: "-D warnings" diff --git a/crates/bitwarden/src/auth/api/response/identity_payload_response.rs b/crates/bitwarden/src/auth/api/response/identity_payload_response.rs index b35b398d7..47543510b 100644 --- a/crates/bitwarden/src/auth/api/response/identity_payload_response.rs +++ b/crates/bitwarden/src/auth/api/response/identity_payload_response.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, PartialEq)] +#[cfg_attr(test, derive(Default))] pub struct IdentityTokenPayloadResponse { pub access_token: String, pub expires_in: u64, @@ -10,21 +11,3 @@ pub struct IdentityTokenPayloadResponse { pub(crate) encrypted_payload: String, } - -#[cfg(test)] -mod test { - use super::*; - - impl Default for IdentityTokenPayloadResponse { - fn default() -> Self { - Self { - access_token: Default::default(), - expires_in: Default::default(), - refresh_token: Default::default(), - token_type: Default::default(), - scope: Default::default(), - encrypted_payload: Default::default(), - } - } - } -} diff --git a/crates/bitwarden/src/auth/api/response/identity_refresh_response.rs b/crates/bitwarden/src/auth/api/response/identity_refresh_response.rs index eaf5c142f..c5733bd36 100644 --- a/crates/bitwarden/src/auth/api/response/identity_refresh_response.rs +++ b/crates/bitwarden/src/auth/api/response/identity_refresh_response.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, PartialEq)] +#[cfg_attr(test, derive(Default))] pub struct IdentityTokenRefreshResponse { pub access_token: String, pub expires_in: u64, @@ -8,20 +9,3 @@ pub struct IdentityTokenRefreshResponse { token_type: String, scope: String, } - -#[cfg(test)] -mod test { - use super::*; - - impl Default for IdentityTokenRefreshResponse { - fn default() -> Self { - Self { - access_token: Default::default(), - expires_in: Default::default(), - refresh_token: Default::default(), - token_type: Default::default(), - scope: Default::default(), - } - } - } -} diff --git a/crates/bitwarden/src/auth/api/response/identity_token_response.rs b/crates/bitwarden/src/auth/api/response/identity_token_response.rs index ae6cc81e5..b22263bc5 100644 --- a/crates/bitwarden/src/auth/api/response/identity_token_response.rs +++ b/crates/bitwarden/src/auth/api/response/identity_token_response.rs @@ -57,7 +57,7 @@ mod test { #[test] fn two_factor() { - let expected = Box::new(IdentityTwoFactorResponse::default()); + let expected = Box::::default(); let two_factor = serde_json::to_string(&expected).unwrap(); let expected = IdentityTokenResponse::TwoFactorRequired(expected); let actual = parse_identity_response(StatusCode::BAD_REQUEST, two_factor).unwrap(); diff --git a/crates/bitwarden/src/vault/send.rs b/crates/bitwarden/src/vault/send.rs index 5c463a0cf..3aec03639 100644 --- a/crates/bitwarden/src/vault/send.rs +++ b/crates/bitwarden/src/vault/send.rs @@ -280,7 +280,7 @@ mod tests { iterations: 345123.try_into().unwrap(), }, }, - "asdfasdfasdf".into(), + "asdfasdfasdf", "2.majkL1/hNz9yptLqNAUSnw==|RiOzMTTJMG948qu8O3Zm1EQUO2E8BuTwFKnO9LWQjMzxMWJM5GbyOq2/A+tumPbTERt4JWur/FKfgHb+gXuYiEYlXPMuVBvT7nv4LPytJuM=|IVqMxHJeR1ZXY0sGngTC0x+WqbG8p6V+BTrdgBbQXjM=".parse().unwrap(), "2.kmLY8NJVuiKBFJtNd/ZFpA==|qOodlRXER+9ogCe3yOibRHmUcSNvjSKhdDuztLlucs10jLiNoVVVAc+9KfNErLSpx5wmUF1hBOJM8zwVPjgQTrmnNf/wuDpwiaCxNYb/0v4FygPy7ccAHK94xP1lfqq7U9+tv+/yiZSwgcT+xF0wFpoxQeNdNRFzPTuD9o4134n8bzacD9DV/WjcrXfRjbBCzzuUGj1e78+A7BWN7/5IWLz87KWk8G7O/W4+8PtEzlwkru6Wd1xO19GYU18oArCWCNoegSmcGn7w7NDEXlwD403oY8Oa7ylnbqGE28PVJx+HLPNIdSC6YKXeIOMnVs7Mctd/wXC93zGxAWD6ooTCzHSPVV50zKJmWIG2cVVUS7j35H3rGDtUHLI+ASXMEux9REZB8CdVOZMzp2wYeiOpggebJy6MKOZqPT1R3X0fqF2dHtRFPXrNsVr1Qt6bS9qTyO4ag1/BCvXF3P1uJEsI812BFAne3cYHy5bIOxuozPfipJrTb5WH35bxhElqwT3y/o/6JWOGg3HLDun31YmiZ2HScAsUAcEkA4hhoTNnqy4O2s3yVbCcR7jF7NLsbQc0MDTbnjxTdI4VnqUIn8s2c9hIJy/j80pmO9Bjxp+LQ9a2hUkfHgFhgHxZUVaeGVth8zG2kkgGdrp5VHhxMVFfvB26Ka6q6qE/UcS2lONSv+4T8niVRJz57qwctj8MNOkA3PTEfe/DP/LKMefke31YfT0xogHsLhDkx+mS8FCc01HReTjKLktk/Jh9mXwC5oKwueWWwlxI935ecn+3I2kAuOfMsgPLkoEBlwgiREC1pM7VVX1x8WmzIQVQTHd4iwnX96QewYckGRfNYWz/zwvWnjWlfcg8kRSe+68EHOGeRtC5r27fWLqRc0HNcjwpgHkI/b6czerCe8+07TWql4keJxJxhBYj3iOH7r9ZS8ck51XnOb8tGL1isimAJXodYGzakwktqHAD7MZhS+P02O+6jrg7d+yPC2ZCuS/3TOplYOCHQIhnZtR87PXTUwr83zfOwAwCyv6KP84JUQ45+DItrXLap7nOVZKQ5QxYIlbThAO6eima6Zu5XHfqGPMNWv0bLf5+vAjIa5np5DJrSwz9no/hj6CUh0iyI+SJq4RGI60lKtypMvF6MR3nHLEHOycRUQbZIyTHWl4QQLdHzuwN9lv10ouTEvNr6sFflAX2yb6w3hlCo7oBytH3rJekjb3IIOzBpeTPIejxzVlh0N9OT5MZdh4sNKYHUoWJ8mnfjdM+L4j5Q2Kgk/XiGDgEebkUxiEOQUdVpePF5uSCE+TPav/9FIRGXGiFn6NJMaU7aBsDTFBLloffFLYDpd8/bTwoSvifkj7buwLYM+h/qcnfdy5FWau1cKav+Blq/ZC0qBpo658RTC8ZtseAFDgXoQZuksM10hpP9bzD04Bx30xTGX81QbaSTNwSEEVrOtIhbDrj9OI43KH4O6zLzK+t30QxAv5zjk10RZ4+5SAdYndIlld9Y62opCfPDzRy3ubdve4ZEchpIKWTQvIxq3T5ogOhGaWBVYnkMtM2GVqvWV//46gET5SH/MdcwhACUcZ9kCpMnWH9CyyUwYvTT3UlNyV+DlS27LMPvaw7tx7qa+GfNCoCBd8S4esZpQYK/WReiS8=|pc7qpD42wxyXemdNPuwxbh8iIaryrBPu8f/DGwYdHTw=".parse().unwrap(), ).unwrap(); From d4571858fcf840cf081186773cc0c6f5f8c56f44 Mon Sep 17 00:00:00 2001 From: Leandro Carneiro <42899277+carnei-ro@users.noreply.github.com> Date: Tue, 12 Dec 2023 08:10:08 -0300 Subject: [PATCH 087/157] Disable bitwarden-c from printing command input (#422) --- crates/bitwarden-c/src/c.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/bitwarden-c/src/c.rs b/crates/bitwarden-c/src/c.rs index 99180367b..f9ac57f30 100644 --- a/crates/bitwarden-c/src/c.rs +++ b/crates/bitwarden-c/src/c.rs @@ -12,7 +12,6 @@ pub async extern "C" fn run_command( ) -> *mut c_char { let client = unsafe { ffi_ref!(client_ptr) }; let input_str = str::from_utf8(unsafe { CStr::from_ptr(c_str_ptr).to_bytes() }).unwrap(); - println!("{}", input_str); let result = client.run_command(input_str).await; match std::ffi::CString::new(result) { From afb1f7810d033ba5fc2bc58a1b453a3c0df7984e Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 12 Dec 2023 14:21:35 +0100 Subject: [PATCH 088/157] Remove dependency on assert_matches (#419) --- Cargo.lock | 7 ------- crates/bitwarden/Cargo.toml | 1 - crates/bitwarden/src/client/client_settings.rs | 4 ---- 3 files changed, 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e7ba84c53..0b89bd2e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -184,12 +184,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "assert_matches" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" - [[package]] name = "async-channel" version = "1.9.0" @@ -339,7 +333,6 @@ version = "0.3.1" dependencies = [ "aes", "argon2", - "assert_matches", "base64 0.21.5", "bitwarden-api-api", "bitwarden-api-identity", diff --git a/crates/bitwarden/Cargo.toml b/crates/bitwarden/Cargo.toml index 4d92e4270..8f7c1f4d1 100644 --- a/crates/bitwarden/Cargo.toml +++ b/crates/bitwarden/Cargo.toml @@ -24,7 +24,6 @@ aes = ">=0.8.2, <0.9" argon2 = { version = ">=0.5.0, <0.6", features = [ "alloc", ], default-features = false } -assert_matches = ">=1.5.0, <2.0" base64 = ">=0.21.2, <0.22" bitwarden-api-api = { path = "../bitwarden-api-api", version = "=0.2.2" } bitwarden-api-identity = { path = "../bitwarden-api-identity", version = "=0.2.2" } diff --git a/crates/bitwarden/src/client/client_settings.rs b/crates/bitwarden/src/client/client_settings.rs index 68ef7b3a9..172baf733 100644 --- a/crates/bitwarden/src/client/client_settings.rs +++ b/crates/bitwarden/src/client/client_settings.rs @@ -8,7 +8,6 @@ use serde::{Deserialize, Serialize}; /// /// ``` /// # use bitwarden::client::client_settings::{ClientSettings, DeviceType}; -/// # use assert_matches::assert_matches; /// let settings = ClientSettings { /// identity_url: "https://identity.bitwarden.com".to_string(), /// api_url: "https://api.bitwarden.com".to_string(), @@ -16,10 +15,7 @@ use serde::{Deserialize, Serialize}; /// device_type: DeviceType::SDK, /// }; /// let default = ClientSettings::default(); -/// assert_matches!(settings, default); /// ``` -/// -/// Targets `localhost:8080` for debug builds. #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(default, rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] From 826d272516b4963db77d1448f4512606c7158c38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Tue, 12 Dec 2023 14:46:48 +0100 Subject: [PATCH 089/157] [PM-4694] Implement PIN unlock (#421) ## Type of change ``` - [ ] Bug fix - [x] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Implement support for deriving the PIN encrypted user key and initializing crypto with it. Updated the mobile app demos to use this new functionality. --- crates/bitwarden-uniffi/src/crypto.rs | 10 +- crates/bitwarden/src/client/client.rs | 24 +++- crates/bitwarden/src/crypto/master_key.rs | 41 ++++-- .../src/crypto/symmetric_crypto_key.rs | 10 ++ crates/bitwarden/src/mobile/client_crypto.rs | 9 +- crates/bitwarden/src/mobile/crypto.rs | 118 +++++++++++++++++- .../bitwarden/myapplication/MainActivity.kt | 89 ++++++++++++- languages/kotlin/doc.md | 39 ++++++ languages/swift/iOS/App/ContentView.swift | 61 ++++++++- 9 files changed, 381 insertions(+), 20 deletions(-) diff --git a/crates/bitwarden-uniffi/src/crypto.rs b/crates/bitwarden-uniffi/src/crypto.rs index 995750b7f..25d577ba5 100644 --- a/crates/bitwarden-uniffi/src/crypto.rs +++ b/crates/bitwarden-uniffi/src/crypto.rs @@ -1,6 +1,8 @@ use std::sync::Arc; -use bitwarden::mobile::crypto::{InitOrgCryptoRequest, InitUserCryptoRequest}; +use bitwarden::mobile::crypto::{ + DerivePinKeyResponse, InitOrgCryptoRequest, InitUserCryptoRequest, +}; use crate::{error::Result, Client}; @@ -45,4 +47,10 @@ impl ClientCrypto { .get_user_encryption_key() .await?) } + + /// Generates a PIN protected user key from the provided PIN. The result can be stored and later used + /// to initialize another client instance by using the PIN and the PIN key with `initialize_user_crypto`. + pub async fn derive_pin_key(&self, pin: String) -> Result { + Ok(self.0 .0.write().await.crypto().derive_pin_key(pin).await?) + } } diff --git a/crates/bitwarden/src/client/client.rs b/crates/bitwarden/src/client/client.rs index 2b6b76a2f..2165b8dc7 100644 --- a/crates/bitwarden/src/client/client.rs +++ b/crates/bitwarden/src/client/client.rs @@ -224,10 +224,9 @@ impl Client { #[cfg(feature = "mobile")] pub(crate) fn initialize_user_crypto_decrypted_key( &mut self, - decrypted_user_key: &str, + user_key: SymmetricCryptoKey, private_key: EncString, ) -> Result<&EncryptionSettings> { - let user_key = decrypted_user_key.parse::()?; self.encryption_settings = Some(EncryptionSettings::new_decrypted_key( user_key, private_key, @@ -238,6 +237,27 @@ impl Client { .expect("It was initialized on the previous line")) } + #[cfg(feature = "mobile")] + pub(crate) fn initialize_user_crypto_pin( + &mut self, + pin: &str, + pin_protected_user_key: EncString, + private_key: EncString, + ) -> Result<&EncryptionSettings> { + use crate::crypto::MasterKey; + + let pin_key = match &self.login_method { + Some(LoginMethod::User( + UserLoginMethod::Username { email, kdf, .. } + | UserLoginMethod::ApiKey { email, kdf, .. }, + )) => MasterKey::derive(pin.as_bytes(), email.as_bytes(), kdf)?, + _ => return Err(Error::NotAuthenticated), + }; + + let decrypted_user_key = pin_key.decrypt_user_key(pin_protected_user_key)?; + self.initialize_user_crypto_decrypted_key(decrypted_user_key, private_key) + } + pub(crate) fn initialize_crypto_single_key( &mut self, key: SymmetricCryptoKey, diff --git a/crates/bitwarden/src/crypto/master_key.rs b/crates/bitwarden/src/crypto/master_key.rs index 88e5b9327..9786da3c2 100644 --- a/crates/bitwarden/src/crypto/master_key.rs +++ b/crates/bitwarden/src/crypto/master_key.rs @@ -52,6 +52,16 @@ impl MasterKey { let dec: Vec = user_key.decrypt_with_key(&stretched_key)?; SymmetricCryptoKey::try_from(dec.as_slice()) } + + pub(crate) fn encrypt_user_key(&self, user_key: &SymmetricCryptoKey) -> Result { + let stretched_key = stretch_master_key(self)?; + + EncString::encrypt_aes256_hmac( + user_key.to_vec().as_slice(), + stretched_key.mac_key.unwrap(), + stretched_key.key, + ) + } } /// Generate a new random user key and encrypt it with the master key. @@ -62,15 +72,9 @@ fn make_user_key( let mut user_key = [0u8; 64]; rng.fill(&mut user_key); - let stretched_key = stretch_master_key(master_key)?; - let protected = EncString::encrypt_aes256_hmac( - &user_key, - stretched_key.mac_key.unwrap(), - stretched_key.key, - )?; - - let u: &[u8] = &user_key; - Ok((UserKey::new(SymmetricCryptoKey::try_from(u)?), protected)) + let user_key = SymmetricCryptoKey::try_from(user_key.as_slice())?; + let protected = master_key.encrypt_user_key(&user_key)?; + Ok((UserKey::new(user_key), protected)) } /// Derive a generic key from a secret and salt using the provided KDF. @@ -285,4 +289,23 @@ mod tests { "Decrypted key doesn't match user key" ); } + + #[test] + fn test_make_user_key2() { + let master_key = MasterKey(SymmetricCryptoKey::generate("test1")); + + let user_key = SymmetricCryptoKey::generate("test2"); + + let encrypted = master_key.encrypt_user_key(&user_key).unwrap(); + let decrypted = master_key.decrypt_user_key(encrypted).unwrap(); + + assert_eq!( + decrypted.key, user_key.key, + "Decrypted key doesn't match user key" + ); + assert_eq!( + decrypted.mac_key, user_key.mac_key, + "Decrypted key doesn't match user key" + ); + } } diff --git a/crates/bitwarden/src/crypto/symmetric_crypto_key.rs b/crates/bitwarden/src/crypto/symmetric_crypto_key.rs index 7b2086f75..5bce9e65d 100644 --- a/crates/bitwarden/src/crypto/symmetric_crypto_key.rs +++ b/crates/bitwarden/src/crypto/symmetric_crypto_key.rs @@ -35,6 +35,16 @@ impl SymmetricCryptoKey { BASE64_ENGINE.encode(&buf) } + + #[cfg(feature = "internal")] + pub(super) fn to_vec(&self) -> Vec { + let mut buf = Vec::new(); + buf.extend_from_slice(&self.key); + if let Some(mac) = self.mac_key { + buf.extend_from_slice(&mac); + } + buf + } } impl FromStr for SymmetricCryptoKey { diff --git a/crates/bitwarden/src/mobile/client_crypto.rs b/crates/bitwarden/src/mobile/client_crypto.rs index 118e02726..cddb7c140 100644 --- a/crates/bitwarden/src/mobile/client_crypto.rs +++ b/crates/bitwarden/src/mobile/client_crypto.rs @@ -3,8 +3,8 @@ use crate::Client; use crate::{ error::Result, mobile::crypto::{ - get_user_encryption_key, initialize_org_crypto, initialize_user_crypto, - InitOrgCryptoRequest, InitUserCryptoRequest, + derive_pin_key, get_user_encryption_key, initialize_org_crypto, initialize_user_crypto, + DerivePinKeyResponse, InitOrgCryptoRequest, InitUserCryptoRequest, }, }; @@ -27,6 +27,11 @@ impl<'a> ClientCrypto<'a> { pub async fn get_user_encryption_key(&mut self) -> Result { get_user_encryption_key(self.client).await } + + #[cfg(feature = "internal")] + pub async fn derive_pin_key(&mut self, pin: String) -> Result { + derive_pin_key(self.client, pin) + } } impl<'a> Client { diff --git a/crates/bitwarden/src/mobile/crypto.rs b/crates/bitwarden/src/mobile/crypto.rs index 9c2c8478f..edb44fadc 100644 --- a/crates/bitwarden/src/mobile/crypto.rs +++ b/crates/bitwarden/src/mobile/crypto.rs @@ -40,10 +40,18 @@ pub enum InitUserCryptoMethod { /// The user's decrypted encryption key, obtained using `get_user_encryption_key` decrypted_user_key: String, }, + Pin { + /// The user's PIN + pin: String, + /// The user's symmetric crypto key, encrypted with the PIN. Use `derive_pin_key` to obtain this. + pin_protected_user_key: EncString, + }, } #[cfg(feature = "internal")] pub async fn initialize_user_crypto(client: &mut Client, req: InitUserCryptoRequest) -> Result<()> { + use crate::crypto::SymmetricCryptoKey; + let login_method = crate::client::LoginMethod::User(crate::client::UserLoginMethod::Username { client_id: "".to_string(), email: req.email, @@ -59,7 +67,14 @@ pub async fn initialize_user_crypto(client: &mut Client, req: InitUserCryptoRequ client.initialize_user_crypto(&password, user_key, private_key)?; } InitUserCryptoMethod::DecryptedKey { decrypted_user_key } => { - client.initialize_user_crypto_decrypted_key(&decrypted_user_key, private_key)?; + let user_key = decrypted_user_key.parse::()?; + client.initialize_user_crypto_decrypted_key(user_key, private_key)?; + } + InitUserCryptoMethod::Pin { + pin, + pin_protected_user_key, + } => { + client.initialize_user_crypto_pin(&pin, pin_protected_user_key, private_key)?; } } @@ -91,3 +106,104 @@ pub async fn get_user_encryption_key(client: &mut Client) -> Result { Ok(user_key.to_base64()) } + +#[cfg(feature = "internal")] +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "mobile", derive(uniffi::Record))] +pub struct DerivePinKeyResponse { + pin_protected_user_key: EncString, + encrypted_pin: EncString, +} + +#[cfg(feature = "internal")] +pub fn derive_pin_key(client: &mut Client, pin: String) -> Result { + use crate::{ + client::{LoginMethod, UserLoginMethod}, + crypto::{KeyEncryptable, MasterKey}, + }; + + let derived_key = match &client.login_method { + Some(LoginMethod::User( + UserLoginMethod::Username { email, kdf, .. } + | UserLoginMethod::ApiKey { email, kdf, .. }, + )) => MasterKey::derive(pin.as_bytes(), email.as_bytes(), kdf)?, + _ => return Err(Error::NotAuthenticated), + }; + + let user_key = client + .get_encryption_settings()? + .get_key(&None) + .ok_or(Error::VaultLocked)?; + + Ok(DerivePinKeyResponse { + pin_protected_user_key: derived_key.encrypt_user_key(user_key)?, + encrypted_pin: pin.encrypt_with_key(user_key)?, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{client::kdf::Kdf, Client}; + + #[tokio::test] + async fn test_initialize_user_crypto_pin() { + let mut client = Client::new(None); + + let priv_key = "2.kmLY8NJVuiKBFJtNd/ZFpA==|qOodlRXER+9ogCe3yOibRHmUcSNvjSKhdDuztLlucs10jLiNoVVVAc+9KfNErLSpx5wmUF1hBOJM8zwVPjgQTrmnNf/wuDpwiaCxNYb/0v4FygPy7ccAHK94xP1lfqq7U9+tv+/yiZSwgcT+xF0wFpoxQeNdNRFzPTuD9o4134n8bzacD9DV/WjcrXfRjbBCzzuUGj1e78+A7BWN7/5IWLz87KWk8G7O/W4+8PtEzlwkru6Wd1xO19GYU18oArCWCNoegSmcGn7w7NDEXlwD403oY8Oa7ylnbqGE28PVJx+HLPNIdSC6YKXeIOMnVs7Mctd/wXC93zGxAWD6ooTCzHSPVV50zKJmWIG2cVVUS7j35H3rGDtUHLI+ASXMEux9REZB8CdVOZMzp2wYeiOpggebJy6MKOZqPT1R3X0fqF2dHtRFPXrNsVr1Qt6bS9qTyO4ag1/BCvXF3P1uJEsI812BFAne3cYHy5bIOxuozPfipJrTb5WH35bxhElqwT3y/o/6JWOGg3HLDun31YmiZ2HScAsUAcEkA4hhoTNnqy4O2s3yVbCcR7jF7NLsbQc0MDTbnjxTdI4VnqUIn8s2c9hIJy/j80pmO9Bjxp+LQ9a2hUkfHgFhgHxZUVaeGVth8zG2kkgGdrp5VHhxMVFfvB26Ka6q6qE/UcS2lONSv+4T8niVRJz57qwctj8MNOkA3PTEfe/DP/LKMefke31YfT0xogHsLhDkx+mS8FCc01HReTjKLktk/Jh9mXwC5oKwueWWwlxI935ecn+3I2kAuOfMsgPLkoEBlwgiREC1pM7VVX1x8WmzIQVQTHd4iwnX96QewYckGRfNYWz/zwvWnjWlfcg8kRSe+68EHOGeRtC5r27fWLqRc0HNcjwpgHkI/b6czerCe8+07TWql4keJxJxhBYj3iOH7r9ZS8ck51XnOb8tGL1isimAJXodYGzakwktqHAD7MZhS+P02O+6jrg7d+yPC2ZCuS/3TOplYOCHQIhnZtR87PXTUwr83zfOwAwCyv6KP84JUQ45+DItrXLap7nOVZKQ5QxYIlbThAO6eima6Zu5XHfqGPMNWv0bLf5+vAjIa5np5DJrSwz9no/hj6CUh0iyI+SJq4RGI60lKtypMvF6MR3nHLEHOycRUQbZIyTHWl4QQLdHzuwN9lv10ouTEvNr6sFflAX2yb6w3hlCo7oBytH3rJekjb3IIOzBpeTPIejxzVlh0N9OT5MZdh4sNKYHUoWJ8mnfjdM+L4j5Q2Kgk/XiGDgEebkUxiEOQUdVpePF5uSCE+TPav/9FIRGXGiFn6NJMaU7aBsDTFBLloffFLYDpd8/bTwoSvifkj7buwLYM+h/qcnfdy5FWau1cKav+Blq/ZC0qBpo658RTC8ZtseAFDgXoQZuksM10hpP9bzD04Bx30xTGX81QbaSTNwSEEVrOtIhbDrj9OI43KH4O6zLzK+t30QxAv5zjk10RZ4+5SAdYndIlld9Y62opCfPDzRy3ubdve4ZEchpIKWTQvIxq3T5ogOhGaWBVYnkMtM2GVqvWV//46gET5SH/MdcwhACUcZ9kCpMnWH9CyyUwYvTT3UlNyV+DlS27LMPvaw7tx7qa+GfNCoCBd8S4esZpQYK/WReiS8=|pc7qpD42wxyXemdNPuwxbh8iIaryrBPu8f/DGwYdHTw="; + + initialize_user_crypto( + &mut client, + InitUserCryptoRequest { + kdf_params: Kdf::PBKDF2 { + iterations: 100_000.try_into().unwrap(), + }, + email: "test@bitwarden.com".into(), + private_key: priv_key.to_owned(), + method: InitUserCryptoMethod::Password { + password: "asdfasdfasdf".into(), + user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".into(), + }, + }, + ) + .await + .unwrap(); + + let pin_key = derive_pin_key(&mut client, "1234".into()).unwrap(); + + let mut client2 = Client::new(None); + + initialize_user_crypto( + &mut client2, + InitUserCryptoRequest { + kdf_params: Kdf::PBKDF2 { + iterations: 100_000.try_into().unwrap(), + }, + email: "test@bitwarden.com".into(), + private_key: priv_key.to_owned(), + method: InitUserCryptoMethod::Pin { + pin: "1234".into(), + pin_protected_user_key: pin_key.pin_protected_user_key, + }, + }, + ) + .await + .unwrap(); + + assert_eq!( + client + .get_encryption_settings() + .unwrap() + .get_key(&None) + .unwrap() + .to_base64(), + client2 + .get_encryption_settings() + .unwrap() + .get_key(&None) + .unwrap() + .to_base64() + ); + } +} diff --git a/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt b/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt index b41b12b8c..c38c14a07 100644 --- a/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt +++ b/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt @@ -72,6 +72,8 @@ const val IDENTITY_URL = SERVER_URL + "identity/" const val EMAIL = "test@bitwarden.com" const val PASSWORD = "asdfasdfasdf" +const val PIN = "1234" + // We should separate keys for each user by appending the user_id const val BIOMETRIC_KEY = "biometric_key" @@ -99,10 +101,12 @@ class MainActivity : FragmentActivity() { ) { val setupBiometrics = remember { mutableStateOf(true) } + val setupPin = remember { mutableStateOf(true) } val outputText = remember { mutableStateOf("") } Row { - Checkbox(checked = setupBiometrics.value, + Checkbox( + checked = setupBiometrics.value, onCheckedChange = { isChecked -> setupBiometrics.value = isChecked }) @@ -112,10 +116,20 @@ class MainActivity : FragmentActivity() { ) } + Row { + Checkbox(checked = setupPin.value, onCheckedChange = { isChecked -> + setupPin.value = isChecked + }) + Text( + "Setup pin unlock after login", + modifier = Modifier.align(CenterVertically) + ) + } + Button({ GlobalScope.launch { clientExamplePassword( - client, http, outputText, setupBiometrics.value + client, http, outputText, setupBiometrics.value, setupPin.value ) } }) { @@ -136,6 +150,14 @@ class MainActivity : FragmentActivity() { Text("Unlock with biometrics") } + Button({ + GlobalScope.launch { + clientExamplePin(client, http, outputText) + } + }) { + Text("Unlock with PIN") + } + Button({ GlobalScope.launch { client.destroy() @@ -157,7 +179,11 @@ class MainActivity : FragmentActivity() { } private suspend fun clientExamplePassword( - client: Client, http: HttpClient, outputText: MutableState, setupBiometrics: Boolean + client: Client, + http: HttpClient, + outputText: MutableState, + setupBiometrics: Boolean, + setupPin: Boolean ) { println("### Logging in with username and password ###") ///////////////////////////// Get master password hash ///////////////////////////// @@ -248,6 +274,25 @@ class MainActivity : FragmentActivity() { } } } + + if (setupPin) { + val pinOptions = client.crypto().derivePinKey(PIN); + + val sharedPref = getPreferences(Context.MODE_PRIVATE) + with(sharedPref.edit()) { + putString("accessToken", accessToken) + putString("privateKey", loginBody.PrivateKey) + + putInt("kdfType", prelogin_body.kdf.toInt()) + putInt("kdfIterations", prelogin_body.kdfIterations.toInt()) + putInt("kdfMemory", (prelogin_body.kdfMemory ?: 0u).toInt()) + putInt("kdfParallelism", (prelogin_body.kdfParallelism ?: 0u).toInt()) + + putString("encryptedPin", pinOptions.encryptedPin) + putString("pinProtectedUserKey", pinOptions.pinProtectedUserKey) + apply() + } + } } private suspend fun clientExampleBiometrics( @@ -290,6 +335,44 @@ class MainActivity : FragmentActivity() { } } + private suspend fun clientExamplePin( + client: Client, http: HttpClient, outputText: MutableState + ) { + println("### Unlocking with PIN ###") + + val pref = getPreferences(Context.MODE_PRIVATE) + accessToken = pref.getString("accessToken", "").orEmpty() + val privateKey = pref.getString("privateKey", "") + + val kdf = if (pref.getInt("kdfType", 0) == 0) { + Kdf.Pbkdf2(pref.getInt("kdfIterations", 0).toUInt()) + } else { + Kdf.Argon2id( + pref.getInt("kdfIterations", 0).toUInt(), + pref.getInt("kdfMemory", 0).toUInt(), + pref.getInt("kdfParallelism", 0).toUInt() + ) + } + + val encryptedPin = pref.getString("encryptedPin", "")!! + val pinProtectedUserKey = pref.getString("pinProtectedUserKey", "")!! + + GlobalScope.launch { + client.crypto().initializeUserCrypto( + InitUserCryptoRequest( + kdfParams = kdf, + email = EMAIL, + privateKey = privateKey!!, + method = InitUserCryptoMethod.Pin( + pinProtectedUserKey = pinProtectedUserKey, pin = PIN + ) + ) + ) + + decryptVault(client, http, outputText) + } + } + suspend fun decryptVault(client: Client, http: HttpClient, outputText: MutableState) { ///////////////////////////// Sync ///////////////////////////// diff --git a/languages/kotlin/doc.md b/languages/kotlin/doc.md index f9f47db64..9249751f0 100644 --- a/languages/kotlin/doc.md +++ b/languages/kotlin/doc.md @@ -233,6 +233,19 @@ as it can be used to decrypt all of the user's data **Output**: std::result::Result +### `derive_pin_key` + +Generates a PIN protected user key from the provided PIN. The result can be stored and later used to +initialize another client instance by using the PIN and the PIN key with +`initialize_user_crypto`. + +**Arguments**: + +- self: +- pin: String + +**Output**: std::result::Result + ## ClientExporters ### `export_vault` @@ -998,6 +1011,32 @@ implementations. + + pin + object + + + + + + + + + + + + + + + + + + + + +
KeyTypeDescription
pinstringThe user's PIN
pin_protected_user_keyThe user's symmetric crypto key, encrypted with the PIN. Use `derive_pin_key` to obtain this.
+ + ## `InitUserCryptoRequest` diff --git a/languages/swift/iOS/App/ContentView.swift b/languages/swift/iOS/App/ContentView.swift index c2b488b8f..b5fc0d4f0 100644 --- a/languages/swift/iOS/App/ContentView.swift +++ b/languages/swift/iOS/App/ContentView.swift @@ -23,6 +23,8 @@ let IDENTITY_URL = SERVER_URL + "identity/" let EMAIL = "test@bitwarden.com" let PASSWORD = "asdfasdfasdf" +let PIN = "1234" + struct ContentView: View { private var http: URLSession @@ -40,16 +42,18 @@ struct ContentView: View { } @State var setupBiometrics: Bool = true + @State var setupPin: Bool = true @State var outputText: String = "" var body: some View { VStack { Toggle("Setup biometric unlock after login", isOn: $setupBiometrics).padding(.init(top: 0, leading: 20, bottom: 0, trailing: 20)) + Toggle("Setup PIN unlock after login", isOn: $setupPin).padding(.init(top: 0, leading: 20, bottom: 0, trailing: 20)) Button(action: { Task { do { - try await clientExamplePassword(clientAuth: client.auth(), clientCrypto: client.crypto(), setupBiometrics: setupBiometrics) + try await clientExamplePassword(clientAuth: client.auth(), clientCrypto: client.crypto(), setupBiometrics: setupBiometrics, setupPin: setupPin) try await decryptVault(clientCrypto: client.crypto(), clientVault: client.vault()) } catch { print("ERROR:", error) @@ -74,6 +78,19 @@ struct ContentView: View { Text("Unlock with biometrics") }) + Button(action: { + Task { + do { + try await clientExamplePin(clientCrypto: client.crypto()) + try await decryptVault(clientCrypto: client.crypto(), clientVault: client.vault()) + } catch { + print("ERROR:", error) + } + } + }, label: { + Text("Unlock with PIN") + }) + Button(action: { client = Client(settings: nil) }, label: { @@ -85,7 +102,7 @@ struct ContentView: View { .padding() } - func clientExamplePassword(clientAuth: ClientAuthProtocol, clientCrypto: ClientCryptoProtocol, setupBiometrics: Bool) async throws { + func clientExamplePassword(clientAuth: ClientAuthProtocol, clientCrypto: ClientCryptoProtocol, setupBiometrics: Bool, setupPin: Bool) async throws { ////////////////////////////// Get master password hash ////////////////////////////// struct PreloginRequest: Codable { let email: String } @@ -183,6 +200,22 @@ struct ContentView: View { let key = try await clientCrypto.getUserEncryptionKey() biometricStoreValue(value: key) } + + if (setupPin) { + let pinOptions = try await clientCrypto.derivePinKey(pin: PIN) + + let defaults = UserDefaults.standard + defaults.set(loginData.PrivateKey, forKey: "privateKey") + defaults.set(preloginData.kdf, forKey: "kdfType") + defaults.set(preloginData.kdfIterations, forKey: "kdfIterations") + defaults.set(preloginData.kdfMemory, forKey: "kdfMemory") + defaults.set(preloginData.kdfParallelism, forKey: "kdfParallelism") + + defaults.set(pinOptions.encryptedPin, forKey: "encryptedPin") + defaults.set(pinOptions.pinProtectedUserKey, forKey: "pinProtectedUserKey") + + defaults.synchronize() + } } func clientExampleBiometrics(clientCrypto: ClientCryptoProtocol) async throws { @@ -210,6 +243,30 @@ struct ContentView: View { )) } + func clientExamplePin(clientCrypto: ClientCryptoProtocol) async throws { + let defaults = UserDefaults.standard + let privateKey = defaults.string(forKey: "privateKey")! + let kdf = if defaults.integer(forKey: "kdfType") == 0 { + Kdf.pbkdf2(iterations: UInt32(defaults.integer(forKey: "kdfIterations"))) + } else { + Kdf.argon2id( + iterations: UInt32(defaults.integer(forKey: "kdfIterations")), + memory: UInt32(defaults.integer(forKey: "kdfMemory")), + parallelism: UInt32(defaults.integer(forKey: "kdfParallelism")) + ) + } + + let encryptedPin = defaults.string(forKey: "encryptedPin")! + let pinProtectedUserKey = defaults.string(forKey: "pinProtectedUserKey")! + + try await clientCrypto.initializeUserCrypto(req: InitUserCryptoRequest( + kdfParams: kdf, + email: EMAIL, + privateKey: privateKey, + method: InitUserCryptoMethod.pin(pin: PIN, pinProtectedUserKey: pinProtectedUserKey) + )) + } + func decryptVault(clientCrypto: ClientCryptoProtocol, clientVault: ClientVaultProtocol) async throws { ///////////////////////////// Sync ///////////////////////////// From 2c85c94206fd0a91e75dce0951f7eee192e8a827 Mon Sep 17 00:00:00 2001 From: Vince Grassia <593223+vgrassia@users.noreply.github.com> Date: Tue, 12 Dec 2023 10:22:28 -0500 Subject: [PATCH 090/157] Update Version Bump workflow (#424) --- .github/workflows/version-bump.yml | 117 ++++++++++++++++------------- 1 file changed, 64 insertions(+), 53 deletions(-) diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index 9881b9b0e..f136c1dfe 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -1,7 +1,18 @@ --- name: Version Bump +run-name: Version Bump - v${{ inputs.version_number }} on: + workflow_call: + inputs: + project: + description: "Project" + required: true + type: string + version_number: + description: "New version (example: '2024.1.0')" + required: true + type: string workflow_dispatch: inputs: project: @@ -16,16 +27,12 @@ on: - cli - bitwarden-json version_number: - description: "New Version" + description: "New version (example: '2024.1.0')" required: true -defaults: - run: - shell: bash - jobs: bump_version: - name: "Bump ${{ github.event.inputs.project }} Version" + name: "Bump ${{ inputs.project }} Version to v${{ inputs.version_number }}" runs-on: ubuntu-22.04 steps: - name: Checkout Branch @@ -42,7 +49,7 @@ jobs: - name: Install cargo-release run: cargo install cargo-edit - - name: Login to Azure - Prod Subscription + - name: Login to Azure - CI Subscription uses: Azure/login@de95379fe4dadc2defb305917eaa7e5dde727294 # v1.5.1 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} @@ -52,7 +59,9 @@ jobs: uses: bitwarden/gh-actions/get-keyvault-secrets@main with: keyvault: "bitwarden-ci" - secrets: "github-gpg-private-key, github-gpg-private-key-passphrase" + secrets: "github-gpg-private-key, + github-gpg-private-key-passphrase, + github-pat-bitwarden-devops-bot-repo-scope" - name: Import GPG key uses: crazy-max/ghaction-import-gpg@82a020f1f7f605c65dd2449b392a52c3fcfef7ef # v6.0.0 @@ -65,69 +74,62 @@ jobs: - name: Create Version Branch id: branch env: - VERSION: ${{ github.event.inputs.version_number }} - PROJECT: ${{ github.event.inputs.project }} + VERSION: ${{ inputs.version_number }} + PROJECT: ${{ inputs.project }} run: git switch -c sdk-${PROJECT}_version_bump_${VERSION} + - name: Create Version Branch + id: create-branch + run: | + NAME=version_bump_${{ github.ref_name }}_${{ inputs.project }}_${{ inputs.version_number }} + git switch -c $NAME + echo "name=$NAME" >> $GITHUB_OUTPUT + ######################## # VERSION BUMP SECTION # ######################## ### napi - name: Bump @bitwarden/sdk-napi Version - if: ${{ github.event.inputs.project == 'napi' }} - env: - VERSION: ${{ github.event.inputs.version_number }} + if: ${{ inputs.project == 'napi' }} working-directory: "crates/bitwarden-napi" run: | npm ci - npm version ${VERSION} + npm version ${{ inputs.version_number }} - name: Bump napi crate Version - if: ${{ github.event.inputs.project == 'napi' }} - env: - VERSION: ${{ github.event.inputs.version_number }} - run: cargo-set-version set-version -p bitwarden-napi ${VERSION} + if: ${{ inputs.project == 'napi' }} + run: cargo-set-version set-version -p bitwarden-napi ${{ inputs.version_number }} ### bitwarden - name: Bump bitwarden crate Version - if: ${{ github.event.inputs.project == 'bitwarden' }} - env: - VERSION: ${{ github.event.inputs.version_number }} - run: cargo-set-version set-version -p bitwarden ${VERSION} + if: ${{ inputs.project == 'bitwarden' }} + run: cargo-set-version set-version -p bitwarden ${{ inputs.version_number }} ### bitwarden-api-api - name: Bump bitwarden-api-api crate Version - if: ${{ github.event.inputs.project == 'bitwarden-api-api' }} - env: - VERSION: ${{ github.event.inputs.version_number }} - run: cargo-set-version set-version -p bitwarden-api-api ${VERSION} + if: ${{ inputs.project == 'bitwarden-api-api' }} + run: cargo-set-version set-version -p bitwarden-api-api ${{ inputs.version_number }} ### bitwarden-api-identity - name: Bump bitwarden-api-identity crate Version - if: ${{ github.event.inputs.project == 'bitwarden-api-identity' }} - env: - VERSION: ${{ github.event.inputs.version_number }} - run: cargo-set-version set-version -p bitwarden-api-identity ${VERSION} + if: ${{ inputs.project == 'bitwarden-api-identity' }} + run: cargo-set-version set-version -p bitwarden-api-identity ${{ inputs.version_number }} ### cli - name: Bump cli Version - if: ${{ github.event.inputs.project == 'cli' }} - env: - VERSION: ${{ github.event.inputs.version_number }} - run: cargo-set-version set-version -p bws ${VERSION} + if: ${{ inputs.project == 'cli' }} + run: cargo-set-version set-version -p bws ${{ inputs.version_number }} ### bitwarden-json - name: Bump bitwarden-json crate Version - if: ${{ github.event.inputs.project == 'bitwarden-json' }} - env: - VERSION: ${{ github.event.inputs.version_number }} - run: cargo-set-version set-version -p bitwarden-json ${VERSION} + if: ${{ inputs.project == 'bitwarden-json' }} + run: cargo-set-version set-version -p bitwarden-json ${{ inputs.version_number }} ############################ # VERSION BUMP SECTION END # @@ -156,28 +158,24 @@ jobs: - name: Commit files if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }} - env: - VERSION: ${{ github.event.inputs.version_number }} - PROJECT: ${{ github.event.inputs.project }} - run: git commit -m "Bumped sdk-${PROJECT} version to ${VERSION}" -a + run: git commit -m "Bumped sdk-${{ inputs.project }} version to ${{ inputs.version_number }}" -a - name: Push changes if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }} env: - VERSION: ${{ github.event.inputs.version_number }} - PROJECT: ${{ github.event.inputs.project }} - run: git push -u origin sdk-${PROJECT}_version_bump_${VERSION} + PR_BRANCH: ${{ steps.create-branch.outputs.name }} + run: git push -u origin $PR_BRANCH - - name: Create Bump Version PR + - name: Create Version PR if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }} + id: create-pr env: - PR_BRANCH: "sdk-${{ github.event.inputs.project }}_version_bump_${{ github.event.inputs.version_number }}" - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - BASE_BRANCH: master - TITLE: "Bump ${{ github.event.inputs.project }} version to ${{ github.event.inputs.version_number }}" + PR_BRANCH: ${{ steps.create-branch.outputs.name }} + GH_TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }} + TITLE: "Bump version to ${{ inputs.version_number }}" run: | - gh pr create --title "$TITLE" \ - --base "$BASE" \ + PR_URL=$(gh pr create --title "$TITLE" \ + --base "$GITHUB_REF" \ --head "$PR_BRANCH" \ --label "version update" \ --label "automated pr" \ @@ -190,4 +188,17 @@ jobs: - [X] Other ## Objective - Automated ${{ github.event.inputs.project }} version bump to ${{ github.event.inputs.version_number }}" + Automated ${{ inputs.project }} version bump to ${{ inputs.version_number }}") + echo "pr_number=${PR_URL##*/}" >> $GITHUB_OUTPUT + + - name: Approve PR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ steps.create-pr.outputs.pr_number }} + run: gh pr review $PR_NUMBER --approve + + - name: Merge PR + env: + GH_TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }} + PR_NUMBER: ${{ steps.create-pr.outputs.pr_number }} + run: gh pr merge $PR_NUMBER --squash --auto --delete-branch From f17bac368509b00e78abd3883f0e4e5525e0e720 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 12 Dec 2023 16:25:39 +0100 Subject: [PATCH 091/157] Map from Requests to Domain models (#298) Implements many of the translators from Request models to Domain models. Wires us up for hooking-up the sync response handling. --- crates/bitwarden/src/admin_console/mod.rs | 3 + crates/bitwarden/src/admin_console/policy.rs | 70 +++++++++++++++++++ crates/bitwarden/src/crypto/enc_string.rs | 24 +++++-- crates/bitwarden/src/lib.rs | 4 +- crates/bitwarden/src/mobile/mod.rs | 12 ---- crates/bitwarden/src/platform/domain.rs | 23 ++++++ crates/bitwarden/src/platform/mod.rs | 1 + crates/bitwarden/src/platform/sync.rs | 60 ++++++++++++---- .../bitwarden/src/vault/cipher/attachment.rs | 21 +++++- crates/bitwarden/src/vault/cipher/card.rs | 18 ++++- crates/bitwarden/src/vault/cipher/cipher.rs | 68 +++++++++++++++++- crates/bitwarden/src/vault/cipher/field.rs | 30 +++++++- crates/bitwarden/src/vault/cipher/identity.rs | 30 +++++++- .../bitwarden/src/vault/cipher/linked_id.rs | 38 ++++++++++ crates/bitwarden/src/vault/cipher/login.rs | 48 ++++++++++++- .../bitwarden/src/vault/cipher/secure_note.rs | 21 +++++- crates/bitwarden/src/vault/collection.rs | 18 ++++- crates/bitwarden/src/vault/folder.rs | 15 +++- crates/bitwarden/src/vault/mod.rs | 3 + .../bitwarden/src/vault/password_history.rs | 14 +++- crates/bitwarden/src/vault/send.rs | 61 +++++++++++++++- crates/bw/src/auth/login.rs | 8 +++ 22 files changed, 545 insertions(+), 45 deletions(-) create mode 100644 crates/bitwarden/src/admin_console/mod.rs create mode 100644 crates/bitwarden/src/admin_console/policy.rs create mode 100644 crates/bitwarden/src/platform/domain.rs diff --git a/crates/bitwarden/src/admin_console/mod.rs b/crates/bitwarden/src/admin_console/mod.rs new file mode 100644 index 000000000..e553456cf --- /dev/null +++ b/crates/bitwarden/src/admin_console/mod.rs @@ -0,0 +1,3 @@ +mod policy; + +pub use policy::Policy; diff --git a/crates/bitwarden/src/admin_console/policy.rs b/crates/bitwarden/src/admin_console/policy.rs new file mode 100644 index 000000000..1b4acc310 --- /dev/null +++ b/crates/bitwarden/src/admin_console/policy.rs @@ -0,0 +1,70 @@ +use std::collections::HashMap; + +use bitwarden_api_api::models::PolicyResponseModel; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use serde_repr::{Deserialize_repr, Serialize_repr}; +use uuid::Uuid; + +use crate::error::{Error, Result}; + +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +pub struct Policy { + id: Uuid, + organization_id: Uuid, + r#type: PolicyType, + data: Option>, + enabled: bool, +} + +#[derive(Serialize_repr, Deserialize_repr, Debug, JsonSchema)] +#[repr(u8)] +pub enum PolicyType { + TwoFactorAuthentication = 0, // Requires users to have 2fa enabled + MasterPassword = 1, // Sets minimum requirements for master password complexity + PasswordGenerator = 2, // Sets minimum requirements/default type for generated passwords/passphrases + SingleOrg = 3, // Allows users to only be apart of one organization + RequireSso = 4, // Requires users to authenticate with SSO + PersonalOwnership = 5, // Disables personal vault ownership for adding/cloning items + DisableSend = 6, // Disables the ability to create and edit Bitwarden Sends + SendOptions = 7, // Sets restrictions or defaults for Bitwarden Sends + ResetPassword = 8, // Allows orgs to use reset password : also can enable auto-enrollment during invite flow + MaximumVaultTimeout = 9, // Sets the maximum allowed vault timeout + DisablePersonalVaultExport = 10, // Disable personal vault export + ActivateAutofill = 11, // Activates autofill with page load on the browser extension +} + +impl TryFrom for Policy { + type Error = Error; + + fn try_from(policy: PolicyResponseModel) -> Result { + Ok(Self { + id: policy.id.ok_or(Error::MissingFields)?, + organization_id: policy.organization_id.ok_or(Error::MissingFields)?, + r#type: policy.r#type.ok_or(Error::MissingFields)?.into(), + data: policy.data, + enabled: policy.enabled.ok_or(Error::MissingFields)?, + }) + } +} + +impl From for PolicyType { + fn from(policy_type: bitwarden_api_api::models::PolicyType) -> Self { + match policy_type { + bitwarden_api_api::models::PolicyType::Variant0 => PolicyType::TwoFactorAuthentication, + bitwarden_api_api::models::PolicyType::Variant1 => PolicyType::MasterPassword, + bitwarden_api_api::models::PolicyType::Variant2 => PolicyType::PasswordGenerator, + bitwarden_api_api::models::PolicyType::Variant3 => PolicyType::SingleOrg, + bitwarden_api_api::models::PolicyType::Variant4 => PolicyType::RequireSso, + bitwarden_api_api::models::PolicyType::Variant5 => PolicyType::PersonalOwnership, + bitwarden_api_api::models::PolicyType::Variant6 => PolicyType::DisableSend, + bitwarden_api_api::models::PolicyType::Variant7 => PolicyType::SendOptions, + bitwarden_api_api::models::PolicyType::Variant8 => PolicyType::ResetPassword, + bitwarden_api_api::models::PolicyType::Variant9 => PolicyType::MaximumVaultTimeout, + bitwarden_api_api::models::PolicyType::Variant10 => { + PolicyType::DisablePersonalVaultExport + } + bitwarden_api_api::models::PolicyType::Variant11 => PolicyType::ActivateAutofill, + } + } +} diff --git a/crates/bitwarden/src/crypto/enc_string.rs b/crates/bitwarden/src/crypto/enc_string.rs index 09f877c50..3004d2dea 100644 --- a/crates/bitwarden/src/crypto/enc_string.rs +++ b/crates/bitwarden/src/crypto/enc_string.rs @@ -4,14 +4,13 @@ use aes::cipher::{generic_array::GenericArray, typenum::U32}; use base64::Engine; use serde::{de::Visitor, Deserialize}; +use super::{KeyDecryptable, KeyEncryptable, LocateKey}; use crate::{ crypto::{decrypt_aes256_hmac, SymmetricCryptoKey}, error::{CryptoError, EncStringParseError, Error, Result}, util::BASE64_ENGINE, }; -use super::{KeyDecryptable, KeyEncryptable, LocateKey}; - /// # Encrypted string primitive /// /// [EncString] is a Bitwarden specific primitive that represents an encrypted string. They are @@ -165,6 +164,12 @@ impl FromStr for EncString { } impl EncString { + /// Synthetic sugar for mapping `Option` to `Result>` + #[cfg(feature = "internal")] + pub(crate) fn try_from_optional(s: Option) -> Result, Error> { + s.map(|s| s.parse()).transpose() + } + #[cfg(feature = "mobile")] pub(crate) fn from_buffer(buf: &[u8]) -> Result { if buf.is_empty() { @@ -397,11 +402,22 @@ impl KeyDecryptable for EncString { } } +/// Usually we wouldn't want to expose EncStrings in the API or the schemas. +/// But during the transition phase we will expose endpoints using the EncString type. +impl schemars::JsonSchema for crate::crypto::EncString { + fn schema_name() -> String { + "EncString".to_string() + } + + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + gen.subschema_for::() + } +} + #[cfg(test)] mod tests { - use crate::crypto::{KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}; - use super::EncString; + use crate::crypto::{KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}; #[test] fn test_enc_string_roundtrip() { diff --git a/crates/bitwarden/src/lib.rs b/crates/bitwarden/src/lib.rs index 20e36d237..0017ecff9 100644 --- a/crates/bitwarden/src/lib.rs +++ b/crates/bitwarden/src/lib.rs @@ -51,6 +51,8 @@ #[cfg(feature = "mobile")] uniffi::setup_scaffolding!(); +#[cfg(feature = "internal")] +pub mod admin_console; pub mod auth; pub mod client; pub mod crypto; @@ -66,7 +68,7 @@ pub mod tool; #[cfg(feature = "mobile")] pub(crate) mod uniffi_support; mod util; -#[cfg(feature = "mobile")] +#[cfg(feature = "internal")] pub mod vault; pub mod wordlist; diff --git a/crates/bitwarden/src/mobile/mod.rs b/crates/bitwarden/src/mobile/mod.rs index fe3083aa8..e0ba50d00 100644 --- a/crates/bitwarden/src/mobile/mod.rs +++ b/crates/bitwarden/src/mobile/mod.rs @@ -8,15 +8,3 @@ mod client_kdf; pub use client_crypto::ClientCrypto; pub use client_kdf::ClientKdf; - -// Usually we wouldn't want to expose EncStrings in the API or the schemas, -// but we need them in the mobile API, so define it here to limit the scope -impl schemars::JsonSchema for crate::crypto::EncString { - fn schema_name() -> String { - "EncString".to_string() - } - - fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { - gen.subschema_for::() - } -} diff --git a/crates/bitwarden/src/platform/domain.rs b/crates/bitwarden/src/platform/domain.rs new file mode 100644 index 000000000..c903dd5a5 --- /dev/null +++ b/crates/bitwarden/src/platform/domain.rs @@ -0,0 +1,23 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::error::{Error, Result}; + +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +pub struct GlobalDomains { + pub r#type: i32, + pub domains: Vec, + pub excluded: bool, +} + +impl TryFrom for GlobalDomains { + type Error = Error; + + fn try_from(global_domains: bitwarden_api_api::models::GlobalDomains) -> Result { + Ok(Self { + r#type: global_domains.r#type.ok_or(Error::MissingFields)?, + domains: global_domains.domains.ok_or(Error::MissingFields)?, + excluded: global_domains.excluded.ok_or(Error::MissingFields)?, + }) + } +} diff --git a/crates/bitwarden/src/platform/mod.rs b/crates/bitwarden/src/platform/mod.rs index 0224e8d3a..0e234f00b 100644 --- a/crates/bitwarden/src/platform/mod.rs +++ b/crates/bitwarden/src/platform/mod.rs @@ -1,3 +1,4 @@ +mod domain; mod generate_fingerprint; mod get_user_api_key; mod secret_verification_request; diff --git a/crates/bitwarden/src/platform/sync.rs b/crates/bitwarden/src/platform/sync.rs index fd33e4343..870b88ab9 100644 --- a/crates/bitwarden/src/platform/sync.rs +++ b/crates/bitwarden/src/platform/sync.rs @@ -1,14 +1,16 @@ use bitwarden_api_api::models::{ - CipherDetailsResponseModel, ProfileOrganizationResponseModel, ProfileResponseModel, - SyncResponseModel, + DomainsResponseModel, ProfileOrganizationResponseModel, ProfileResponseModel, SyncResponseModel, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; +use super::domain::GlobalDomains; use crate::{ + admin_console::Policy, client::{encryption_settings::EncryptionSettings, Client}, error::{Error, Result}, + vault::{Cipher, Collection, Folder}, }; #[derive(Serialize, Deserialize, Debug, JsonSchema)] @@ -59,15 +61,23 @@ pub struct ProfileOrganizationResponse { #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct CipherDetailsResponse {} +pub struct DomainResponse { + pub equivalent_domains: Vec>, + pub global_equivalent_domains: Vec, +} #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct SyncResponse { /// Data about the user, including their encryption keys and the organizations they are a part of pub profile: ProfileResponse, - /// List of ciphers accesible by the user - pub ciphers: Vec, + pub folders: Vec, + pub collections: Vec, + /// List of ciphers accessible by the user + pub ciphers: Vec, + pub domains: Option, + pub policies: Vec, + pub sends: Vec, } impl SyncResponse { @@ -78,22 +88,27 @@ impl SyncResponse { let profile = *response.profile.ok_or(Error::MissingFields)?; let ciphers = response.ciphers.ok_or(Error::MissingFields)?; + fn try_into_iter(iter: In) -> Result + where + In: IntoIterator, + InItem: TryInto, + Out: FromIterator, + { + iter.into_iter().map(|i| i.try_into()).collect() + } + Ok(SyncResponse { profile: ProfileResponse::process_response(profile, enc)?, - ciphers: ciphers - .into_iter() - .map(CipherDetailsResponse::process_response) - .collect::>()?, + folders: try_into_iter(response.folders.ok_or(Error::MissingFields)?)?, + collections: try_into_iter(response.collections.ok_or(Error::MissingFields)?)?, + ciphers: try_into_iter(ciphers)?, + domains: response.domains.map(|d| (*d).try_into()).transpose()?, + policies: try_into_iter(response.policies.ok_or(Error::MissingFields)?)?, + sends: try_into_iter(response.sends.ok_or(Error::MissingFields)?)?, }) } } -impl CipherDetailsResponse { - fn process_response(_response: CipherDetailsResponseModel) -> Result { - Ok(CipherDetailsResponse {}) - } -} - impl ProfileOrganizationResponse { fn process_response( response: ProfileOrganizationResponseModel, @@ -124,3 +139,18 @@ impl ProfileResponse { }) } } + +impl TryFrom for DomainResponse { + type Error = Error; + fn try_from(value: DomainsResponseModel) -> Result { + Ok(Self { + equivalent_domains: value.equivalent_domains.unwrap_or_default(), + global_equivalent_domains: value + .global_equivalent_domains + .unwrap_or_default() + .into_iter() + .map(|s| s.try_into()) + .collect::>>()?, + }) + } +} diff --git a/crates/bitwarden/src/vault/cipher/attachment.rs b/crates/bitwarden/src/vault/cipher/attachment.rs index 101c8f4bc..ace4937da 100644 --- a/crates/bitwarden/src/vault/cipher/attachment.rs +++ b/crates/bitwarden/src/vault/cipher/attachment.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::{ crypto::{EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}, - error::Result, + error::{Error, Result}, }; #[derive(Serialize, Deserialize, Debug, JsonSchema)] @@ -28,7 +28,7 @@ pub struct AttachmentView { pub size: Option, pub size_name: Option, pub file_name: Option, - pub key: Option, + pub key: Option>, // TODO: Should be made into SymmetricCryptoKey } impl KeyEncryptable for AttachmentView { @@ -39,7 +39,7 @@ impl KeyEncryptable for AttachmentView { size: self.size, size_name: self.size_name, file_name: self.file_name.encrypt_with_key(key)?, - key: self.key.encrypt_with_key(key)?, + key: self.key.as_deref().encrypt_with_key(key)?, }) } } @@ -56,3 +56,18 @@ impl KeyDecryptable for Attachment { }) } } + +impl TryFrom for Attachment { + type Error = Error; + + fn try_from(attachment: bitwarden_api_api::models::AttachmentResponseModel) -> Result { + Ok(Self { + id: attachment.id, + url: attachment.url, + size: attachment.size, + size_name: attachment.size_name, + file_name: EncString::try_from_optional(attachment.file_name)?, + key: EncString::try_from_optional(attachment.key)?, + }) + } +} diff --git a/crates/bitwarden/src/vault/cipher/card.rs b/crates/bitwarden/src/vault/cipher/card.rs index 1545ad171..6f96041e9 100644 --- a/crates/bitwarden/src/vault/cipher/card.rs +++ b/crates/bitwarden/src/vault/cipher/card.rs @@ -1,9 +1,10 @@ +use bitwarden_api_api::models::CipherCardModel; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use crate::{ crypto::{EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}, - error::Result, + error::{Error, Result}, }; #[derive(Serialize, Deserialize, Debug, JsonSchema)] @@ -55,3 +56,18 @@ impl KeyDecryptable for Card { }) } } + +impl TryFrom for Card { + type Error = Error; + + fn try_from(card: CipherCardModel) -> Result { + Ok(Self { + cardholder_name: EncString::try_from_optional(card.cardholder_name)?, + exp_month: EncString::try_from_optional(card.exp_month)?, + exp_year: EncString::try_from_optional(card.exp_year)?, + code: EncString::try_from_optional(card.code)?, + brand: EncString::try_from_optional(card.brand)?, + number: EncString::try_from_optional(card.number)?, + }) + } +} diff --git a/crates/bitwarden/src/vault/cipher/cipher.rs b/crates/bitwarden/src/vault/cipher/cipher.rs index b95a2f090..e44a3757a 100644 --- a/crates/bitwarden/src/vault/cipher/cipher.rs +++ b/crates/bitwarden/src/vault/cipher/cipher.rs @@ -1,3 +1,4 @@ +use bitwarden_api_api::models::CipherDetailsResponseModel; use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -12,7 +13,7 @@ use super::{ use crate::{ client::encryption_settings::EncryptionSettings, crypto::{EncString, KeyDecryptable, KeyEncryptable, LocateKey, SymmetricCryptoKey}, - error::Result, + error::{Error, Result}, vault::password_history, }; @@ -332,3 +333,68 @@ impl LocateKey for CipherView { enc.get_key(&self.organization_id) } } + +impl TryFrom for Cipher { + type Error = Error; + + fn try_from(cipher: CipherDetailsResponseModel) -> Result { + Ok(Self { + id: cipher.id, + organization_id: cipher.organization_id, + folder_id: cipher.folder_id, + collection_ids: cipher.collection_ids.unwrap_or_default(), + name: EncString::try_from_optional(cipher.name)?.ok_or(Error::MissingFields)?, + notes: EncString::try_from_optional(cipher.notes)?, + r#type: cipher.r#type.ok_or(Error::MissingFields)?.into(), + login: cipher.login.map(|l| (*l).try_into()).transpose()?, + identity: cipher.identity.map(|i| (*i).try_into()).transpose()?, + card: cipher.card.map(|c| (*c).try_into()).transpose()?, + secure_note: cipher.secure_note.map(|s| (*s).try_into()).transpose()?, + favorite: cipher.favorite.unwrap_or(false), + reprompt: cipher + .reprompt + .map(|r| r.into()) + .unwrap_or(CipherRepromptType::None), + organization_use_totp: cipher.organization_use_totp.unwrap_or(true), + edit: cipher.edit.unwrap_or(true), + view_password: cipher.view_password.unwrap_or(true), + local_data: None, // Not sent from server + attachments: cipher + .attachments + .map(|a| a.into_iter().map(|a| a.try_into()).collect()) + .transpose()?, + fields: cipher + .fields + .map(|f| f.into_iter().map(|f| f.try_into()).collect()) + .transpose()?, + password_history: cipher + .password_history + .map(|p| p.into_iter().map(|p| p.try_into()).collect()) + .transpose()?, + creation_date: cipher.creation_date.ok_or(Error::MissingFields)?.parse()?, + deleted_date: cipher.deleted_date.map(|d| d.parse()).transpose()?, + revision_date: cipher.revision_date.ok_or(Error::MissingFields)?.parse()?, + key: EncString::try_from_optional(cipher.key)?, + }) + } +} + +impl From for CipherType { + fn from(t: bitwarden_api_api::models::CipherType) -> Self { + match t { + bitwarden_api_api::models::CipherType::Variant1 => CipherType::Login, + bitwarden_api_api::models::CipherType::Variant2 => CipherType::SecureNote, + bitwarden_api_api::models::CipherType::Variant3 => CipherType::Card, + bitwarden_api_api::models::CipherType::Variant4 => CipherType::Identity, + } + } +} + +impl From for CipherRepromptType { + fn from(t: bitwarden_api_api::models::CipherRepromptType) -> Self { + match t { + bitwarden_api_api::models::CipherRepromptType::Variant0 => CipherRepromptType::None, + bitwarden_api_api::models::CipherRepromptType::Variant1 => CipherRepromptType::Password, + } + } +} diff --git a/crates/bitwarden/src/vault/cipher/field.rs b/crates/bitwarden/src/vault/cipher/field.rs index 13f7dc9bb..dee71872b 100644 --- a/crates/bitwarden/src/vault/cipher/field.rs +++ b/crates/bitwarden/src/vault/cipher/field.rs @@ -1,3 +1,4 @@ +use bitwarden_api_api::models::CipherFieldModel; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; @@ -5,7 +6,7 @@ use serde_repr::{Deserialize_repr, Serialize_repr}; use super::linked_id::LinkedIdType; use crate::{ crypto::{EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}, - error::Result, + error::{Error, Result}, }; #[derive(Clone, Copy, Serialize_repr, Deserialize_repr, Debug, JsonSchema)] @@ -61,3 +62,30 @@ impl KeyDecryptable for Field { }) } } + +impl TryFrom for Field { + type Error = Error; + + fn try_from(model: CipherFieldModel) -> Result { + Ok(Self { + name: EncString::try_from_optional(model.name)?, + value: EncString::try_from_optional(model.value)?, + r#type: model.r#type.map(|t| t.into()).ok_or(Error::MissingFields)?, + linked_id: model + .linked_id + .map(|id| (id as u32).try_into()) + .transpose()?, + }) + } +} + +impl From for FieldType { + fn from(model: bitwarden_api_api::models::FieldType) -> Self { + match model { + bitwarden_api_api::models::FieldType::Variant0 => FieldType::Text, + bitwarden_api_api::models::FieldType::Variant1 => FieldType::Hidden, + bitwarden_api_api::models::FieldType::Variant2 => FieldType::Boolean, + bitwarden_api_api::models::FieldType::Variant3 => FieldType::Linked, + } + } +} diff --git a/crates/bitwarden/src/vault/cipher/identity.rs b/crates/bitwarden/src/vault/cipher/identity.rs index d40991866..922d2aee6 100644 --- a/crates/bitwarden/src/vault/cipher/identity.rs +++ b/crates/bitwarden/src/vault/cipher/identity.rs @@ -1,9 +1,10 @@ +use bitwarden_api_api::models::CipherIdentityModel; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use crate::{ crypto::{EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}, - error::Result, + error::{Error, Result}, }; #[derive(Serialize, Deserialize, Debug, JsonSchema)] @@ -103,3 +104,30 @@ impl KeyDecryptable for Identity { }) } } + +impl TryFrom for Identity { + type Error = Error; + + fn try_from(identity: CipherIdentityModel) -> Result { + Ok(Self { + title: EncString::try_from_optional(identity.title)?, + first_name: EncString::try_from_optional(identity.first_name)?, + middle_name: EncString::try_from_optional(identity.middle_name)?, + last_name: EncString::try_from_optional(identity.last_name)?, + address1: EncString::try_from_optional(identity.address1)?, + address2: EncString::try_from_optional(identity.address2)?, + address3: EncString::try_from_optional(identity.address3)?, + city: EncString::try_from_optional(identity.city)?, + state: EncString::try_from_optional(identity.state)?, + postal_code: EncString::try_from_optional(identity.postal_code)?, + country: EncString::try_from_optional(identity.country)?, + company: EncString::try_from_optional(identity.company)?, + email: EncString::try_from_optional(identity.email)?, + phone: EncString::try_from_optional(identity.phone)?, + ssn: EncString::try_from_optional(identity.ssn)?, + username: EncString::try_from_optional(identity.username)?, + passport_number: EncString::try_from_optional(identity.passport_number)?, + license_number: EncString::try_from_optional(identity.license_number)?, + }) + } +} diff --git a/crates/bitwarden/src/vault/cipher/linked_id.rs b/crates/bitwarden/src/vault/cipher/linked_id.rs index 692d59b77..6fb676dfe 100644 --- a/crates/bitwarden/src/vault/cipher/linked_id.rs +++ b/crates/bitwarden/src/vault/cipher/linked_id.rs @@ -10,6 +10,7 @@ pub enum LinkedIdType { Identity(IdentityLinkedIdType), } +use crate::error::{Error, Result}; #[cfg(feature = "mobile")] use crate::UniffiCustomTypeConverter; #[cfg(feature = "mobile")] @@ -73,6 +74,43 @@ pub enum IdentityLinkedIdType { FullName = 418, } +impl TryFrom for LinkedIdType { + type Error = Error; + + fn try_from(value: u32) -> Result { + match value { + 100 => Ok(LinkedIdType::Login(LoginLinkedIdType::Username)), + 101 => Ok(LinkedIdType::Login(LoginLinkedIdType::Password)), + 300 => Ok(LinkedIdType::Card(CardLinkedIdType::CardholderName)), + 301 => Ok(LinkedIdType::Card(CardLinkedIdType::ExpMonth)), + 302 => Ok(LinkedIdType::Card(CardLinkedIdType::ExpYear)), + 303 => Ok(LinkedIdType::Card(CardLinkedIdType::Code)), + 304 => Ok(LinkedIdType::Card(CardLinkedIdType::Brand)), + 305 => Ok(LinkedIdType::Card(CardLinkedIdType::Number)), + 400 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::Title)), + 401 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::MiddleName)), + 402 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::Address1)), + 403 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::Address2)), + 404 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::Address3)), + 405 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::City)), + 406 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::State)), + 407 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::PostalCode)), + 408 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::Country)), + 409 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::Company)), + 410 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::Email)), + 411 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::Phone)), + 412 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::Ssn)), + 413 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::Username)), + 414 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::PassportNumber)), + 415 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::LicenseNumber)), + 416 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::FirstName)), + 417 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::LastName)), + 418 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::FullName)), + _ => Err(Error::MissingFields), + } + } +} + #[cfg(test)] mod tests { diff --git a/crates/bitwarden/src/vault/cipher/login.rs b/crates/bitwarden/src/vault/cipher/login.rs index 7a8e18411..da71f963f 100644 --- a/crates/bitwarden/src/vault/cipher/login.rs +++ b/crates/bitwarden/src/vault/cipher/login.rs @@ -1,3 +1,4 @@ +use bitwarden_api_api::models::{CipherLoginModel, CipherLoginUriModel}; use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -5,7 +6,7 @@ use serde_repr::{Deserialize_repr, Serialize_repr}; use crate::{ crypto::{EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}, - error::Result, + error::{Error, Result}, }; #[derive(Clone, Copy, Serialize_repr, Deserialize_repr, Debug, JsonSchema)] @@ -106,3 +107,48 @@ impl KeyDecryptable for Login { }) } } + +impl TryFrom for Login { + type Error = Error; + + fn try_from(login: CipherLoginModel) -> Result { + Ok(Self { + username: EncString::try_from_optional(login.username)?, + password: EncString::try_from_optional(login.password)?, + password_revision_date: login + .password_revision_date + .map(|d| d.parse()) + .transpose()?, + uris: login + .uris + .map(|v| v.into_iter().map(|u| u.try_into()).collect()) + .transpose()?, + totp: EncString::try_from_optional(login.totp)?, + autofill_on_page_load: login.autofill_on_page_load, + }) + } +} + +impl TryFrom for LoginUri { + type Error = Error; + + fn try_from(uri: CipherLoginUriModel) -> Result { + Ok(Self { + uri: EncString::try_from_optional(uri.uri)?, + r#match: uri.r#match.map(|m| m.into()), + }) + } +} + +impl From for UriMatchType { + fn from(value: bitwarden_api_api::models::UriMatchType) -> Self { + match value { + bitwarden_api_api::models::UriMatchType::Variant0 => Self::Domain, + bitwarden_api_api::models::UriMatchType::Variant1 => Self::Host, + bitwarden_api_api::models::UriMatchType::Variant2 => Self::StartsWith, + bitwarden_api_api::models::UriMatchType::Variant3 => Self::Exact, + bitwarden_api_api::models::UriMatchType::Variant4 => Self::RegularExpression, + bitwarden_api_api::models::UriMatchType::Variant5 => Self::Never, + } + } +} diff --git a/crates/bitwarden/src/vault/cipher/secure_note.rs b/crates/bitwarden/src/vault/cipher/secure_note.rs index 0c7b4c799..5fbb81811 100644 --- a/crates/bitwarden/src/vault/cipher/secure_note.rs +++ b/crates/bitwarden/src/vault/cipher/secure_note.rs @@ -1,10 +1,11 @@ +use bitwarden_api_api::models::CipherSecureNoteModel; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use crate::{ crypto::{KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}, - error::Result, + error::{Error, Result}, }; #[derive(Clone, Copy, Serialize_repr, Deserialize_repr, Debug, JsonSchema)] @@ -43,3 +44,21 @@ impl KeyDecryptable for SecureNote { }) } } + +impl TryFrom for SecureNote { + type Error = Error; + + fn try_from(model: CipherSecureNoteModel) -> Result { + Ok(Self { + r#type: model.r#type.map(|t| t.into()).ok_or(Error::MissingFields)?, + }) + } +} + +impl From for SecureNoteType { + fn from(model: bitwarden_api_api::models::SecureNoteType) -> Self { + match model { + bitwarden_api_api::models::SecureNoteType::Variant0 => SecureNoteType::Generic, + } + } +} diff --git a/crates/bitwarden/src/vault/collection.rs b/crates/bitwarden/src/vault/collection.rs index 58492ef17..11983dca5 100644 --- a/crates/bitwarden/src/vault/collection.rs +++ b/crates/bitwarden/src/vault/collection.rs @@ -1,3 +1,4 @@ +use bitwarden_api_api::models::CollectionDetailsResponseModel; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -5,7 +6,7 @@ use uuid::Uuid; use crate::{ client::encryption_settings::EncryptionSettings, crypto::{EncString, KeyDecryptable, LocateKey, SymmetricCryptoKey}, - error::Result, + error::{Error, Result}, }; #[derive(Serialize, Deserialize, Debug, JsonSchema)] @@ -59,3 +60,18 @@ impl KeyDecryptable for Collection { }) } } + +impl TryFrom for Collection { + type Error = Error; + + fn try_from(collection: CollectionDetailsResponseModel) -> Result { + Ok(Collection { + id: collection.id.ok_or(Error::MissingFields)?, + organization_id: collection.organization_id.ok_or(Error::MissingFields)?, + name: collection.name.ok_or(Error::MissingFields)?.parse()?, + external_id: collection.external_id, + hide_passwords: collection.hide_passwords.unwrap_or(false), + read_only: collection.read_only.unwrap_or(false), + }) + } +} diff --git a/crates/bitwarden/src/vault/folder.rs b/crates/bitwarden/src/vault/folder.rs index f9ae06055..d04bf3110 100644 --- a/crates/bitwarden/src/vault/folder.rs +++ b/crates/bitwarden/src/vault/folder.rs @@ -1,3 +1,4 @@ +use bitwarden_api_api::models::FolderResponseModel; use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -5,7 +6,7 @@ use uuid::Uuid; use crate::{ crypto::{EncString, KeyDecryptable, KeyEncryptable, LocateKey, SymmetricCryptoKey}, - error::Result, + error::{Error, Result}, }; #[derive(Serialize, Deserialize, Debug, JsonSchema)] @@ -47,3 +48,15 @@ impl KeyDecryptable for Folder { }) } } + +impl TryFrom for Folder { + type Error = Error; + + fn try_from(folder: FolderResponseModel) -> Result { + Ok(Folder { + id: folder.id.ok_or(Error::MissingFields)?, + name: EncString::try_from_optional(folder.name)?.ok_or(Error::MissingFields)?, + revision_date: folder.revision_date.ok_or(Error::MissingFields)?.parse()?, + }) + } +} diff --git a/crates/bitwarden/src/vault/mod.rs b/crates/bitwarden/src/vault/mod.rs index ca9b12c1c..6ac43a4d0 100644 --- a/crates/bitwarden/src/vault/mod.rs +++ b/crates/bitwarden/src/vault/mod.rs @@ -3,6 +3,7 @@ mod collection; mod folder; mod password_history; mod send; +#[cfg(feature = "mobile")] mod totp; pub use cipher::{Cipher, CipherListView, CipherView}; @@ -10,5 +11,7 @@ pub use collection::{Collection, CollectionView}; pub use folder::{Folder, FolderView}; pub use password_history::{PasswordHistory, PasswordHistoryView}; pub use send::{Send, SendListView, SendView}; +#[cfg(feature = "mobile")] pub(crate) use totp::generate_totp; +#[cfg(feature = "mobile")] pub use totp::TotpResponse; diff --git a/crates/bitwarden/src/vault/password_history.rs b/crates/bitwarden/src/vault/password_history.rs index da6a4b19e..786c7a8f2 100644 --- a/crates/bitwarden/src/vault/password_history.rs +++ b/crates/bitwarden/src/vault/password_history.rs @@ -1,10 +1,11 @@ +use bitwarden_api_api::models::CipherPasswordHistoryModel; use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use crate::{ crypto::{EncString, KeyDecryptable, KeyEncryptable, LocateKey, SymmetricCryptoKey}, - error::Result, + error::{Error, Result}, }; #[derive(Serialize, Deserialize, Debug, JsonSchema)] @@ -42,3 +43,14 @@ impl KeyDecryptable for PasswordHistory { }) } } + +impl TryFrom for PasswordHistory { + type Error = Error; + + fn try_from(model: CipherPasswordHistoryModel) -> Result { + Ok(Self { + password: model.password.parse()?, + last_used_date: model.last_used_date.parse()?, + }) + } +} diff --git a/crates/bitwarden/src/vault/send.rs b/crates/bitwarden/src/vault/send.rs index 3aec03639..255f6b9fd 100644 --- a/crates/bitwarden/src/vault/send.rs +++ b/crates/bitwarden/src/vault/send.rs @@ -1,3 +1,4 @@ +use bitwarden_api_api::models::{SendFileModel, SendResponseModel, SendTextModel}; use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -9,7 +10,7 @@ use crate::{ derive_shareable_key, EncString, KeyDecryptable, KeyEncryptable, LocateKey, SymmetricCryptoKey, }, - error::Result, + error::{Error, Result}, }; #[derive(Serialize, Deserialize, Debug, JsonSchema)] @@ -264,6 +265,64 @@ impl KeyEncryptable for SendView { } } +impl TryFrom for Send { + type Error = Error; + + fn try_from(send: SendResponseModel) -> Result { + Ok(Send { + id: send.id.ok_or(Error::MissingFields)?, + access_id: send.access_id.ok_or(Error::MissingFields)?, + name: send.name.ok_or(Error::MissingFields)?.parse()?, + notes: EncString::try_from_optional(send.notes)?, + key: send.key.ok_or(Error::MissingFields)?.parse()?, + password: send.password, + r#type: send.r#type.ok_or(Error::MissingFields)?.into(), + file: send.file.map(|f| (*f).try_into()).transpose()?, + text: send.text.map(|t| (*t).try_into()).transpose()?, + max_access_count: send.max_access_count.map(|s| s as u32), + access_count: send.access_count.ok_or(Error::MissingFields)? as u32, + disabled: send.disabled.unwrap_or(false), + hide_email: send.hide_email.unwrap_or(false), + revision_date: send.revision_date.ok_or(Error::MissingFields)?.parse()?, + deletion_date: send.deletion_date.ok_or(Error::MissingFields)?.parse()?, + expiration_date: send.expiration_date.map(|s| s.parse()).transpose()?, + }) + } +} + +impl From for SendType { + fn from(t: bitwarden_api_api::models::SendType) -> Self { + match t { + bitwarden_api_api::models::SendType::Variant0 => SendType::Text, + bitwarden_api_api::models::SendType::Variant1 => SendType::File, + } + } +} + +impl TryFrom for SendFile { + type Error = Error; + + fn try_from(file: SendFileModel) -> Result { + Ok(SendFile { + id: file.id.ok_or(Error::MissingFields)?, + file_name: file.file_name.ok_or(Error::MissingFields)?.parse()?, + size: file.size.ok_or(Error::MissingFields)?.to_string(), + size_name: file.size_name.ok_or(Error::MissingFields)?, + }) + } +} + +impl TryFrom for SendText { + type Error = Error; + + fn try_from(text: SendTextModel) -> Result { + Ok(SendText { + text: EncString::try_from_optional(text.text)?, + hidden: text.hidden.unwrap_or(false), + }) + } +} + #[cfg(test)] mod tests { use super::Send; diff --git a/crates/bw/src/auth/login.rs b/crates/bw/src/auth/login.rs index 1fcd15414..53b9b609e 100644 --- a/crates/bw/src/auth/login.rs +++ b/crates/bw/src/auth/login.rs @@ -3,6 +3,7 @@ use bitwarden::{ ApiKeyLoginRequest, PasswordLoginRequest, TwoFactorEmailRequest, TwoFactorProvider, TwoFactorRequest, }, + platform::SyncRequest, Client, }; use bitwarden_cli::text_prompt_when_none; @@ -80,6 +81,13 @@ pub(crate) async fn login_password(mut client: Client, email: Option) -> debug!("{:?}", result); } + let res = client + .sync(&SyncRequest { + exclude_subdomains: Some(true), + }) + .await?; + info!("{:#?}", res); + Ok(()) } From ff5bb1f6fb9ea4b775502fb756e60455bf73b383 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 12 Dec 2023 16:25:59 +0100 Subject: [PATCH 092/157] Rework the WASM code (#44) --- .github/workflows/generate_schemas.yml | 2 +- .gitignore | 10 +- Cargo.lock | 2 + crates/bitwarden-wasm/Cargo.toml | 7 +- crates/bitwarden-wasm/README.md | 23 + crates/bitwarden-wasm/build.sh | 23 + crates/bitwarden/Cargo.toml | 4 +- .../sdk-client}/.gitignore | 0 languages/js/sdk-client/package-lock.json | 616 +++ languages/js/sdk-client/package.json | 20 + .../index.ts => js/sdk-client/src/client.ts} | 75 +- languages/js/sdk-client/src/lib.ts | 2 + .../sdk-client/src}/logging_level.ts | 0 languages/js/sdk-client/tsconfig.json | 12 + languages/js/wasm/.gitignore | 6 + languages/js/wasm/index.js | 25 + languages/js/wasm/package.json | 22 + languages/js_webassembly/index.html | 10 - languages/js_webassembly/index.ts | 26 - languages/js_webassembly/package-lock.json | 4195 ----------------- languages/js_webassembly/package.json | 15 - languages/js_webassembly/styles.css | 7 - languages/js_webassembly/tsconfig.json | 11 - languages/js_webassembly/webpack.config.js | 57 - support/scripts/schemas.ts | 2 +- 25 files changed, 807 insertions(+), 4365 deletions(-) create mode 100644 crates/bitwarden-wasm/README.md create mode 100755 crates/bitwarden-wasm/build.sh rename languages/{js_webassembly => js/sdk-client}/.gitignore (100%) create mode 100644 languages/js/sdk-client/package-lock.json create mode 100644 languages/js/sdk-client/package.json rename languages/{js_webassembly/bitwarden_client/index.ts => js/sdk-client/src/client.ts} (62%) create mode 100644 languages/js/sdk-client/src/lib.ts rename languages/{js_webassembly/bitwarden_client => js/sdk-client/src}/logging_level.ts (100%) create mode 100644 languages/js/sdk-client/tsconfig.json create mode 100644 languages/js/wasm/.gitignore create mode 100644 languages/js/wasm/index.js create mode 100644 languages/js/wasm/package.json delete mode 100644 languages/js_webassembly/index.html delete mode 100644 languages/js_webassembly/index.ts delete mode 100644 languages/js_webassembly/package-lock.json delete mode 100644 languages/js_webassembly/package.json delete mode 100644 languages/js_webassembly/styles.css delete mode 100644 languages/js_webassembly/tsconfig.json delete mode 100644 languages/js_webassembly/webpack.config.js diff --git a/.github/workflows/generate_schemas.yml b/.github/workflows/generate_schemas.yml index efd7dd68d..807da7520 100644 --- a/.github/workflows/generate_schemas.yml +++ b/.github/workflows/generate_schemas.yml @@ -40,7 +40,7 @@ jobs: uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 with: name: schemas.ts - path: ${{ github.workspace }}/languages/js_webassembly/bitwarden_client/schemas.ts + path: ${{ github.workspace }}/languages/js/sdk-client/src/schemas.ts if-no-files-found: error - name: Upload c# schemas artifact diff --git a/.gitignore b/.gitignore index 6ddcd3205..23cbaba20 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ build/ bld/ [Bb]in/ [Oo]bj/ +*.wasm # Binary files *.dylib @@ -42,6 +43,7 @@ crates/bitwarden-napi/sdk-napi.*.node # Complied TypeScript client crates/bitwarden-napi/dist +languages/js/sdk-client/dist/ # Uniffi languages/swift/BitwardenFFI.xcframework @@ -52,11 +54,11 @@ languages/kotlin/sdk/src/main/java/com/bitwarden/sdk/bitwarden_uniffi.kt languages/kotlin/sdk/src/main/java/com/bitwarden/core/bitwarden.kt # Schemas -support/schemas crates/bitwarden-napi/src-ts/bitwarden_client/schemas.ts -languages/csharp/Bitwarden.Sdk/schemas.cs -languages/js_webassembly/bitwarden_client/schemas.ts -languages/python/bitwarden_sdk/schemas.py languages/cpp/include/schemas.hpp +languages/csharp/Bitwarden.Sdk/schemas.cs languages/go/schema.go languages/java/src/main/java/com/bitwarden/sdk/schema +languages/js/sdk-client/src/schemas.ts +languages/python/bitwarden_sdk/schemas.py +support/schemas diff --git a/Cargo.lock b/Cargo.lock index 0b89bd2e0..73609e295 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -647,8 +647,10 @@ checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-targets 0.48.5", ] diff --git a/crates/bitwarden-wasm/Cargo.toml b/crates/bitwarden-wasm/Cargo.toml index dc5bf9ba5..176edeaa9 100644 --- a/crates/bitwarden-wasm/Cargo.toml +++ b/crates/bitwarden-wasm/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" rust-version = "1.57" [lib] -crate-type = ["cdylib", "rlib"] +crate-type = ["cdylib"] [dependencies] console_error_panic_hook = "0.1.7" @@ -16,7 +16,10 @@ serde = { version = "1.0.193", features = ["derive"] } wasm-bindgen = { version = "0.2.89", features = ["serde-serialize"] } wasm-bindgen-futures = "0.4.39" -bitwarden-json = { path = "../bitwarden-json", features = ["secrets"] } +bitwarden-json = { path = "../bitwarden-json", features = [ + "secrets", + "internal", +] } [dev-dependencies] wasm-bindgen-test = "0.3.39" diff --git a/crates/bitwarden-wasm/README.md b/crates/bitwarden-wasm/README.md new file mode 100644 index 000000000..6fa8870f2 --- /dev/null +++ b/crates/bitwarden-wasm/README.md @@ -0,0 +1,23 @@ +# Bitwarden-wasm + +Requirements: + +- `wasm32-unknown-unknown` rust target. +- `wasm-bindgen-cli` installed. +- `binaryen` installed for `wasm-opt` and `wasm2js`. + +```bash +rustup target add wasm32-unknown-unknown +cargo install -f wasm-bindgen-cli +brew install binaryen +``` + +#### Build + +```bash +# dev +./build.sh + +# release +./build.sh -r +``` diff --git a/crates/bitwarden-wasm/build.sh b/crates/bitwarden-wasm/build.sh new file mode 100755 index 000000000..ae32c775d --- /dev/null +++ b/crates/bitwarden-wasm/build.sh @@ -0,0 +1,23 @@ +# Move to the root of the repository +cd "$(dirname "$0")" +cd ../../ + +if [ "$1" != "-r" ]; then + # Dev + cargo build -p bitwarden -p bitwarden-wasm --target wasm32-unknown-unknown --features wasm-bindgen + wasm-bindgen --target bundler --out-dir languages/js/wasm ./target/wasm32-unknown-unknown/debug/bitwarden_wasm.wasm + wasm-bindgen --target nodejs --out-dir languages/js/wasm/node ./target/wasm32-unknown-unknown/debug/bitwarden_wasm.wasm +else + # Release + cargo build -p bitwarden -p bitwarden-wasm --target wasm32-unknown-unknown --features wasm-bindgen --release + wasm-bindgen --target bundler --out-dir languages/js/wasm ./target/wasm32-unknown-unknown/release/bitwarden_wasm.wasm + wasm-bindgen --target nodejs --out-dir languages/js/wasm/node ./target/wasm32-unknown-unknown/release/bitwarden_wasm.wasm +fi + +# Optimize size +wasm-opt -Os ./languages/js/wasm/bitwarden_wasm_bg.wasm -o ./languages/js/wasm/bitwarden_wasm_bg.wasm +wasm-opt -Os ./languages/js/wasm/node/bitwarden_wasm_bg.wasm -o ./languages/js/wasm/node/bitwarden_wasm_bg.wasm + +# Transpile to JS +wasm2js ./languages/js/wasm/bitwarden_wasm_bg.wasm -o ./languages/js/wasm/bitwarden_wasm_bg.wasm.js +npx terser ./languages/js/wasm/bitwarden_wasm_bg.wasm.js -o ./languages/js/wasm/bitwarden_wasm_bg.wasm.js diff --git a/crates/bitwarden/Cargo.toml b/crates/bitwarden/Cargo.toml index 8f7c1f4d1..a9cafc4da 100644 --- a/crates/bitwarden/Cargo.toml +++ b/crates/bitwarden/Cargo.toml @@ -19,6 +19,8 @@ secrets = [] # Secrets manager API internal = [] # Internal testing methods mobile = ["uniffi", "internal"] # Mobile-specific features +wasm-bindgen = ["chrono/wasmbind"] + [dependencies] aes = ">=0.8.2, <0.9" argon2 = { version = ">=0.5.0, <0.6", features = [ @@ -46,7 +48,7 @@ pbkdf2 = { version = ">=0.12.1, <0.13", default-features = false } rand = ">=0.8.5, <0.9" reqwest = { version = ">=0.11, <0.12", features = ["json"] } rsa = ">=0.9.2, <0.10" -schemars = { version = ">=0.8, <0.9", features = ["uuid1", "chrono"] } +schemars = { version = ">=0.8.9, <0.9", features = ["uuid1", "chrono"] } serde = { version = ">=1.0, <2.0", features = ["derive"] } serde_json = ">=1.0.96, <2.0" serde_qs = ">=0.12.0, <0.13" diff --git a/languages/js_webassembly/.gitignore b/languages/js/sdk-client/.gitignore similarity index 100% rename from languages/js_webassembly/.gitignore rename to languages/js/sdk-client/.gitignore diff --git a/languages/js/sdk-client/package-lock.json b/languages/js/sdk-client/package-lock.json new file mode 100644 index 000000000..ff252e970 --- /dev/null +++ b/languages/js/sdk-client/package-lock.json @@ -0,0 +1,616 @@ +{ + "name": "@bitwarden/sdk-client", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@bitwarden/sdk-client", + "devDependencies": { + "@types/node": "^18.15.11", + "rimraf": "^5.0.0", + "typescript": "^5.0.3" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@types/node": { + "version": "18.16.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.1.tgz", + "integrity": "sha512-DZxSZWXxFfOlx7k7Rv4LAyiMroaxa3Ly/7OOzZO8cBNho0YzAi4qlbrx8W27JGqG57IgR/6J7r+nOJWw6kcvZA==", + "dev": true + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.2.tgz", + "integrity": "sha512-Xsa0BcxIC6th9UwNjZkhrMtNo/MnyRL8jGCP+uEwhA5oFOCY1f2s1/oNKY47xQ0Bg5nkjsfAEIej1VeH62bDDQ==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.0.3", + "minimatch": "^9.0.0", + "minipass": "^5.0.0", + "path-scurry": "^1.7.0" + }, + "bin": { + "glob": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jackspeak": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.1.0.tgz", + "integrity": "sha512-DiEwVPqsieUzZBNxQ2cxznmFzfg/AMgJUjYw5xl6rSmCxAQXECcbSdwcLM6Ds6T09+SBfSNCGPhYUoQ96P4h7A==", + "dev": true, + "dependencies": { + "cliui": "^7.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/lru-cache": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.1.tgz", + "integrity": "sha512-65/Jky17UwSb0BuB9V+MyDpsOtXKmYwzhyl+cOa9XUiI4uV2Ouy/2voFP3+al0BjZbJgMBD8FojMpAf+Z+qn4A==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/minimatch": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", + "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.7.0.tgz", + "integrity": "sha512-UkZUeDjczjYRE495+9thsgcVgsaCPkaw80slmfVFgllxY+IO8ubTsOpFVjDPROBqJdHfVPUFRHPBV/WciOVfWg==", + "dev": true, + "dependencies": { + "lru-cache": "^9.0.0", + "minipass": "^5.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.0.tgz", + "integrity": "sha512-Jf9llaP+RvaEVS5nPShYFhtXIrb3LRKP281ib3So0KkeZKo2wIKyq0Re7TOSwanasA423PSr6CCIL4bP6T040g==", + "dev": true, + "dependencies": { + "glob": "^10.0.0" + }, + "bin": { + "rimraf": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.1.tgz", + "integrity": "sha512-uUWsN4aOxJAS8KOuf3QMyFtgm1pkb6I+KRZbRF/ghdf5T7sM+B1lLLzPDxswUjkmHyxQAVzEgG35E3NzDM9GVw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/typescript": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", + "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + } + }, + "dependencies": { + "@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true + }, + "@types/node": { + "version": "18.16.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.1.tgz", + "integrity": "sha512-DZxSZWXxFfOlx7k7Rv4LAyiMroaxa3Ly/7OOzZO8cBNho0YzAi4qlbrx8W27JGqG57IgR/6J7r+nOJWw6kcvZA==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + } + }, + "glob": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.2.tgz", + "integrity": "sha512-Xsa0BcxIC6th9UwNjZkhrMtNo/MnyRL8jGCP+uEwhA5oFOCY1f2s1/oNKY47xQ0Bg5nkjsfAEIej1VeH62bDDQ==", + "dev": true, + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.0.3", + "minimatch": "^9.0.0", + "minipass": "^5.0.0", + "path-scurry": "^1.7.0" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "jackspeak": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.1.0.tgz", + "integrity": "sha512-DiEwVPqsieUzZBNxQ2cxznmFzfg/AMgJUjYw5xl6rSmCxAQXECcbSdwcLM6Ds6T09+SBfSNCGPhYUoQ96P4h7A==", + "dev": true, + "requires": { + "@pkgjs/parseargs": "^0.11.0", + "cliui": "^7.0.4" + } + }, + "lru-cache": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.1.tgz", + "integrity": "sha512-65/Jky17UwSb0BuB9V+MyDpsOtXKmYwzhyl+cOa9XUiI4uV2Ouy/2voFP3+al0BjZbJgMBD8FojMpAf+Z+qn4A==", + "dev": true + }, + "minimatch": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", + "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-scurry": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.7.0.tgz", + "integrity": "sha512-UkZUeDjczjYRE495+9thsgcVgsaCPkaw80slmfVFgllxY+IO8ubTsOpFVjDPROBqJdHfVPUFRHPBV/WciOVfWg==", + "dev": true, + "requires": { + "lru-cache": "^9.0.0", + "minipass": "^5.0.0" + } + }, + "rimraf": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.0.tgz", + "integrity": "sha512-Jf9llaP+RvaEVS5nPShYFhtXIrb3LRKP281ib3So0KkeZKo2wIKyq0Re7TOSwanasA423PSr6CCIL4bP6T040g==", + "dev": true, + "requires": { + "glob": "^10.0.0" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "signal-exit": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.1.tgz", + "integrity": "sha512-uUWsN4aOxJAS8KOuf3QMyFtgm1pkb6I+KRZbRF/ghdf5T7sM+B1lLLzPDxswUjkmHyxQAVzEgG35E3NzDM9GVw==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "typescript": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", + "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } + } +} diff --git a/languages/js/sdk-client/package.json b/languages/js/sdk-client/package.json new file mode 100644 index 000000000..9e4998b4f --- /dev/null +++ b/languages/js/sdk-client/package.json @@ -0,0 +1,20 @@ +{ + "name": "@bitwarden/sdk-client", + "exports": { + ".": { + "types": "./dist/src/lib.d.ts", + "default": "./dist/src/lib.js" + } + }, + "main": "dist/src/lib.js", + "types": "dist/src/lib.d.ts", + "scripts": { + "build": "npm run clean && tsc", + "clean": "rimraf dist" + }, + "devDependencies": { + "@types/node": "^18.15.11", + "rimraf": "^5.0.0", + "typescript": "^5.0.3" + } +} diff --git a/languages/js_webassembly/bitwarden_client/index.ts b/languages/js/sdk-client/src/client.ts similarity index 62% rename from languages/js_webassembly/bitwarden_client/index.ts rename to languages/js/sdk-client/src/client.ts index 3f858fd39..f77ba1420 100644 --- a/languages/js_webassembly/bitwarden_client/index.ts +++ b/languages/js/sdk-client/src/client.ts @@ -1,8 +1,6 @@ -import * as rust from "../pkg/bitwarden_wasm"; -import { LoggingLevel } from "./logging_level"; import { - ClientSettings, Convert, + ResponseForFingerprintResponse, ResponseForPasswordLoginResponse, ResponseForSecretIdentifiersResponse, ResponseForSecretResponse, @@ -11,20 +9,28 @@ import { ResponseForUserAPIKeyResponse, } from "./schemas"; +interface BitwardenSDKClient { + run_command(js_input: string): Promise; +} + export class BitwardenClient { - client: rust.BitwardenClient; + client: BitwardenSDKClient; - constructor(settings?: ClientSettings, logging_level?: LoggingLevel) { - const settings_json = settings == null ? null : Convert.clientSettingsToJson(settings); - this.client = new rust.BitwardenClient(settings_json, logging_level ?? LoggingLevel.Info); + constructor(client: BitwardenSDKClient) { + this.client = client; } - async login(email: string, password: string): Promise { + async login(email: string, password: string, pbkdf_iter: number): Promise { const response = await this.client.run_command( Convert.commandToJson({ passwordLogin: { email: email, password: password, + kdf: { + pBKDF2: { + iterations: pbkdf_iter, + } + }, }, }) ); @@ -48,14 +54,11 @@ export class BitwardenClient { return Convert.toResponseForUserAPIKeyResponse(response); } - - async sync( - excludeSubdomains: boolean = false - ): Promise { + async sync(excludeSubdomains: boolean = false): Promise { const response = await this.client.run_command( Convert.commandToJson({ sync: { - excludeSubdomains + excludeSubdomains, }, }) ); @@ -63,25 +66,32 @@ export class BitwardenClient { return Convert.toResponseForSyncResponse(response); } - secrets(): SecretsClient { - return new SecretsClient(this.client); - } + async fingerprint(fingerprintMaterial: string, publicKey: string): Promise { + const response = await this.client.run_command( + Convert.commandToJson({ + fingerprint: { + fingerprintMaterial: fingerprintMaterial, + publicKey: publicKey, + } + }) + ) + + return Convert.toResponseForFingerprintResponse(response).data.fingerprint; + }; } export class SecretsClient { - client: rust.BitwardenClient; + client: BitwardenSDKClient; - constructor(client: rust.BitwardenClient) { + constructor(client: BitwardenSDKClient) { this.client = client; } - async get( - id: string - ): Promise { + async get(id: string): Promise { const response = await this.client.run_command( Convert.commandToJson({ secrets: { - get: { id } + get: { id }, }, }) ); @@ -93,12 +103,12 @@ export class SecretsClient { key: string, note: string, organizationId: string, - value: string, + value: string ): Promise { const response = await this.client.run_command( Convert.commandToJson({ secrets: { - create: { key, note, organizationId, value } + create: { key, note, organizationId, value }, }, }) ); @@ -106,13 +116,11 @@ export class SecretsClient { return Convert.toResponseForSecretResponse(response); } - async list( - organizationId: string - ): Promise { + async list(organizationId: string): Promise { const response = await this.client.run_command( Convert.commandToJson({ secrets: { - list: { organizationId } + list: { organizationId }, }, }) ); @@ -125,12 +133,12 @@ export class SecretsClient { key: string, note: string, organizationId: string, - value: string, + value: string ): Promise { const response = await this.client.run_command( Convert.commandToJson({ secrets: { - update: { id, key, note, organizationId, value } + update: { id, key, note, organizationId, value }, }, }) ); @@ -138,18 +146,15 @@ export class SecretsClient { return Convert.toResponseForSecretResponse(response); } - async delete( - ids: string[] - ): Promise { + async delete(ids: string[]): Promise { const response = await this.client.run_command( Convert.commandToJson({ secrets: { - delete: { ids } + delete: { ids }, }, }) ); return Convert.toResponseForSecretsDeleteResponse(response); } - } diff --git a/languages/js/sdk-client/src/lib.ts b/languages/js/sdk-client/src/lib.ts new file mode 100644 index 000000000..7fd93ce86 --- /dev/null +++ b/languages/js/sdk-client/src/lib.ts @@ -0,0 +1,2 @@ +export * from "./client"; +export * from "./schemas"; diff --git a/languages/js_webassembly/bitwarden_client/logging_level.ts b/languages/js/sdk-client/src/logging_level.ts similarity index 100% rename from languages/js_webassembly/bitwarden_client/logging_level.ts rename to languages/js/sdk-client/src/logging_level.ts diff --git a/languages/js/sdk-client/tsconfig.json b/languages/js/sdk-client/tsconfig.json new file mode 100644 index 000000000..453623d5f --- /dev/null +++ b/languages/js/sdk-client/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "outDir": "./dist/", + "module": "esnext", + "target": "es5", + "moduleResolution": "node", + "sourceMap": true, + "composite": true, + "declarationMap": true + }, + "include": ["./src/**/*"], +} diff --git a/languages/js/wasm/.gitignore b/languages/js/wasm/.gitignore new file mode 100644 index 000000000..c4f6a47f4 --- /dev/null +++ b/languages/js/wasm/.gitignore @@ -0,0 +1,6 @@ +bitwarden_wasm_bg.js +bitwarden_wasm_bg.wasm +bitwarden_wasm_bg.wasm.d.ts +bitwarden_wasm_bg.wasm.js +bitwarden_wasm.d.ts +bitwarden_wasm.js diff --git a/languages/js/wasm/index.js b/languages/js/wasm/index.js new file mode 100644 index 000000000..c2b512005 --- /dev/null +++ b/languages/js/wasm/index.js @@ -0,0 +1,25 @@ +// https://stackoverflow.com/a/47880734 +const supported = (() => { + try { + if (typeof WebAssembly === "object" + && typeof WebAssembly.instantiate === "function") { + const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00)); + if (module instanceof WebAssembly.Module) + return new WebAssembly.Instance(module) instanceof WebAssembly.Instance; + } + } catch (e) { + } + return false; +})(); + +let wasm; + +if (supported) { + wasm = await import('./bitwarden_wasm_bg.wasm'); +} else { + wasm = await import('./bitwarden_wasm_bg.wasm.js'); +} + +import { __wbg_set_wasm } from "./bitwarden_wasm_bg.js"; +__wbg_set_wasm(wasm); +export * from "./bitwarden_wasm_bg.js"; diff --git a/languages/js/wasm/package.json b/languages/js/wasm/package.json new file mode 100644 index 000000000..26379c9a6 --- /dev/null +++ b/languages/js/wasm/package.json @@ -0,0 +1,22 @@ +{ + "name": "@bitwarden/sdk-wasm", + "version": "0.1.0", + "files": [ + "bitwarden_wasm_bg.js", + "bitwarden_wasm_bg.wasm", + "bitwarden_wasm.d.ts", + "bitwarden_wasm.js", + "index.js", + "node/bitwarden_wasm_bg.wasm.d.ts", + "node/bitwarden_wasm_bg.wasm", + "node/bitwarden_wasm.d.ts", + "node/bitwarden_wasm.js" + ], + "main": "node/bitwarden_wasm.js", + "module": "index.js", + "types": "bitwarden_wasm.d.ts", + "sideEffects": [ + "./bitwarden_wasm.js", + "./snippets/*" + ] +} diff --git a/languages/js_webassembly/index.html b/languages/js_webassembly/index.html deleted file mode 100644 index 661517934..000000000 --- a/languages/js_webassembly/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/languages/js_webassembly/index.ts b/languages/js_webassembly/index.ts deleted file mode 100644 index 047a815bb..000000000 --- a/languages/js_webassembly/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { LoggingLevel } from "./bitwarden_client/logging_level"; -import { DeviceType } from "./bitwarden_client/schemas"; - -import("./bitwarden_client").then(async (module) => { - const client = new module.BitwardenClient({ - apiUrl: "http://localhost:8081/api", - identityUrl: "http://localhost:8081/identity", - deviceType: DeviceType.SDK, - userAgent: "Bitwarden JS SDK", - }, LoggingLevel.Debug); - const result = await client.login("test@bitwarden.com", "asdfasdf"); - console.log(`auth result success: ${result.success}`); - - const apikeyResponse = await client.getUserApiKey("asdfasdf"); - console.log(`user API key: ${apikeyResponse.data.apiKey}`); - - const sync = await client.sync(); - console.log("Sync result", sync); - - const org_id = sync.data.profile.organizations[0].id; - - const secret = await client.secrets().create("TEST_KEY", "This is a test secret", org_id, "Secret1234!"); - console.log("New secret: ", secret.data); - - await client.secrets().delete([secret.data.id]); -}); diff --git a/languages/js_webassembly/package-lock.json b/languages/js_webassembly/package-lock.json deleted file mode 100644 index c4a2126e6..000000000 --- a/languages/js_webassembly/package-lock.json +++ /dev/null @@ -1,4195 +0,0 @@ -{ - "name": "js_webassembly", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "devDependencies": { - "html-webpack-plugin": "5.5.3", - "text-encoding": "0.7.0", - "ts-loader": "9.5.1", - "wasm-pack": "0.12.1", - "webpack": "5.89.0", - "webpack-cli": "5.1.4", - "webpack-dev-server": "4.15.1" - } - }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", - "dev": true, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", - "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", - "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", - "dev": true - }, - "node_modules/@types/body-parser": { - "version": "1.19.5", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", - "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", - "dev": true, - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/bonjour": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", - "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect-history-api-fallback": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", - "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", - "dev": true, - "dependencies": { - "@types/express-serve-static-core": "*", - "@types/node": "*" - } - }, - "node_modules/@types/eslint": { - "version": "8.44.8", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.8.tgz", - "integrity": "sha512-4K8GavROwhrYl2QXDXm0Rv9epkA8GBFu0EI+XrrnnuCl7u8CWBRusX7fXJfanhZTDWSAL24gDI/UqXyUM0Injw==", - "dev": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true - }, - "node_modules/@types/express": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", - "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", - "dev": true, - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.41", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", - "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", - "dev": true, - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", - "dev": true - }, - "node_modules/@types/http-errors": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", - "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", - "dev": true - }, - "node_modules/@types/http-proxy": { - "version": "1.17.14", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", - "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true - }, - "node_modules/@types/node": { - "version": "20.10.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.3.tgz", - "integrity": "sha512-XJavIpZqiXID5Yxnxv3RUDKTN5b81ddNC3ecsA0SoFXz/QU8OGBwZGMomiq0zw+uuqbL/krztv/DINAQ/EV4gg==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/node-forge": { - "version": "1.3.10", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.10.tgz", - "integrity": "sha512-y6PJDYN4xYBxwd22l+OVH35N+1fCYWiuC3aiP2SlXVE6Lo7SS+rSx9r89hLxrP4pn6n1lBGhHJ12pj3F3Mpttw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/qs": { - "version": "6.9.10", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.10.tgz", - "integrity": "sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw==", - "dev": true - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true - }, - "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", - "dev": true - }, - "node_modules/@types/send": { - "version": "0.17.4", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", - "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", - "dev": true, - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/serve-index": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", - "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", - "dev": true, - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", - "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", - "dev": true, - "dependencies": { - "@types/http-errors": "*", - "@types/mime": "*", - "@types/node": "*" - } - }, - "node_modules/@types/sockjs": { - "version": "0.3.36", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", - "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", - "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", - "dev": true, - "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", - "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", - "dev": true, - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", - "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", - "dev": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", - "dev": true, - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", - "dev": true - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", - "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-opt": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6", - "@webassemblyjs/wast-printer": "1.11.6" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", - "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", - "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", - "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", - "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webpack-cli/configtest": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", - "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", - "dev": true, - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - } - }, - "node_modules/@webpack-cli/info": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", - "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", - "dev": true, - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - } - }, - "node_modules/@webpack-cli/serve": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", - "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", - "dev": true, - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - }, - "peerDependenciesMeta": { - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dev": true, - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", - "dev": true, - "peerDependencies": { - "acorn": "^8" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/ansi-html-community": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", - "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", - "dev": true, - "engines": [ - "node >= 0.8.0" - ], - "bin": { - "ansi-html": "bin/ansi-html" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", - "dev": true - }, - "node_modules/axios": { - "version": "0.26.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", - "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", - "dev": true, - "dependencies": { - "follow-redirects": "^1.14.8" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", - "dev": true - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/binary-install": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/binary-install/-/binary-install-1.1.0.tgz", - "integrity": "sha512-rkwNGW+3aQVSZoD0/o3mfPN6Yxh3Id0R/xzTVBVVpGNlVz8EGwusksxRlbk/A5iKTZt9zkMn3qIqmAt3vpfbzg==", - "dev": true, - "dependencies": { - "axios": "^0.26.1", - "rimraf": "^3.0.2", - "tar": "^6.1.11" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/bonjour-service": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", - "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==", - "dev": true, - "dependencies": { - "array-flatten": "^2.1.2", - "dns-equal": "^1.0.0", - "fast-deep-equal": "^3.1.3", - "multicast-dns": "^7.2.5" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.22.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", - "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001565", - "electron-to-chromium": "^1.4.601", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "dev": true, - "dependencies": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001566", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001566.tgz", - "integrity": "sha512-ggIhCsTxmITBAMmK8yZjEhCO5/47jKXPu6Dha/wuCS4JePVL+3uiDEBuhu2aIoT+bqTOR8L76Ip1ARL9xYsEJA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/clean-css": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", - "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", - "dev": true, - "dependencies": { - "source-map": "~0.6.0" - }, - "engines": { - "node": ">= 10.0" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true - }, - "node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "dev": true, - "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/connect-history-api-fallback": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", - "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dev": true, - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dev": true - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "dev": true, - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/default-gateway": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", - "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", - "dev": true, - "dependencies": { - "execa": "^5.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "dev": true, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "dev": true - }, - "node_modules/dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", - "dev": true - }, - "node_modules/dns-packet": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", - "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", - "dev": true, - "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/dom-converter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", - "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", - "dev": true, - "dependencies": { - "utila": "~0.4" - } - }, - "node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "dev": true, - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "dev": true, - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dev": true, - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "dev": true, - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true - }, - "node_modules/electron-to-chromium": { - "version": "1.4.601", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.601.tgz", - "integrity": "sha512-SpwUMDWe9tQu8JX5QCO1+p/hChAi9AE9UpoC3rcHVc+gdCGlbT3SGb5I1klgb952HRIyvt9wZhSz9bNBYz9swA==", - "dev": true - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/envinfo": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz", - "integrity": "sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg==", - "dev": true, - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/es-module-lexer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", - "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", - "dev": true - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "dev": true, - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express/node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true, - "engines": { - "node": ">= 4.9.1" - } - }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "dev": true, - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "bin": { - "flat": "cli.js" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs-monkey": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz", - "integrity": "sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==", - "dev": true - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "dev": true - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, - "node_modules/hpack.js/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/hpack.js/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/hpack.js/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/html-entities": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", - "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/mdevils" - }, - { - "type": "patreon", - "url": "https://patreon.com/mdevils" - } - ] - }, - "node_modules/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", - "dev": true, - "dependencies": { - "camel-case": "^4.1.2", - "clean-css": "^5.2.2", - "commander": "^8.3.0", - "he": "^1.2.0", - "param-case": "^3.0.4", - "relateurl": "^0.2.7", - "terser": "^5.10.0" - }, - "bin": { - "html-minifier-terser": "cli.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/html-webpack-plugin": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.3.tgz", - "integrity": "sha512-6YrDKTuqaP/TquFH7h4srYWsZx+x6k6+FbsTm0ziCwGHDP78Unr1r9F/H4+sGmMbX08GQcJ+K64x55b+7VM/jg==", - "dev": true, - "dependencies": { - "@types/html-minifier-terser": "^6.0.0", - "html-minifier-terser": "^6.0.2", - "lodash": "^4.17.21", - "pretty-error": "^4.0.0", - "tapable": "^2.0.0" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/html-webpack-plugin" - }, - "peerDependencies": { - "webpack": "^5.20.0" - } - }, - "node_modules/htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "node_modules/http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", - "dev": true - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-parser-js": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", - "dev": true - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/http-proxy-middleware": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", - "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", - "dev": true, - "dependencies": { - "@types/http-proxy": "^1.17.8", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@types/express": "^4.17.13" - }, - "peerDependenciesMeta": { - "@types/express": { - "optional": true - } - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/interpret": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", - "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/ipaddr.js": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", - "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", - "dev": true, - "dependencies": { - "hasown": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/launch-editor": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.1.tgz", - "integrity": "sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw==", - "dev": true, - "dependencies": { - "picocolors": "^1.0.0", - "shell-quote": "^1.8.1" - } - }, - "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true, - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dev": true, - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memfs": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", - "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", - "dev": true, - "dependencies": { - "fs-monkey": "^1.0.4" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "dev": true - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/multicast-dns": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", - "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", - "dev": true, - "dependencies": { - "dns-packet": "^5.2.2", - "thunky": "^1.0.2" - }, - "bin": { - "multicast-dns": "cli.js" - } - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dev": true, - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "dev": true, - "engines": { - "node": ">= 6.13.0" - } - }, - "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "dev": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "dev": true - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", - "dev": true, - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "dev": true, - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/param-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "dev": true, - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "dev": true, - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", - "dev": true - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pretty-error": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", - "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", - "dev": true, - "dependencies": { - "lodash": "^4.17.20", - "renderkid": "^3.0.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-addr/node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/rechoir": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", - "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", - "dev": true, - "dependencies": { - "resolve": "^1.20.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/renderkid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", - "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", - "dev": true, - "dependencies": { - "css-select": "^4.1.3", - "dom-converter": "^0.2.0", - "htmlparser2": "^6.1.0", - "lodash": "^4.17.21", - "strip-ansi": "^6.0.1" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", - "dev": true - }, - "node_modules/selfsigned": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", - "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", - "dev": true, - "dependencies": { - "@types/node-forge": "^1.3.0", - "node-forge": "^1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", - "dev": true, - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-index/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "dev": true, - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true - }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "node_modules/serve-index/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "dev": true, - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", - "dev": true, - "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/shell-quote": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", - "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/sockjs": { - "version": "0.3.24", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", - "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", - "dev": true, - "dependencies": { - "faye-websocket": "^0.11.3", - "uuid": "^8.3.2", - "websocket-driver": "^0.7.4" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "dev": true, - "dependencies": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "dev": true, - "dependencies": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, - "node_modules/spdy-transport/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/spdy-transport/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/spdy/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/spdy/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/tar": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", - "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", - "dev": true, - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser": { - "version": "5.24.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.24.0.tgz", - "integrity": "sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==", - "dev": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", - "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.16.8" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/text-encoding": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.7.0.tgz", - "integrity": "sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA==", - "deprecated": "no longer maintained", - "dev": true - }, - "node_modules/thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", - "dev": true - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/ts-loader": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", - "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4", - "source-map": "^0.7.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "typescript": "*", - "webpack": "^5.0.0" - } - }, - "node_modules/ts-loader/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typescript": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", - "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", - "dev": true, - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "node_modules/utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", - "dev": true - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "dev": true, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/wasm-pack": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/wasm-pack/-/wasm-pack-0.12.1.tgz", - "integrity": "sha512-dIyKWUumPFsGohdndZjDXRFaokUT/kQS+SavbbiXVAvA/eN4riX5QNdB6AhXQx37zNxluxQkuixZUgJ8adKjOg==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "binary-install": "^1.0.1" - }, - "bin": { - "wasm-pack": "run.js" - } - }, - "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dev": true, - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "dev": true, - "dependencies": { - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/webpack": { - "version": "5.89.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", - "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", - "dev": true, - "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.15.0", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-cli": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", - "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", - "dev": true, - "dependencies": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.1.1", - "@webpack-cli/info": "^2.0.2", - "@webpack-cli/serve": "^2.0.5", - "colorette": "^2.0.14", - "commander": "^10.0.1", - "cross-spawn": "^7.0.3", - "envinfo": "^7.7.3", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^3.1.1", - "rechoir": "^0.8.0", - "webpack-merge": "^5.7.3" - }, - "bin": { - "webpack-cli": "bin/cli.js" - }, - "engines": { - "node": ">=14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "5.x.x" - }, - "peerDependenciesMeta": { - "@webpack-cli/generators": { - "optional": true - }, - "webpack-bundle-analyzer": { - "optional": true - }, - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/webpack-cli/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/webpack-dev-middleware": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", - "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", - "dev": true, - "dependencies": { - "colorette": "^2.0.10", - "memfs": "^3.4.3", - "mime-types": "^2.1.31", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/webpack-dev-middleware/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack-dev-middleware/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/webpack-dev-middleware/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/webpack-dev-middleware/node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/webpack-dev-server": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", - "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", - "dev": true, - "dependencies": { - "@types/bonjour": "^3.5.9", - "@types/connect-history-api-fallback": "^1.3.5", - "@types/express": "^4.17.13", - "@types/serve-index": "^1.9.1", - "@types/serve-static": "^1.13.10", - "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.5", - "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.0.11", - "chokidar": "^3.5.3", - "colorette": "^2.0.10", - "compression": "^1.7.4", - "connect-history-api-fallback": "^2.0.0", - "default-gateway": "^6.0.3", - "express": "^4.17.3", - "graceful-fs": "^4.2.6", - "html-entities": "^2.3.2", - "http-proxy-middleware": "^2.0.3", - "ipaddr.js": "^2.0.1", - "launch-editor": "^2.6.0", - "open": "^8.0.9", - "p-retry": "^4.5.0", - "rimraf": "^3.0.2", - "schema-utils": "^4.0.0", - "selfsigned": "^2.1.1", - "serve-index": "^1.9.1", - "sockjs": "^0.3.24", - "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.1", - "ws": "^8.13.0" - }, - "bin": { - "webpack-dev-server": "bin/webpack-dev-server.js" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.37.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - }, - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-dev-server/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack-dev-server/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/webpack-dev-server/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/webpack-dev-server/node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", - "dev": true, - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dev": true, - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wildcard": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", - "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", - "dev": true - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } -} diff --git a/languages/js_webassembly/package.json b/languages/js_webassembly/package.json deleted file mode 100644 index d5d7342df..000000000 --- a/languages/js_webassembly/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "scripts": { - "build": "wasm-pack build ../../crates/bitwarden-wasm --out-dir ../../languages/js_webassembly/pkg --dev", - "build:watch": "npm run build && webpack serve" - }, - "devDependencies": { - "html-webpack-plugin": "5.5.3", - "text-encoding": "0.7.0", - "ts-loader": "9.5.1", - "wasm-pack": "0.12.1", - "webpack": "5.89.0", - "webpack-cli": "5.1.4", - "webpack-dev-server": "4.15.1" - } -} diff --git a/languages/js_webassembly/styles.css b/languages/js_webassembly/styles.css deleted file mode 100644 index 4c336b64d..000000000 --- a/languages/js_webassembly/styles.css +++ /dev/null @@ -1,7 +0,0 @@ -html, -body { - height: 400px; - width: 200px; - margin: 0; - box-sizing: border-box; -} diff --git a/languages/js_webassembly/tsconfig.json b/languages/js_webassembly/tsconfig.json deleted file mode 100644 index ac51138c0..000000000 --- a/languages/js_webassembly/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "outDir": "./dist/", - "module": "es2020", - "target": "es5", - "jsx": "react", - "allowJs": true, - "moduleResolution": "node", - "sourceMap": true - } -} diff --git a/languages/js_webassembly/webpack.config.js b/languages/js_webassembly/webpack.config.js deleted file mode 100644 index 430df86f6..000000000 --- a/languages/js_webassembly/webpack.config.js +++ /dev/null @@ -1,57 +0,0 @@ -const path = require("path"); -const HtmlWebpackPlugin = require("html-webpack-plugin"); -const webpack = require("webpack"); - -module.exports = { - entry: "./index.ts", - module: { - rules: [ - { - test: /\.tsx?$/, - use: "ts-loader", - exclude: /node_modules/, - }, - { - test: /\.wasm$/, - type: "webassembly/sync", - }, - ], - }, - resolve: { - extensions: [".tsx", ".ts", ".js"], - }, - output: { - path: path.resolve(__dirname, "dist"), - filename: "index.js", - }, - plugins: [ - new HtmlWebpackPlugin(), - // Have this example work in Edge which doesn't ship `TextEncoder` or - // `TextDecoder` at this time. - new webpack.ProvidePlugin({ - TextDecoder: ["text-encoding", "TextDecoder"], - TextEncoder: ["text-encoding", "TextEncoder"], - }), - ], - experiments: { - syncWebAssembly: true, - }, - devServer: { - proxy: { - "/api": { - target: "http://localhost:4000", - pathRewrite: { "^/api": "" }, - secure: false, - changeOrigin: true, - }, - "/identity": { - target: "http://localhost:33656", - pathRewrite: { "^/identity": "" }, - secure: false, - changeOrigin: true, - }, - }, - }, - mode: "development", - devtool: "source-map", -}; diff --git a/support/scripts/schemas.ts b/support/scripts/schemas.ts index 0ca6f023b..f045b0f5a 100644 --- a/support/scripts/schemas.ts +++ b/support/scripts/schemas.ts @@ -39,7 +39,7 @@ async function main() { rendererOptions: {}, }); - writeToFile("./languages/js_webassembly/bitwarden_client/schemas.ts", ts.lines); + writeToFile("./languages/js/sdk-client/src/schemas.ts", ts.lines); writeToFile("./crates/bitwarden-napi/src-ts/bitwarden_client/schemas.ts", ts.lines); const python = await quicktype({ From 33c8612f301eabe6aec411d89376c2603b0f483d Mon Sep 17 00:00:00 2001 From: Vince Grassia <593223+vgrassia@users.noreply.github.com> Date: Tue, 12 Dec 2023 11:55:32 -0500 Subject: [PATCH 093/157] Fix version bump workflow on call (#426) --- .github/workflows/version-bump.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index f136c1dfe..fd9c9bef7 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -37,6 +37,9 @@ jobs: steps: - name: Checkout Branch uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + repository: bitwarden/sdk + ref: master - name: Install rust uses: dtolnay/rust-toolchain@be73d7920c329f220ce78e0234b8f96b7ae60248 # stable @@ -175,7 +178,7 @@ jobs: TITLE: "Bump version to ${{ inputs.version_number }}" run: | PR_URL=$(gh pr create --title "$TITLE" \ - --base "$GITHUB_REF" \ + --base "master" \ --head "$PR_BRANCH" \ --label "version update" \ --label "automated pr" \ From b0243ce4d51a2119228a64530f560b5c7229b7f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Tue, 12 Dec 2023 17:57:23 +0100 Subject: [PATCH 094/157] Split set_login_method from set_tokens (#425) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective There's no reason for these two to be combined, and it forces `renew_tokens` to clone login_method every time --- crates/bitwarden/src/auth/login/access_token.rs | 8 +++++--- crates/bitwarden/src/auth/login/api_key.rs | 12 ++++++------ crates/bitwarden/src/auth/login/password.rs | 10 +++++----- crates/bitwarden/src/auth/renew.rs | 6 ++---- crates/bitwarden/src/client/client.rs | 9 +++------ 5 files changed, 21 insertions(+), 24 deletions(-) diff --git a/crates/bitwarden/src/auth/login/access_token.rs b/crates/bitwarden/src/auth/login/access_token.rs index 8d5996119..147480576 100644 --- a/crates/bitwarden/src/auth/login/access_token.rs +++ b/crates/bitwarden/src/auth/login/access_token.rs @@ -58,12 +58,14 @@ pub(crate) async fn login_access_token( r.access_token.clone(), r.refresh_token.clone(), r.expires_in, - LoginMethod::ServiceAccount(ServiceAccountLoginMethod::AccessToken { + ); + client.set_login_method(LoginMethod::ServiceAccount( + ServiceAccountLoginMethod::AccessToken { access_token_id: access_token.access_token_id, client_secret: access_token.client_secret, organization_id, - }), - ); + }, + )); client.initialize_crypto_single_key(encryption_key); } diff --git a/crates/bitwarden/src/auth/login/api_key.rs b/crates/bitwarden/src/auth/login/api_key.rs index db5cbe01d..6f75b0033 100644 --- a/crates/bitwarden/src/auth/login/api_key.rs +++ b/crates/bitwarden/src/auth/login/api_key.rs @@ -36,13 +36,13 @@ pub(crate) async fn login_api_key( r.access_token.clone(), r.refresh_token.clone(), r.expires_in, - LoginMethod::User(UserLoginMethod::ApiKey { - client_id: input.client_id.to_owned(), - client_secret: input.client_secret.to_owned(), - email, - kdf, - }), ); + client.set_login_method(LoginMethod::User(UserLoginMethod::ApiKey { + client_id: input.client_id.to_owned(), + client_secret: input.client_secret.to_owned(), + email, + kdf, + })); let user_key: EncString = r.key.as_deref().unwrap().parse().unwrap(); let private_key: EncString = r.private_key.as_deref().unwrap().parse().unwrap(); diff --git a/crates/bitwarden/src/auth/login/password.rs b/crates/bitwarden/src/auth/login/password.rs index 08d7d2054..7a93c68e0 100644 --- a/crates/bitwarden/src/auth/login/password.rs +++ b/crates/bitwarden/src/auth/login/password.rs @@ -42,12 +42,12 @@ pub(crate) async fn login_password( r.access_token.clone(), r.refresh_token.clone(), r.expires_in, - LoginMethod::User(UserLoginMethod::Username { - client_id: "web".to_owned(), - email: input.email.to_owned(), - kdf: input.kdf.to_owned(), - }), ); + client.set_login_method(LoginMethod::User(UserLoginMethod::Username { + client_id: "web".to_owned(), + email: input.email.to_owned(), + kdf: input.kdf.to_owned(), + })); let user_key: EncString = r.key.as_deref().unwrap().parse().unwrap(); let private_key: EncString = r.private_key.as_deref().unwrap().parse().unwrap(); diff --git a/crates/bitwarden/src/auth/renew.rs b/crates/bitwarden/src/auth/renew.rs index 7973947ab..f215024d2 100644 --- a/crates/bitwarden/src/auth/renew.rs +++ b/crates/bitwarden/src/auth/renew.rs @@ -57,13 +57,11 @@ pub(crate) async fn renew_token(client: &mut Client) -> Result<()> { match res { IdentityTokenResponse::Refreshed(r) => { - let login_method = login_method.to_owned(); - client.set_tokens(r.access_token, r.refresh_token, r.expires_in, login_method); + client.set_tokens(r.access_token, r.refresh_token, r.expires_in); return Ok(()); } IdentityTokenResponse::Authenticated(r) => { - let login_method = login_method.to_owned(); - client.set_tokens(r.access_token, r.refresh_token, r.expires_in, login_method); + client.set_tokens(r.access_token, r.refresh_token, r.expires_in); return Ok(()); } _ => { diff --git a/crates/bitwarden/src/client/client.rs b/crates/bitwarden/src/client/client.rs index 2165b8dc7..fbbfb8798 100644 --- a/crates/bitwarden/src/client/client.rs +++ b/crates/bitwarden/src/client/client.rs @@ -29,7 +29,7 @@ pub(crate) struct ApiConfigurations { pub device_type: DeviceType, } -#[derive(Debug, Clone)] +#[derive(Debug)] pub(crate) enum LoginMethod { #[cfg(feature = "internal")] User(UserLoginMethod), @@ -38,7 +38,7 @@ pub(crate) enum LoginMethod { ServiceAccount(ServiceAccountLoginMethod), } -#[derive(Debug, Clone)] +#[derive(Debug)] #[cfg(feature = "internal")] pub(crate) enum UserLoginMethod { Username { @@ -55,7 +55,7 @@ pub(crate) enum UserLoginMethod { }, } -#[derive(Debug, Clone)] +#[derive(Debug)] pub(crate) enum ServiceAccountLoginMethod { AccessToken { access_token_id: Uuid, @@ -172,7 +172,6 @@ impl Client { self.encryption_settings.as_ref().ok_or(Error::VaultLocked) } - #[cfg(feature = "mobile")] pub(crate) fn set_login_method(&mut self, login_method: LoginMethod) { use log::debug; @@ -185,12 +184,10 @@ impl Client { token: String, refresh_token: Option, expires_in: u64, - login_method: LoginMethod, ) { self.token = Some(token.clone()); self.refresh_token = refresh_token; self.token_expires_in = Some(Utc::now().timestamp() + expires_in as i64); - self.login_method = Some(login_method); self.__api_configurations.identity.oauth_access_token = Some(token.clone()); self.__api_configurations.api.oauth_access_token = Some(token); } From c76e52e85baef7ebb802a84065da0ff4a1570275 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 12 Dec 2023 17:58:04 +0100 Subject: [PATCH 095/157] [deps]: Update ts-node to v10.9.2 (#413) 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 | |---|---|---|---|---|---| | [ts-node](https://typestrong.org/ts-node) ([source](https://togithub.com/TypeStrong/ts-node)) | [`10.9.1` -> `10.9.2`](https://renovatebot.com/diffs/npm/ts-node/10.9.1/10.9.2) | [![age](https://developer.mend.io/api/mc/badges/age/npm/ts-node/10.9.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/ts-node/10.9.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/ts-node/10.9.1/10.9.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/ts-node/10.9.1/10.9.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
TypeStrong/ts-node (ts-node) ### [`v10.9.2`](https://togithub.com/TypeStrong/ts-node/releases/tag/v10.9.2): Fix `tsconfig.json` file not found [Compare Source](https://togithub.com/TypeStrong/ts-node/compare/v10.9.1...v10.9.2) **Fixed** - Fixed `tsconfig.json` file not found on latest TypeScript version ([https://github.com/TypeStrong/ts-node/pull/2091](https://togithub.com/TypeStrong/ts-node/pull/2091))
--- ### Configuration πŸ“… **Schedule**: Branch creation - "every weekend" (UTC), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. β™» **Rebasing**: Whenever PR is behind base branch, 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, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/bitwarden/sdk). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bitwarden-napi/package-lock.json | 8 ++++---- crates/bitwarden-napi/package.json | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/bitwarden-napi/package-lock.json b/crates/bitwarden-napi/package-lock.json index 00e591aaf..7a1281560 100644 --- a/crates/bitwarden-napi/package-lock.json +++ b/crates/bitwarden-napi/package-lock.json @@ -10,7 +10,7 @@ "license": "SEE LICENSE IN LICENSE", "devDependencies": { "@napi-rs/cli": "^2.13.2", - "ts-node": "10.9.1", + "ts-node": "10.9.2", "typescript": "^5.0.0" }, "engines": { @@ -153,9 +153,9 @@ "dev": true }, "node_modules/ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", diff --git a/crates/bitwarden-napi/package.json b/crates/bitwarden-napi/package.json index a0dbcee3b..509cf1763 100644 --- a/crates/bitwarden-napi/package.json +++ b/crates/bitwarden-napi/package.json @@ -30,7 +30,7 @@ }, "devDependencies": { "@napi-rs/cli": "^2.13.2", - "ts-node": "10.9.1", + "ts-node": "10.9.2", "typescript": "^5.0.0" }, "engines": { diff --git a/package-lock.json b/package-lock.json index faafd1f1c..9a7fa0a74 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "prettier": "3.1.1", "quicktype-core": "23.0.80", "rimraf": "5.0.5", - "ts-node": "10.9.1", + "ts-node": "10.9.2", "typescript": "5.3.2" } }, @@ -1875,9 +1875,9 @@ } }, "node_modules/ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", diff --git a/package.json b/package.json index 0332edade..464d12ecc 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "prettier": "3.1.1", "quicktype-core": "23.0.80", "rimraf": "5.0.5", - "ts-node": "10.9.1", + "ts-node": "10.9.2", "typescript": "5.3.2" } } From 1b5d01b6a920a3c4d9a54decc5df9e23473b8372 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 12 Dec 2023 17:04:04 +0000 Subject: [PATCH 096/157] [deps]: Update typescript to v5.3.3 (#414) 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 | |---|---|---|---|---|---| | [typescript](https://www.typescriptlang.org/) ([source](https://togithub.com/Microsoft/TypeScript)) | [`5.3.2` -> `5.3.3`](https://renovatebot.com/diffs/npm/typescript/5.3.2/5.3.3) | [![age](https://developer.mend.io/api/mc/badges/age/npm/typescript/5.3.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/typescript/5.3.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/typescript/5.3.2/5.3.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/typescript/5.3.2/5.3.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
Microsoft/TypeScript (typescript) ### [`v5.3.3`](https://togithub.com/microsoft/TypeScript/releases/tag/v5.3.3): TypeScript 5.3.3 [Compare Source](https://togithub.com/Microsoft/TypeScript/compare/v5.3.2...v5.3.3) For release notes, check out the [release announcement](https://devblogs.microsoft.com/typescript/announcing-typescript-5-3/). For the complete list of fixed issues, check out the - [fixed issues query for Typescript 5.3.0 (Beta)](https://togithub.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+5.3.0%22+is%3Aclosed+). - [fixed issues query for Typescript 5.3.1 (RC)](https://togithub.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+5.3.1%22+is%3Aclosed+). - [fixed issues query for Typescript 5.3.2 (Stable)](https://togithub.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+5.3.2%22+is%3Aclosed+). - [fixed issues query for Typescript 5.3.3 (Stable)](https://togithub.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+5.3.3%22+is%3Aclosed+). Downloads are available on: - [NuGet package](https://www.nuget.org/packages/Microsoft.TypeScript.MSBuild)
--- ### Configuration πŸ“… **Schedule**: Branch creation - "every weekend" (UTC), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. β™» **Rebasing**: Whenever PR is behind base branch, 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, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/bitwarden/sdk). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9a7fa0a74..729222a14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "quicktype-core": "23.0.80", "rimraf": "5.0.5", "ts-node": "10.9.2", - "typescript": "5.3.2" + "typescript": "5.3.3" } }, "node_modules/@babel/runtime": { @@ -1936,9 +1936,9 @@ } }, "node_modules/typescript": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", - "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true, "bin": { "tsc": "bin/tsc", diff --git a/package.json b/package.json index 464d12ecc..683635f01 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,6 @@ "quicktype-core": "23.0.80", "rimraf": "5.0.5", "ts-node": "10.9.2", - "typescript": "5.3.2" + "typescript": "5.3.3" } } From 9c65e0775497cd43c4fb27896844580a1ed62db4 Mon Sep 17 00:00:00 2001 From: Vince Grassia <593223+vgrassia@users.noreply.github.com> Date: Tue, 12 Dec 2023 12:38:22 -0500 Subject: [PATCH 097/157] Add token to checkout step (#428) --- .github/workflows/version-bump.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index fd9c9bef7..19af72c1d 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -35,12 +35,6 @@ jobs: name: "Bump ${{ inputs.project }} Version to v${{ inputs.version_number }}" runs-on: ubuntu-22.04 steps: - - name: Checkout Branch - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - with: - repository: bitwarden/sdk - ref: master - - name: Install rust uses: dtolnay/rust-toolchain@be73d7920c329f220ce78e0234b8f96b7ae60248 # stable with: @@ -66,6 +60,13 @@ jobs: github-gpg-private-key-passphrase, github-pat-bitwarden-devops-bot-repo-scope" + - name: Checkout Branch + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + repository: bitwarden/sdk + ref: master + token: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }} + - name: Import GPG key uses: crazy-max/ghaction-import-gpg@82a020f1f7f605c65dd2449b392a52c3fcfef7ef # v6.0.0 with: @@ -173,8 +174,8 @@ jobs: if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }} id: create-pr env: - PR_BRANCH: ${{ steps.create-branch.outputs.name }} GH_TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }} + PR_BRANCH: ${{ steps.create-branch.outputs.name }} TITLE: "Bump version to ${{ inputs.version_number }}" run: | PR_URL=$(gh pr create --title "$TITLE" \ From 6a190a19a3bd765f61e1189c83859c85be2b731f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Tue, 12 Dec 2023 19:08:56 +0100 Subject: [PATCH 098/157] Change login_method to have the access token directly (#427) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Now that set_login_method is split from set_tokens, we can just change this to directly contain the token without adding the Clone bound to it. This way we avoid duplicating 2/3rds of the AccessToken. This should help with #388 --- crates/bitwarden/src/auth/login/access_token.rs | 3 +-- crates/bitwarden/src/auth/renew.rs | 15 +++++++-------- crates/bitwarden/src/client/access_token.rs | 11 ++++++++++- crates/bitwarden/src/client/client.rs | 5 +++-- 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/crates/bitwarden/src/auth/login/access_token.rs b/crates/bitwarden/src/auth/login/access_token.rs index 147480576..ed664437d 100644 --- a/crates/bitwarden/src/auth/login/access_token.rs +++ b/crates/bitwarden/src/auth/login/access_token.rs @@ -61,8 +61,7 @@ pub(crate) async fn login_access_token( ); client.set_login_method(LoginMethod::ServiceAccount( ServiceAccountLoginMethod::AccessToken { - access_token_id: access_token.access_token_id, - client_secret: access_token.client_secret, + access_token, organization_id, }, )); diff --git a/crates/bitwarden/src/auth/renew.rs b/crates/bitwarden/src/auth/renew.rs index f215024d2..fe1920e62 100644 --- a/crates/bitwarden/src/auth/renew.rs +++ b/crates/bitwarden/src/auth/renew.rs @@ -43,14 +43,13 @@ pub(crate) async fn renew_token(client: &mut Client) -> Result<()> { } }, LoginMethod::ServiceAccount(s) => match s { - ServiceAccountLoginMethod::AccessToken { - access_token_id, - client_secret, - .. - } => { - AccessTokenRequest::new(*access_token_id, client_secret) - .send(&client.__api_configurations) - .await? + ServiceAccountLoginMethod::AccessToken { access_token, .. } => { + AccessTokenRequest::new( + access_token.access_token_id, + &access_token.client_secret, + ) + .send(&client.__api_configurations) + .await? } }, }; diff --git a/crates/bitwarden/src/client/access_token.rs b/crates/bitwarden/src/client/access_token.rs index db6b78f20..c3d6f2fea 100644 --- a/crates/bitwarden/src/client/access_token.rs +++ b/crates/bitwarden/src/client/access_token.rs @@ -1,4 +1,4 @@ -use std::str::FromStr; +use std::{fmt::Debug, str::FromStr}; use base64::Engine; use uuid::Uuid; @@ -15,6 +15,15 @@ pub struct AccessToken { pub encryption_key: SymmetricCryptoKey, } +// We don't want to log the more sensitive fields from an AccessToken +impl Debug for AccessToken { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AccessToken") + .field("access_token_id", &self.access_token_id) + .finish() + } +} + impl FromStr for AccessToken { type Err = crate::error::Error; diff --git a/crates/bitwarden/src/client/client.rs b/crates/bitwarden/src/client/client.rs index fbbfb8798..0c69ba917 100644 --- a/crates/bitwarden/src/client/client.rs +++ b/crates/bitwarden/src/client/client.rs @@ -22,6 +22,8 @@ use crate::{ error::{Error, Result}, }; +use super::AccessToken; + #[derive(Debug)] pub(crate) struct ApiConfigurations { pub identity: bitwarden_api_identity::apis::configuration::Configuration, @@ -58,8 +60,7 @@ pub(crate) enum UserLoginMethod { #[derive(Debug)] pub(crate) enum ServiceAccountLoginMethod { AccessToken { - access_token_id: Uuid, - client_secret: String, + access_token: AccessToken, organization_id: Uuid, }, } From c362462efb8edf9c9187a8e61e8907eb98e24002 Mon Sep 17 00:00:00 2001 From: Joseph Flinn <58369717+joseph-flinn@users.noreply.github.com> Date: Tue, 12 Dec 2023 12:05:00 -0800 Subject: [PATCH 099/157] Point workflows at `main` (#423) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [x] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Update workflows to point to `main` ## Code changes - **.github/workflows/*:** Change all relevant references of internal `master` branches to `main` ## Before you submit - Please add **unit tests** where it makes sense to do so (encouraged but not required) - If this change requires a **documentation update** - notify the documentation team - If this change has particular **deployment requirements** - notify the DevOps team - Ensure that all UI additions follow [WCAG AA requirements](https://contributing.bitwarden.com/contributing/accessibility/) --- .github/workflows/build-android.yml | 2 +- .github/workflows/build-cli.yml | 2 +- .github/workflows/build-dotnet.yml | 2 +- .github/workflows/build-java.yml | 2 +- .github/workflows/build-napi.yml | 2 +- .github/workflows/build-rust-crates.yml | 4 ++-- .github/workflows/cloc.yml | 2 +- .github/workflows/direct-minimal-versions.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/publish-php.yml | 2 +- .github/workflows/publish-ruby.yml | 2 +- .github/workflows/release-cli.yml | 2 +- .github/workflows/release-napi.yml | 4 ++-- .github/workflows/rust-test.yml | 2 +- .github/workflows/version-bump.yml | 2 +- 15 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 22eda7f88..424d048e9 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -5,7 +5,7 @@ on: pull_request: push: branches: - - "master" + - "main" workflow_dispatch: defaults: diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index 4239a9a0a..c8b1e97dd 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -5,7 +5,7 @@ on: pull_request: push: branches: - - "master" + - "main" - "rc" - "hotfix-rc" workflow_dispatch: diff --git a/.github/workflows/build-dotnet.yml b/.github/workflows/build-dotnet.yml index 7ebb03526..91f21210b 100644 --- a/.github/workflows/build-dotnet.yml +++ b/.github/workflows/build-dotnet.yml @@ -3,7 +3,7 @@ name: Build .NET SDK on: push: branches: - - master + - main jobs: generate_schemas: diff --git a/.github/workflows/build-java.yml b/.github/workflows/build-java.yml index 002164647..663f0d493 100644 --- a/.github/workflows/build-java.yml +++ b/.github/workflows/build-java.yml @@ -3,7 +3,7 @@ name: Build Java SDK on: push: branches: - - master + - main jobs: generate_schemas: diff --git a/.github/workflows/build-napi.yml b/.github/workflows/build-napi.yml index d5bd935e9..96d96fb8c 100644 --- a/.github/workflows/build-napi.yml +++ b/.github/workflows/build-napi.yml @@ -5,7 +5,7 @@ on: pull_request: push: branches: - - "master" + - "main" - "rc" - "hotfix-rc" workflow_dispatch: diff --git a/.github/workflows/build-rust-crates.yml b/.github/workflows/build-rust-crates.yml index 7504887f0..b008540fb 100644 --- a/.github/workflows/build-rust-crates.yml +++ b/.github/workflows/build-rust-crates.yml @@ -6,7 +6,7 @@ on: pull_request: push: branches: - - "master" + - "main" - "rc" - "hotfix-rc" @@ -59,7 +59,7 @@ jobs: release-dry-run: name: Release dry-run runs-on: ubuntu-latest - if: ${{ github.ref == 'refs/head/master' || github.ref == 'refs/head/rc' || github.ref == 'refs/head/hotfix-rc' }} + if: ${{ github.ref == 'refs/head/main' || github.ref == 'refs/head/rc' || github.ref == 'refs/head/hotfix-rc' }} needs: - build steps: diff --git a/.github/workflows/cloc.yml b/.github/workflows/cloc.yml index a3afad257..f87ba6aa8 100644 --- a/.github/workflows/cloc.yml +++ b/.github/workflows/cloc.yml @@ -3,7 +3,7 @@ name: CLOC on: workflow_dispatch: push: - branches: ["master"] + branches: ["main"] pull_request: jobs: diff --git a/.github/workflows/direct-minimal-versions.yml b/.github/workflows/direct-minimal-versions.yml index f164a2433..eb911072f 100644 --- a/.github/workflows/direct-minimal-versions.yml +++ b/.github/workflows/direct-minimal-versions.yml @@ -5,7 +5,7 @@ on: pull_request: push: branches: - - "master" + - "main" - "rc" - "hotfix-rc" workflow_dispatch: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d6e57740e..0d2dbed91 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -3,7 +3,7 @@ name: Lint on: workflow_dispatch: push: - branches: ["master"] + branches: ["main"] pull_request: env: diff --git a/.github/workflows/publish-php.yml b/.github/workflows/publish-php.yml index b033319ec..ef03eca4a 100644 --- a/.github/workflows/publish-php.yml +++ b/.github/workflows/publish-php.yml @@ -3,7 +3,7 @@ name: Publish PHP SDK on: push: branches: - - master + - main jobs: build_rust: diff --git a/.github/workflows/publish-ruby.yml b/.github/workflows/publish-ruby.yml index fa46af292..c7d5ded20 100644 --- a/.github/workflows/publish-ruby.yml +++ b/.github/workflows/publish-ruby.yml @@ -3,7 +3,7 @@ name: Publish Ruby SDK on: push: branches: - - master + - main jobs: generate_schemas: diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index 04863f34e..ee08c2be8 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -72,7 +72,7 @@ jobs: workflow: build-cli.yml path: packages workflow_conclusion: success - branch: master + branch: main - name: Get checksum files uses: bitwarden/gh-actions/get-checksum@main diff --git a/.github/workflows/release-napi.yml b/.github/workflows/release-napi.yml index 3171cfb33..19595832a 100644 --- a/.github/workflows/release-napi.yml +++ b/.github/workflows/release-napi.yml @@ -117,7 +117,7 @@ jobs: artifacts: schemas.ts path: ${{ github.workspace }}/crates/bitwarden-napi/src-ts/bitwarden_client/ workflow_conclusion: success - branch: master + branch: main - name: Install dependencies run: npm ci @@ -153,7 +153,7 @@ jobs: workflow: build-napi.yml path: ${{ github.workspace }}/crates/bitwarden-napi/artifacts workflow_conclusion: success - branch: master + branch: main - name: Move artifacts run: npm run artifacts diff --git a/.github/workflows/rust-test.yml b/.github/workflows/rust-test.yml index 2ea0ad5eb..49775481b 100644 --- a/.github/workflows/rust-test.yml +++ b/.github/workflows/rust-test.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: push: branches: - - "master" + - "main" - "rc" - "hotfix-rc" pull_request: diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index 19af72c1d..31e8b4a3a 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -179,7 +179,7 @@ jobs: TITLE: "Bump version to ${{ inputs.version_number }}" run: | PR_URL=$(gh pr create --title "$TITLE" \ - --base "master" \ + --base "main" \ --head "$PR_BRANCH" \ --label "version update" \ --label "automated pr" \ From 92f19dc401964e3acea6c35e3118d44d75b71007 Mon Sep 17 00:00:00 2001 From: Joseph Flinn <58369717+joseph-flinn@users.noreply.github.com> Date: Wed, 13 Dec 2023 02:56:42 -0800 Subject: [PATCH 100/157] Fix branch (#430) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [x] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Fix changed branch ## Code changes * **.github/workflow/version-bump.yml:** Switch from `master` to `main` ## Before you submit - Please check for formatting errors (`dotnet format --verify-no-changes`) (required) - If making database changes - make sure you also update Entity Framework queries and/or migrations - Please add **unit tests** where it makes sense to do so (encouraged but not required) - If this change requires a **documentation update** - notify the documentation team - If this change has particular **deployment requirements** - notify the DevOps team --- .github/workflows/version-bump.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index 31e8b4a3a..4fb83ae8c 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -64,7 +64,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: repository: bitwarden/sdk - ref: master + ref: main token: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }} - name: Import GPG key From f8114ab16ca7fc19ef04f68f36077d094e2f6dc6 Mon Sep 17 00:00:00 2001 From: Robyn MacCallum Date: Wed, 13 Dec 2023 10:24:31 -0500 Subject: [PATCH 101/157] Update Changelog with env output item (#429) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [x] Other ``` ## Objective Add env output item in the changelog ## Before you submit - Please add **unit tests** where it makes sense to do so (encouraged but not required) --- crates/bws/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/bws/CHANGELOG.md b/crates/bws/CHANGELOG.md index 2dc71f152..3ae1d9009 100644 --- a/crates/bws/CHANGELOG.md +++ b/crates/bws/CHANGELOG.md @@ -7,6 +7,10 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added + +- Ability to output secrets in an `env` format with `bws` (#320) + ## [0.3.1] - 2023-10-13 ### Added From a8ebf5acef922ddc642b8ab4566b78711981ed50 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 14 Dec 2023 13:19:49 +0100 Subject: [PATCH 102/157] Tweak ruby to not emit null in settings (#432) --- languages/ruby/bitwarden_sdk/lib/bitwarden-sdk.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/languages/ruby/bitwarden_sdk/lib/bitwarden-sdk.rb b/languages/ruby/bitwarden_sdk/lib/bitwarden-sdk.rb index c2ce7e576..b023402da 100644 --- a/languages/ruby/bitwarden_sdk/lib/bitwarden-sdk.rb +++ b/languages/ruby/bitwarden_sdk/lib/bitwarden-sdk.rb @@ -37,7 +37,7 @@ def initialize(bitwarden_settings) ) @bitwarden = BitwardenLib - @handle = @bitwarden.init(client_settings.to_json) + @handle = @bitwarden.init(client_settings.to_dynamic.compact.to_json) @command_runner = CommandRunner.new(@bitwarden, @handle) @project_client = ProjectsClient.new(@command_runner) @secrets_client = SecretsClient.new(@command_runner) From 7ec2183ab8194a05c994c9c67909dec538da1096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Thu, 14 Dec 2023 15:11:08 +0100 Subject: [PATCH 103/157] Improve SDK bindings examples (#377) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Updated all the samples to load access token, organization id and urls from env variables. This made it much easier to quickly test all the bindings. Also added one Java example. --- languages/cpp/examples/Wrapper.cpp | 15 +++---- languages/go/example/example.go | 1 + languages/java/Example.java | 33 +++++++++++++++ .../com/bitwarden/sdk/ExampleProgram.java | 42 ------------------- languages/php/example.php | 10 +++-- languages/ruby/examples/example.rb | 13 +++--- 6 files changed, 56 insertions(+), 58 deletions(-) create mode 100644 languages/java/Example.java delete mode 100644 languages/java/src/main/java/com/bitwarden/sdk/ExampleProgram.java diff --git a/languages/cpp/examples/Wrapper.cpp b/languages/cpp/examples/Wrapper.cpp index df4aa164c..e94980698 100644 --- a/languages/cpp/examples/Wrapper.cpp +++ b/languages/cpp/examples/Wrapper.cpp @@ -7,6 +7,9 @@ int main() { const char* accessTokenEnv = std::getenv("ACCESS_TOKEN"); const char* organizationIdEnv = std::getenv("ORGANIZATION_ID"); + const char* apiUrl = std::getenv("API_URL"); + const char* identityUrl = std::getenv("IDENTITY_URL"); + if (!accessTokenEnv || !organizationIdEnv) { std::cerr << "Error: Environment variables ACCESS_TOKEN or ORGANIZATION_ID not set." << std::endl; return 1; @@ -15,15 +18,13 @@ int main() { std::string accessToken = accessTokenEnv; std::string organizationId = organizationIdEnv; - - - // Optional - commented to use default values - // BitwardenSettings bitwardenSettings; - // bitwardenSettings.set_api_url(""); - // bitwardenSettings.set_identity_url(""); + // Configuring the URLS is optional, remove them to use the default values + BitwardenSettings bitwardenSettings; + bitwardenSettings.set_api_url(apiUrl); + bitwardenSettings.set_identity_url(identityUrl); // Create a Bitwarden client instance - BitwardenClient bitwardenClient = BitwardenClient(); + BitwardenClient bitwardenClient = BitwardenClient(bitwardenSettings); // // Access token login bitwardenClient.accessTokenLogin(accessToken); // Organization ID diff --git a/languages/go/example/example.go b/languages/go/example/example.go index 5935d8002..154c14618 100644 --- a/languages/go/example/example.go +++ b/languages/go/example/example.go @@ -9,6 +9,7 @@ import ( ) func main() { + // Configuring the URLS is optional, set them to nil to use the default values apiURL := os.Getenv("API_URL") identityURL := os.Getenv("IDENTITY_URL") diff --git a/languages/java/Example.java b/languages/java/Example.java new file mode 100644 index 000000000..eacf23f8a --- /dev/null +++ b/languages/java/Example.java @@ -0,0 +1,33 @@ +import java.lang.System; +import java.util.UUID; + +import com.bitwarden.sdk.*; +import com.bitwarden.sdk.schema.*; + +class Example { + public static void main(String[] args) { + + String accessToken = System.getenv("ACCESS_TOKEN"); + UUID organizationId = UUID.fromString(System.getenv("ORGANIZATION_ID")); + String apiUrl = System.getenv("API_URL"); + String identityUrl = System.getenv("IDENTITY_URL"); + + // Configuring the URLS is optional, remove them to use the default values + BitwardenSettings bitwardenSettings = new BitwardenSettings(); + bitwardenSettings.setApiUrl(apiUrl); + bitwardenSettings.setIdentityUrl(identityUrl); + BitwardenClient client = new BitwardenClient(bitwardenSettings); + client.accessTokenLogin(accessToken); + + + ProjectResponse project = client.projects().create(organizationId, "Test Project"); + ProjectsResponse list = client.projects().list(organizationId); + + SecretResponse secret = client.secrets().create("Secret Key", "Secret Value", "Secret Note", organizationId, new UUID[] { project.getID() }); + + System.out.println("Secret: " + secret.getValue()); + + client.secrets().delete(new UUID[] { secret.getID() }); + client.projects().delete(new UUID[] { project.getID() }); + } +} diff --git a/languages/java/src/main/java/com/bitwarden/sdk/ExampleProgram.java b/languages/java/src/main/java/com/bitwarden/sdk/ExampleProgram.java deleted file mode 100644 index f3ad53036..000000000 --- a/languages/java/src/main/java/com/bitwarden/sdk/ExampleProgram.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.bitwarden.sdk; - -import com.bitwarden.sdk.schema.*; - -import java.util.UUID; - -public class ExampleProgram { - - public static void main(String[] args) { - BitwardenSettings bitwardenSettings = new BitwardenSettings(); - bitwardenSettings.setApiUrl("https://api.bitwarden.com"); - bitwardenSettings.setIdentityUrl("https://identity.bitwarden.com"); - - try (BitwardenClient bitwardenClient = new BitwardenClient(bitwardenSettings)) { - APIKeyLoginResponse apiKeyLoginResponse = bitwardenClient.accessTokenLogin(""); - - UUID organizationId = UUID.fromString(""); - ProjectResponse projectResponse = bitwardenClient.projects().create(organizationId, "NewTestProject"); - UUID projectId = projectResponse.getID(); - ProjectsResponse projectsResponse = bitwardenClient.projects().list(organizationId); - projectResponse = bitwardenClient.projects().get(projectId); - projectResponse = bitwardenClient.projects().update(projectId, organizationId, "NewTestProject2"); - - String key = "key"; - String value = "value"; - String note = "note"; - SecretResponse secretResponse = bitwardenClient.secrets().create(key, value, note, organizationId, - new UUID[]{projectId}); - UUID secretId = secretResponse.getID(); - SecretIdentifiersResponse secretIdentifiersResponse = bitwardenClient.secrets().list(organizationId); - secretResponse = bitwardenClient.secrets().get(secretId); - key = "key2"; - value = "value2"; - note = "note2"; - secretResponse = bitwardenClient.secrets().update(secretId, key, value, note, organizationId, - new UUID[]{projectId}); - - SecretsDeleteResponse secretsDeleteResponse = bitwardenClient.secrets().delete(new UUID[]{secretId}); - ProjectsDeleteResponse projectsDeleteResponse = bitwardenClient.projects().delete(new UUID[]{projectId}); - } - } -} diff --git a/languages/php/example.php b/languages/php/example.php index 0fdb6930a..7eafcb96a 100644 --- a/languages/php/example.php +++ b/languages/php/example.php @@ -2,10 +2,14 @@ require_once 'vendor/autoload.php'; -$access_token = ''; -$organization_id = ""; +$access_token = getenv('ACCESS_TOKEN'); +$organization_id = getenv('ORGANIZATION_ID'); -$client_settings = new \Bitwarden\Sdk\BitwardenSettings(); +// Configuring the URLS is optional, set them to null to use the default values +$api_url = getenv('API_URL'); +$identity_url = getenv('IDENTITY_URL'); + +$client_settings = new \Bitwarden\Sdk\BitwardenSettings($api_url, $identity_url); $bitwarden_client = new \Bitwarden\Sdk\BitwardenClient($client_settings); $bitwarden_client->access_token_login($access_token); diff --git a/languages/ruby/examples/example.rb b/languages/ruby/examples/example.rb index c0deeb437..09192aa5d 100644 --- a/languages/ruby/examples/example.rb +++ b/languages/ruby/examples/example.rb @@ -1,13 +1,14 @@ # NOTE - for example purpose only - import gem instead require 'bitwarden-sdk' -token = '' -organization_id = '' +token = ENV['ACCESS_TOKEN'] +organization_id = ENV['ORGANIZATION_ID'] -bitwarden_settings = BitwardenSDK::BitwardenSettings.new( - 'https://api.bitwarden.com', - 'https://identity.bitwarden.com/connect/token' -) +# Configuring the URLS is optional, set them to nil to use the default values +api_url = ENV['API_URL'] +identity_url = ENV['IDENTITY_URL'] + +bitwarden_settings = BitwardenSDK::BitwardenSettings.new(api_url, identity_url) bw_client = BitwardenSDK::BitwardenClient.new(bitwarden_settings) response = bw_client.access_token_login(token) From e6d0759541d3c65b9338d809bf6876d396d475c6 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Fri, 15 Dec 2023 11:11:33 +0100 Subject: [PATCH 104/157] Add support for Hmac org keys (#435) --- .../src/client/encryption_settings.rs | 11 +-- crates/bitwarden/src/crypto/enc_string.rs | 73 ++++++++++++++++++- 2 files changed, 73 insertions(+), 11 deletions(-) diff --git a/crates/bitwarden/src/client/encryption_settings.rs b/crates/bitwarden/src/client/encryption_settings.rs index 7c16725e7..f56932700 100644 --- a/crates/bitwarden/src/client/encryption_settings.rs +++ b/crates/bitwarden/src/client/encryption_settings.rs @@ -9,7 +9,7 @@ use { crypto::{EncString, KeyDecryptable}, error::{CryptoError, Result}, }, - rsa::{pkcs8::DecodePrivateKey, Oaep}, + rsa::pkcs8::DecodePrivateKey, }; use crate::crypto::SymmetricCryptoKey; @@ -96,14 +96,7 @@ impl EncryptionSettings { // Decrypt the org keys with the private key for (org_id, org_enc_key) in org_enc_keys { - let data = match org_enc_key { - EncString::Rsa2048_OaepSha1_B64 { data } => data, - _ => return Err(CryptoError::InvalidKey.into()), - }; - - let dec = private_key - .decrypt(Oaep::new::(), &data) - .map_err(|_| CryptoError::KeyDecrypt)?; + let dec = org_enc_key.decrypt_with_private_key(private_key)?; let org_key = SymmetricCryptoKey::try_from(dec.as_slice())?; diff --git a/crates/bitwarden/src/crypto/enc_string.rs b/crates/bitwarden/src/crypto/enc_string.rs index 3004d2dea..34ec359ab 100644 --- a/crates/bitwarden/src/crypto/enc_string.rs +++ b/crates/bitwarden/src/crypto/enc_string.rs @@ -2,6 +2,8 @@ use std::{fmt::Display, str::FromStr}; use aes::cipher::{generic_array::GenericArray, typenum::U32}; use base64::Engine; +#[cfg(feature = "internal")] +use rsa::{Oaep, RsaPrivateKey}; use serde::{de::Visitor, Deserialize}; use super::{KeyDecryptable, KeyEncryptable, LocateKey}; @@ -144,12 +146,12 @@ impl FromStr for EncString { Ok(EncString::Rsa2048_OaepSha1_B64 { data }) } #[allow(deprecated)] - ("5", 1) => { + ("5", 2) => { let data = from_b64_vec(parts[0])?; Ok(EncString::Rsa2048_OaepSha256_HmacSha256_B64 { data }) } #[allow(deprecated)] - ("6", 1) => { + ("6", 2) => { let data = from_b64_vec(parts[0])?; Ok(EncString::Rsa2048_OaepSha1_HmacSha256_B64 { data }) } @@ -170,6 +172,29 @@ impl EncString { s.map(|s| s.parse()).transpose() } + /// TODO: Convert this to a trait method + #[cfg(feature = "internal")] + pub(crate) fn decrypt_with_private_key(&self, key: &RsaPrivateKey) -> Result> { + Ok(match self { + EncString::Rsa2048_OaepSha256_B64 { data } => { + key.decrypt(Oaep::new::(), data) + } + EncString::Rsa2048_OaepSha1_B64 { data } => { + key.decrypt(Oaep::new::(), data) + } + #[allow(deprecated)] + EncString::Rsa2048_OaepSha256_HmacSha256_B64 { data } => { + key.decrypt(Oaep::new::(), data) + } + #[allow(deprecated)] + EncString::Rsa2048_OaepSha1_HmacSha256_B64 { data } => { + key.decrypt(Oaep::new::(), data) + } + _ => return Err(CryptoError::InvalidKey.into()), + } + .map_err(|_| CryptoError::KeyDecrypt)?) + } + #[cfg(feature = "mobile")] pub(crate) fn from_buffer(buf: &[u8]) -> Result { if buf.is_empty() { @@ -430,6 +455,50 @@ mod tests { assert_eq!(decrypted_str, test_string); } + #[cfg(feature = "internal")] + #[test] + fn test_enc_string_rsa2048_oaep_sha1_hmac_sha256_b64() { + use rsa::{pkcs8::DecodePrivateKey, RsaPrivateKey}; + + let rsa_private_key: &str = "-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXRVrCX+2hfOQS +8HzYUS2oc/jGVTZpv+/Ryuoh9d8ihYX9dd0cYh2tl6KWdFc88lPUH11Oxqy20Rk2 +e5r/RF6T9yM0Me3NPnaKt+hlhLtfoc0h86LnhD56A9FDUfuI0dVnPcrwNv0YJIo9 +4LwxtbqBULNvXl6wJ7WAbODrCQy5ZgMVg+iH+gGpwiqsZqHt+KuoHWcN53MSPDfa +F4/YMB99U3TziJMOOJask1TEEnakMPln11PczNDazT17DXIxYrbPfutPdh6sLs6A +QOajdZijfEvepgnOe7cQ7aeatiOJFrjTApKPGxOVRzEMX4XS4xbyhH0QxQeB6l16 +l8C0uxIBAgMBAAECggEASaWfeVDA3cVzOPFSpvJm20OTE+R6uGOU+7vh36TX/POq +92qBuwbd0h0oMD32FxsXywd2IxtBDUSiFM9699qufTVuM0Q3tZw6lHDTOVG08+tP +dr8qSbMtw7PGFxN79fHLBxejjO4IrM9lapjWpxEF+11x7r+wM+0xRZQ8sNFYG46a +PfIaty4BGbL0I2DQ2y8I57iBCAy69eht59NLMm27fRWGJIWCuBIjlpfzET1j2HLX +UIh5bTBNzqaN039WH49HczGE3mQKVEJZc/efk3HaVd0a1Sjzyn0QY+N1jtZN3jTR +buDWA1AknkX1LX/0tUhuS3/7C3ejHxjw4Dk1ZLo5/QKBgQDIWvqFn0+IKRSu6Ua2 +hDsufIHHUNLelbfLUMmFthxabcUn4zlvIscJO00Tq/ezopSRRvbGiqnxjv/mYxuc +vOUBeZtlus0Q9RTACBtw9TGoNTmQbEunJ2FOSlqbQxkBBAjgGEppRPt30iGj/VjA +hCATq2MYOa/X4dVR51BqQAFIEwKBgQDBSIfTFKC/hDk6FKZlgwvupWYJyU9Rkyfs +tPErZFmzoKhPkQ3YORo2oeAYmVUbS9I2iIYpYpYQJHX8jMuCbCz4ONxTCuSIXYQY +UcUq4PglCKp31xBAE6TN8SvhfME9/MvuDssnQinAHuF0GDAhF646T3LLS1not6Vs +zv7brwSoGwKBgQC88v/8cGfi80ssQZeMnVvq1UTXIeQcQnoY5lGHJl3K8mbS3TnX +E6c9j417Fdz+rj8KWzBzwWXQB5pSPflWcdZO886Xu/mVGmy9RWgLuVFhXwCwsVEP +jNX5ramRb0/vY0yzenUCninBsIxFSbIfrPtLUYCc4hpxr+sr2Mg/y6jpvQKBgBez +MRRs3xkcuXepuI2R+BCXL1/b02IJTUf1F+1eLLGd7YV0H+J3fgNc7gGWK51hOrF9 +JBZHBGeOUPlaukmPwiPdtQZpu4QNE3l37VlIpKTF30E6mb+BqR+nht3rUjarnMXg +AoEZ18y6/KIjpSMpqC92Nnk/EBM9EYe6Cf4eA9ApAoGAeqEUg46UTlJySkBKURGp +Is3v1kkf5I0X8DnOhwb+HPxNaiEdmO7ckm8+tPVgppLcG0+tMdLjigFQiDUQk2y3 +WjyxP5ZvXu7U96jaJRI8PFMoE06WeVYcdIzrID2HvqH+w0UQJFrLJ/0Mn4stFAEz +XKZBokBGnjFnTnKcs7nv/O8= +-----END PRIVATE KEY-----"; + let private_key = RsaPrivateKey::from_pkcs8_pem(rsa_private_key).unwrap(); + let enc_str: &str = "6.ThnNc67nNr7GELyuhGGfsXNP2zJnNqhrIsjntEQ27r2qmn8vwdHbTbfO0cwt6YgSibDN0PjiCZ1O3Wb/IFq+vwvyRwFqF9145wBF8CQCbkhV+M0XvO99kh0daovtt120Nve/5ETI5PbPag9VdalKRQWZypJaqQHm5TAQVf4F5wtLlCLMBkzqTk+wkFe7BPMTGn07T+O3eJbTxXvyMZewQ7icJF0MZVA7VyWX9qElmZ89FCKowbf1BMr5pbcQ+0KdXcSVW3to43VkTp7k7COwsuH3M/i1AuVP5YN8ixjyRpvaeGqX/ap2nCHK2Wj5VxgCGT7XEls6ZknnAp9nB9qVjQ==|s3ntw5H/KKD/qsS0lUghTHl5Sm9j6m7YEdNHf0OeAFQ="; + let enc_string: EncString = enc_str.parse().unwrap(); + + assert_eq!(enc_string.enc_type(), 6); + + let res = enc_string.decrypt_with_private_key(&private_key).unwrap(); + + assert_eq!(std::str::from_utf8(&res).unwrap(), "EncryptMe!"); + } + #[test] fn test_enc_string_serialization() { #[derive(serde::Serialize, serde::Deserialize)] From 2a163b7bf6bbd006e8ba070a843246d17224141c Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Fri, 15 Dec 2023 16:39:14 +0100 Subject: [PATCH 105/157] Wire up code coverage (#433) --- .github/workflows/rust-test.yml | 40 ++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/.github/workflows/rust-test.yml b/.github/workflows/rust-test.yml index 49775481b..ee8c55a22 100644 --- a/.github/workflows/rust-test.yml +++ b/.github/workflows/rust-test.yml @@ -15,7 +15,7 @@ env: jobs: ci-pass: name: CI is green - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: - test - wasm @@ -25,14 +25,14 @@ jobs: test: name: ${{ matrix.os }} / ${{matrix.target || 'default' }} - runs-on: ${{ matrix.os || 'ubuntu-latest' }} + runs-on: ${{ matrix.os || 'ubuntu-22.04' }} strategy: matrix: os: - - ubuntu-latest - - macOS-latest - - windows-latest + - ubuntu-22.04 + - macOS-12 + - windows-2022 steps: - name: Checkout @@ -49,10 +49,38 @@ jobs: - name: Test run: cargo test --all-features + coverage: + name: Coverage + runs-on: ubuntu-22.04 + + steps: + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Install rust + uses: dtolnay/rust-toolchain@be73d7920c329f220ce78e0234b8f96b7ae60248 # stable + with: + toolchain: stable + components: llvm-tools + + - name: Cache cargo registry + uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 # v2.7.1 + + - name: Install cargo-llvm-cov + run: cargo install cargo-llvm-cov --version 0.5.38 + + - name: Generate coverage + run: cargo llvm-cov --all-features --lcov --output-path lcov.info --ignore-filename-regex "crates/bitwarden-api-" + + - name: Upload to codecov.io + uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + wasm: name: WASM - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout From e32e2ff80f907081729427405da6aa9b5727997b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 10:56:15 +0100 Subject: [PATCH 106/157] [deps]: Update Rust crate thiserror to 1.0.51 (#437) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- crates/bws/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 73609e295..e181f8e76 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3190,18 +3190,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" dependencies = [ "proc-macro2", "quote", diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index ba59ece0e..d9f008043 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -35,7 +35,7 @@ serde = "^1.0.193" serde_json = "^1.0.108" serde_yaml = "0.9" supports-color = "2.1.0" -thiserror = "1.0.50" +thiserror = "1.0.51" tokio = { version = "1.35.0", features = ["rt-multi-thread", "macros"] } toml = "0.8.8" uuid = { version = "^1.6.1", features = ["serde"] } From c071d476e0441d74abecc41e0d1a1babff496c70 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 10:57:15 +0100 Subject: [PATCH 107/157] [deps]: Lock file maintenance (#445) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 158 +++---- crates/bitwarden-napi/package-lock.json | 12 +- languages/js/sdk-client/package-lock.json | 485 +++++++++------------- package-lock.json | 24 +- 4 files changed, 299 insertions(+), 380 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e181f8e76..fcc4155e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,9 +63,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" dependencies = [ "anstyle", "anstyle-parse", @@ -83,30 +83,30 @@ checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -156,7 +156,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -227,7 +227,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -704,7 +704,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -815,9 +815,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "content_inspector" @@ -873,9 +873,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f" dependencies = [ "cfg-if", ] @@ -930,12 +930,12 @@ dependencies = [ [[package]] name = "ctor" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e366bff8cd32dd8754b0991fb66b279dc48f598c3a18914852a6673deef583" +checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e" dependencies = [ "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -1101,9 +1101,9 @@ dependencies = [ [[package]] name = "eyre" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f656be11ddf91bd709454d15d5bd896fbaf4cc3314e69349e4d1569f5b46cd" +checksum = "b6267a1fa6f59179ea4afc8e50fd8612a3cc60bc858f786ff877a4a8cb042799" dependencies = [ "indenter", "once_cell", @@ -1244,7 +1244,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -1411,9 +1411,9 @@ checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "hkdf" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ "hmac", ] @@ -1429,11 +1429,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.5" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1449,9 +1449,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", "http", @@ -1675,9 +1675,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" @@ -1699,9 +1699,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.150" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "libloading" @@ -1832,9 +1832,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "log", @@ -1864,23 +1864,23 @@ checksum = "d4b4532cf86bfef556348ac65e561e3123879f0e7566cca6d43a6ff5326f13df" [[package]] name = "napi-derive" -version = "2.14.2" +version = "2.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0cca5738c6e81eb5ffd2c8ff2b4f05ece9c4c60c7e2b36cec6524492cf7f330" +checksum = "9b5af262f1d8e660742eb722abc7113a5b3c3de4144d0ef23ede2518672ceff1" dependencies = [ "cfg-if", "convert_case", "napi-derive-backend", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.41", ] [[package]] name = "napi-derive-backend" -version = "1.0.55" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35960e5f33228192a9b661447d0dfe8f5a3790ff5b4058c4d67680ded4f65b91" +checksum = "4ea236321b521d6926213a2021e407b0562e28a257c037a45919e414d2cdb4f8" dependencies = [ "convert_case", "once_cell", @@ -1888,7 +1888,7 @@ dependencies = [ "quote", "regex", "semver", - "syn 1.0.109", + "syn 2.0.41", ] [[package]] @@ -2036,9 +2036,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oneshot" @@ -2073,9 +2073,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.60" +version = "0.10.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800" +checksum = "6b8419dc8cc6d866deb801274bba2e6f8f6108c1bb7fcc10ee5ab864931dbb45" dependencies = [ "bitflags 2.4.1", "cfg-if", @@ -2094,7 +2094,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -2105,18 +2105,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.1.6+3.1.4" +version = "300.2.1+3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439fac53e092cd7442a3660c85dde4643ab3b5bd39040912388dcdabf6b88085" +checksum = "3fe476c29791a5ca0d1273c697e96085bbabbbea2ef7afd5617e78a4b40332d3" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.96" +version = "0.9.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f" +checksum = "c3eaad34cdd97d81de97964fc7f29e2d104f483840d906ef56daa1912338460b" dependencies = [ "cc", "libc", @@ -2384,7 +2384,7 @@ dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -2396,7 +2396,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -2640,9 +2640,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.26" +version = "0.38.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9470c4bf8246c8daf25f9598dca807fb6510347b1e1cfa55749113850c79d88a" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" dependencies = [ "bitflags 2.4.1", "errno", @@ -2659,9 +2659,9 @@ checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "safemem" @@ -2743,7 +2743,7 @@ checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -2808,7 +2808,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -2863,7 +2863,7 @@ checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -3068,7 +3068,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -3100,9 +3100,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" dependencies = [ "proc-macro2", "quote", @@ -3205,7 +3205,7 @@ checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -3287,7 +3287,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -3382,7 +3382,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -3436,9 +3436,9 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" @@ -3457,9 +3457,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" [[package]] name = "unicode-ident" @@ -3555,7 +3555,7 @@ version = "0.25.2" source = "git+https://github.com/mozilla/uniffi-rs?rev=23711c8151bbb794369aa1f9d383db386792dff9#23711c8151bbb794369aa1f9d383db386792dff9" dependencies = [ "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -3586,7 +3586,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.39", + "syn 2.0.41", "toml 0.5.11", "uniffi_build", "uniffi_meta", @@ -3635,9 +3635,9 @@ checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" [[package]] name = "unsafe-libyaml" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" +checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" [[package]] name = "url" @@ -3744,7 +3744,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", "wasm-bindgen-shared", ] @@ -3778,7 +3778,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3811,7 +3811,7 @@ checksum = "794645f5408c9a039fd09f4d113cdfb2e7eba5ff1956b07bcf701cf4b394fe89" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -4081,9 +4081,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.5.19" +version = "0.5.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" +checksum = "6c830786f7720c2fd27a1a0e27a709dbd3c4d009b56d098fc742d4f4eab91fe2" dependencies = [ "memchr", ] diff --git a/crates/bitwarden-napi/package-lock.json b/crates/bitwarden-napi/package-lock.json index 7a1281560..e10a23742 100644 --- a/crates/bitwarden-napi/package-lock.json +++ b/crates/bitwarden-napi/package-lock.json @@ -95,9 +95,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.10.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.3.tgz", - "integrity": "sha512-XJavIpZqiXID5Yxnxv3RUDKTN5b81ddNC3ecsA0SoFXz/QU8OGBwZGMomiq0zw+uuqbL/krztv/DINAQ/EV4gg==", + "version": "20.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", + "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==", "dev": true, "peer": true, "dependencies": { @@ -117,9 +117,9 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", - "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz", + "integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==", "dev": true, "engines": { "node": ">=0.4.0" diff --git a/languages/js/sdk-client/package-lock.json b/languages/js/sdk-client/package-lock.json index ff252e970..12a2bb7c7 100644 --- a/languages/js/sdk-client/package-lock.json +++ b/languages/js/sdk-client/package-lock.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/sdk-client", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { @@ -11,6 +11,23 @@ "typescript": "^5.0.3" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -22,30 +39,33 @@ } }, "node_modules/@types/node": { - "version": "18.16.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.1.tgz", - "integrity": "sha512-DZxSZWXxFfOlx7k7Rv4LAyiMroaxa3Ly/7OOzZO8cBNho0YzAi4qlbrx8W27JGqG57IgR/6J7r+nOJWw6kcvZA==", - "dev": true + "version": "18.19.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.3.tgz", + "integrity": "sha512-k5fggr14DwAytoA/t8rPrIz++lXK7/DqckthCmoZOKNsEbJkId4Z//BqgApXBUGrGddrigYa1oqheo/7YmW4rg==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" @@ -66,17 +86,6 @@ "balanced-match": "^1.0.0" } }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -109,10 +118,16 @@ "node": ">= 8" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, "node_modules/foreground-child": { @@ -132,19 +147,19 @@ } }, "node_modules/glob": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.2.tgz", - "integrity": "sha512-Xsa0BcxIC6th9UwNjZkhrMtNo/MnyRL8jGCP+uEwhA5oFOCY1f2s1/oNKY47xQ0Bg5nkjsfAEIej1VeH62bDDQ==", + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.0.3", - "minimatch": "^9.0.0", - "minipass": "^5.0.0", - "path-scurry": "^1.7.0" + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" }, "bin": { - "glob": "dist/cjs/src/bin.js" + "glob": "dist/esm/bin.mjs" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -169,12 +184,12 @@ "dev": true }, "node_modules/jackspeak": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.1.0.tgz", - "integrity": "sha512-DiEwVPqsieUzZBNxQ2cxznmFzfg/AMgJUjYw5xl6rSmCxAQXECcbSdwcLM6Ds6T09+SBfSNCGPhYUoQ96P4h7A==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", "dev": true, "dependencies": { - "cliui": "^7.0.4" + "@isaacs/cliui": "^8.0.2" }, "engines": { "node": ">=14" @@ -187,18 +202,18 @@ } }, "node_modules/lru-cache": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.1.tgz", - "integrity": "sha512-65/Jky17UwSb0BuB9V+MyDpsOtXKmYwzhyl+cOa9XUiI4uV2Ouy/2voFP3+al0BjZbJgMBD8FojMpAf+Z+qn4A==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", + "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", "dev": true, "engines": { "node": "14 || >=16.14" } }, "node_modules/minimatch": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", - "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -211,12 +226,12 @@ } }, "node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", "dev": true, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/path-key": { @@ -229,13 +244,13 @@ } }, "node_modules/path-scurry": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.7.0.tgz", - "integrity": "sha512-UkZUeDjczjYRE495+9thsgcVgsaCPkaw80slmfVFgllxY+IO8ubTsOpFVjDPROBqJdHfVPUFRHPBV/WciOVfWg==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", "dev": true, "dependencies": { - "lru-cache": "^9.0.0", - "minipass": "^5.0.0" + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -245,15 +260,15 @@ } }, "node_modules/rimraf": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.0.tgz", - "integrity": "sha512-Jf9llaP+RvaEVS5nPShYFhtXIrb3LRKP281ib3So0KkeZKo2wIKyq0Re7TOSwanasA423PSr6CCIL4bP6T040g==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz", + "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==", "dev": true, "dependencies": { - "glob": "^10.0.0" + "glob": "^10.3.7" }, "bin": { - "rimraf": "dist/cjs/src/bin.js" + "rimraf": "dist/esm/bin.mjs" }, "engines": { "node": ">=14" @@ -284,9 +299,9 @@ } }, "node_modules/signal-exit": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.1.tgz", - "integrity": "sha512-uUWsN4aOxJAS8KOuf3QMyFtgm1pkb6I+KRZbRF/ghdf5T7sM+B1lLLzPDxswUjkmHyxQAVzEgG35E3NzDM9GVw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "engines": { "node": ">=14" @@ -296,6 +311,24 @@ } }, "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", @@ -309,7 +342,50 @@ "node": ">=8" } }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", @@ -321,19 +397,34 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/typescript": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", - "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=12.20" + "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -350,6 +441,24 @@ } }, "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", @@ -365,251 +474,61 @@ "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } - } - }, - "dependencies": { - "@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "optional": true }, - "@types/node": { - "version": "18.16.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.1.tgz", - "integrity": "sha512-DZxSZWXxFfOlx7k7Rv4LAyiMroaxa3Ly/7OOzZO8cBNho0YzAi4qlbrx8W27JGqG57IgR/6J7r+nOJWw6kcvZA==", - "dev": true - }, - "ansi-regex": { + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "ansi-styles": { + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "requires": { + "dependencies": { "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "emoji-regex": { + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - } - }, - "glob": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.2.tgz", - "integrity": "sha512-Xsa0BcxIC6th9UwNjZkhrMtNo/MnyRL8jGCP+uEwhA5oFOCY1f2s1/oNKY47xQ0Bg5nkjsfAEIej1VeH62bDDQ==", - "dev": true, - "requires": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.0.3", - "minimatch": "^9.0.0", - "minipass": "^5.0.0", - "path-scurry": "^1.7.0" - } - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "jackspeak": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.1.0.tgz", - "integrity": "sha512-DiEwVPqsieUzZBNxQ2cxznmFzfg/AMgJUjYw5xl6rSmCxAQXECcbSdwcLM6Ds6T09+SBfSNCGPhYUoQ96P4h7A==", - "dev": true, - "requires": { - "@pkgjs/parseargs": "^0.11.0", - "cliui": "^7.0.4" - } - }, - "lru-cache": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.1.tgz", - "integrity": "sha512-65/Jky17UwSb0BuB9V+MyDpsOtXKmYwzhyl+cOa9XUiI4uV2Ouy/2voFP3+al0BjZbJgMBD8FojMpAf+Z+qn4A==", - "dev": true - }, - "minimatch": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", - "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-scurry": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.7.0.tgz", - "integrity": "sha512-UkZUeDjczjYRE495+9thsgcVgsaCPkaw80slmfVFgllxY+IO8ubTsOpFVjDPROBqJdHfVPUFRHPBV/WciOVfWg==", - "dev": true, - "requires": { - "lru-cache": "^9.0.0", - "minipass": "^5.0.0" - } - }, - "rimraf": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.0.tgz", - "integrity": "sha512-Jf9llaP+RvaEVS5nPShYFhtXIrb3LRKP281ib3So0KkeZKo2wIKyq0Re7TOSwanasA423PSr6CCIL4bP6T040g==", - "dev": true, - "requires": { - "glob": "^10.0.0" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "signal-exit": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.1.tgz", - "integrity": "sha512-uUWsN4aOxJAS8KOuf3QMyFtgm1pkb6I+KRZbRF/ghdf5T7sM+B1lLLzPDxswUjkmHyxQAVzEgG35E3NzDM9GVw==", - "dev": true - }, - "string-width": { + "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "requires": { + "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, - "strip-ansi": { + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "requires": { + "dependencies": { "ansi-regex": "^5.0.1" - } - }, - "typescript": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", - "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" } } } diff --git a/package-lock.json b/package-lock.json index 729222a14..3aa3c5092 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,9 +19,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.5.tgz", - "integrity": "sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.6.tgz", + "integrity": "sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==", "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" @@ -363,9 +363,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.10.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.3.tgz", - "integrity": "sha512-XJavIpZqiXID5Yxnxv3RUDKTN5b81ddNC3ecsA0SoFXz/QU8OGBwZGMomiq0zw+uuqbL/krztv/DINAQ/EV4gg==", + "version": "20.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", + "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==", "dev": true, "peer": true, "dependencies": { @@ -403,9 +403,9 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", - "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz", + "integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==", "dev": true, "engines": { "node": ">=0.4.0" @@ -1577,9 +1577,9 @@ "dev": true }, "node_modules/regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "dev": true }, "node_modules/require-directory": { From dda075cabe5b48339f7a65604cd1ac63cdcf150f Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 18 Dec 2023 11:02:37 +0100 Subject: [PATCH 108/157] Update old references to master branch (#446) --- languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj | 2 +- languages/java/build.gradle | 4 ++-- languages/kotlin/sdk/build.gradle | 4 ++-- languages/ruby/bitwarden_sdk/bitwarden-sdk.gemspec | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj b/languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj index 951b893ab..183ea8db9 100644 --- a/languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj +++ b/languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj @@ -12,7 +12,7 @@ Bitwarden Inc. SDK - https://github.com/bitwarden/sdk/tree/master/languages/csharp + https://github.com/bitwarden/sdk/tree/main/languages/csharp Git https://bitwarden.com/products/secrets-manager/ diff --git a/languages/java/build.gradle b/languages/java/build.gradle index dc17386c4..f044ea485 100644 --- a/languages/java/build.gradle +++ b/languages/java/build.gradle @@ -31,11 +31,11 @@ repositories { // Determine the version from the git history. // // PRs: use the branch name. - // Master: Grab it from `crates/bitwarden/Cargo.toml` + // Main: Grab it from `crates/bitwarden/Cargo.toml` def branchName = "git branch --show-current".execute().text.trim() - if (branchName == "master") { + if (branchName == "main") { def content = ['grep', '-o', '^version = ".*"', '../../crates/bitwarden/Cargo.toml'].execute().text.trim() def match = ~/version = "(.*)"/ def matcher = match.matcher(content) diff --git a/languages/kotlin/sdk/build.gradle b/languages/kotlin/sdk/build.gradle index 3fb6956e2..a4ee06814 100644 --- a/languages/kotlin/sdk/build.gradle +++ b/languages/kotlin/sdk/build.gradle @@ -46,11 +46,11 @@ publishing { // Determine the version from the git history. // // PRs: use the branch name. - // Master: Grab it from `crates/bitwarden/Cargo.toml` + // Main: Grab it from `crates/bitwarden/Cargo.toml` def branchName = "git branch --show-current".execute().text.trim() - if (branchName == "master") { + if (branchName == "main") { def content = ['grep', '-o', '^version = ".*"', '../../crates/bitwarden/Cargo.toml'].execute().text.trim() def match = ~/version = "(.*)"/ def matcher = match.matcher(content) diff --git a/languages/ruby/bitwarden_sdk/bitwarden-sdk.gemspec b/languages/ruby/bitwarden_sdk/bitwarden-sdk.gemspec index 12a4f5fdc..596531035 100644 --- a/languages/ruby/bitwarden_sdk/bitwarden-sdk.gemspec +++ b/languages/ruby/bitwarden_sdk/bitwarden-sdk.gemspec @@ -15,7 +15,7 @@ Gem::Specification.new do |spec| spec.metadata['homepage_uri'] = spec.homepage spec.metadata['source_code_uri'] = 'https://github.com/bitwarden/sdk' - spec.metadata['changelog_uri'] = 'https://github.com/bitwarden/sdk/blob/master/languages/ruby/CHANGELOG.md' + spec.metadata['changelog_uri'] = 'https://github.com/bitwarden/sdk/blob/main/languages/ruby/CHANGELOG.md' # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. From c8a1d77c7dc4559247608c94ecf973bb6498e912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Mon, 18 Dec 2023 12:46:27 +0100 Subject: [PATCH 109/157] Improve internal error (#436) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Allow using dynamic strings on the Internal error variant, tested on the passphrase length error. The Internal variant is using a `Cow<'static, str>`, to avoid unnecessary allocations on all the cases where a `&'static str` would suffice. I've implemented `From<&'static str>` and `From` for Error. It feels a bit magic, but it avoids repeating `Error::Internal` all over the place. --- crates/bitwarden/src/auth/jwt_token.rs | 4 +--- crates/bitwarden/src/auth/login/api_key.rs | 4 ++-- crates/bitwarden/src/client/kdf.rs | 2 +- crates/bitwarden/src/crypto/fingerprint.rs | 15 +++++---------- crates/bitwarden/src/crypto/mod.rs | 8 +++----- crates/bitwarden/src/crypto/rsa.rs | 6 +++--- crates/bitwarden/src/error.rs | 16 ++++++++++++++-- .../bitwarden/src/platform/get_user_api_key.rs | 2 +- .../bitwarden/src/tool/generators/passphrase.rs | 9 +++------ crates/bitwarden/src/tool/generators/password.rs | 14 ++++---------- crates/bitwarden/src/uniffi_support.rs | 2 +- crates/bitwarden/src/vault/totp.rs | 6 +++--- 12 files changed, 41 insertions(+), 47 deletions(-) diff --git a/crates/bitwarden/src/auth/jwt_token.rs b/crates/bitwarden/src/auth/jwt_token.rs index 9097b84f2..17e0e13e9 100644 --- a/crates/bitwarden/src/auth/jwt_token.rs +++ b/crates/bitwarden/src/auth/jwt_token.rs @@ -29,9 +29,7 @@ impl FromStr for JWTToken { fn from_str(s: &str) -> Result { let split = s.split('.').collect::>(); if split.len() != 3 { - return Err(crate::error::Error::Internal( - "JWT token has an invalid number of parts", - )); + return Err("JWT token has an invalid number of parts".into()); } let decoded = BASE64_ENGINE.decode(split[1])?; Ok(serde_json::from_slice(&decoded)?) diff --git a/crates/bitwarden/src/auth/login/api_key.rs b/crates/bitwarden/src/auth/login/api_key.rs index 6f75b0033..9370559b4 100644 --- a/crates/bitwarden/src/auth/login/api_key.rs +++ b/crates/bitwarden/src/auth/login/api_key.rs @@ -9,7 +9,7 @@ use crate::{ }, client::{LoginMethod, UserLoginMethod}, crypto::EncString, - error::{Error, Result}, + error::Result, Client, }; @@ -28,7 +28,7 @@ pub(crate) async fn login_api_key( // This should always be Some() when logging in with an api key let email = access_token_obj .email - .ok_or(Error::Internal("Access token doesn't contain email"))?; + .ok_or("Access token doesn't contain email")?; let kdf = client.auth().prelogin(email.clone()).await?; diff --git a/crates/bitwarden/src/client/kdf.rs b/crates/bitwarden/src/client/kdf.rs index 4f1edfdb0..2f6787d8d 100644 --- a/crates/bitwarden/src/client/kdf.rs +++ b/crates/bitwarden/src/client/kdf.rs @@ -32,7 +32,7 @@ impl TryFrom for Kdf { default_pbkdf2_iterations, }; - let kdf = response.kdf.ok_or(Error::Internal("KDF not found"))?; + let kdf = response.kdf.ok_or("KDF not found")?; Ok(match kdf { KdfType::Variant0 => Kdf::PBKDF2 { diff --git a/crates/bitwarden/src/crypto/fingerprint.rs b/crates/bitwarden/src/crypto/fingerprint.rs index ddcf89d40..707a07ca6 100644 --- a/crates/bitwarden/src/crypto/fingerprint.rs +++ b/crates/bitwarden/src/crypto/fingerprint.rs @@ -2,10 +2,7 @@ use num_bigint::BigUint; use num_traits::cast::ToPrimitive; use sha2::Digest; -use crate::{ - error::{Error, Result}, - wordlist::EFF_LONG_WORD_LIST, -}; +use crate::{error::Result, wordlist::EFF_LONG_WORD_LIST}; /// Computes a fingerprint of the given `fingerprint_material` using the given `public_key`. /// @@ -17,12 +14,12 @@ pub(crate) fn fingerprint(fingerprint_material: &str, public_key: &[u8]) -> Resu h.update(public_key); h.finalize(); - let hkdf = hkdf::Hkdf::::from_prk(public_key) - .map_err(|_| Error::Internal("hkdf::InvalidLength"))?; + let hkdf = + hkdf::Hkdf::::from_prk(public_key).map_err(|_| "hkdf::InvalidLength")?; let mut user_fingerprint = [0u8; 32]; hkdf.expand(fingerprint_material.as_bytes(), &mut user_fingerprint) - .map_err(|_| Error::Internal("hkdf::expand"))?; + .map_err(|_| "hkdf::expand")?; Ok(hash_word(user_fingerprint).unwrap()) } @@ -37,9 +34,7 @@ fn hash_word(hash: [u8; 32]) -> Result { let hash_arr: Vec = hash.to_vec(); let entropy_available = hash_arr.len() * 4; if num_words as f64 * entropy_per_word > entropy_available as f64 { - return Err(Error::Internal( - "Output entropy of hash function is too small", - )); + return Err("Output entropy of hash function is too small".into()); } let mut phrase = Vec::new(); diff --git a/crates/bitwarden/src/crypto/mod.rs b/crates/bitwarden/src/crypto/mod.rs index fc79ad8f5..3d8f7b0ce 100644 --- a/crates/bitwarden/src/crypto/mod.rs +++ b/crates/bitwarden/src/crypto/mod.rs @@ -24,7 +24,7 @@ use aes::cipher::{generic_array::GenericArray, ArrayLength, Unsigned}; use hmac::digest::OutputSizeUser; -use crate::error::{Error, Result}; +use crate::error::Result; mod enc_string; pub use enc_string::EncString; @@ -65,13 +65,11 @@ pub(crate) const PBKDF_SHA256_HMAC_OUT_SIZE: usize = /// RFC5869 HKDF-Expand operation fn hkdf_expand>(prk: &[u8], info: Option<&str>) -> Result> { - let hkdf = hkdf::Hkdf::::from_prk(prk) - .map_err(|_| Error::Internal("invalid prk length"))?; + let hkdf = hkdf::Hkdf::::from_prk(prk).map_err(|_| "invalid prk length")?; let mut key = GenericArray::::default(); let i = info.map(|i| i.as_bytes()).unwrap_or(&[]); - hkdf.expand(i, &mut key) - .map_err(|_| Error::Internal("invalid length"))?; + hkdf.expand(i, &mut key).map_err(|_| "invalid length")?; Ok(key) } diff --git a/crates/bitwarden/src/crypto/rsa.rs b/crates/bitwarden/src/crypto/rsa.rs index f105dbfe2..6feb6ef47 100644 --- a/crates/bitwarden/src/crypto/rsa.rs +++ b/crates/bitwarden/src/crypto/rsa.rs @@ -6,7 +6,7 @@ use rsa::{ use crate::{ crypto::{EncString, SymmetricCryptoKey}, - error::{Error, Result}, + error::Result, util::BASE64_ENGINE, }; @@ -26,12 +26,12 @@ pub(super) fn make_key_pair(key: &SymmetricCryptoKey) -> Result { let spki = pub_key .to_public_key_der() - .map_err(|_| Error::Internal("unable to create public key"))?; + .map_err(|_| "unable to create public key")?; let b64 = BASE64_ENGINE.encode(spki.as_bytes()); let pkcs = priv_key .to_pkcs8_der() - .map_err(|_| Error::Internal("unable to create private key"))?; + .map_err(|_| "unable to create private key")?; let protected = EncString::encrypt_aes256_hmac(pkcs.as_bytes(), key.mac_key.unwrap(), key.key)?; diff --git a/crates/bitwarden/src/error.rs b/crates/bitwarden/src/error.rs index 6a9b0d8aa..e5560b479 100644 --- a/crates/bitwarden/src/error.rs +++ b/crates/bitwarden/src/error.rs @@ -1,6 +1,6 @@ //! Errors that can occur when using this SDK -use std::fmt::Debug; +use std::{borrow::Cow, fmt::Debug}; use bitwarden_api_api::apis::Error as ApiError; use bitwarden_api_identity::apis::Error as IdentityError; @@ -47,7 +47,19 @@ pub enum Error { ResponseContent { status: StatusCode, message: String }, #[error("Internal error: {0}")] - Internal(&'static str), + Internal(Cow<'static, str>), +} + +impl From for Error { + fn from(s: String) -> Self { + Self::Internal(s.into()) + } +} + +impl From<&'static str> for Error { + fn from(s: &'static str) -> Self { + Self::Internal(s.into()) + } } #[derive(Debug, Error)] diff --git a/crates/bitwarden/src/platform/get_user_api_key.rs b/crates/bitwarden/src/platform/get_user_api_key.rs index a1cfc389d..994a7a7ab 100644 --- a/crates/bitwarden/src/platform/get_user_api_key.rs +++ b/crates/bitwarden/src/platform/get_user_api_key.rs @@ -62,7 +62,7 @@ fn get_secret_verification_request( auth_request_access_code: None, }) } else { - Err(Error::Internal("Unsupported login method")) + Err("Unsupported login method".into()) } } diff --git a/crates/bitwarden/src/tool/generators/passphrase.rs b/crates/bitwarden/src/tool/generators/passphrase.rs index e62fa8f9a..5abad368b 100644 --- a/crates/bitwarden/src/tool/generators/passphrase.rs +++ b/crates/bitwarden/src/tool/generators/passphrase.rs @@ -1,7 +1,4 @@ -use crate::{ - error::{Error, Result}, - wordlist::EFF_LONG_WORD_LIST, -}; +use crate::{error::Result, wordlist::EFF_LONG_WORD_LIST}; use rand::{seq::SliceRandom, Rng, RngCore}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -52,11 +49,11 @@ impl PassphraseGeneratorRequest { if !(MINIMUM_PASSPHRASE_NUM_WORDS..=MAXIMUM_PASSPHRASE_NUM_WORDS).contains(&self.num_words) { - return Err(Error::Internal("'num_words' must be between 3 and 20")); + return Err(format!("'num_words' must be between {MINIMUM_PASSPHRASE_NUM_WORDS} and {MAXIMUM_PASSPHRASE_NUM_WORDS}").into()); } if self.word_separator.chars().next().is_none() { - return Err(Error::Internal("'word_separator' cannot be empty")); + return Err("'word_separator' cannot be empty".into()); }; Ok(ValidPassphraseGeneratorOptions { diff --git a/crates/bitwarden/src/tool/generators/password.rs b/crates/bitwarden/src/tool/generators/password.rs index cf986495c..d091d1a45 100644 --- a/crates/bitwarden/src/tool/generators/password.rs +++ b/crates/bitwarden/src/tool/generators/password.rs @@ -4,7 +4,7 @@ use rand::{distributions::Distribution, seq::SliceRandom, RngCore}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::error::{Error, Result}; +use crate::error::Result; /// Password generator request options. #[derive(Serialize, Deserialize, Debug, JsonSchema)] @@ -133,15 +133,11 @@ impl PasswordGeneratorRequest { // We always have to have at least one character set enabled if !self.lowercase && !self.uppercase && !self.numbers && !self.special { - return Err(Error::Internal( - "At least one character set must be enabled", - )); + return Err("At least one character set must be enabled".into()); } if self.length < 4 { - return Err(Error::Internal( - "A password must be at least 4 characters long", - )); + return Err("A password must be at least 4 characters long".into()); } // Make sure the minimum values are zero when the character @@ -163,9 +159,7 @@ impl PasswordGeneratorRequest { // Check that the minimum lengths aren't larger than the password length let minimum_length = min_lowercase + min_uppercase + min_number + min_special; if minimum_length > length { - return Err(Error::Internal( - "Password length can't be less than the sum of the minimums", - )); + return Err("Password length can't be less than the sum of the minimums".into()); } let lower = ( diff --git a/crates/bitwarden/src/uniffi_support.rs b/crates/bitwarden/src/uniffi_support.rs index 701f142b9..5e25d764e 100644 --- a/crates/bitwarden/src/uniffi_support.rs +++ b/crates/bitwarden/src/uniffi_support.rs @@ -10,7 +10,7 @@ impl UniffiCustomTypeConverter for NonZeroU32 { type Builtin = u32; fn into_custom(val: Self::Builtin) -> uniffi::Result { - Self::new(val).ok_or(Error::Internal("Number is zero").into()) + Self::new(val).ok_or(Error::from("Number is zero").into()) } fn from_custom(obj: Self) -> Self::Builtin { diff --git a/crates/bitwarden/src/vault/totp.rs b/crates/bitwarden/src/vault/totp.rs index e080a85a9..4adbe4dac 100644 --- a/crates/bitwarden/src/vault/totp.rs +++ b/crates/bitwarden/src/vault/totp.rs @@ -130,11 +130,11 @@ impl FromStr for Totp { fn decode_secret(secret: &str) -> Result> { BASE32 .decode(secret.as_bytes()) - .map_err(|_| Error::Internal("Unable to decode secret")) + .map_err(|_| "Unable to decode secret".into()) } let params = if key.starts_with("otpauth://") { - let url = Url::parse(key).map_err(|_| Error::Internal("Unable to parse URL"))?; + let url = Url::parse(key).map_err(|_| "Unable to parse URL")?; let parts: HashMap<_, _> = url.query_pairs().collect(); Totp { @@ -161,7 +161,7 @@ impl FromStr for Totp { &parts .get("secret") .map(|v| v.to_string()) - .ok_or(Error::Internal("Missing secret in otpauth URI"))?, + .ok_or("Missing secret in otpauth URI")?, )?, } } else if let Some(secret) = key.strip_prefix("steam://") { From 4c079e85a94320d5b42c88291671acd2cbc7dccf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Mon, 18 Dec 2023 18:08:41 +0100 Subject: [PATCH 110/157] [PM-3436] Username generator (#285) ## Type of change ``` - [ ] Bug fix - [x] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Implement username generation. Some of the methods are shared with password and passphrase generation (like `capitalize_first_letter`, `random_lowercase_string` and the username word generation). To avoid dependencies between the PRs, I included them here again, once we're done with all the generators I'll unify them. --- crates/bitwarden-uniffi/src/tool/mod.rs | 17 +- crates/bitwarden/src/client/client.rs | 5 + .../src/tool/generators/client_generator.rs | 33 ++- crates/bitwarden/src/tool/generators/mod.rs | 3 + .../src/tool/generators/passphrase.rs | 13 +- .../bitwarden/src/tool/generators/username.rs | 244 ++++++++++++++++++ .../generators/username_forwarders/addyio.rs | 143 ++++++++++ .../username_forwarders/duckduckgo.rs | 86 ++++++ .../username_forwarders/fastmail.rs | 188 ++++++++++++++ .../generators/username_forwarders/firefox.rs | 120 +++++++++ .../username_forwarders/forwardemail.rs | 193 ++++++++++++++ .../generators/username_forwarders/mod.rs | 22 ++ .../username_forwarders/simplelogin.rs | 114 ++++++++ crates/bitwarden/src/tool/mod.rs | 5 +- crates/bitwarden/src/util.rs | 12 + 15 files changed, 1180 insertions(+), 18 deletions(-) create mode 100644 crates/bitwarden/src/tool/generators/username.rs create mode 100644 crates/bitwarden/src/tool/generators/username_forwarders/addyio.rs create mode 100644 crates/bitwarden/src/tool/generators/username_forwarders/duckduckgo.rs create mode 100644 crates/bitwarden/src/tool/generators/username_forwarders/fastmail.rs create mode 100644 crates/bitwarden/src/tool/generators/username_forwarders/firefox.rs create mode 100644 crates/bitwarden/src/tool/generators/username_forwarders/forwardemail.rs create mode 100644 crates/bitwarden/src/tool/generators/username_forwarders/mod.rs create mode 100644 crates/bitwarden/src/tool/generators/username_forwarders/simplelogin.rs diff --git a/crates/bitwarden-uniffi/src/tool/mod.rs b/crates/bitwarden-uniffi/src/tool/mod.rs index 6622a1b2d..6b443618c 100644 --- a/crates/bitwarden-uniffi/src/tool/mod.rs +++ b/crates/bitwarden-uniffi/src/tool/mod.rs @@ -1,7 +1,10 @@ use std::sync::Arc; use bitwarden::{ - tool::{ExportFormat, PassphraseGeneratorRequest, PasswordGeneratorRequest}, + tool::{ + ExportFormat, PassphraseGeneratorRequest, PasswordGeneratorRequest, + UsernameGeneratorRequest, + }, vault::{Cipher, Collection, Folder}, }; @@ -35,6 +38,18 @@ impl ClientGenerators { .passphrase(settings) .await?) } + + /// **API Draft:** Generate Username + pub async fn username(&self, settings: UsernameGeneratorRequest) -> Result { + Ok(self + .0 + .0 + .read() + .await + .generator() + .username(settings) + .await?) + } } #[derive(uniffi::Object)] diff --git a/crates/bitwarden/src/client/client.rs b/crates/bitwarden/src/client/client.rs index 0c69ba917..bb0f65c09 100644 --- a/crates/bitwarden/src/client/client.rs +++ b/crates/bitwarden/src/client/client.rs @@ -132,6 +132,11 @@ impl Client { &self.__api_configurations } + #[cfg(feature = "mobile")] + pub(crate) fn get_http_client(&self) -> &reqwest::Client { + &self.__api_configurations.api.client + } + #[cfg(feature = "secrets")] #[deprecated(note = "Use auth().login_access_token() instead")] pub async fn access_token_login( diff --git a/crates/bitwarden/src/tool/generators/client_generator.rs b/crates/bitwarden/src/tool/generators/client_generator.rs index 4675ad99d..301383635 100644 --- a/crates/bitwarden/src/tool/generators/client_generator.rs +++ b/crates/bitwarden/src/tool/generators/client_generator.rs @@ -1,12 +1,15 @@ use crate::{ error::Result, - tool::generators::passphrase::{passphrase, PassphraseGeneratorRequest}, - tool::generators::password::{password, PasswordGeneratorRequest}, + tool::generators::{ + passphrase::{passphrase, PassphraseGeneratorRequest}, + password::{password, PasswordGeneratorRequest}, + username::{username, UsernameGeneratorRequest}, + }, Client, }; pub struct ClientGenerator<'a> { - pub(crate) _client: &'a crate::Client, + pub(crate) client: &'a crate::Client, } impl<'a> ClientGenerator<'a> { @@ -59,10 +62,32 @@ impl<'a> ClientGenerator<'a> { pub async fn passphrase(&self, input: PassphraseGeneratorRequest) -> Result { passphrase(input) } + + /// Generates a random username. + /// There are different username generation strategies, which can be customized using the `input` parameter. + /// + /// Note that most generation strategies will be executed on the client side, but `Forwarded` will use third-party + /// services, which may require a specific setup or API key. + /// + /// ``` + /// use bitwarden::{Client, tool::{UsernameGeneratorRequest}, error::Result}; + /// async fn test() -> Result<()> { + /// let input = UsernameGeneratorRequest::Word { + /// capitalize: true, + /// include_number: true, + /// }; + /// let username = Client::new(None).generator().username(input).await.unwrap(); + /// println!("{}", username); + /// Ok(()) + /// } + /// ``` + pub async fn username(&self, input: UsernameGeneratorRequest) -> Result { + username(input, self.client.get_http_client()).await + } } impl<'a> Client { pub fn generator(&'a self) -> ClientGenerator<'a> { - ClientGenerator { _client: self } + ClientGenerator { client: self } } } diff --git a/crates/bitwarden/src/tool/generators/mod.rs b/crates/bitwarden/src/tool/generators/mod.rs index 12c526930..7966c58a9 100644 --- a/crates/bitwarden/src/tool/generators/mod.rs +++ b/crates/bitwarden/src/tool/generators/mod.rs @@ -1,7 +1,10 @@ mod client_generator; mod passphrase; mod password; +mod username; +mod username_forwarders; pub use client_generator::ClientGenerator; pub use passphrase::PassphraseGeneratorRequest; pub use password::PasswordGeneratorRequest; +pub use username::{AppendType, ForwarderServiceType, UsernameGeneratorRequest}; diff --git a/crates/bitwarden/src/tool/generators/passphrase.rs b/crates/bitwarden/src/tool/generators/passphrase.rs index 5abad368b..7c5c8e434 100644 --- a/crates/bitwarden/src/tool/generators/passphrase.rs +++ b/crates/bitwarden/src/tool/generators/passphrase.rs @@ -1,4 +1,4 @@ -use crate::{error::Result, wordlist::EFF_LONG_WORD_LIST}; +use crate::{error::Result, util::capitalize_first_letter, wordlist::EFF_LONG_WORD_LIST}; use rand::{seq::SliceRandom, Rng, RngCore}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -109,17 +109,6 @@ fn capitalize_words(words: &mut [String]) { .for_each(|w| *w = capitalize_first_letter(w)); } -fn capitalize_first_letter(s: &str) -> String { - // Unicode case conversion can change the length of the string, so we can't capitalize in place. - // Instead we extract the first character and convert it to uppercase. This returns - // an iterator which we collect into a string, and then append the rest of the input. - let mut c = s.chars(); - match c.next() { - None => String::new(), - Some(f) => f.to_uppercase().collect::() + c.as_str(), - } -} - #[cfg(test)] mod tests { use rand::SeedableRng; diff --git a/crates/bitwarden/src/tool/generators/username.rs b/crates/bitwarden/src/tool/generators/username.rs new file mode 100644 index 000000000..666772714 --- /dev/null +++ b/crates/bitwarden/src/tool/generators/username.rs @@ -0,0 +1,244 @@ +use crate::{error::Result, util::capitalize_first_letter, wordlist::EFF_LONG_WORD_LIST}; +use rand::{distributions::Distribution, seq::SliceRandom, Rng, RngCore}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "mobile", derive(uniffi::Enum))] +pub enum AppendType { + /// Generates a random string of 8 lowercase characters as part of your username + Random, + /// Uses the websitename as part of your username + WebsiteName { website: String }, +} + +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "mobile", derive(uniffi::Enum))] +/// Configures the email forwarding service to use. +/// For instructions on how to configure each service, see the documentation: +/// +pub enum ForwarderServiceType { + /// Previously known as "AnonAddy" + AddyIo { + api_token: String, + domain: String, + base_url: String, + }, + DuckDuckGo { + token: String, + }, + Firefox { + api_token: String, + }, + Fastmail { + api_token: String, + }, + ForwardEmail { + api_token: String, + domain: String, + }, + SimpleLogin { + api_key: String, + }, +} + +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "mobile", derive(uniffi::Enum))] +pub enum UsernameGeneratorRequest { + /// Generates a single word username + Word { + /// Capitalize the first letter of the word + capitalize: bool, + /// Include a 4 digit number at the end of the word + include_number: bool, + }, + /// Generates an email using your provider's subaddressing capabilities. + /// Note that not all providers support this functionality. + /// This will generate an address of the format `youremail+generated@domain.tld` + Subaddress { + /// The type of subaddress to add to the base email + r#type: AppendType, + /// The full email address to use as the base for the subaddress + email: String, + }, + Catchall { + /// The type of username to use with the catchall email domain + r#type: AppendType, + /// The domain to use for the catchall email address + domain: String, + }, + Forwarded { + /// The email forwarding service to use, see [ForwarderServiceType] + /// for instructions on how to configure each + service: ForwarderServiceType, + /// The website for which the email address is being generated + /// This is not used in all services, and is only used for display purposes + website: Option, + }, +} + +impl ForwarderServiceType { + // Generate a username using the specified email forwarding service + // This requires an HTTP client to be passed in, as the service will need to make API calls + pub async fn generate(self, http: &reqwest::Client, website: Option) -> Result { + use crate::tool::generators::username_forwarders::*; + use ForwarderServiceType::*; + + match self { + AddyIo { + api_token, + domain, + base_url, + } => addyio::generate(http, api_token, domain, base_url, website).await, + DuckDuckGo { token } => duckduckgo::generate(http, token).await, + Firefox { api_token } => firefox::generate(http, api_token, website).await, + Fastmail { api_token } => fastmail::generate(http, api_token, website).await, + ForwardEmail { api_token, domain } => { + forwardemail::generate(http, api_token, domain, website).await + } + SimpleLogin { api_key } => simplelogin::generate(http, api_key, website).await, + } + } +} + +/// Implementation of the username generator. This is not accessible to the public API. +/// See [`ClientGenerator::username`](crate::ClientGenerator::username) for the API function. +/// Note: The HTTP client is passed in as a required parameter for convenience, +/// as some username generators require making API calls. +pub(super) async fn username( + input: UsernameGeneratorRequest, + http: &reqwest::Client, +) -> Result { + use rand::thread_rng; + use UsernameGeneratorRequest::*; + match input { + Word { + capitalize, + include_number, + } => Ok(username_word(&mut thread_rng(), capitalize, include_number)), + Subaddress { r#type, email } => Ok(username_subaddress(&mut thread_rng(), r#type, email)), + Catchall { r#type, domain } => Ok(username_catchall(&mut thread_rng(), r#type, domain)), + Forwarded { service, website } => service.generate(http, website).await, + } +} + +fn username_word(mut rng: impl RngCore, capitalize: bool, include_number: bool) -> String { + let word = EFF_LONG_WORD_LIST + .choose(&mut rng) + .expect("slice is not empty"); + + let mut word = if capitalize { + capitalize_first_letter(word) + } else { + word.to_string() + }; + + if include_number { + word.push_str(&random_number(&mut rng)); + } + + word +} + +/// Generate a random 4 digit number, including leading zeros +fn random_number(mut rng: impl RngCore) -> String { + let num = rng.gen_range(0..=9999); + format!("{num:0>4}") +} + +/// Generate a username using a plus addressed email address +/// The format is +@ +fn username_subaddress(mut rng: impl RngCore, r#type: AppendType, email: String) -> String { + if email.len() < 3 { + return email; + } + + let (email_begin, email_end) = match email.find('@') { + Some(pos) if pos > 0 && pos < email.len() - 1 => { + email.split_once('@').expect("The email contains @") + } + _ => return email, + }; + + let email_middle = match r#type { + AppendType::Random => random_lowercase_string(&mut rng, 8), + AppendType::WebsiteName { website } => website, + }; + + format!("{}+{}@{}", email_begin, email_middle, email_end) +} + +/// Generate a username using a catchall email address +/// The format is @ +fn username_catchall(mut rng: impl RngCore, r#type: AppendType, domain: String) -> String { + if domain.is_empty() { + return domain; + } + + let email_start = match r#type { + AppendType::Random => random_lowercase_string(&mut rng, 8), + AppendType::WebsiteName { website } => website, + }; + + format!("{}@{}", email_start, domain) +} + +fn random_lowercase_string(mut rng: impl RngCore, length: usize) -> String { + const LOWERCASE_ALPHANUMERICAL: &[u8] = b"abcdefghijklmnopqrstuvwxyz1234567890"; + let dist = rand::distributions::Slice::new(LOWERCASE_ALPHANUMERICAL).expect("Non-empty slice"); + + dist.sample_iter(&mut rng) + .take(length) + .map(|&b| b as char) + .collect() +} + +#[cfg(test)] +mod tests { + use rand::SeedableRng; + + pub use super::*; + + #[test] + fn test_username_word() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + assert_eq!(username_word(&mut rng, true, true), "Subsystem6314"); + assert_eq!(username_word(&mut rng, true, false), "Silenced"); + assert_eq!(username_word(&mut rng, false, true), "dinginess4487"); + } + + #[test] + fn test_username_subaddress() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + let user = username_subaddress(&mut rng, AppendType::Random, "demo@test.com".into()); + assert_eq!(user, "demo+5wiejdaj@test.com"); + + let user = username_subaddress( + &mut rng, + AppendType::WebsiteName { + website: "bitwarden.com".into(), + }, + "demo@test.com".into(), + ); + assert_eq!(user, "demo+bitwarden.com@test.com"); + } + + #[test] + fn test_username_catchall() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([1u8; 32]); + let user = username_catchall(&mut rng, AppendType::Random, "test.com".into()); + assert_eq!(user, "k9y6yw7j@test.com"); + + let user = username_catchall( + &mut rng, + AppendType::WebsiteName { + website: "bitwarden.com".into(), + }, + "test.com".into(), + ); + assert_eq!(user, "bitwarden.com@test.com"); + } +} diff --git a/crates/bitwarden/src/tool/generators/username_forwarders/addyio.rs b/crates/bitwarden/src/tool/generators/username_forwarders/addyio.rs new file mode 100644 index 000000000..0fc5937f6 --- /dev/null +++ b/crates/bitwarden/src/tool/generators/username_forwarders/addyio.rs @@ -0,0 +1,143 @@ +use reqwest::{header::CONTENT_TYPE, StatusCode}; + +use crate::error::Result; +pub async fn generate( + http: &reqwest::Client, + api_token: String, + domain: String, + base_url: String, + website: Option, +) -> Result { + let description = super::format_description(&website); + + #[derive(serde::Serialize)] + struct Request { + domain: String, + description: String, + } + + let response = http + .post(format!("{base_url}/api/v1/aliases")) + .header(CONTENT_TYPE, "application/json") + .bearer_auth(api_token) + .header("X-Requested-With", "XMLHttpRequest") + .json(&Request { + domain, + description, + }) + .send() + .await?; + + if response.status() == StatusCode::UNAUTHORIZED { + return Err("Invalid addy.io API token.".into()); + } + + // Throw any other errors + response.error_for_status_ref()?; + + #[derive(serde::Deserialize)] + struct ResponseData { + email: String, + } + #[derive(serde::Deserialize)] + struct Response { + data: ResponseData, + } + let response: Response = response.json().await?; + + Ok(response.data.email) +} + +#[cfg(test)] +mod tests { + use serde_json::json; + #[tokio::test] + async fn test_mock_server() { + use wiremock::{matchers, Mock, ResponseTemplate}; + + let (server, _client) = crate::util::start_mock(vec![ + // Mock the request to the addy.io API, and verify that the correct request is made + Mock::given(matchers::path("/api/v1/aliases")) + .and(matchers::method("POST")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header("Authorization", "Bearer MY_TOKEN")) + .and(matchers::body_json(json!({ + "domain": "myemail.com", + "description": "Website: example.com. Generated by Bitwarden." + }))) + .respond_with(ResponseTemplate::new(201).set_body_json(json!({ + "data": { + "id": "50c9e585-e7f5-41c4-9016-9014c15454bc", + "user_id": "ca0a4e09-c266-4f6f-845c-958db5090f09", + "local_part": "50c9e585-e7f5-41c4-9016-9014c15454bc", + "domain": "myemail.com", + "email": "50c9e585-e7f5-41c4-9016-9014c15454bc@myemail.com", + "active": true + } + }))) + .expect(1), + // Mock an invalid API token request + Mock::given(matchers::path("/api/v1/aliases")) + .and(matchers::method("POST")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header("Authorization", "Bearer MY_FAKE_TOKEN")) + .and(matchers::body_json(json!({ + "domain": "myemail.com", + "description": "Website: example.com. Generated by Bitwarden." + }))) + .respond_with(ResponseTemplate::new(401)) + .expect(1), + // Mock an invalid domain + Mock::given(matchers::path("/api/v1/aliases")) + .and(matchers::method("POST")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header("Authorization", "Bearer MY_TOKEN")) + .and(matchers::body_json(json!({ + "domain": "gmail.com", + "description": "Website: example.com. Generated by Bitwarden." + }))) + .respond_with(ResponseTemplate::new(403)) + .expect(1), + ]) + .await; + + let address = super::generate( + &reqwest::Client::new(), + "MY_TOKEN".into(), + "myemail.com".into(), + format!("http://{}", server.address()), + Some("example.com".into()), + ) + .await + .unwrap(); + + let fake_token_error = super::generate( + &reqwest::Client::new(), + "MY_FAKE_TOKEN".into(), + "myemail.com".into(), + format!("http://{}", server.address()), + Some("example.com".into()), + ) + .await + .unwrap_err(); + + assert!(fake_token_error + .to_string() + .contains("Invalid addy.io API token.")); + + let fake_domain_error = super::generate( + &reqwest::Client::new(), + "MY_TOKEN".into(), + "gmail.com".into(), + format!("http://{}", server.address()), + Some("example.com".into()), + ) + .await + .unwrap_err(); + + assert!(fake_domain_error.to_string().contains("403 Forbidden")); + + server.verify().await; + assert_eq!(address, "50c9e585-e7f5-41c4-9016-9014c15454bc@myemail.com"); + } +} diff --git a/crates/bitwarden/src/tool/generators/username_forwarders/duckduckgo.rs b/crates/bitwarden/src/tool/generators/username_forwarders/duckduckgo.rs new file mode 100644 index 000000000..512db7812 --- /dev/null +++ b/crates/bitwarden/src/tool/generators/username_forwarders/duckduckgo.rs @@ -0,0 +1,86 @@ +use reqwest::{header::CONTENT_TYPE, StatusCode}; + +use crate::error::Result; +pub async fn generate(http: &reqwest::Client, token: String) -> Result { + generate_with_api_url(http, token, "https://quack.duckduckgo.com".into()).await +} + +async fn generate_with_api_url( + http: &reqwest::Client, + token: String, + api_url: String, +) -> Result { + let response = http + .post(format!("{api_url}/api/email/addresses")) + .header(CONTENT_TYPE, "application/json") + .bearer_auth(token) + .send() + .await?; + + if response.status() == StatusCode::UNAUTHORIZED { + return Err("Invalid DuckDuckGo API token".into()); + } + + // Throw any other errors + response.error_for_status_ref()?; + + #[derive(serde::Deserialize)] + struct Response { + address: String, + } + let response: Response = response.json().await?; + + Ok(format!("{}@duck.com", response.address)) +} + +#[cfg(test)] +mod tests { + use serde_json::json; + #[tokio::test] + async fn test_mock_server() { + use wiremock::{matchers, Mock, ResponseTemplate}; + + let (server, _client) = crate::util::start_mock(vec![ + // Mock the request to the DDG API, and verify that the correct request is made + Mock::given(matchers::path("/api/email/addresses")) + .and(matchers::method("POST")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header("Authorization", "Bearer MY_TOKEN")) + .respond_with(ResponseTemplate::new(201).set_body_json(json!({ + "address": "bw7prt" + }))) + .expect(1), + // Mock an invalid token request + Mock::given(matchers::path("/api/email/addresses")) + .and(matchers::method("POST")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header("Authorization", "Bearer MY_FAKE_TOKEN")) + .respond_with(ResponseTemplate::new(401)) + .expect(1), + ]) + .await; + + let address = super::generate_with_api_url( + &reqwest::Client::new(), + "MY_TOKEN".into(), + format!("http://{}", server.address()), + ) + .await + .unwrap(); + assert_eq!(address, "bw7prt@duck.com"); + + let fake_token_error = super::generate_with_api_url( + &reqwest::Client::new(), + "MY_FAKE_TOKEN".into(), + format!("http://{}", server.address()), + ) + .await + .unwrap_err(); + + assert!(fake_token_error + .to_string() + .contains("Invalid DuckDuckGo API token")); + + server.verify().await; + } +} diff --git a/crates/bitwarden/src/tool/generators/username_forwarders/fastmail.rs b/crates/bitwarden/src/tool/generators/username_forwarders/fastmail.rs new file mode 100644 index 000000000..d2a7554e2 --- /dev/null +++ b/crates/bitwarden/src/tool/generators/username_forwarders/fastmail.rs @@ -0,0 +1,188 @@ +use std::collections::HashMap; + +use reqwest::{header::CONTENT_TYPE, StatusCode}; +use serde_json::json; + +use crate::error::Result; +pub async fn generate( + http: &reqwest::Client, + api_token: String, + website: Option, +) -> Result { + generate_with_api_url(http, api_token, website, "https://api.fastmail.com".into()).await +} + +pub async fn generate_with_api_url( + http: &reqwest::Client, + api_token: String, + website: Option, + api_url: String, +) -> Result { + let account_id = get_account_id(http, &api_token, &api_url).await?; + + let response = http + .post(format!("{api_url}/jmap/api/")) + .header(CONTENT_TYPE, "application/json") + .bearer_auth(api_token) + .json(&json!({ + "using": ["https://www.fastmail.com/dev/maskedemail", "urn:ietf:params:jmap:core"], + "methodCalls": [[ + "MaskedEmail/set", { + "accountId": account_id, + "create": { + "new-masked-email": { + "state": "enabled", + "description": "", + "url": website, + "emailPrefix": null, + }, + }, + }, + "0", + ]], + })) + .send() + .await?; + + if response.status() == StatusCode::UNAUTHORIZED { + return Err("Invalid Fastmail API token".into()); + } + + let response: serde_json::Value = response.json().await?; + let Some(r) = response.get("methodResponses").and_then(|r| r.get(0)) else { + return Err("Unknown Fastmail error occurred.".into()); + }; + let method_response = r.get(0).and_then(|r| r.as_str()); + let response_value = r.get(1); + + if method_response == Some("MaskedEmail/set") { + if let Some(email) = response_value + .and_then(|r| r.get("created")) + .and_then(|r| r.get("new-masked-email")) + .and_then(|r| r.get("email")) + .and_then(|r| r.as_str()) + { + return Ok(email.to_owned()); + }; + + let error_description = response_value + .and_then(|r| r.get("notCreated")) + .and_then(|r| r.get("new-masked-email")) + .and_then(|r| r.get("description")) + .and_then(|r| r.as_str()) + .unwrap_or("Unknown error"); + + return Err(format!("Fastmail error: {error_description}").into()); + } else if method_response == Some("error") { + let error_description = response_value + .and_then(|r| r.get("description")) + .and_then(|r| r.as_str()) + .unwrap_or("Unknown error"); + + return Err(format!("Fastmail error: {error_description}").into()); + } + + Err("Unknown Fastmail error occurred.".into()) +} + +async fn get_account_id( + client: &reqwest::Client, + api_token: &str, + api_url: &str, +) -> Result { + #[derive(serde::Deserialize)] + struct Response { + #[serde(rename = "primaryAccounts")] + primary_accounts: HashMap, + } + let response = client + .get(format!("{api_url}/.well-known/jmap")) + .bearer_auth(api_token) + .send() + .await?; + + if response.status() == StatusCode::UNAUTHORIZED { + return Err("Invalid Fastmail API token".into()); + } + + response.error_for_status_ref()?; + let mut response: Response = response.json().await?; + + Ok(response + .primary_accounts + .remove("https://www.fastmail.com/dev/maskedemail") + .unwrap_or_default()) +} + +#[cfg(test)] +mod tests { + use serde_json::json; + #[tokio::test] + async fn test_mock_server() { + use wiremock::{matchers, Mock, ResponseTemplate}; + + let (server, _client) = crate::util::start_mock(vec![ + // Mock a valid request to FastMail API + Mock::given(matchers::path("/.well-known/jmap")) + .and(matchers::method("GET")) + .and(matchers::header("Authorization", "Bearer MY_TOKEN")) + .respond_with(ResponseTemplate::new(201).set_body_json(json!({ + "primaryAccounts": { + "https://www.fastmail.com/dev/maskedemail": "ca0a4e09-c266-4f6f-845c-958db5090f09" + } + }))) + .expect(1), + + Mock::given(matchers::path("/jmap/api/")) + .and(matchers::method("POST")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header("Authorization", "Bearer MY_TOKEN")) + .respond_with(ResponseTemplate::new(201).set_body_json(json!({ + "methodResponses": [ + ["MaskedEmail/set", {"created": {"new-masked-email": {"email": "9f823dq23d123ds@mydomain.com"}}}] + ] + }))) + .expect(1), + + // Mock an invalid token request + Mock::given(matchers::path("/.well-known/jmap")) + .and(matchers::method("GET")) + .and(matchers::header("Authorization", "Bearer MY_FAKE_TOKEN")) + .respond_with(ResponseTemplate::new(401)) + .expect(1), + + Mock::given(matchers::path("/jmap/api/")) + .and(matchers::method("POST")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header("Authorization", "Bearer MY_FAKE_TOKEN")) + .respond_with(ResponseTemplate::new(201)) + .expect(0), + ]) + .await; + + let address = super::generate_with_api_url( + &reqwest::Client::new(), + "MY_TOKEN".into(), + Some("example.com".into()), + format!("http://{}", server.address()), + ) + .await + .unwrap(); + assert_eq!(address, "9f823dq23d123ds@mydomain.com"); + + let fake_token_error = super::generate_with_api_url( + &reqwest::Client::new(), + "MY_FAKE_TOKEN".into(), + Some("example.com".into()), + format!("http://{}", server.address()), + ) + .await + .unwrap_err(); + + assert!(fake_token_error + .to_string() + .contains("Invalid Fastmail API token")); + + server.verify().await; + } +} diff --git a/crates/bitwarden/src/tool/generators/username_forwarders/firefox.rs b/crates/bitwarden/src/tool/generators/username_forwarders/firefox.rs new file mode 100644 index 000000000..2c45803a5 --- /dev/null +++ b/crates/bitwarden/src/tool/generators/username_forwarders/firefox.rs @@ -0,0 +1,120 @@ +use reqwest::{ + header::{self}, + StatusCode, +}; + +use crate::error::Result; + +pub async fn generate( + http: &reqwest::Client, + api_token: String, + website: Option, +) -> Result { + generate_with_api_url(http, api_token, website, "https://relay.firefox.com".into()).await +} + +async fn generate_with_api_url( + http: &reqwest::Client, + api_token: String, + website: Option, + api_url: String, +) -> Result { + #[derive(serde::Serialize)] + struct Request { + enabled: bool, + generated_for: Option, + description: String, + } + + let description = super::format_description_ff(&website); + + let response = http + .post(format!("{api_url}/api/v1/relayaddresses/")) + .header(header::AUTHORIZATION, format!("Token {api_token}")) + .json(&Request { + enabled: true, + generated_for: website, + description, + }) + .send() + .await?; + + if response.status() == StatusCode::UNAUTHORIZED { + return Err("Invalid Firefox Relay API key".into()); + } + + // Throw any other errors + response.error_for_status_ref()?; + + #[derive(serde::Deserialize)] + struct Response { + full_address: String, + } + let response: Response = response.json().await?; + + Ok(response.full_address) +} + +#[cfg(test)] +mod tests { + use serde_json::json; + #[tokio::test] + async fn test_mock_server() { + use wiremock::{matchers, Mock, ResponseTemplate}; + + let (server, _client) = crate::util::start_mock(vec![ + // Mock the request to the Firefox API, and verify that the correct request is made + Mock::given(matchers::path("/api/v1/relayaddresses/")) + .and(matchers::method("POST")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header("Authorization", "Token MY_TOKEN")) + .and(matchers::body_json(json!({ + "enabled": true, + "generated_for": "example.com", + "description": "example.com - Generated by Bitwarden." + }))) + .respond_with(ResponseTemplate::new(201).set_body_json(json!({ + "full_address": "ofuj4d4qw@mozmail.com" + }))) + .expect(1), + // Mock an invalid API key + Mock::given(matchers::path("/api/v1/relayaddresses/")) + .and(matchers::method("POST")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header("Authorization", "Token MY_FAKE_TOKEN")) + .and(matchers::body_json(json!({ + "enabled": true, + "generated_for": "example.com", + "description": "example.com - Generated by Bitwarden." + }))) + .respond_with(ResponseTemplate::new(401)) + .expect(1), + ]) + .await; + + let address = super::generate_with_api_url( + &reqwest::Client::new(), + "MY_TOKEN".into(), + Some("example.com".into()), + format!("http://{}", server.address()), + ) + .await + .unwrap(); + assert_eq!(address, "ofuj4d4qw@mozmail.com"); + + let fake_token_error = super::generate_with_api_url( + &reqwest::Client::new(), + "MY_FAKE_TOKEN".into(), + Some("example.com".into()), + format!("http://{}", server.address()), + ) + .await + .unwrap_err(); + + assert!(fake_token_error + .to_string() + .contains("Invalid Firefox Relay API key")); + + server.verify().await; + } +} diff --git a/crates/bitwarden/src/tool/generators/username_forwarders/forwardemail.rs b/crates/bitwarden/src/tool/generators/username_forwarders/forwardemail.rs new file mode 100644 index 000000000..f4ba6ced6 --- /dev/null +++ b/crates/bitwarden/src/tool/generators/username_forwarders/forwardemail.rs @@ -0,0 +1,193 @@ +use reqwest::{header::CONTENT_TYPE, StatusCode}; + +use crate::error::{Error, Result}; + +pub async fn generate( + http: &reqwest::Client, + api_token: String, + domain: String, + website: Option, +) -> Result { + generate_with_api_url( + http, + api_token, + domain, + website, + "https://api.forwardemail.net".into(), + ) + .await +} + +async fn generate_with_api_url( + http: &reqwest::Client, + api_token: String, + domain: String, + website: Option, + api_url: String, +) -> Result { + let description = super::format_description(&website); + + #[derive(serde::Serialize)] + struct Request { + labels: Option, + description: String, + } + + let response = http + .post(format!("{api_url}/v1/domains/{domain}/aliases")) + .header(CONTENT_TYPE, "application/json") + .basic_auth(api_token, None::) + .json(&Request { + description, + labels: website, + }) + .send() + .await?; + + if response.status() == StatusCode::UNAUTHORIZED { + return Err("Invalid Forward Email API key.".into()); + } + + #[derive(serde::Deserialize)] + struct ResponseDomain { + name: Option, + } + #[derive(serde::Deserialize)] + struct Response { + name: Option, + domain: Option, + + message: Option, + error: Option, + } + let status = response.status(); + let response: Response = response.json().await?; + + if status.is_success() { + if let Some(name) = response.name { + if let Some(response_domain) = response.domain { + return Ok(format!( + "{}@{}", + name, + response_domain.name.unwrap_or(domain) + )); + } + } + } + + if let Some(message) = response.message { + return Err(Error::ResponseContent { status, message }); + } + if let Some(message) = response.error { + return Err(Error::ResponseContent { status, message }); + } + + Err("Unknown ForwardEmail error.".into()) +} + +#[cfg(test)] +mod tests { + use serde_json::json; + + #[tokio::test] + async fn test_mock_server() { + use wiremock::{matchers, Mock, ResponseTemplate}; + + let (server, _client) = crate::util::start_mock(vec![ + // Mock the request to the ForwardEmail API, and verify that the correct request is made + Mock::given(matchers::path("/v1/domains/mydomain.com/aliases")) + .and(matchers::method("POST")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header("Authorization", "Basic TVlfVE9LRU46")) + .and(matchers::body_json(json!({ + "labels": "example.com", + "description": "Website: example.com. Generated by Bitwarden." + }))) + .respond_with(ResponseTemplate::new(201).set_body_json(json!({ + "name": "wertg8ad", + "domain": { + "name": "mydomain.com" + } + }))) + .expect(1), + // Mock an invalid API token request + Mock::given(matchers::path("/v1/domains/mydomain.com/aliases")) + .and(matchers::method("POST")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header( + "Authorization", + "Basic TVlfRkFLRV9UT0tFTjo=", + )) + .and(matchers::body_json(json!({ + "labels": "example.com", + "description": "Website: example.com. Generated by Bitwarden." + }))) + .respond_with(ResponseTemplate::new(401).set_body_json(json!({ + "statusCode": 401, + "error": "Unauthorized", + "message": "Invalid API token." + }))) + .expect(1), + // Mock a free API token request + Mock::given(matchers::path("/v1/domains/mydomain.com/aliases")) + .and(matchers::method("POST")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header( + "Authorization", + "Basic TVlfRlJFRV9UT0tFTjo=", + )) + .and(matchers::body_json(json!({ + "labels": "example.com", + "description": "Website: example.com. Generated by Bitwarden." + }))) + .respond_with(ResponseTemplate::new(402).set_body_json(json!({ + "statusCode": 402, + "error": "Payment required", + "message": "Please upgrade to a paid plan to unlock this feature." + }))) + .expect(1), + ]) + .await; + + let address = super::generate_with_api_url( + &reqwest::Client::new(), + "MY_TOKEN".into(), + "mydomain.com".into(), + Some("example.com".into()), + format!("http://{}", server.address()), + ) + .await + .unwrap(); + assert_eq!(address, "wertg8ad@mydomain.com"); + + let invalid_token_error = super::generate_with_api_url( + &reqwest::Client::new(), + "MY_FAKE_TOKEN".into(), + "mydomain.com".into(), + Some("example.com".into()), + format!("http://{}", server.address()), + ) + .await + .unwrap_err(); + + assert!(invalid_token_error + .to_string() + .contains("Invalid Forward Email API key.")); + + let free_token_error = super::generate_with_api_url( + &reqwest::Client::new(), + "MY_FREE_TOKEN".into(), + "mydomain.com".into(), + Some("example.com".into()), + format!("http://{}", server.address()), + ) + .await + .unwrap_err(); + + assert!(free_token_error + .to_string() + .contains("Please upgrade to a paid plan")); + + server.verify().await; + } +} diff --git a/crates/bitwarden/src/tool/generators/username_forwarders/mod.rs b/crates/bitwarden/src/tool/generators/username_forwarders/mod.rs new file mode 100644 index 000000000..8d445bc04 --- /dev/null +++ b/crates/bitwarden/src/tool/generators/username_forwarders/mod.rs @@ -0,0 +1,22 @@ +pub(super) mod addyio; +pub(super) mod duckduckgo; +pub(super) mod fastmail; +pub(super) mod firefox; +pub(super) mod forwardemail; +pub(super) mod simplelogin; + +fn format_description(website: &Option) -> String { + let description = website + .as_ref() + .map(|w| format!("Website: {w}. ")) + .unwrap_or_default(); + format!("{description}Generated by Bitwarden.") +} + +fn format_description_ff(website: &Option) -> String { + let description = website + .as_ref() + .map(|w| format!("{w} - ")) + .unwrap_or_default(); + format!("{description}Generated by Bitwarden.") +} diff --git a/crates/bitwarden/src/tool/generators/username_forwarders/simplelogin.rs b/crates/bitwarden/src/tool/generators/username_forwarders/simplelogin.rs new file mode 100644 index 000000000..3c09fc691 --- /dev/null +++ b/crates/bitwarden/src/tool/generators/username_forwarders/simplelogin.rs @@ -0,0 +1,114 @@ +use reqwest::{header::CONTENT_TYPE, StatusCode}; + +use crate::error::Result; + +pub async fn generate( + http: &reqwest::Client, + api_key: String, + website: Option, +) -> Result { + generate_with_api_url(http, api_key, website, "https://app.simplelogin.io".into()).await +} + +async fn generate_with_api_url( + http: &reqwest::Client, + api_key: String, + website: Option, + api_url: String, +) -> Result { + let query = website + .as_ref() + .map(|w| format!("?hostname={}", w)) + .unwrap_or_default(); + + let note = super::format_description(&website); + + #[derive(serde::Serialize)] + struct Request { + note: String, + } + + let response = http + .post(format!("{api_url}/api/alias/random/new{query}")) + .header(CONTENT_TYPE, "application/json") + .bearer_auth(api_key) + .json(&Request { note }) + .send() + .await?; + + if response.status() == StatusCode::UNAUTHORIZED { + return Err("Invalid SimpleLogin API key.".into()); + } + + // Throw any other errors + response.error_for_status_ref()?; + + #[derive(serde::Deserialize)] + struct Response { + alias: String, + } + let response: Response = response.json().await?; + + Ok(response.alias) +} + +#[cfg(test)] +mod tests { + use serde_json::json; + #[tokio::test] + async fn test_mock_server() { + use wiremock::{matchers, Mock, ResponseTemplate}; + + let (server, _client) = crate::util::start_mock(vec![ + // Mock the request to the SimpleLogin API, and verify that the correct request is made + Mock::given(matchers::path("/api/alias/random/new")) + .and(matchers::method("POST")) + .and(matchers::query_param("hostname", "example.com")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header("Authorization", "Bearer MY_TOKEN")) + .and(matchers::body_json(json!({ + "note": "Website: example.com. Generated by Bitwarden." + }))) + .respond_with(ResponseTemplate::new(201).set_body_json(json!({ + "alias": "simplelogin.yut3g8@aleeas.com", + }))) + .expect(1), + // Mock an invalid token request + Mock::given(matchers::path("/api/alias/random/new")) + .and(matchers::method("POST")) + .and(matchers::query_param("hostname", "example.com")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header("Authorization", "Bearer MY_FAKE_TOKEN")) + .and(matchers::body_json(json!({ + "note": "Website: example.com. Generated by Bitwarden." + }))) + .respond_with(ResponseTemplate::new(401)) + .expect(1), + ]) + .await; + + let address = super::generate_with_api_url( + &reqwest::Client::new(), + "MY_TOKEN".into(), + Some("example.com".into()), + format!("http://{}", server.address()), + ) + .await + .unwrap(); + assert_eq!(address, "simplelogin.yut3g8@aleeas.com"); + + let fake_token_error = super::generate_with_api_url( + &reqwest::Client::new(), + "MY_FAKE_TOKEN".into(), + Some("example.com".into()), + format!("http://{}", server.address()), + ) + .await + .unwrap_err(); + assert!(fake_token_error + .to_string() + .contains("Invalid SimpleLogin API key.")); + + server.verify().await; + } +} diff --git a/crates/bitwarden/src/tool/mod.rs b/crates/bitwarden/src/tool/mod.rs index 212ce4bf7..fe41b68db 100644 --- a/crates/bitwarden/src/tool/mod.rs +++ b/crates/bitwarden/src/tool/mod.rs @@ -2,4 +2,7 @@ mod exporters; mod generators; pub use exporters::{ClientExporters, ExportFormat}; -pub use generators::{ClientGenerator, PassphraseGeneratorRequest, PasswordGeneratorRequest}; +pub use generators::{ + AppendType, ClientGenerator, ForwarderServiceType, PassphraseGeneratorRequest, + PasswordGeneratorRequest, UsernameGeneratorRequest, +}; diff --git a/crates/bitwarden/src/util.rs b/crates/bitwarden/src/util.rs index b6c8465ec..4dbe21ca6 100644 --- a/crates/bitwarden/src/util.rs +++ b/crates/bitwarden/src/util.rs @@ -28,6 +28,18 @@ const BASE64_ENGINE_CONFIG: GeneralPurposeConfig = GeneralPurposeConfig::new() pub const BASE64_ENGINE: GeneralPurpose = GeneralPurpose::new(&alphabet::STANDARD, BASE64_ENGINE_CONFIG); +#[cfg(feature = "mobile")] +pub(crate) fn capitalize_first_letter(s: &str) -> String { + // Unicode case conversion can change the length of the string, so we can't capitalize in place. + // Instead we extract the first character and convert it to uppercase. This returns + // an iterator which we collect into a string, and then append the rest of the input. + let mut c = s.chars(); + match c.next() { + None => String::new(), + Some(f) => f.to_uppercase().collect::() + c.as_str(), + } +} + #[cfg(test)] pub async fn start_mock(mocks: Vec) -> (wiremock::MockServer, crate::Client) { let server = wiremock::MockServer::start().await; From a5328e5f86fa5fc5c225f539b850bd758bf3a366 Mon Sep 17 00:00:00 2001 From: Vince Grassia <593223+vgrassia@users.noreply.github.com> Date: Mon, 18 Dec 2023 12:59:52 -0500 Subject: [PATCH 111/157] Update version bump workflow (#434) --- .github/workflows/version-bump.yml | 42 +++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index 4fb83ae8c..fae8b0bc5 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -3,16 +3,6 @@ name: Version Bump run-name: Version Bump - v${{ inputs.version_number }} on: - workflow_call: - inputs: - project: - description: "Project" - required: true - type: string - version_number: - description: "New version (example: '2024.1.0')" - required: true - type: string workflow_dispatch: inputs: project: @@ -29,6 +19,10 @@ on: version_number: description: "New version (example: '2024.1.0')" required: true + cut_rc_branch: + description: "Cut RC branch?" + default: true + type: boolean jobs: bump_version: @@ -63,9 +57,8 @@ jobs: - name: Checkout Branch uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: - repository: bitwarden/sdk ref: main - token: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }} + repository: bitwarden/sdk - name: Import GPG key uses: crazy-max/ghaction-import-gpg@82a020f1f7f605c65dd2449b392a52c3fcfef7ef # v6.0.0 @@ -206,3 +199,28 @@ jobs: GH_TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }} PR_NUMBER: ${{ steps.create-pr.outputs.pr_number }} run: gh pr merge $PR_NUMBER --squash --auto --delete-branch + + cut_rc: + name: Cut RC branch + needs: bump_version + if: ${{ inputs.cut_rc_branch == true }} + runs-on: ubuntu-22.04 + steps: + - name: Checkout Branch + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + ref: main + + - name: Check if RC branch exists + run: | + remote_rc_branch_check=$(git ls-remote --heads origin rc | wc -l) + if [[ "${remote_rc_branch_check}" -gt 0 ]]; then + echo "Remote RC branch exists." + echo "Please delete current RC branch before running again." + exit 1 + fi + + - name: Cut RC branch + run: | + git switch --quiet --create rc + git push --quiet --set-upstream origin rc From fd0659ebba00169655b7ce265fcf8eb418b083f7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 13:06:03 -0500 Subject: [PATCH 112/157] [deps]: Update actions/upload-artifact action to v4 (#444) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-android.yml | 2 +- .github/workflows/build-cli.yml | 6 +++--- .github/workflows/build-dotnet.yml | 2 +- .github/workflows/build-napi.yml | 2 +- .github/workflows/build-rust-cross-platform.yml | 2 +- .github/workflows/generate_schemas.yml | 14 +++++++------- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 424d048e9..7ac229535 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -46,7 +46,7 @@ jobs: run: cross build -p bitwarden-uniffi --release --target=${{ matrix.settings.target }} - name: Upload artifact - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 with: name: android-${{ matrix.settings.target }} path: ./target/${{ matrix.settings.target }}/release/libbitwarden_uniffi.so diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index c8b1e97dd..408015860 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -99,7 +99,7 @@ jobs: run: zip -j ./bws-${{ matrix.settings.target }}-${{ env._PACKAGE_VERSION }}.zip ./target/${{ matrix.settings.target }}/release/bws - name: Upload artifact - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 with: name: bws-${{ matrix.settings.target }}-${{ env._PACKAGE_VERSION }}.zip path: ./bws-${{ matrix.settings.target }}-${{ env._PACKAGE_VERSION }}.zip @@ -142,7 +142,7 @@ jobs: run: zip ./bws-macos-universal-${{ env._PACKAGE_VERSION }}.zip ./bws-macos-universal/bws - name: Upload artifact - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 with: name: bws-macos-universal-${{ env._PACKAGE_VERSION }}.zip path: ./bws-macos-universal-${{ env._PACKAGE_VERSION }}.zip @@ -177,7 +177,7 @@ jobs: sed -i.bak 's/\$NAME\$/Bitwarden Secrets Manager CLI/g' THIRDPARTY.html - name: Upload artifact - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 with: name: THIRDPARTY.html path: ./crates/bws/THIRDPARTY.html diff --git a/.github/workflows/build-dotnet.yml b/.github/workflows/build-dotnet.yml index 91f21210b..8794a50a8 100644 --- a/.github/workflows/build-dotnet.yml +++ b/.github/workflows/build-dotnet.yml @@ -71,7 +71,7 @@ jobs: working-directory: languages/csharp/Bitwarden.Sdk - name: Upload NuGet package - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 with: name: Bitwarden.Sdk.0.0.1.nupkg path: | diff --git a/.github/workflows/build-napi.yml b/.github/workflows/build-napi.yml index 96d96fb8c..1c009f8da 100644 --- a/.github/workflows/build-napi.yml +++ b/.github/workflows/build-napi.yml @@ -84,7 +84,7 @@ jobs: run: ${{ matrix.settings.build }} - name: Upload artifact - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 with: name: sdk-bitwarden-napi-${{ matrix.settings.target }} path: ${{ github.workspace }}/crates/bitwarden-napi/sdk-napi.*.node diff --git a/.github/workflows/build-rust-cross-platform.yml b/.github/workflows/build-rust-cross-platform.yml index 39039359f..8999295a2 100644 --- a/.github/workflows/build-rust-cross-platform.yml +++ b/.github/workflows/build-rust-cross-platform.yml @@ -41,7 +41,7 @@ jobs: run: cargo build --target ${{ matrix.settings.target }} --release - name: Upload Artifact - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 with: name: libbitwarden_c_files-${{ matrix.settings.target }} path: | diff --git a/.github/workflows/generate_schemas.yml b/.github/workflows/generate_schemas.yml index 807da7520..93904b2a6 100644 --- a/.github/workflows/generate_schemas.yml +++ b/.github/workflows/generate_schemas.yml @@ -37,48 +37,48 @@ jobs: run: npm run schemas - name: Upload ts schemas artifact - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 with: name: schemas.ts path: ${{ github.workspace }}/languages/js/sdk-client/src/schemas.ts if-no-files-found: error - name: Upload c# schemas artifact - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 with: name: schemas.cs path: ${{ github.workspace }}/languages/csharp/Bitwarden.Sdk/schemas.cs if-no-files-found: error - name: Upload python schemas artifact - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 with: name: schemas.py path: ${{ github.workspace }}/languages/python/bitwarden_sdk/schemas.py if-no-files-found: error - name: Upload ruby schemas artifact - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 with: name: schemas.rb path: ${{ github.workspace }}/languages/ruby/bitwarden_sdk/lib/schemas.rb if-no-files-found: error - name: Upload json schemas artifact - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 with: name: sdk-schemas-json path: ${{ github.workspace }}/support/schemas/* if-no-files-found: error - name: Upload Go schemas artifact - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 with: name: schemas.go path: ${{ github.workspace }}/languages/go/schema.go - name: Upload java schemas artifact - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 with: name: sdk-schemas-java path: ${{ github.workspace }}/languages/java/src/main/java/com/bitwarden/sdk/schema/* From 214142206cdee259f4c1c0305f0d0274aac71746 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 13:06:27 -0500 Subject: [PATCH 113/157] [deps]: Update actions/download-artifact action to v4 (#443) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-android.yml | 2 +- .github/workflows/build-cli.yml | 4 ++-- .github/workflows/build-dotnet.yml | 10 +++++----- .github/workflows/build-java.yml | 10 +++++----- .github/workflows/build-napi.yml | 2 +- .github/workflows/publish-dotnet.yml | 10 +++++----- .github/workflows/publish-php.yml | 8 ++++---- .github/workflows/publish-ruby.yml | 10 +++++----- 8 files changed, 28 insertions(+), 28 deletions(-) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 7ac229535..f1c956944 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -86,7 +86,7 @@ jobs: java-version: 17 - name: Download Artifacts - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 - name: Move artifacts working-directory: languages/kotlin/sdk/src/main/jniLibs diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index 408015860..eef2d84b5 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -118,12 +118,12 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Download x86_64-apple-darwin artifact - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: bws-x86_64-apple-darwin-${{ env._PACKAGE_VERSION }}.zip - name: Download aarch64-apple-darwin artifact - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: bws-aarch64-apple-darwin-${{ env._PACKAGE_VERSION }}.zip diff --git a/.github/workflows/build-dotnet.yml b/.github/workflows/build-dotnet.yml index 8794a50a8..a7cad42c0 100644 --- a/.github/workflows/build-dotnet.yml +++ b/.github/workflows/build-dotnet.yml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Download C# schemas artifact - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: schemas.cs path: languages/csharp/Bitwarden.Sdk @@ -35,25 +35,25 @@ jobs: global-json-file: languages/csharp/global.json - name: Download x86_64-apple-darwin files - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: libbitwarden_c_files-x86_64-apple-darwin path: languages/csharp/Bitwarden.Sdk/macos-x64 - name: Download aarch64-apple-darwin files - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: libbitwarden_c_files-aarch64-apple-darwin path: languages/csharp/Bitwarden.Sdk/macos-arm64 - name: Download x86_64-unknown-linux-gnu files - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: libbitwarden_c_files-x86_64-unknown-linux-gnu path: languages/csharp/Bitwarden.Sdk/ubuntu-x64 - name: Download x86_64-pc-windows-msvc files - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: libbitwarden_c_files-x86_64-pc-windows-msvc path: languages/csharp/Bitwarden.Sdk/windows-x64 diff --git a/.github/workflows/build-java.yml b/.github/workflows/build-java.yml index 663f0d493..9534b51ac 100644 --- a/.github/workflows/build-java.yml +++ b/.github/workflows/build-java.yml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Download Java schemas artifact - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: sdk-schemas-java path: languages/java/src/main/java/bit/sdk/schema/ @@ -36,25 +36,25 @@ jobs: java-version: 17 - name: Download x86_64-apple-darwin files - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: libbitwarden_c_files-x86_64-apple-darwin path: languages/java/src/main/resources/darwin-x64 - name: Download aarch64-apple-darwin files - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: libbitwarden_c_files-aarch64-apple-darwin path: languages/java/src/main/resources/darwin-aarch64 - name: Download x86_64-unknown-linux-gnu files - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: libbitwarden_c_files-x86_64-unknown-linux-gnu path: languages/java/src/main/resources/ubuntu-x64 - name: Download x86_64-pc-windows-msvc files - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: libbitwarden_c_files-x86_64-pc-windows-msvc path: languages/java/src/main/resources/windows-x64 diff --git a/.github/workflows/build-napi.yml b/.github/workflows/build-napi.yml index 1c009f8da..a08bb8b04 100644 --- a/.github/workflows/build-napi.yml +++ b/.github/workflows/build-napi.yml @@ -72,7 +72,7 @@ jobs: key: ${{ matrix.settings.target }}-cargo-${{ matrix.settings.os }} - name: Retrieve schemas - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: schemas.ts path: ${{ github.workspace }}/crates/bitwarden-napi/src-ts/bitwarden_client/ diff --git a/.github/workflows/publish-dotnet.yml b/.github/workflows/publish-dotnet.yml index b52656de2..e9b1076fe 100644 --- a/.github/workflows/publish-dotnet.yml +++ b/.github/workflows/publish-dotnet.yml @@ -26,7 +26,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Download C# schemas artifact - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: schemas.cs path: languages/csharp/Bitwarden.Sdk @@ -37,25 +37,25 @@ jobs: global-json-file: languages/csharp/global.json - name: Download x86_64-apple-darwin files - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: libbitwarden_c_files-x86_64-apple-darwin path: languages/csharp/Bitwarden.Sdk/macos-x64 - name: Download aarch64-apple-darwin files - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: libbitwarden_c_files-aarch64-apple-darwin path: languages/csharp/Bitwarden.Sdk/macos-arm64 - name: Download x86_64-unknown-linux-gnu files - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: libbitwarden_c_files-x86_64-unknown-linux-gnu path: languages/csharp/Bitwarden.Sdk/ubuntu-x64 - name: Download x86_64-pc-windows-msvc files - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: libbitwarden_c_files-x86_64-pc-windows-msvc path: languages/csharp/Bitwarden.Sdk/windows-x64 diff --git a/.github/workflows/publish-php.yml b/.github/workflows/publish-php.yml index ef03eca4a..c9d88a29e 100644 --- a/.github/workflows/publish-php.yml +++ b/.github/workflows/publish-php.yml @@ -33,25 +33,25 @@ jobs: working-directory: languages/php/ - name: Download x86_64-apple-darwin files - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: libbitwarden_c_files-x86_64-apple-darwin path: temp/macos-x64 - name: Download aarch64-apple-darwin files - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: libbitwarden_c_files-aarch64-apple-darwin path: temp/macos-arm64 - name: Download x86_64-unknown-linux-gnu files - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: libbitwarden_c_files-x86_64-unknown-linux-gnu path: temp/ubuntu-x64 - name: Download x86_64-pc-windows-msvc files - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: libbitwarden_c_files-x86_64-pc-windows-msvc path: temp/windows-x64 diff --git a/.github/workflows/publish-ruby.yml b/.github/workflows/publish-ruby.yml index c7d5ded20..3ed03fadc 100644 --- a/.github/workflows/publish-ruby.yml +++ b/.github/workflows/publish-ruby.yml @@ -28,31 +28,31 @@ jobs: ruby-version: 3.2 - name: Download Ruby schemas artifact - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: schemas.rb path: languages/ruby/bitwarden_sdk/lib - name: Download x86_64-apple-darwin files - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: libbitwarden_c_files-x86_64-apple-darwin path: temp/macos-x64 - name: Download aarch64-apple-darwin files - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: libbitwarden_c_files-aarch64-apple-darwin path: temp/macos-arm64 - name: Download x86_64-unknown-linux-gnu files - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: libbitwarden_c_files-x86_64-unknown-linux-gnu path: temp/ubuntu-x64 - name: Download x86_64-pc-windows-msvc files - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: libbitwarden_c_files-x86_64-pc-windows-msvc path: temp/windows-x64 From 65df2ede079117e4bc2f5e18999b6a3fc9838ba6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 13:11:58 -0500 Subject: [PATCH 114/157] [deps]: Update gh minor (#440) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-android.yml | 2 +- .github/workflows/build-java.yml | 2 +- .github/workflows/build-napi.yml | 2 +- .github/workflows/generate_schemas.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/publish-ruby.yml | 2 +- .github/workflows/release-napi.yml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index f1c956944..2171fb515 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -102,7 +102,7 @@ jobs: run: ./build-schemas.sh - name: Publish - uses: gradle/gradle-build-action@87a9a15658c426a54dd469d4fc7dc1a73ca9d4a6 # v2.10.0 + uses: gradle/gradle-build-action@8cbcb9948b5892387aed077daf6f90e1f0ba5b27 # v2.11.0 with: arguments: sdk:publish build-root-directory: languages/kotlin diff --git a/.github/workflows/build-java.yml b/.github/workflows/build-java.yml index 9534b51ac..a2c0938ec 100644 --- a/.github/workflows/build-java.yml +++ b/.github/workflows/build-java.yml @@ -60,7 +60,7 @@ jobs: path: languages/java/src/main/resources/windows-x64 - name: Publish Maven - uses: gradle/gradle-build-action@87a9a15658c426a54dd469d4fc7dc1a73ca9d4a6 # v2.10.0 + uses: gradle/gradle-build-action@8cbcb9948b5892387aed077daf6f90e1f0ba5b27 # v2.11.0 with: arguments: publish build-root-directory: languages/java diff --git a/.github/workflows/build-napi.yml b/.github/workflows/build-napi.yml index a08bb8b04..39674c2ea 100644 --- a/.github/workflows/build-napi.yml +++ b/.github/workflows/build-napi.yml @@ -54,7 +54,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup Node - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0 + uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 with: node-version: 18 cache: "npm" diff --git a/.github/workflows/generate_schemas.yml b/.github/workflows/generate_schemas.yml index 93904b2a6..53bbf34e3 100644 --- a/.github/workflows/generate_schemas.yml +++ b/.github/workflows/generate_schemas.yml @@ -21,7 +21,7 @@ jobs: toolchain: stable - name: Set up Node - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0 + uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 with: cache: "npm" cache-dependency-path: "package-lock.json" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0d2dbed91..f11ff4ced 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -31,7 +31,7 @@ jobs: run: cargo fmt --check - name: Set up Node - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0 + uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 with: cache: "npm" cache-dependency-path: "package-lock.json" diff --git a/.github/workflows/publish-ruby.yml b/.github/workflows/publish-ruby.yml index 3ed03fadc..01f6be58d 100644 --- a/.github/workflows/publish-ruby.yml +++ b/.github/workflows/publish-ruby.yml @@ -23,7 +23,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Set up Ruby - uses: ruby/setup-ruby@8575951200e472d5f2d95c625da0c7bec8217c42 # v1.161.0 + uses: ruby/setup-ruby@af848b40be8bb463a751551a1180d74782ba8a72 # v1.162.0 with: ruby-version: 3.2 diff --git a/.github/workflows/release-napi.yml b/.github/workflows/release-napi.yml index 19595832a..57498c681 100644 --- a/.github/workflows/release-napi.yml +++ b/.github/workflows/release-napi.yml @@ -93,7 +93,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup Node - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0 + uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 with: node-version: 18 cache: "npm" From 9d4ec76d2811bf4d2e52b7a0d0cba029f3ba42c6 Mon Sep 17 00:00:00 2001 From: Colton Hurst Date: Mon, 18 Dec 2023 13:27:40 -0500 Subject: [PATCH 115/157] SM-866: Add state to the SDK & BWS CLI (#388) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Type of change ``` - [ ] Bug fix - [x] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective This PR adds the ability to manage basic state via the SDK. It also updates the `bws` crate to use this SDK change, implementing basic state management for auth tokens. The core benefit is that reauthentication per command will no longer be needed, so that server auth requests will be mitigated. ## Code changes _(to be added)_ - **file.ext:** Description of what was changed and why ## Before you submit - Please add **unit tests** where it makes sense to do so (encouraged but not required) --------- Co-authored-by: Oscar Hinton Co-authored-by: Daniel García --- crates/bitwarden/CHANGELOG.md | 5 ++ crates/bitwarden/README.md | 2 +- crates/bitwarden/src/auth/client_auth.rs | 3 +- .../bitwarden/src/auth/login/access_token.rs | 63 ++++++++++++++++++- crates/bitwarden/src/auth/renew.rs | 28 +++++++-- crates/bitwarden/src/client/client.rs | 9 ++- crates/bitwarden/src/error.rs | 6 ++ crates/bitwarden/src/lib.rs | 2 +- crates/bitwarden/src/secrets_manager/mod.rs | 1 + crates/bitwarden/src/secrets_manager/state.rs | 52 +++++++++++++++ crates/bws/CHANGELOG.md | 2 + crates/bws/src/config.rs | 6 +- crates/bws/src/main.rs | 13 +++- crates/bws/src/state.rs | 18 ++++++ 14 files changed, 195 insertions(+), 15 deletions(-) create mode 100644 crates/bitwarden/src/secrets_manager/state.rs create mode 100644 crates/bws/src/state.rs diff --git a/crates/bitwarden/CHANGELOG.md b/crates/bitwarden/CHANGELOG.md index 8bb18ff72..1341eef68 100644 --- a/crates/bitwarden/CHANGELOG.md +++ b/crates/bitwarden/CHANGELOG.md @@ -7,6 +7,11 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added + +- Support for basic state to avoid reauthenticating when creating a new `Client`. This is a breaking + change because of adding `state_file` to the `AccessTokenLoginRequest` struct. (#388) + ### Deprecated - `client.access_token_login()` is now deprecated and will be removed in a future release. Please diff --git a/crates/bitwarden/README.md b/crates/bitwarden/README.md index 67c347583..abb2b5dd4 100644 --- a/crates/bitwarden/README.md +++ b/crates/bitwarden/README.md @@ -41,7 +41,7 @@ async fn test() -> Result<()> { let mut client = Client::new(Some(settings)); // Before we operate, we need to authenticate with a token - let token = AccessTokenLoginRequest { access_token: String::from("") }; + let token = AccessTokenLoginRequest { access_token: String::from(""), state_file: None }; client.auth().login_access_token(&token).await.unwrap(); let org_id = SecretIdentifiersRequest { organization_id: Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap() }; diff --git a/crates/bitwarden/src/auth/client_auth.rs b/crates/bitwarden/src/auth/client_auth.rs index 3c096835a..6c9ddbd4d 100644 --- a/crates/bitwarden/src/auth/client_auth.rs +++ b/crates/bitwarden/src/auth/client_auth.rs @@ -170,7 +170,8 @@ mod tests { .auth() .login_access_token(&AccessTokenLoginRequest { access_token: "0.ec2c1d46-6a4b-4751-a310-af9601317f2d.C2IgxjjLF7qSshsbwe8JGcbM075YXw:X8vbvA0bduihIDe/qrzIQQ==".into(), - }) + state_file: None, + },) .await .unwrap(); assert!(res.authenticated); diff --git a/crates/bitwarden/src/auth/login/access_token.rs b/crates/bitwarden/src/auth/login/access_token.rs index ed664437d..2e11035c6 100644 --- a/crates/bitwarden/src/auth/login/access_token.rs +++ b/crates/bitwarden/src/auth/login/access_token.rs @@ -1,6 +1,10 @@ +use std::path::{Path, PathBuf}; + use base64::Engine; +use chrono::Utc; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use uuid::Uuid; use crate::{ auth::{ @@ -11,6 +15,7 @@ use crate::{ client::{AccessToken, LoginMethod, ServiceAccountLoginMethod}, crypto::{EncString, KeyDecryptable, SymmetricCryptoKey}, error::{Error, Result}, + secrets_manager::state::{self, ClientState}, util::BASE64_ENGINE, Client, }; @@ -24,6 +29,25 @@ pub(crate) async fn login_access_token( let access_token: AccessToken = input.access_token.parse()?; + if let Some(state_file) = &input.state_file { + if let Ok(organization_id) = load_tokens_from_state(client, state_file, &access_token) { + client.set_login_method(LoginMethod::ServiceAccount( + ServiceAccountLoginMethod::AccessToken { + access_token, + organization_id, + state_file: Some(state_file.to_path_buf()), + }, + )); + + return Ok(AccessTokenLoginResponse { + authenticated: true, + reset_master_password: false, + force_password_reset: false, + two_factor: None, + }); + } + } + let response = request_access_token(client, &access_token).await?; if let IdentityTokenResponse::Payload(r) = &response { @@ -40,9 +64,7 @@ pub(crate) async fn login_access_token( } let payload: Payload = serde_json::from_slice(&decrypted_payload)?; - - let encryption_key = BASE64_ENGINE.decode(payload.encryption_key)?; - + let encryption_key = BASE64_ENGINE.decode(payload.encryption_key.clone())?; let encryption_key = SymmetricCryptoKey::try_from(encryption_key.as_slice())?; let access_token_obj: JWTToken = r.access_token.parse()?; @@ -54,6 +76,11 @@ pub(crate) async fn login_access_token( .parse() .map_err(|_| Error::InvalidResponse)?; + if let Some(state_file) = &input.state_file { + let state = ClientState::new(r.access_token.clone(), payload.encryption_key); + _ = state::set(state_file, &access_token, state); + } + client.set_tokens( r.access_token.clone(), r.refresh_token.clone(), @@ -63,6 +90,7 @@ pub(crate) async fn login_access_token( ServiceAccountLoginMethod::AccessToken { access_token, organization_id, + state_file: input.state_file.clone(), }, )); @@ -82,12 +110,41 @@ async fn request_access_token( .await } +fn load_tokens_from_state( + client: &mut Client, + state_file: &Path, + access_token: &AccessToken, +) -> Result { + let client_state = state::get(state_file, access_token)?; + + let token: JWTToken = client_state.token.parse()?; + + if let Some(organization_id) = token.organization { + let time_till_expiration = (token.exp as i64) - Utc::now().timestamp(); + + if time_till_expiration > 0 { + let organization_id: Uuid = organization_id + .parse() + .map_err(|_| "Bad organization id.")?; + let encryption_key: SymmetricCryptoKey = client_state.encryption_key.parse()?; + + client.set_tokens(client_state.token, None, time_till_expiration as u64); + client.initialize_crypto_single_key(encryption_key); + + return Ok(organization_id); + } + } + + Err(Error::InvalidStateFile) +} + /// Login to Bitwarden with access token #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct AccessTokenLoginRequest { /// Bitwarden service API access token pub access_token: String, + pub state_file: Option, } #[derive(Serialize, Deserialize, Debug, JsonSchema)] diff --git a/crates/bitwarden/src/auth/renew.rs b/crates/bitwarden/src/auth/renew.rs index fe1920e62..6f93f7482 100644 --- a/crates/bitwarden/src/auth/renew.rs +++ b/crates/bitwarden/src/auth/renew.rs @@ -6,12 +6,13 @@ use crate::{ auth::api::{request::AccessTokenRequest, response::IdentityTokenResponse}, client::{Client, LoginMethod, ServiceAccountLoginMethod}, error::{Error, Result}, + secrets_manager::state::{self, ClientState}, }; pub(crate) async fn renew_token(client: &mut Client) -> Result<()> { const TOKEN_RENEW_MARGIN_SECONDS: i64 = 5 * 60; - if let (Some(expires), Some(login_method)) = (&client.token_expires_in, &client.login_method) { + if let (Some(expires), Some(login_method)) = (&client.token_expires_on, &client.login_method) { if Utc::now().timestamp() < expires - TOKEN_RENEW_MARGIN_SECONDS { return Ok(()); } @@ -43,13 +44,32 @@ pub(crate) async fn renew_token(client: &mut Client) -> Result<()> { } }, LoginMethod::ServiceAccount(s) => match s { - ServiceAccountLoginMethod::AccessToken { access_token, .. } => { - AccessTokenRequest::new( + ServiceAccountLoginMethod::AccessToken { + access_token, + state_file, + .. + } => { + let result = AccessTokenRequest::new( access_token.access_token_id, &access_token.client_secret, ) .send(&client.__api_configurations) - .await? + .await?; + + if let ( + IdentityTokenResponse::Authenticated(r), + Some(state_file), + Ok(enc_settings), + ) = (&result, state_file, client.get_encryption_settings()) + { + if let Some(enc_key) = enc_settings.get_key(&None) { + let state = + ClientState::new(r.access_token.clone(), enc_key.to_base64()); + _ = state::set(state_file, access_token, state); + } + } + + result } }, }; diff --git a/crates/bitwarden/src/client/client.rs b/crates/bitwarden/src/client/client.rs index bb0f65c09..1057532a6 100644 --- a/crates/bitwarden/src/client/client.rs +++ b/crates/bitwarden/src/client/client.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use chrono::Utc; use reqwest::header::{self}; use uuid::Uuid; @@ -62,6 +64,7 @@ pub(crate) enum ServiceAccountLoginMethod { AccessToken { access_token: AccessToken, organization_id: Uuid, + state_file: Option, }, } @@ -69,7 +72,7 @@ pub(crate) enum ServiceAccountLoginMethod { pub struct Client { token: Option, pub(crate) refresh_token: Option, - pub(crate) token_expires_in: Option, + pub(crate) token_expires_on: Option, pub(crate) login_method: Option, /// Use Client::get_api_configurations() to access this. @@ -114,7 +117,7 @@ impl Client { Self { token: None, refresh_token: None, - token_expires_in: None, + token_expires_on: None, login_method: None, __api_configurations: ApiConfigurations { identity, @@ -193,7 +196,7 @@ impl Client { ) { self.token = Some(token.clone()); self.refresh_token = refresh_token; - self.token_expires_in = Some(Utc::now().timestamp() + expires_in as i64); + self.token_expires_on = Some(Utc::now().timestamp() + expires_in as i64); self.__api_configurations.identity.oauth_access_token = Some(token.clone()); self.__api_configurations.api.oauth_access_token = Some(token); } diff --git a/crates/bitwarden/src/error.rs b/crates/bitwarden/src/error.rs index e5560b479..ede8ef806 100644 --- a/crates/bitwarden/src/error.rs +++ b/crates/bitwarden/src/error.rs @@ -46,6 +46,12 @@ pub enum Error { #[error("Received error message from server: [{}] {}", .status, .message)] ResponseContent { status: StatusCode, message: String }, + #[error("The state file version is invalid")] + InvalidStateFileVersion, + + #[error("The state file could not be read")] + InvalidStateFile, + #[error("Internal error: {0}")] Internal(Cow<'static, str>), } diff --git a/crates/bitwarden/src/lib.rs b/crates/bitwarden/src/lib.rs index 0017ecff9..a8dd60399 100644 --- a/crates/bitwarden/src/lib.rs +++ b/crates/bitwarden/src/lib.rs @@ -38,7 +38,7 @@ //! let mut client = Client::new(Some(settings)); //! //! // Before we operate, we need to authenticate with a token -//! let token = AccessTokenLoginRequest { access_token: String::from("") }; +//! let token = AccessTokenLoginRequest { access_token: String::from(""), state_file: None }; //! client.auth().login_access_token(&token).await.unwrap(); //! //! let org_id = SecretIdentifiersRequest { organization_id: Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap() }; diff --git a/crates/bitwarden/src/secrets_manager/mod.rs b/crates/bitwarden/src/secrets_manager/mod.rs index 27b84121e..181edf6b6 100644 --- a/crates/bitwarden/src/secrets_manager/mod.rs +++ b/crates/bitwarden/src/secrets_manager/mod.rs @@ -1,5 +1,6 @@ pub mod projects; pub mod secrets; +pub mod state; mod client_projects; mod client_secrets; diff --git a/crates/bitwarden/src/secrets_manager/state.rs b/crates/bitwarden/src/secrets_manager/state.rs new file mode 100644 index 000000000..d39603d34 --- /dev/null +++ b/crates/bitwarden/src/secrets_manager/state.rs @@ -0,0 +1,52 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + client::AccessToken, + crypto::{EncString, KeyDecryptable, KeyEncryptable}, + error::{Error, Result}, +}; +use std::{fmt::Debug, path::Path}; + +const STATE_VERSION: u32 = 1; + +#[cfg(feature = "secrets")] +#[derive(Serialize, Deserialize, Debug)] +pub struct ClientState { + pub(crate) version: u32, + pub(crate) token: String, + pub(crate) encryption_key: String, +} + +impl ClientState { + pub fn new(token: String, encryption_key: String) -> Self { + Self { + version: STATE_VERSION, + token, + encryption_key, + } + } +} + +pub fn get(state_file: &Path, access_token: &AccessToken) -> Result { + let file_content = std::fs::read_to_string(state_file)?; + + let encrypted_state: EncString = file_content.parse()?; + let decrypted_state: String = encrypted_state.decrypt_with_key(&access_token.encryption_key)?; + let client_state: ClientState = serde_json::from_str(&decrypted_state)?; + + if client_state.version != STATE_VERSION { + return Err(Error::InvalidStateFileVersion); + } + + Ok(client_state) +} + +pub fn set(state_file: &Path, access_token: &AccessToken, state: ClientState) -> Result<()> { + let serialized_state: String = serde_json::to_string(&state)?; + let encrypted_state: EncString = + serialized_state.encrypt_with_key(&access_token.encryption_key)?; + let state_string: String = encrypted_state.to_string(); + + std::fs::write(state_file, state_string) + .map_err(|_| "Failure writing to the state file.".into()) +} diff --git a/crates/bws/CHANGELOG.md b/crates/bws/CHANGELOG.md index 3ae1d9009..30db20a04 100644 --- a/crates/bws/CHANGELOG.md +++ b/crates/bws/CHANGELOG.md @@ -10,6 +10,8 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Added - Ability to output secrets in an `env` format with `bws` (#320) +- Basic state to avoid reauthenticating every run, used when setting the `state_file_dir` key in the + config (#388) ## [0.3.1] - 2023-10-13 diff --git a/crates/bws/src/config.rs b/crates/bws/src/config.rs index ecad2f6a5..0ea1259f9 100644 --- a/crates/bws/src/config.rs +++ b/crates/bws/src/config.rs @@ -19,6 +19,7 @@ pub(crate) struct Profile { pub server_base: Option, pub server_api: Option, pub server_identity: Option, + pub state_file_dir: Option, } // TODO: This could probably be derived with a macro if we start adding more fields @@ -28,6 +29,7 @@ pub(crate) enum ProfileKey { server_base, server_api, server_identity, + state_file_dir, } impl ProfileKey { @@ -36,6 +38,7 @@ impl ProfileKey { ProfileKey::server_base => p.server_base = Some(value), ProfileKey::server_api => p.server_api = Some(value), ProfileKey::server_identity => p.server_identity = Some(value), + ProfileKey::state_file_dir => p.state_file_dir = Some(value), } } } @@ -43,7 +46,7 @@ impl ProfileKey { pub(crate) const FILENAME: &str = "config"; pub(crate) const DIRECTORY: &str = ".bws"; -fn get_config_path(config_file: Option<&Path>, ensure_folder_exists: bool) -> PathBuf { +pub(crate) fn get_config_path(config_file: Option<&Path>, ensure_folder_exists: bool) -> PathBuf { let config_file = config_file.map(ToOwned::to_owned).unwrap_or_else(|| { let base_dirs = BaseDirs::new().unwrap(); base_dirs.home_dir().join(DIRECTORY).join(FILENAME) @@ -118,6 +121,7 @@ impl Profile { server_base: Some(url.to_string()), server_api: None, server_identity: None, + state_file_dir: None, }) } pub(crate) fn api_url(&self) -> Result { diff --git a/crates/bws/src/main.rs b/crates/bws/src/main.rs index 22e3b5dcd..117186663 100644 --- a/crates/bws/src/main.rs +++ b/crates/bws/src/main.rs @@ -21,6 +21,7 @@ use log::error; mod config; mod render; +mod state; use config::ProfileKey; use render::{serialize_response, Color, Output}; @@ -302,6 +303,7 @@ async fn process_commands() -> Result<()> { Some(key) => key, None => bail!("Missing access token"), }; + let access_token_obj: AccessToken = access_token.parse()?; let profile = get_config_profile( &cli.server_url, @@ -311,6 +313,7 @@ async fn process_commands() -> Result<()> { )?; let settings = profile + .clone() .map(|p| -> Result<_> { Ok(ClientSettings { identity_url: p.identity_url()?, @@ -320,12 +323,20 @@ async fn process_commands() -> Result<()> { }) .transpose()?; + let state_file_path = state::get_state_file_path( + profile.and_then(|p| p.state_file_dir).map(Into::into), + access_token_obj.access_token_id.to_string(), + ); + let mut client = bitwarden::Client::new(settings); // Load session or return if no session exists let _ = client .auth() - .login_access_token(&AccessTokenLoginRequest { access_token }) + .login_access_token(&AccessTokenLoginRequest { + access_token, + state_file: state_file_path, + }) .await?; let organization_id = match client.get_access_token_organization() { diff --git a/crates/bws/src/state.rs b/crates/bws/src/state.rs new file mode 100644 index 000000000..ef0ef07e2 --- /dev/null +++ b/crates/bws/src/state.rs @@ -0,0 +1,18 @@ +use std::path::PathBuf; + +pub(crate) fn get_state_file_path( + state_file_dir: Option, + access_token_id: String, +) -> Option { + if let Some(mut state_file_path) = state_file_dir { + state_file_path.push(access_token_id); + + if let Some(parent_folder) = state_file_path.parent() { + std::fs::create_dir_all(parent_folder).unwrap(); + } + + return Some(state_file_path); + } + + None +} From f3d200f3428341f703b52a28fa79eabe1d3b465d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Tue, 19 Dec 2023 10:52:58 +0100 Subject: [PATCH 116/157] Change attachment key to EncString (#449) ## Type of change ``` - [x] Bug fix - [ ] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Change `AttachmentView` key to be an `EncString`, and skip decrypting it. This is to avoid a uniffi issue with the representation of Vec on Android. Note that this is very similar to what we're doing with Sends, where we don't decrypt the key on the model either until we're ready to decrypt the file. --- crates/bitwarden/src/vault/cipher/attachment.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bitwarden/src/vault/cipher/attachment.rs b/crates/bitwarden/src/vault/cipher/attachment.rs index ace4937da..e2d11f592 100644 --- a/crates/bitwarden/src/vault/cipher/attachment.rs +++ b/crates/bitwarden/src/vault/cipher/attachment.rs @@ -28,7 +28,7 @@ pub struct AttachmentView { pub size: Option, pub size_name: Option, pub file_name: Option, - pub key: Option>, // TODO: Should be made into SymmetricCryptoKey + pub key: Option, } impl KeyEncryptable for AttachmentView { @@ -39,7 +39,7 @@ impl KeyEncryptable for AttachmentView { size: self.size, size_name: self.size_name, file_name: self.file_name.encrypt_with_key(key)?, - key: self.key.as_deref().encrypt_with_key(key)?, + key: self.key, }) } } @@ -52,7 +52,7 @@ impl KeyDecryptable for Attachment { size: self.size.clone(), size_name: self.size_name.clone(), file_name: self.file_name.decrypt_with_key(key)?, - key: self.key.decrypt_with_key(key)?, + key: self.key.clone(), }) } } From 1067d35508852f2dc92a26545659f563a53ac422 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Tue, 19 Dec 2023 16:04:47 +0100 Subject: [PATCH 117/157] Split EncString for asymmetric support (#448) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Split `EncString` into an asymmetric variant. I've left the symmetric variant with the name `EncString` as it's the most common variant to avoid major changes. This is somewhat related to #447 and should be merged first so that PR can take advantage of the symm/asymm separation. I've moved the `EncString` files into their own folder to organize the crypto directory a bit better too. --- crates/bitwarden/src/client/client.rs | 4 +- .../src/client/encryption_settings.rs | 6 +- .../src/crypto/enc_string/asymmetric.rs | 256 ++++++++++++++++++ crates/bitwarden/src/crypto/enc_string/mod.rs | 80 ++++++ .../symmetric.rs} | 232 +--------------- crates/bitwarden/src/crypto/mod.rs | 2 +- crates/bitwarden/src/error.rs | 6 +- crates/bitwarden/src/mobile/crypto.rs | 4 +- crates/bitwarden/src/uniffi_support.rs | 20 +- 9 files changed, 379 insertions(+), 231 deletions(-) create mode 100644 crates/bitwarden/src/crypto/enc_string/asymmetric.rs create mode 100644 crates/bitwarden/src/crypto/enc_string/mod.rs rename crates/bitwarden/src/crypto/{enc_string.rs => enc_string/symmetric.rs} (52%) diff --git a/crates/bitwarden/src/client/client.rs b/crates/bitwarden/src/client/client.rs index 1057532a6..272d4210a 100644 --- a/crates/bitwarden/src/client/client.rs +++ b/crates/bitwarden/src/client/client.rs @@ -9,7 +9,7 @@ use crate::auth::login::{AccessTokenLoginRequest, AccessTokenLoginResponse}; #[cfg(feature = "internal")] use crate::{ client::kdf::Kdf, - crypto::EncString, + crypto::{AsymmEncString, EncString}, platform::{ generate_fingerprint, get_user_api_key, sync, FingerprintRequest, FingerprintResponse, SecretVerificationRequest, SyncRequest, SyncResponse, UserApiKeyResponse, @@ -275,7 +275,7 @@ impl Client { #[cfg(feature = "internal")] pub(crate) fn initialize_org_crypto( &mut self, - org_keys: Vec<(Uuid, EncString)>, + org_keys: Vec<(Uuid, AsymmEncString)>, ) -> Result<&EncryptionSettings> { let enc = self .encryption_settings diff --git a/crates/bitwarden/src/client/encryption_settings.rs b/crates/bitwarden/src/client/encryption_settings.rs index f56932700..d7321aeb4 100644 --- a/crates/bitwarden/src/client/encryption_settings.rs +++ b/crates/bitwarden/src/client/encryption_settings.rs @@ -6,7 +6,7 @@ use uuid::Uuid; use { crate::{ client::UserLoginMethod, - crypto::{EncString, KeyDecryptable}, + crypto::{AsymmEncString, EncString, KeyDecryptable}, error::{CryptoError, Result}, }, rsa::pkcs8::DecodePrivateKey, @@ -84,7 +84,7 @@ impl EncryptionSettings { #[cfg(feature = "internal")] pub(crate) fn set_org_keys( &mut self, - org_enc_keys: Vec<(Uuid, EncString)>, + org_enc_keys: Vec<(Uuid, AsymmEncString)>, ) -> Result<&mut Self> { use crate::error::Error; @@ -96,7 +96,7 @@ impl EncryptionSettings { // Decrypt the org keys with the private key for (org_id, org_enc_key) in org_enc_keys { - let dec = org_enc_key.decrypt_with_private_key(private_key)?; + let dec = org_enc_key.decrypt(private_key)?; let org_key = SymmetricCryptoKey::try_from(dec.as_slice())?; diff --git a/crates/bitwarden/src/crypto/enc_string/asymmetric.rs b/crates/bitwarden/src/crypto/enc_string/asymmetric.rs new file mode 100644 index 000000000..f90315afa --- /dev/null +++ b/crates/bitwarden/src/crypto/enc_string/asymmetric.rs @@ -0,0 +1,256 @@ +use std::{fmt::Display, str::FromStr}; + +use base64::Engine; +#[cfg(feature = "internal")] +use rsa::{Oaep, RsaPrivateKey}; +use serde::Deserialize; + +use crate::{ + error::{EncStringParseError, Error, Result}, + util::BASE64_ENGINE, +}; + +#[cfg(feature = "internal")] +use crate::error::CryptoError; + +use super::{from_b64_vec, split_enc_string}; + +/// # Encrypted string primitive +/// +/// [AsymmEncString] is a Bitwarden specific primitive that represents an asymmetrically encrypted string. They are +/// are used together with the KeyDecryptable and KeyEncryptable traits to encrypt and decrypt +/// data using AsymmetricCryptoKeys. +/// +/// The flexibility of the [AsymmEncString] type allows for different encryption algorithms to be used +/// which is represented by the different variants of the enum. +/// +/// ## Note +/// +/// For backwards compatibility we will rarely if ever be able to remove support for decrypting old +/// variants, but we should be opinionated in which variants are used for encrypting. +/// +/// ## Variants +/// - [Rsa2048_OaepSha256_B64](AsymmEncString::Rsa2048_OaepSha256_B64) +/// - [Rsa2048_OaepSha1_B64](AsymmEncString::Rsa2048_OaepSha1_B64) +/// +/// ## Serialization +/// +/// [AsymmEncString] implements [Display] and [FromStr] to allow for easy serialization and uses a +/// custom scheme to represent the different variants. +/// +/// The scheme is one of the following schemes: +/// - `[type].[data]` +/// +/// Where: +/// - `[type]`: is a digit number representing the variant. +/// - `[data]`: is the encrypted data. +#[derive(Clone)] +#[allow(unused, non_camel_case_types)] +pub enum AsymmEncString { + /// 3 + Rsa2048_OaepSha256_B64 { data: Vec }, + /// 4 + Rsa2048_OaepSha1_B64 { data: Vec }, + /// 5 + #[deprecated] + Rsa2048_OaepSha256_HmacSha256_B64 { data: Vec, mac: Vec }, + /// 6 + #[deprecated] + Rsa2048_OaepSha1_HmacSha256_B64 { data: Vec, mac: Vec }, +} + +/// To avoid printing sensitive information, [AsymmEncString] debug prints to `AsymmEncString`. +impl std::fmt::Debug for AsymmEncString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AsymmEncString").finish() + } +} + +/// Deserializes an [AsymmEncString] from a string. +impl FromStr for AsymmEncString { + type Err = Error; + + fn from_str(s: &str) -> Result { + let (enc_type, parts) = split_enc_string(s); + match (enc_type, parts.len()) { + ("3", 1) => { + let data = from_b64_vec(parts[0])?; + Ok(AsymmEncString::Rsa2048_OaepSha256_B64 { data }) + } + ("4", 1) => { + let data = from_b64_vec(parts[0])?; + Ok(AsymmEncString::Rsa2048_OaepSha1_B64 { data }) + } + #[allow(deprecated)] + ("5", 2) => { + let data = from_b64_vec(parts[0])?; + let mac: Vec = from_b64_vec(parts[1])?; + Ok(AsymmEncString::Rsa2048_OaepSha256_HmacSha256_B64 { data, mac }) + } + #[allow(deprecated)] + ("6", 2) => { + let data = from_b64_vec(parts[0])?; + let mac: Vec = from_b64_vec(parts[1])?; + Ok(AsymmEncString::Rsa2048_OaepSha1_HmacSha256_B64 { data, mac }) + } + + (enc_type, parts) => Err(EncStringParseError::InvalidTypeAsymm { + enc_type: enc_type.to_string(), + parts, + } + .into()), + } + } +} + +#[allow(unused)] +impl AsymmEncString { + /// TODO: Convert this to a trait method + #[cfg(feature = "internal")] + pub(crate) fn decrypt(&self, key: &RsaPrivateKey) -> Result> { + Ok(match self { + Self::Rsa2048_OaepSha256_B64 { data } => key.decrypt(Oaep::new::(), data), + Self::Rsa2048_OaepSha1_B64 { data } => key.decrypt(Oaep::new::(), data), + #[allow(deprecated)] + Self::Rsa2048_OaepSha256_HmacSha256_B64 { data, mac: _ } => { + key.decrypt(Oaep::new::(), data) + } + #[allow(deprecated)] + Self::Rsa2048_OaepSha1_HmacSha256_B64 { data, mac: _ } => { + key.decrypt(Oaep::new::(), data) + } + } + .map_err(|_| CryptoError::KeyDecrypt)?) + } +} + +impl Display for AsymmEncString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let parts: Vec<&[u8]> = match self { + AsymmEncString::Rsa2048_OaepSha256_B64 { data } => vec![data], + AsymmEncString::Rsa2048_OaepSha1_B64 { data } => vec![data], + #[allow(deprecated)] + AsymmEncString::Rsa2048_OaepSha256_HmacSha256_B64 { data, mac } => vec![data, mac], + #[allow(deprecated)] + AsymmEncString::Rsa2048_OaepSha1_HmacSha256_B64 { data, mac } => vec![data, mac], + }; + + let encoded_parts: Vec = parts + .iter() + .map(|part| BASE64_ENGINE.encode(part)) + .collect(); + + write!(f, "{}.{}", self.enc_type(), encoded_parts.join("|"))?; + + Ok(()) + } +} + +impl<'de> Deserialize<'de> for AsymmEncString { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_str(super::FromStrVisitor::new()) + } +} + +impl serde::Serialize for AsymmEncString { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl AsymmEncString { + /// The numerical representation of the encryption type of the [AsymmEncString]. + const fn enc_type(&self) -> u8 { + match self { + AsymmEncString::Rsa2048_OaepSha256_B64 { .. } => 3, + AsymmEncString::Rsa2048_OaepSha1_B64 { .. } => 4, + #[allow(deprecated)] + AsymmEncString::Rsa2048_OaepSha256_HmacSha256_B64 { .. } => 5, + #[allow(deprecated)] + AsymmEncString::Rsa2048_OaepSha1_HmacSha256_B64 { .. } => 6, + } + } +} + +/// Usually we wouldn't want to expose AsymmEncStrings in the API or the schemas. +/// But during the transition phase we will expose endpoints using the AsymmEncString type. +impl schemars::JsonSchema for AsymmEncString { + fn schema_name() -> String { + "AsymmEncString".to_string() + } + + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + gen.subschema_for::() + } +} + +#[cfg(test)] +mod tests { + use super::AsymmEncString; + + #[cfg(feature = "internal")] + #[test] + fn test_enc_string_rsa2048_oaep_sha1_hmac_sha256_b64() { + use rsa::{pkcs8::DecodePrivateKey, RsaPrivateKey}; + + let rsa_private_key: &str = "-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXRVrCX+2hfOQS +8HzYUS2oc/jGVTZpv+/Ryuoh9d8ihYX9dd0cYh2tl6KWdFc88lPUH11Oxqy20Rk2 +e5r/RF6T9yM0Me3NPnaKt+hlhLtfoc0h86LnhD56A9FDUfuI0dVnPcrwNv0YJIo9 +4LwxtbqBULNvXl6wJ7WAbODrCQy5ZgMVg+iH+gGpwiqsZqHt+KuoHWcN53MSPDfa +F4/YMB99U3TziJMOOJask1TEEnakMPln11PczNDazT17DXIxYrbPfutPdh6sLs6A +QOajdZijfEvepgnOe7cQ7aeatiOJFrjTApKPGxOVRzEMX4XS4xbyhH0QxQeB6l16 +l8C0uxIBAgMBAAECggEASaWfeVDA3cVzOPFSpvJm20OTE+R6uGOU+7vh36TX/POq +92qBuwbd0h0oMD32FxsXywd2IxtBDUSiFM9699qufTVuM0Q3tZw6lHDTOVG08+tP +dr8qSbMtw7PGFxN79fHLBxejjO4IrM9lapjWpxEF+11x7r+wM+0xRZQ8sNFYG46a +PfIaty4BGbL0I2DQ2y8I57iBCAy69eht59NLMm27fRWGJIWCuBIjlpfzET1j2HLX +UIh5bTBNzqaN039WH49HczGE3mQKVEJZc/efk3HaVd0a1Sjzyn0QY+N1jtZN3jTR +buDWA1AknkX1LX/0tUhuS3/7C3ejHxjw4Dk1ZLo5/QKBgQDIWvqFn0+IKRSu6Ua2 +hDsufIHHUNLelbfLUMmFthxabcUn4zlvIscJO00Tq/ezopSRRvbGiqnxjv/mYxuc +vOUBeZtlus0Q9RTACBtw9TGoNTmQbEunJ2FOSlqbQxkBBAjgGEppRPt30iGj/VjA +hCATq2MYOa/X4dVR51BqQAFIEwKBgQDBSIfTFKC/hDk6FKZlgwvupWYJyU9Rkyfs +tPErZFmzoKhPkQ3YORo2oeAYmVUbS9I2iIYpYpYQJHX8jMuCbCz4ONxTCuSIXYQY +UcUq4PglCKp31xBAE6TN8SvhfME9/MvuDssnQinAHuF0GDAhF646T3LLS1not6Vs +zv7brwSoGwKBgQC88v/8cGfi80ssQZeMnVvq1UTXIeQcQnoY5lGHJl3K8mbS3TnX +E6c9j417Fdz+rj8KWzBzwWXQB5pSPflWcdZO886Xu/mVGmy9RWgLuVFhXwCwsVEP +jNX5ramRb0/vY0yzenUCninBsIxFSbIfrPtLUYCc4hpxr+sr2Mg/y6jpvQKBgBez +MRRs3xkcuXepuI2R+BCXL1/b02IJTUf1F+1eLLGd7YV0H+J3fgNc7gGWK51hOrF9 +JBZHBGeOUPlaukmPwiPdtQZpu4QNE3l37VlIpKTF30E6mb+BqR+nht3rUjarnMXg +AoEZ18y6/KIjpSMpqC92Nnk/EBM9EYe6Cf4eA9ApAoGAeqEUg46UTlJySkBKURGp +Is3v1kkf5I0X8DnOhwb+HPxNaiEdmO7ckm8+tPVgppLcG0+tMdLjigFQiDUQk2y3 +WjyxP5ZvXu7U96jaJRI8PFMoE06WeVYcdIzrID2HvqH+w0UQJFrLJ/0Mn4stFAEz +XKZBokBGnjFnTnKcs7nv/O8= +-----END PRIVATE KEY-----"; + let private_key = RsaPrivateKey::from_pkcs8_pem(rsa_private_key).unwrap(); + let enc_str: &str = "6.ThnNc67nNr7GELyuhGGfsXNP2zJnNqhrIsjntEQ27r2qmn8vwdHbTbfO0cwt6YgSibDN0PjiCZ1O3Wb/IFq+vwvyRwFqF9145wBF8CQCbkhV+M0XvO99kh0daovtt120Nve/5ETI5PbPag9VdalKRQWZypJaqQHm5TAQVf4F5wtLlCLMBkzqTk+wkFe7BPMTGn07T+O3eJbTxXvyMZewQ7icJF0MZVA7VyWX9qElmZ89FCKowbf1BMr5pbcQ+0KdXcSVW3to43VkTp7k7COwsuH3M/i1AuVP5YN8ixjyRpvaeGqX/ap2nCHK2Wj5VxgCGT7XEls6ZknnAp9nB9qVjQ==|s3ntw5H/KKD/qsS0lUghTHl5Sm9j6m7YEdNHf0OeAFQ="; + let enc_string: AsymmEncString = enc_str.parse().unwrap(); + + assert_eq!(enc_string.enc_type(), 6); + + let res = enc_string.decrypt(&private_key).unwrap(); + + assert_eq!(std::str::from_utf8(&res).unwrap(), "EncryptMe!"); + } + + #[test] + fn test_enc_string_serialization() { + #[derive(serde::Serialize, serde::Deserialize)] + struct Test { + key: AsymmEncString, + } + + let cipher = "6.ThnNc67nNr7GELyuhGGfsXNP2zJnNqhrIsjntEQ27r2qmn8vwdHbTbfO0cwt6YgSibDN0PjiCZ1O3Wb/IFq+vwvyRwFqF9145wBF8CQCbkhV+M0XvO99kh0daovtt120Nve/5ETI5PbPag9VdalKRQWZypJaqQHm5TAQVf4F5wtLlCLMBkzqTk+wkFe7BPMTGn07T+O3eJbTxXvyMZewQ7icJF0MZVA7VyWX9qElmZ89FCKowbf1BMr5pbcQ+0KdXcSVW3to43VkTp7k7COwsuH3M/i1AuVP5YN8ixjyRpvaeGqX/ap2nCHK2Wj5VxgCGT7XEls6ZknnAp9nB9qVjQ==|s3ntw5H/KKD/qsS0lUghTHl5Sm9j6m7YEdNHf0OeAFQ="; + let serialized = format!("{{\"key\":\"{cipher}\"}}"); + + let t = serde_json::from_str::(&serialized).unwrap(); + assert_eq!(t.key.enc_type(), 6); + assert_eq!(t.key.to_string(), cipher); + assert_eq!(serde_json::to_string(&t).unwrap(), serialized); + } +} diff --git a/crates/bitwarden/src/crypto/enc_string/mod.rs b/crates/bitwarden/src/crypto/enc_string/mod.rs new file mode 100644 index 000000000..f8608638a --- /dev/null +++ b/crates/bitwarden/src/crypto/enc_string/mod.rs @@ -0,0 +1,80 @@ +mod asymmetric; +mod symmetric; + +use std::str::FromStr; + +pub use asymmetric::AsymmEncString; +use base64::Engine; +pub use symmetric::EncString; + +use crate::{ + error::{EncStringParseError, Result}, + util::BASE64_ENGINE, +}; + +#[cfg(feature = "mobile")] +fn check_length(buf: &[u8], expected: usize) -> Result<()> { + if buf.len() < expected { + return Err(EncStringParseError::InvalidLength { + expected, + got: buf.len(), + } + .into()); + } + Ok(()) +} + +fn from_b64_vec(s: &str) -> Result> { + Ok(BASE64_ENGINE + .decode(s) + .map_err(EncStringParseError::InvalidBase64)?) +} + +fn from_b64(s: &str) -> Result<[u8; N]> { + Ok(from_b64_vec(s)? + .try_into() + .map_err(|e: Vec<_>| EncStringParseError::InvalidLength { + expected: N, + got: e.len(), + })?) +} + +fn split_enc_string(s: &str) -> (&str, Vec<&str>) { + let header_parts: Vec<_> = s.split('.').collect(); + + if header_parts.len() == 2 { + (header_parts[0], header_parts[1].split('|').collect()) + } else { + // Support legacy format with no header + let parts: Vec<_> = s.split('|').collect(); + if parts.len() == 3 { + ("1", parts) // AesCbc128_HmacSha256_B64 + } else { + ("0", parts) // AesCbc256_B64 + } + } +} + +struct FromStrVisitor(std::marker::PhantomData); +impl FromStrVisitor { + fn new() -> Self { + Self(Default::default()) + } +} +impl serde::de::Visitor<'_> for FromStrVisitor +where + T::Err: std::fmt::Debug, +{ + type Value = T; + + fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "a valid string") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + T::from_str(v).map_err(|e| E::custom(format!("{:?}", e))) + } +} diff --git a/crates/bitwarden/src/crypto/enc_string.rs b/crates/bitwarden/src/crypto/enc_string/symmetric.rs similarity index 52% rename from crates/bitwarden/src/crypto/enc_string.rs rename to crates/bitwarden/src/crypto/enc_string/symmetric.rs index 34ec359ab..eed7c09ad 100644 --- a/crates/bitwarden/src/crypto/enc_string.rs +++ b/crates/bitwarden/src/crypto/enc_string/symmetric.rs @@ -2,20 +2,20 @@ use std::{fmt::Display, str::FromStr}; use aes::cipher::{generic_array::GenericArray, typenum::U32}; use base64::Engine; -#[cfg(feature = "internal")] -use rsa::{Oaep, RsaPrivateKey}; -use serde::{de::Visitor, Deserialize}; +use serde::Deserialize; -use super::{KeyDecryptable, KeyEncryptable, LocateKey}; +#[cfg(feature = "mobile")] +use super::check_length; +use super::{from_b64, from_b64_vec, split_enc_string}; use crate::{ - crypto::{decrypt_aes256_hmac, SymmetricCryptoKey}, + crypto::{decrypt_aes256_hmac, KeyDecryptable, KeyEncryptable, LocateKey, SymmetricCryptoKey}, error::{CryptoError, EncStringParseError, Error, Result}, util::BASE64_ENGINE, }; /// # Encrypted string primitive /// -/// [EncString] is a Bitwarden specific primitive that represents an encrypted string. They are +/// [EncString] is a Bitwarden specific primitive that represents a symmetrically encrypted string. They are /// are used together with the [KeyDecryptable] and [KeyEncryptable] traits to encrypt and decrypt /// data using [SymmetricCryptoKey]s. /// @@ -24,9 +24,6 @@ use crate::{ /// /// ## Note /// -/// We are currently in the progress of splitting the [EncString] into distinct AES and RSA -/// variants. To provide better control of which encryption algorithm is expected. -/// /// For backwards compatibility we will rarely if ever be able to remove support for decrypting old /// variants, but we should be opinionated in which variants are used for encrypting. /// @@ -34,8 +31,6 @@ use crate::{ /// - [AesCbc256_B64](EncString::AesCbc256_B64) /// - [AesCbc128_HmacSha256_B64](EncString::AesCbc128_HmacSha256_B64) /// - [AesCbc256_HmacSha256_B64](EncString::AesCbc256_HmacSha256_B64) -/// - [Rsa2048_OaepSha256_B64](EncString::Rsa2048_OaepSha256_B64) -/// - [Rsa2048_OaepSha1_B64](EncString::Rsa2048_OaepSha1_B64) /// /// ## Serialization /// @@ -45,7 +40,6 @@ use crate::{ /// The scheme is one of the following schemes: /// - `[type].[iv]|[data]` /// - `[type].[iv]|[data]|[mac]` -/// - `[type].[data]` /// /// Where: /// - `[type]`: is a digit number representing the variant. @@ -69,16 +63,6 @@ pub enum EncString { mac: [u8; 32], data: Vec, }, - /// 3 - Rsa2048_OaepSha256_B64 { data: Vec }, - /// 4 - Rsa2048_OaepSha1_B64 { data: Vec }, - /// 5 - #[deprecated] - Rsa2048_OaepSha256_HmacSha256_B64 { data: Vec }, - /// 6 - #[deprecated] - Rsa2048_OaepSha1_HmacSha256_B64 { data: Vec }, } /// To avoid printing sensitive information, [EncString] debug prints to `EncString`. @@ -93,32 +77,7 @@ impl FromStr for EncString { type Err = Error; fn from_str(s: &str) -> Result { - let (enc_type, parts): (&str, Vec<_>) = { - let header_parts: Vec<_> = s.split('.').collect(); - - if header_parts.len() == 2 { - (header_parts[0], header_parts[1].split('|').collect()) - } else { - // Support legacy format with no header - let parts: Vec<_> = s.split('|').collect(); - if parts.len() == 3 { - ("1", parts) // AesCbc128_HmacSha256_B64 - } else { - ("0", parts) // AesCbc256_B64 - } - } - }; - - fn from_b64_vec(s: &str) -> Result> { - Ok(BASE64_ENGINE - .decode(s) - .map_err(EncStringParseError::InvalidBase64)?) - } - - fn from_b64(s: &str) -> Result<[u8; N]> { - Ok(from_b64_vec(s)?.try_into().map_err(invalid_len_error(N))?) - } - + let (enc_type, parts) = split_enc_string(s); match (enc_type, parts.len()) { ("0", 2) => { let iv = from_b64(parts[0])?; @@ -137,26 +96,8 @@ impl FromStr for EncString { Ok(EncString::AesCbc256_HmacSha256_B64 { iv, mac, data }) } } - ("3", 1) => { - let data = from_b64_vec(parts[0])?; - Ok(EncString::Rsa2048_OaepSha256_B64 { data }) - } - ("4", 1) => { - let data = from_b64_vec(parts[0])?; - Ok(EncString::Rsa2048_OaepSha1_B64 { data }) - } - #[allow(deprecated)] - ("5", 2) => { - let data = from_b64_vec(parts[0])?; - Ok(EncString::Rsa2048_OaepSha256_HmacSha256_B64 { data }) - } - #[allow(deprecated)] - ("6", 2) => { - let data = from_b64_vec(parts[0])?; - Ok(EncString::Rsa2048_OaepSha1_HmacSha256_B64 { data }) - } - (enc_type, parts) => Err(EncStringParseError::InvalidType { + (enc_type, parts) => Err(EncStringParseError::InvalidTypeSymm { enc_type: enc_type.to_string(), parts, } @@ -172,29 +113,6 @@ impl EncString { s.map(|s| s.parse()).transpose() } - /// TODO: Convert this to a trait method - #[cfg(feature = "internal")] - pub(crate) fn decrypt_with_private_key(&self, key: &RsaPrivateKey) -> Result> { - Ok(match self { - EncString::Rsa2048_OaepSha256_B64 { data } => { - key.decrypt(Oaep::new::(), data) - } - EncString::Rsa2048_OaepSha1_B64 { data } => { - key.decrypt(Oaep::new::(), data) - } - #[allow(deprecated)] - EncString::Rsa2048_OaepSha256_HmacSha256_B64 { data } => { - key.decrypt(Oaep::new::(), data) - } - #[allow(deprecated)] - EncString::Rsa2048_OaepSha1_HmacSha256_B64 { data } => { - key.decrypt(Oaep::new::(), data) - } - _ => return Err(CryptoError::InvalidKey.into()), - } - .map_err(|_| CryptoError::KeyDecrypt)?) - } - #[cfg(feature = "mobile")] pub(crate) fn from_buffer(buf: &[u8]) -> Result { if buf.is_empty() { @@ -202,17 +120,6 @@ impl EncString { } let enc_type = buf[0]; - fn check_length(buf: &[u8], expected: usize) -> Result<()> { - if buf.len() < expected { - return Err(EncStringParseError::InvalidLength { - expected, - got: buf.len(), - } - .into()); - } - Ok(()) - } - match enc_type { 0 => { check_length(buf, 18)?; @@ -233,29 +140,7 @@ impl EncString { Ok(EncString::AesCbc256_HmacSha256_B64 { iv, mac, data }) } } - 3 => { - check_length(buf, 2)?; - let data = buf[1..].to_vec(); - Ok(EncString::Rsa2048_OaepSha256_B64 { data }) - } - 4 => { - check_length(buf, 2)?; - let data = buf[1..].to_vec(); - Ok(EncString::Rsa2048_OaepSha1_B64 { data }) - } - #[allow(deprecated)] - 5 => { - check_length(buf, 2)?; - let data = buf[1..].to_vec(); - Ok(EncString::Rsa2048_OaepSha256_HmacSha256_B64 { data }) - } - #[allow(deprecated)] - 6 => { - check_length(buf, 2)?; - let data = buf[1..].to_vec(); - Ok(EncString::Rsa2048_OaepSha1_HmacSha256_B64 { data }) - } - _ => Err(EncStringParseError::InvalidType { + _ => Err(EncStringParseError::InvalidTypeSymm { enc_type: enc_type.to_string(), parts: 1, } @@ -282,20 +167,6 @@ impl EncString { buf.extend_from_slice(mac); buf.extend_from_slice(data); } - - EncString::Rsa2048_OaepSha256_B64 { data } - | EncString::Rsa2048_OaepSha1_B64 { data } => { - buf = Vec::with_capacity(1 + data.len()); - buf.push(self.enc_type()); - buf.extend_from_slice(data); - } - #[allow(deprecated)] - EncString::Rsa2048_OaepSha256_HmacSha256_B64 { data } - | EncString::Rsa2048_OaepSha1_HmacSha256_B64 { data } => { - buf = Vec::with_capacity(1 + data.len()); - buf.push(self.enc_type()); - buf.extend_from_slice(data); - } } Ok(buf) @@ -308,12 +179,6 @@ impl Display for EncString { EncString::AesCbc256_B64 { iv, data } => vec![iv, data], EncString::AesCbc128_HmacSha256_B64 { iv, mac, data } => vec![iv, data, mac], EncString::AesCbc256_HmacSha256_B64 { iv, mac, data } => vec![iv, data, mac], - EncString::Rsa2048_OaepSha256_B64 { data } => vec![data], - EncString::Rsa2048_OaepSha1_B64 { data } => vec![data], - #[allow(deprecated)] - EncString::Rsa2048_OaepSha256_HmacSha256_B64 { data } => vec![data], - #[allow(deprecated)] - EncString::Rsa2048_OaepSha1_HmacSha256_B64 { data } => vec![data], }; let encoded_parts: Vec = parts @@ -332,23 +197,7 @@ impl<'de> Deserialize<'de> for EncString { where D: serde::Deserializer<'de>, { - struct CSVisitor; - impl Visitor<'_> for CSVisitor { - type Value = EncString; - - fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "a valid string") - } - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - EncString::from_str(v).map_err(|e| E::custom(format!("{:?}", e))) - } - } - - deserializer.deserialize_str(CSVisitor) + deserializer.deserialize_str(super::FromStrVisitor::new()) } } @@ -367,7 +216,7 @@ impl EncString { mac_key: GenericArray, key: GenericArray, ) -> Result { - let (iv, mac, data) = super::encrypt_aes256_hmac(data_dec, mac_key, key)?; + let (iv, mac, data) = crate::crypto::encrypt_aes256_hmac(data_dec, mac_key, key)?; Ok(EncString::AesCbc256_HmacSha256_B64 { iv, mac, data }) } @@ -377,23 +226,10 @@ impl EncString { EncString::AesCbc256_B64 { .. } => 0, EncString::AesCbc128_HmacSha256_B64 { .. } => 1, EncString::AesCbc256_HmacSha256_B64 { .. } => 2, - EncString::Rsa2048_OaepSha256_B64 { .. } => 3, - EncString::Rsa2048_OaepSha1_B64 { .. } => 4, - #[allow(deprecated)] - EncString::Rsa2048_OaepSha256_HmacSha256_B64 { .. } => 5, - #[allow(deprecated)] - EncString::Rsa2048_OaepSha1_HmacSha256_B64 { .. } => 6, } } } -fn invalid_len_error(expected: usize) -> impl Fn(Vec) -> EncStringParseError { - move |e: Vec<_>| EncStringParseError::InvalidLength { - expected, - got: e.len(), - } -} - impl LocateKey for EncString {} impl KeyEncryptable for &[u8] { fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { @@ -429,7 +265,7 @@ impl KeyDecryptable for EncString { /// Usually we wouldn't want to expose EncStrings in the API or the schemas. /// But during the transition phase we will expose endpoints using the EncString type. -impl schemars::JsonSchema for crate::crypto::EncString { +impl schemars::JsonSchema for EncString { fn schema_name() -> String { "EncString".to_string() } @@ -455,50 +291,6 @@ mod tests { assert_eq!(decrypted_str, test_string); } - #[cfg(feature = "internal")] - #[test] - fn test_enc_string_rsa2048_oaep_sha1_hmac_sha256_b64() { - use rsa::{pkcs8::DecodePrivateKey, RsaPrivateKey}; - - let rsa_private_key: &str = "-----BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXRVrCX+2hfOQS -8HzYUS2oc/jGVTZpv+/Ryuoh9d8ihYX9dd0cYh2tl6KWdFc88lPUH11Oxqy20Rk2 -e5r/RF6T9yM0Me3NPnaKt+hlhLtfoc0h86LnhD56A9FDUfuI0dVnPcrwNv0YJIo9 -4LwxtbqBULNvXl6wJ7WAbODrCQy5ZgMVg+iH+gGpwiqsZqHt+KuoHWcN53MSPDfa -F4/YMB99U3TziJMOOJask1TEEnakMPln11PczNDazT17DXIxYrbPfutPdh6sLs6A -QOajdZijfEvepgnOe7cQ7aeatiOJFrjTApKPGxOVRzEMX4XS4xbyhH0QxQeB6l16 -l8C0uxIBAgMBAAECggEASaWfeVDA3cVzOPFSpvJm20OTE+R6uGOU+7vh36TX/POq -92qBuwbd0h0oMD32FxsXywd2IxtBDUSiFM9699qufTVuM0Q3tZw6lHDTOVG08+tP -dr8qSbMtw7PGFxN79fHLBxejjO4IrM9lapjWpxEF+11x7r+wM+0xRZQ8sNFYG46a -PfIaty4BGbL0I2DQ2y8I57iBCAy69eht59NLMm27fRWGJIWCuBIjlpfzET1j2HLX -UIh5bTBNzqaN039WH49HczGE3mQKVEJZc/efk3HaVd0a1Sjzyn0QY+N1jtZN3jTR -buDWA1AknkX1LX/0tUhuS3/7C3ejHxjw4Dk1ZLo5/QKBgQDIWvqFn0+IKRSu6Ua2 -hDsufIHHUNLelbfLUMmFthxabcUn4zlvIscJO00Tq/ezopSRRvbGiqnxjv/mYxuc -vOUBeZtlus0Q9RTACBtw9TGoNTmQbEunJ2FOSlqbQxkBBAjgGEppRPt30iGj/VjA -hCATq2MYOa/X4dVR51BqQAFIEwKBgQDBSIfTFKC/hDk6FKZlgwvupWYJyU9Rkyfs -tPErZFmzoKhPkQ3YORo2oeAYmVUbS9I2iIYpYpYQJHX8jMuCbCz4ONxTCuSIXYQY -UcUq4PglCKp31xBAE6TN8SvhfME9/MvuDssnQinAHuF0GDAhF646T3LLS1not6Vs -zv7brwSoGwKBgQC88v/8cGfi80ssQZeMnVvq1UTXIeQcQnoY5lGHJl3K8mbS3TnX -E6c9j417Fdz+rj8KWzBzwWXQB5pSPflWcdZO886Xu/mVGmy9RWgLuVFhXwCwsVEP -jNX5ramRb0/vY0yzenUCninBsIxFSbIfrPtLUYCc4hpxr+sr2Mg/y6jpvQKBgBez -MRRs3xkcuXepuI2R+BCXL1/b02IJTUf1F+1eLLGd7YV0H+J3fgNc7gGWK51hOrF9 -JBZHBGeOUPlaukmPwiPdtQZpu4QNE3l37VlIpKTF30E6mb+BqR+nht3rUjarnMXg -AoEZ18y6/KIjpSMpqC92Nnk/EBM9EYe6Cf4eA9ApAoGAeqEUg46UTlJySkBKURGp -Is3v1kkf5I0X8DnOhwb+HPxNaiEdmO7ckm8+tPVgppLcG0+tMdLjigFQiDUQk2y3 -WjyxP5ZvXu7U96jaJRI8PFMoE06WeVYcdIzrID2HvqH+w0UQJFrLJ/0Mn4stFAEz -XKZBokBGnjFnTnKcs7nv/O8= ------END PRIVATE KEY-----"; - let private_key = RsaPrivateKey::from_pkcs8_pem(rsa_private_key).unwrap(); - let enc_str: &str = "6.ThnNc67nNr7GELyuhGGfsXNP2zJnNqhrIsjntEQ27r2qmn8vwdHbTbfO0cwt6YgSibDN0PjiCZ1O3Wb/IFq+vwvyRwFqF9145wBF8CQCbkhV+M0XvO99kh0daovtt120Nve/5ETI5PbPag9VdalKRQWZypJaqQHm5TAQVf4F5wtLlCLMBkzqTk+wkFe7BPMTGn07T+O3eJbTxXvyMZewQ7icJF0MZVA7VyWX9qElmZ89FCKowbf1BMr5pbcQ+0KdXcSVW3to43VkTp7k7COwsuH3M/i1AuVP5YN8ixjyRpvaeGqX/ap2nCHK2Wj5VxgCGT7XEls6ZknnAp9nB9qVjQ==|s3ntw5H/KKD/qsS0lUghTHl5Sm9j6m7YEdNHf0OeAFQ="; - let enc_string: EncString = enc_str.parse().unwrap(); - - assert_eq!(enc_string.enc_type(), 6); - - let res = enc_string.decrypt_with_private_key(&private_key).unwrap(); - - assert_eq!(std::str::from_utf8(&res).unwrap(), "EncryptMe!"); - } - #[test] fn test_enc_string_serialization() { #[derive(serde::Serialize, serde::Deserialize)] diff --git a/crates/bitwarden/src/crypto/mod.rs b/crates/bitwarden/src/crypto/mod.rs index 3d8f7b0ce..84697e8a5 100644 --- a/crates/bitwarden/src/crypto/mod.rs +++ b/crates/bitwarden/src/crypto/mod.rs @@ -27,7 +27,7 @@ use hmac::digest::OutputSizeUser; use crate::error::Result; mod enc_string; -pub use enc_string::EncString; +pub use enc_string::{AsymmEncString, EncString}; mod encryptable; pub use encryptable::{Decryptable, Encryptable, LocateKey}; mod key_encryptable; diff --git a/crates/bitwarden/src/error.rs b/crates/bitwarden/src/error.rs index ede8ef806..bb70adebf 100644 --- a/crates/bitwarden/src/error.rs +++ b/crates/bitwarden/src/error.rs @@ -106,8 +106,10 @@ pub enum CryptoError { pub enum EncStringParseError { #[error("No type detected, missing '.' separator")] NoType, - #[error("Invalid type, got {enc_type} with {parts} parts")] - InvalidType { enc_type: String, parts: usize }, + #[error("Invalid symmetric type, got type {enc_type} with {parts} parts")] + InvalidTypeSymm { enc_type: String, parts: usize }, + #[error("Invalid asymmetric type, got type {enc_type} with {parts} parts")] + InvalidTypeAsymm { enc_type: String, parts: usize }, #[error("Error decoding base64: {0}")] InvalidBase64(#[from] base64::DecodeError), #[error("Invalid length: expected {expected}, got {got}")] diff --git a/crates/bitwarden/src/mobile/crypto.rs b/crates/bitwarden/src/mobile/crypto.rs index edb44fadc..ca5dea703 100644 --- a/crates/bitwarden/src/mobile/crypto.rs +++ b/crates/bitwarden/src/mobile/crypto.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use crate::{ client::kdf::Kdf, - crypto::EncString, + crypto::{AsymmEncString, EncString}, error::{Error, Result}, Client, }; @@ -87,7 +87,7 @@ pub async fn initialize_user_crypto(client: &mut Client, req: InitUserCryptoRequ #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct InitOrgCryptoRequest { /// The encryption keys for all the organizations the user is a part of - pub organization_keys: HashMap, + pub organization_keys: HashMap, } #[cfg(feature = "internal")] diff --git a/crates/bitwarden/src/uniffi_support.rs b/crates/bitwarden/src/uniffi_support.rs index 5e25d764e..795aa9f50 100644 --- a/crates/bitwarden/src/uniffi_support.rs +++ b/crates/bitwarden/src/uniffi_support.rs @@ -2,7 +2,11 @@ use std::{num::NonZeroU32, str::FromStr}; use uuid::Uuid; -use crate::{crypto::EncString, error::Error, UniffiCustomTypeConverter}; +use crate::{ + crypto::{AsymmEncString, EncString}, + error::Error, + UniffiCustomTypeConverter, +}; uniffi::custom_type!(NonZeroU32, u32); @@ -32,6 +36,20 @@ impl UniffiCustomTypeConverter for EncString { } } +uniffi::custom_type!(AsymmEncString, String); + +impl UniffiCustomTypeConverter for AsymmEncString { + type Builtin = String; + + fn into_custom(val: Self::Builtin) -> uniffi::Result { + Self::from_str(&val).map_err(|e| e.into()) + } + + fn from_custom(obj: Self) -> Self::Builtin { + obj.to_string() + } +} + type DateTime = chrono::DateTime; uniffi::custom_type!(DateTime, std::time::SystemTime); From 0360a9a860526a40ad96bf7cb02830b31d0cce8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Wed, 20 Dec 2023 15:39:02 +0100 Subject: [PATCH 118/157] Make model IDs optional (#453) ## Type of change ``` - [x] Bug fix - [ ] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Previously some of the model structs had non-optional IDs, this won't work when trying to encrypt a new item, as you won't have an ID until you POST it to the server. This PR changes the id fields to be optional, matching what we already do on Cipher. --- crates/bitwarden/src/vault/collection.rs | 6 +++--- crates/bitwarden/src/vault/folder.rs | 6 +++--- crates/bitwarden/src/vault/send.rs | 20 ++++++++++---------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/bitwarden/src/vault/collection.rs b/crates/bitwarden/src/vault/collection.rs index 11983dca5..3ff5d1350 100644 --- a/crates/bitwarden/src/vault/collection.rs +++ b/crates/bitwarden/src/vault/collection.rs @@ -13,7 +13,7 @@ use crate::{ #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct Collection { - id: Uuid, + id: Option, organization_id: Uuid, name: EncString, @@ -27,7 +27,7 @@ pub struct Collection { #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct CollectionView { - id: Uuid, + id: Option, organization_id: Uuid, name: String, @@ -66,7 +66,7 @@ impl TryFrom for Collection { fn try_from(collection: CollectionDetailsResponseModel) -> Result { Ok(Collection { - id: collection.id.ok_or(Error::MissingFields)?, + id: collection.id, organization_id: collection.organization_id.ok_or(Error::MissingFields)?, name: collection.name.ok_or(Error::MissingFields)?.parse()?, external_id: collection.external_id, diff --git a/crates/bitwarden/src/vault/folder.rs b/crates/bitwarden/src/vault/folder.rs index d04bf3110..04a4bebf7 100644 --- a/crates/bitwarden/src/vault/folder.rs +++ b/crates/bitwarden/src/vault/folder.rs @@ -13,7 +13,7 @@ use crate::{ #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct Folder { - id: Uuid, + id: Option, name: EncString, revision_date: DateTime, } @@ -22,7 +22,7 @@ pub struct Folder { #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct FolderView { - id: Uuid, + id: Option, name: String, revision_date: DateTime, } @@ -54,7 +54,7 @@ impl TryFrom for Folder { fn try_from(folder: FolderResponseModel) -> Result { Ok(Folder { - id: folder.id.ok_or(Error::MissingFields)?, + id: folder.id, name: EncString::try_from_optional(folder.name)?.ok_or(Error::MissingFields)?, revision_date: folder.revision_date.ok_or(Error::MissingFields)?.parse()?, }) diff --git a/crates/bitwarden/src/vault/send.rs b/crates/bitwarden/src/vault/send.rs index 255f6b9fd..e732170c5 100644 --- a/crates/bitwarden/src/vault/send.rs +++ b/crates/bitwarden/src/vault/send.rs @@ -63,8 +63,8 @@ pub enum SendType { #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct Send { - pub id: Uuid, - pub access_id: String, + pub id: Option, + pub access_id: Option, pub name: EncString, pub notes: Option, @@ -89,8 +89,8 @@ pub struct Send { #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct SendView { - pub id: Uuid, - pub access_id: String, + pub id: Option, + pub access_id: Option, pub name: String, pub notes: Option, @@ -115,8 +115,8 @@ pub struct SendView { #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct SendListView { - pub id: Uuid, - pub access_id: String, + pub id: Option, + pub access_id: Option, pub name: String, @@ -270,8 +270,8 @@ impl TryFrom for Send { fn try_from(send: SendResponseModel) -> Result { Ok(Send { - id: send.id.ok_or(Error::MissingFields)?, - access_id: send.access_id.ok_or(Error::MissingFields)?, + id: send.id, + access_id: send.access_id, name: send.name.ok_or(Error::MissingFields)?.parse()?, notes: EncString::try_from_optional(send.notes)?, key: send.key.ok_or(Error::MissingFields)?.parse()?, @@ -348,8 +348,8 @@ mod tests { // Create a send object, the only value we really care about here is the key let send = Send { - id: "d7fb1e7f-9053-43c0-a02c-b0690098685a".parse().unwrap(), - access_id: "fx7711OQwEOgLLBpAJhoWg".into(), + id: Some("d7fb1e7f-9053-43c0-a02c-b0690098685a".parse().unwrap()), + access_id: Some("fx7711OQwEOgLLBpAJhoWg".into()), name: "2.u5vXPAepUZ+4lI2vGGKiGg==|hEouC4SvCCb/ifzZzLcfSw==|E2VZUVffehczfIuRSlX2vnPRfflBtXef5tzsWvRrtfM=" .parse() .unwrap(), From ac3e93ef65cd7ff2d04bed6b31b6bf8740104d1e Mon Sep 17 00:00:00 2001 From: Jake Fink Date: Wed, 20 Dec 2023 14:41:34 -0500 Subject: [PATCH 119/157] [PM-3437] add validation for master password policy (#452) ## Type of change ``` - [ ] Bug fix - [x] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Implements the validation for master password policy ## Code changes - **file.ext:** Description of what was changed and why ## Screenshots ## Before you submit - Please add **unit tests** where it makes sense to do so (encouraged but not required) --- crates/bitwarden-uniffi/src/auth/mod.rs | 2 +- crates/bitwarden/src/auth/password.rs | 154 +++++++++++++++++++++++- 2 files changed, 151 insertions(+), 5 deletions(-) diff --git a/crates/bitwarden-uniffi/src/auth/mod.rs b/crates/bitwarden-uniffi/src/auth/mod.rs index ded3f776a..1dc102b3e 100644 --- a/crates/bitwarden-uniffi/src/auth/mod.rs +++ b/crates/bitwarden-uniffi/src/auth/mod.rs @@ -29,7 +29,7 @@ impl ClientAuth { .await } - /// **API Draft:** Evaluate if the provided password satisfies the provided policy + /// Evaluate if the provided password satisfies the provided policy pub async fn satisfies_policy( &self, password: String, diff --git a/crates/bitwarden/src/auth/password.rs b/crates/bitwarden/src/auth/password.rs index eec68861a..94fe0faa0 100644 --- a/crates/bitwarden/src/auth/password.rs +++ b/crates/bitwarden/src/auth/password.rs @@ -16,11 +16,36 @@ pub(super) fn password_strength( 2 } +/// Validate the provided password passes the provided Master Password Requirements Policy. pub(super) fn satisfies_policy( - _password: String, - _strength: u8, - _policy: &MasterPasswordPolicyOptions, + password: String, + strength: u8, + policy: &MasterPasswordPolicyOptions, ) -> bool { + if policy.min_complexity > 0 && policy.min_complexity > strength { + return false; + } + + if policy.min_length > 0 && usize::from(policy.min_length) > password.len() { + return false; + } + + if policy.require_upper && password.to_lowercase() == password { + return false; + } + + if policy.require_lower && password.to_uppercase() == password { + return false; + } + + if policy.require_numbers && !password.chars().any(|c| c.is_numeric()) { + return false; + } + + if policy.require_special && !password.chars().any(|c| "!@#$%^&*".contains(c)) { + return false; + } + true } @@ -69,8 +94,129 @@ pub struct MasterPasswordPolicyOptions { } #[cfg(test)] - mod tests { + mod satisfies_policy { + use crate::auth::password::{satisfies_policy, MasterPasswordPolicyOptions}; + + #[test] + fn satisfies_policy_gives_success() { + let password = "lkasfo!icbb$2323ALKJCO22".to_string(); + let options = MasterPasswordPolicyOptions { + min_complexity: 3, + min_length: 5, + require_upper: true, + require_lower: true, + require_numbers: true, + require_special: true, + enforce_on_login: false, + }; + + let result = satisfies_policy(password, 4, &options); + assert!(result); + } + + #[test] + fn satisfies_policy_evaluates_strength() { + let password = "password123".to_string(); + let options = MasterPasswordPolicyOptions { + min_complexity: 3, + min_length: 0, + require_upper: false, + require_lower: false, + require_numbers: false, + require_special: false, + enforce_on_login: false, + }; + + let result = satisfies_policy(password, 0, &options); + assert!(!result); + } + + #[test] + fn satisfies_policy_evaluates_length() { + let password = "password123".to_string(); + let options = MasterPasswordPolicyOptions { + min_complexity: 0, + min_length: 20, + require_upper: false, + require_lower: false, + require_numbers: false, + require_special: false, + enforce_on_login: false, + }; + + let result = satisfies_policy(password, 0, &options); + assert!(!result); + } + + #[test] + fn satisfies_policy_evaluates_upper() { + let password = "password123".to_string(); + let options = MasterPasswordPolicyOptions { + min_complexity: 0, + min_length: 0, + require_upper: true, + require_lower: false, + require_numbers: false, + require_special: false, + enforce_on_login: false, + }; + + let result = satisfies_policy(password, 0, &options); + assert!(!result); + } + + #[test] + fn satisfies_policy_evaluates_lower() { + let password = "ABCDEFG123".to_string(); + let options = MasterPasswordPolicyOptions { + min_complexity: 0, + min_length: 0, + require_upper: false, + require_lower: true, + require_numbers: false, + require_special: false, + enforce_on_login: false, + }; + + let result = satisfies_policy(password, 0, &options); + assert!(!result); + } + + #[test] + fn satisfies_policy_evaluates_numbers() { + let password = "password".to_string(); + let options = MasterPasswordPolicyOptions { + min_complexity: 0, + min_length: 0, + require_upper: false, + require_lower: false, + require_numbers: true, + require_special: false, + enforce_on_login: false, + }; + + let result = satisfies_policy(password, 0, &options); + assert!(!result); + } + + #[test] + fn satisfies_policy_evaluates_special() { + let password = "Password123".to_string(); + let options = MasterPasswordPolicyOptions { + min_complexity: 0, + min_length: 0, + require_upper: false, + require_lower: false, + require_numbers: false, + require_special: true, + enforce_on_login: false, + }; + + let result = satisfies_policy(password, 0, &options); + assert!(!result); + } + } #[cfg(feature = "mobile")] #[tokio::test] From f191c662ee5f7e86c8d945d2cd9936e4bd3842e1 Mon Sep 17 00:00:00 2001 From: Bitwarden DevOps <106330231+bitwarden-devops-bot@users.noreply.github.com> Date: Thu, 21 Dec 2023 11:27:22 -0500 Subject: [PATCH 120/157] Bump version to 0.2.3 (#455) ## Type of change - [ ] Bug fix - [ ] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [X] Other ## Objective Automated bitwarden-api-api version bump to 0.2.3 --- Cargo.lock | 2 +- crates/bitwarden-api-api/Cargo.toml | 2 +- crates/bitwarden/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fcc4155e9..527f496a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -368,7 +368,7 @@ dependencies = [ [[package]] name = "bitwarden-api-api" -version = "0.2.2" +version = "0.2.3" dependencies = [ "reqwest", "serde", diff --git a/crates/bitwarden-api-api/Cargo.toml b/crates/bitwarden-api-api/Cargo.toml index 2e05088e0..e4ec902b1 100644 --- a/crates/bitwarden-api-api/Cargo.toml +++ b/crates/bitwarden-api-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bitwarden-api-api" -version = "0.2.2" +version = "0.2.3" authors = ["Bitwarden Inc"] license-file = "LICENSE" repository = "https://github.com/bitwarden/sdk" diff --git a/crates/bitwarden/Cargo.toml b/crates/bitwarden/Cargo.toml index a9cafc4da..f67fa2ea5 100644 --- a/crates/bitwarden/Cargo.toml +++ b/crates/bitwarden/Cargo.toml @@ -27,7 +27,7 @@ argon2 = { version = ">=0.5.0, <0.6", features = [ "alloc", ], default-features = false } base64 = ">=0.21.2, <0.22" -bitwarden-api-api = { path = "../bitwarden-api-api", version = "=0.2.2" } +bitwarden-api-api = { path = "../bitwarden-api-api", version = "=0.2.3" } bitwarden-api-identity = { path = "../bitwarden-api-identity", version = "=0.2.2" } cbc = { version = ">=0.1.2, <0.2", features = ["alloc"] } chrono = { version = ">=0.4.26, <0.5", features = [ From 0b306e3cf7967ba1ba379b4943e5415b5b41ff87 Mon Sep 17 00:00:00 2001 From: Bitwarden DevOps <106330231+bitwarden-devops-bot@users.noreply.github.com> Date: Thu, 21 Dec 2023 11:33:26 -0500 Subject: [PATCH 121/157] Bump version to 0.2.3 (#457) ## Type of change - [ ] Bug fix - [ ] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [X] Other ## Objective Automated bitwarden-api-identity version bump to 0.2.3 --- Cargo.lock | 2 +- crates/bitwarden-api-identity/Cargo.toml | 2 +- crates/bitwarden/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 527f496a2..ca2b03c78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -381,7 +381,7 @@ dependencies = [ [[package]] name = "bitwarden-api-identity" -version = "0.2.2" +version = "0.2.3" dependencies = [ "reqwest", "serde", diff --git a/crates/bitwarden-api-identity/Cargo.toml b/crates/bitwarden-api-identity/Cargo.toml index 7207c5f8e..929bbc6fa 100644 --- a/crates/bitwarden-api-identity/Cargo.toml +++ b/crates/bitwarden-api-identity/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bitwarden-api-identity" -version = "0.2.2" +version = "0.2.3" authors = ["Bitwarden Inc"] license-file = "LICENSE" repository = "https://github.com/bitwarden/sdk" diff --git a/crates/bitwarden/Cargo.toml b/crates/bitwarden/Cargo.toml index f67fa2ea5..bd94fb00b 100644 --- a/crates/bitwarden/Cargo.toml +++ b/crates/bitwarden/Cargo.toml @@ -28,7 +28,7 @@ argon2 = { version = ">=0.5.0, <0.6", features = [ ], default-features = false } base64 = ">=0.21.2, <0.22" bitwarden-api-api = { path = "../bitwarden-api-api", version = "=0.2.3" } -bitwarden-api-identity = { path = "../bitwarden-api-identity", version = "=0.2.2" } +bitwarden-api-identity = { path = "../bitwarden-api-identity", version = "=0.2.3" } cbc = { version = ">=0.1.2, <0.2", features = ["alloc"] } chrono = { version = ">=0.4.26, <0.5", features = [ "clock", From 3746415ac546a1aadc102e5b63d5daf871fdf4e0 Mon Sep 17 00:00:00 2001 From: Bitwarden DevOps <106330231+bitwarden-devops-bot@users.noreply.github.com> Date: Thu, 21 Dec 2023 11:36:28 -0500 Subject: [PATCH 122/157] Bump version to 0.4.0 (#458) ## Type of change - [ ] Bug fix - [ ] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [X] Other ## Objective Automated bitwarden version bump to 0.4.0 --- Cargo.lock | 2 +- crates/bitwarden/Cargo.toml | 2 +- crates/bw/Cargo.toml | 2 +- crates/bws/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca2b03c78..6aa395e57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -329,7 +329,7 @@ checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "bitwarden" -version = "0.3.1" +version = "0.4.0" dependencies = [ "aes", "argon2", diff --git a/crates/bitwarden/Cargo.toml b/crates/bitwarden/Cargo.toml index bd94fb00b..224ef1096 100644 --- a/crates/bitwarden/Cargo.toml +++ b/crates/bitwarden/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bitwarden" -version = "0.3.1" +version = "0.4.0" authors = ["Bitwarden Inc"] license-file = "LICENSE" repository = "https://github.com/bitwarden/sdk" diff --git a/crates/bw/Cargo.toml b/crates/bw/Cargo.toml index 53712100e..5183d5912 100644 --- a/crates/bw/Cargo.toml +++ b/crates/bw/Cargo.toml @@ -20,7 +20,7 @@ inquire = "0.6.2" log = "0.4.20" tokio = { version = "1.35.0", features = ["rt-multi-thread", "macros"] } -bitwarden = { path = "../bitwarden", version = "0.3.1", features = [ +bitwarden = { path = "../bitwarden", version = "0.4.0", features = [ "internal", "mobile", ] } diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index d9f008043..1c7b95dc8 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -40,7 +40,7 @@ tokio = { version = "1.35.0", features = ["rt-multi-thread", "macros"] } toml = "0.8.8" uuid = { version = "^1.6.1", features = ["serde"] } -bitwarden = { path = "../bitwarden", version = "0.3.1", features = ["secrets"] } +bitwarden = { path = "../bitwarden", version = "0.4.0", features = ["secrets"] } [dev-dependencies] tempfile = "3.8.1" From cf6c2118f052c6412b66df5f1971576f03380c14 Mon Sep 17 00:00:00 2001 From: Bitwarden DevOps <106330231+bitwarden-devops-bot@users.noreply.github.com> Date: Thu, 21 Dec 2023 11:39:00 -0500 Subject: [PATCH 123/157] Bump version to 0.4.0 (#459) ## Type of change - [ ] Bug fix - [ ] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [X] Other ## Objective Automated cli version bump to 0.4.0 --- Cargo.lock | 2 +- crates/bws/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6aa395e57..ca4305f8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -534,7 +534,7 @@ dependencies = [ [[package]] name = "bws" -version = "0.3.1" +version = "0.4.0" dependencies = [ "bat", "bitwarden", diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 1c7b95dc8..124f666cc 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bws" -version = "0.3.1" +version = "0.4.0" edition = "2021" rust-version = "1.60" authors = ["Bitwarden Inc"] From 793ff107eb8d5df7156ab4f30c0cbc78ff0d8f55 Mon Sep 17 00:00:00 2001 From: Colton Hurst Date: Thu, 21 Dec 2023 15:08:44 -0500 Subject: [PATCH 124/157] SM-1058: Update the changelog (#460) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [x] Other ``` ## Objective Changelog update for the release on 2023-12-21. ## Before you submit - Please add **unit tests** where it makes sense to do so (encouraged but not required) --- crates/bitwarden/CHANGELOG.md | 2 ++ crates/bws/CHANGELOG.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/crates/bitwarden/CHANGELOG.md b/crates/bitwarden/CHANGELOG.md index 1341eef68..b4a57cde1 100644 --- a/crates/bitwarden/CHANGELOG.md +++ b/crates/bitwarden/CHANGELOG.md @@ -7,6 +7,8 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [0.4.0] - 2023-12-21 + ### Added - Support for basic state to avoid reauthenticating when creating a new `Client`. This is a breaking diff --git a/crates/bws/CHANGELOG.md b/crates/bws/CHANGELOG.md index 30db20a04..007639800 100644 --- a/crates/bws/CHANGELOG.md +++ b/crates/bws/CHANGELOG.md @@ -7,6 +7,8 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [0.4.0] - 2023-12-21 + ### Added - Ability to output secrets in an `env` format with `bws` (#320) From 52d3100931389d61971e9cbcfb867d56fdee6c4f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 4 Jan 2024 10:09:19 +0100 Subject: [PATCH 125/157] [deps]: Update Rust crate tokio to 1.35.1 (#462) 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 | Type | Update | Change | |---|---|---|---| | [tokio](https://tokio.rs) ([source](https://togithub.com/tokio-rs/tokio)) | dependencies | patch | `1.35.0` -> `1.35.1` | | [tokio](https://tokio.rs) ([source](https://togithub.com/tokio-rs/tokio)) | dev-dependencies | patch | `1.35.0` -> `1.35.1` | --- ### Release Notes
tokio-rs/tokio (tokio) ### [`v1.35.1`](https://togithub.com/tokio-rs/tokio/releases/tag/tokio-1.35.1): Tokio v1.35.1 [Compare Source](https://togithub.com/tokio-rs/tokio/compare/tokio-1.35.0...tokio-1.35.1) ### 1.35.1 (December 19, 2023) This is a forward part of a change that was backported to 1.25.3. ##### Fixed - io: add budgeting to `tokio::runtime::io::registration::async_io` ([#​6221]) [#​6221]: https://togithub.com/tokio-rs/tokio/pull/6221
--- ### Configuration πŸ“… **Schedule**: Branch creation - "every weekend" (UTC), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. β™» **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. πŸ”• **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/bitwarden/sdk). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- crates/bitwarden-py/Cargo.toml | 2 +- crates/bitwarden/Cargo.toml | 2 +- crates/bw/Cargo.toml | 2 +- crates/bws/Cargo.toml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca4305f8d..db391b7e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3264,9 +3264,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.35.0" +version = "1.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d45b238a16291a4e1584e61820b8ae57d696cc5015c459c229ccc6990cc1c" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" dependencies = [ "backtrace", "bytes", diff --git a/crates/bitwarden-py/Cargo.toml b/crates/bitwarden-py/Cargo.toml index 3a7c5ca58..f867cfdc1 100644 --- a/crates/bitwarden-py/Cargo.toml +++ b/crates/bitwarden-py/Cargo.toml @@ -18,7 +18,7 @@ bitwarden-json = { path = "../bitwarden-json", features = ["secrets"] } pyo3-build-config = { version = "0.20.0" } [target.'cfg(not(target_arch="wasm32"))'.dependencies] -tokio = { version = "1.35.0", features = ["rt-multi-thread", "macros"] } +tokio = { version = "1.35.1", features = ["rt-multi-thread", "macros"] } pyo3-asyncio = { version = "0.20.0", features = [ "attributes", "tokio-runtime", diff --git a/crates/bitwarden/Cargo.toml b/crates/bitwarden/Cargo.toml index 224ef1096..5677a73d9 100644 --- a/crates/bitwarden/Cargo.toml +++ b/crates/bitwarden/Cargo.toml @@ -62,5 +62,5 @@ uuid = { version = ">=1.3.3, <2.0", features = ["serde"] } [dev-dependencies] rand_chacha = "0.3.1" -tokio = { version = "1.35.0", features = ["rt", "macros"] } +tokio = { version = "1.35.1", features = ["rt", "macros"] } wiremock = "0.5.22" diff --git a/crates/bw/Cargo.toml b/crates/bw/Cargo.toml index 5183d5912..20e750f14 100644 --- a/crates/bw/Cargo.toml +++ b/crates/bw/Cargo.toml @@ -18,7 +18,7 @@ color-eyre = "0.6" env_logger = "0.10.1" inquire = "0.6.2" log = "0.4.20" -tokio = { version = "1.35.0", features = ["rt-multi-thread", "macros"] } +tokio = { version = "1.35.1", features = ["rt-multi-thread", "macros"] } bitwarden = { path = "../bitwarden", version = "0.4.0", features = [ "internal", diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 124f666cc..3b71a7dad 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -36,7 +36,7 @@ serde_json = "^1.0.108" serde_yaml = "0.9" supports-color = "2.1.0" thiserror = "1.0.51" -tokio = { version = "1.35.0", features = ["rt-multi-thread", "macros"] } +tokio = { version = "1.35.1", features = ["rt-multi-thread", "macros"] } toml = "0.8.8" uuid = { version = "^1.6.1", features = ["serde"] } From 77cd027c14e47fb959a414b214de4a3895eb522e Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 4 Jan 2024 12:59:31 +0100 Subject: [PATCH 126/157] Install cross locked (#472) --- .github/workflows/build-android.yml | 2 +- .github/workflows/build-cli.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 2171fb515..784beb168 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -38,7 +38,7 @@ jobs: key: ${{ matrix.settings.target }}-cargo - name: Install Cross - run: cargo install cross + run: cargo install cross --locked - name: Build env: diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index eef2d84b5..4732a1936 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -75,7 +75,7 @@ jobs: - name: Install Cross (aarch64-unknown-linux-gnu) if: ${{ matrix.settings.target == 'aarch64-unknown-linux-gnu' }} - run: cargo install cross + run: cargo install cross --locked - name: Build if: ${{ matrix.settings.target != 'aarch64-unknown-linux-gnu' }} From fa39a5263ded119f8e482a92c645321f85377653 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 4 Jan 2024 15:17:40 +0100 Subject: [PATCH 127/157] Fix clippy warning (#471) --- crates/bitwarden-c/src/macros/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/bitwarden-c/src/macros/mod.rs b/crates/bitwarden-c/src/macros/mod.rs index e524fcbf1..57ae9b9ee 100644 --- a/crates/bitwarden-c/src/macros/mod.rs +++ b/crates/bitwarden-c/src/macros/mod.rs @@ -1,3 +1 @@ -pub use ffi::*; - mod ffi; From 70abf070e0948458eb719e3fa75f99fe009709ef Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 4 Jan 2024 15:28:11 +0100 Subject: [PATCH 128/157] [deps]: Update @types/node to v18.19.4 (#465) --- languages/js/sdk-client/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/languages/js/sdk-client/package-lock.json b/languages/js/sdk-client/package-lock.json index 12a2bb7c7..76fc3e341 100644 --- a/languages/js/sdk-client/package-lock.json +++ b/languages/js/sdk-client/package-lock.json @@ -39,9 +39,9 @@ } }, "node_modules/@types/node": { - "version": "18.19.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.3.tgz", - "integrity": "sha512-k5fggr14DwAytoA/t8rPrIz++lXK7/DqckthCmoZOKNsEbJkId4Z//BqgApXBUGrGddrigYa1oqheo/7YmW4rg==", + "version": "18.19.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.4.tgz", + "integrity": "sha512-xNzlUhzoHotIsnFoXmJB+yWmBvFZgKCI9TtPIEdYIMM1KWfwuY8zh7wvc1u1OAXlC7dlf6mZVx/s+Y5KfFz19A==", "dev": true, "dependencies": { "undici-types": "~5.26.4" From 94c0d070b899d417ef3cf452b84706592e0d9b84 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 4 Jan 2024 15:28:51 +0100 Subject: [PATCH 129/157] [deps]: Update Rust crate anyhow to 1.0.79 (#461) --- Cargo.lock | 4 ++-- crates/sdk-schemas/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index db391b7e4..9005f4909 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -111,9 +111,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" [[package]] name = "arc-swap" diff --git a/crates/sdk-schemas/Cargo.toml b/crates/sdk-schemas/Cargo.toml index 2e7706200..7ae1874b4 100644 --- a/crates/sdk-schemas/Cargo.toml +++ b/crates/sdk-schemas/Cargo.toml @@ -12,7 +12,7 @@ internal = [ ] [dependencies] -anyhow = "1.0.75" +anyhow = "1.0.79" itertools = "0.12.0" schemars = { version = "0.8.16", features = ["preserve_order"] } serde_json = "1.0.108" From cefa072f9509e9e3de471acac2640d69c6592dfc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 4 Jan 2024 14:30:13 +0000 Subject: [PATCH 130/157] [deps]: Update Rust crate thiserror to 1.0.56 (#468) --- Cargo.lock | 64 +++++++++++++++++++++---------------------- crates/bws/Cargo.toml | 2 +- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9005f4909..3a774fec7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -156,7 +156,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -227,7 +227,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -704,7 +704,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -935,7 +935,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e" dependencies = [ "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -1244,7 +1244,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -1873,7 +1873,7 @@ dependencies = [ "napi-derive-backend", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -1888,7 +1888,7 @@ dependencies = [ "quote", "regex", "semver", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -2094,7 +2094,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -2295,9 +2295,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708" dependencies = [ "unicode-ident", ] @@ -2384,7 +2384,7 @@ dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -2396,7 +2396,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -2410,9 +2410,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -2743,7 +2743,7 @@ checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -2808,7 +2808,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -2863,7 +2863,7 @@ checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -3068,7 +3068,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -3100,9 +3100,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.41" +version = "2.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +checksum = "1726efe18f42ae774cc644f330953a5e7b3c3003d3edcecf18850fe9d4dd9afb" dependencies = [ "proc-macro2", "quote", @@ -3190,22 +3190,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.51" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.51" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -3287,7 +3287,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -3382,7 +3382,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -3555,7 +3555,7 @@ version = "0.25.2" source = "git+https://github.com/mozilla/uniffi-rs?rev=23711c8151bbb794369aa1f9d383db386792dff9#23711c8151bbb794369aa1f9d383db386792dff9" dependencies = [ "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] @@ -3586,7 +3586,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.41", + "syn 2.0.47", "toml 0.5.11", "uniffi_build", "uniffi_meta", @@ -3744,7 +3744,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", "wasm-bindgen-shared", ] @@ -3778,7 +3778,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3811,7 +3811,7 @@ checksum = "794645f5408c9a039fd09f4d113cdfb2e7eba5ff1956b07bcf701cf4b394fe89" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.47", ] [[package]] diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 3b71a7dad..648021c6f 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -35,7 +35,7 @@ serde = "^1.0.193" serde_json = "^1.0.108" serde_yaml = "0.9" supports-color = "2.1.0" -thiserror = "1.0.51" +thiserror = "1.0.56" tokio = { version = "1.35.1", features = ["rt-multi-thread", "macros"] } toml = "0.8.8" uuid = { version = "^1.6.1", features = ["serde"] } From f088f0c7b28c78c5f086399e5393c60eb30ff7f5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 4 Jan 2024 15:55:12 +0100 Subject: [PATCH 131/157] [deps]: Update Rust crate clap_complete to 4.4.6 (#467) --- Cargo.lock | 4 ++-- crates/bws/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3a774fec7..3363cb5e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -688,9 +688,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.4.4" +version = "4.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bffe91f06a11b4b9420f62103854e90867812cd5d01557f853c5ee8e791b12ae" +checksum = "97aeaa95557bd02f23fbb662f981670c3d20c5a26e69f7354b28f57092437fcd" dependencies = [ "clap", ] diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 648021c6f..5f05d2708 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -21,7 +21,7 @@ chrono = { version = "0.4.31", features = [ "std", ], default-features = false } clap = { version = "4.4.11", features = ["derive", "env", "string"] } -clap_complete = "4.4.4" +clap_complete = "4.4.6" color-eyre = "0.6" comfy-table = "^7.1.0" directories = "5.0.1" From 087dfcb77a6216ded7becef75b435ab57cabc49e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 4 Jan 2024 15:55:29 +0100 Subject: [PATCH 132/157] [deps]: Update Rust crate tempfile to 3.9.0 (#469) --- Cargo.lock | 6 +++--- crates/bw/Cargo.toml | 2 +- crates/bws/Cargo.toml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3363cb5e0..1de208b0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3157,15 +3157,15 @@ checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a" [[package]] name = "tempfile" -version = "3.8.1" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" dependencies = [ "cfg-if", "fastrand 2.0.1", "redox_syscall", "rustix", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] diff --git a/crates/bw/Cargo.toml b/crates/bw/Cargo.toml index 20e750f14..d3739fc7d 100644 --- a/crates/bw/Cargo.toml +++ b/crates/bw/Cargo.toml @@ -27,4 +27,4 @@ bitwarden = { path = "../bitwarden", version = "0.4.0", features = [ bitwarden-cli = { path = "../bitwarden-cli", version = "0.1.0" } [dev-dependencies] -tempfile = "3.8.1" +tempfile = "3.9.0" diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 5f05d2708..98dfb668d 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -43,7 +43,7 @@ uuid = { version = "^1.6.1", features = ["serde"] } bitwarden = { path = "../bitwarden", version = "0.4.0", features = ["secrets"] } [dev-dependencies] -tempfile = "3.8.1" +tempfile = "3.9.0" [target.'cfg(target_os = "linux")'.dependencies] openssl = { version = "0.10", features = ["vendored"] } From 8dd2e4581eba476d072cc8d7841b7a01e1f3211a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 4 Jan 2024 15:55:50 +0100 Subject: [PATCH 133/157] [deps]: Update pyo3 non-major to 0.20.1 (#470) --- Cargo.lock | 20 ++++++++++---------- crates/bitwarden-py/Cargo.toml | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1de208b0b..f4b125501 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2304,9 +2304,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04e8453b658fe480c3e70c8ed4e3d3ec33eb74988bd186561b0cc66b85c3bc4b" +checksum = "e82ad98ce1991c9c70c3464ba4187337b9c45fcbbb060d46dca15f0c075e14e2" dependencies = [ "cfg-if", "indoc", @@ -2346,9 +2346,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96fe70b176a89cff78f2fa7b3c930081e163d5379b4dcdf993e3ae29ca662e5" +checksum = "5503d0b3aee2c7a8dbb389cd87cd9649f675d4c7f60ca33699a3e3859d81a891" dependencies = [ "once_cell", "target-lexicon", @@ -2356,9 +2356,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "214929900fd25e6604661ed9cf349727c8920d47deff196c4e28165a6ef2a96b" +checksum = "18a79e8d80486a00d11c0dcb27cd2aa17c022cc95c677b461f01797226ba8f41" dependencies = [ "libc", "pyo3-build-config", @@ -2377,9 +2377,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac53072f717aa1bfa4db832b39de8c875b7c7af4f4a6fe93cdbf9264cf8383b" +checksum = "1f4b0dc7eaa578604fab11c8c7ff8934c71249c61d4def8e272c76ed879f03d4" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -2389,9 +2389,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7774b5a8282bd4f25f803b1f0d945120be959a36c72e08e7cd031c792fdfd424" +checksum = "816a4f709e29ddab2e3cdfe94600d554c5556cad0ddfeea95c47b580c3247fa4" dependencies = [ "heck", "proc-macro2", diff --git a/crates/bitwarden-py/Cargo.toml b/crates/bitwarden-py/Cargo.toml index f867cfdc1..1d52d0b2b 100644 --- a/crates/bitwarden-py/Cargo.toml +++ b/crates/bitwarden-py/Cargo.toml @@ -9,13 +9,13 @@ name = "bitwarden_py" crate-type = ["cdylib"] [dependencies] -pyo3 = { version = "0.20.0", features = ["extension-module"] } +pyo3 = { version = "0.20.1", features = ["extension-module"] } pyo3-log = "0.9.0" bitwarden-json = { path = "../bitwarden-json", features = ["secrets"] } [build-dependencies] -pyo3-build-config = { version = "0.20.0" } +pyo3-build-config = { version = "0.20.1" } [target.'cfg(not(target_arch="wasm32"))'.dependencies] tokio = { version = "1.35.1", features = ["rt-multi-thread", "macros"] } From f3cf39b1c620556950df75b9d1dc1c986825f1d1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 4 Jan 2024 17:38:41 +0100 Subject: [PATCH 134/157] [deps]: Update Rust crate clap to 4.4.12 (#466) --- Cargo.lock | 8 ++++---- crates/bitwarden-cli/Cargo.toml | 2 +- crates/bw/Cargo.toml | 2 +- crates/bws/Cargo.toml | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f4b125501..498aaf85b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -666,9 +666,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.11" +version = "4.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" +checksum = "dcfab8ba68f3668e89f6ff60f5b205cea56aa7b769451a59f34b8682f51c056d" dependencies = [ "clap_builder", "clap_derive", @@ -676,9 +676,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.11" +version = "4.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" +checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9" dependencies = [ "anstream", "anstyle", diff --git a/crates/bitwarden-cli/Cargo.toml b/crates/bitwarden-cli/Cargo.toml index 2591035c2..08381a47c 100644 --- a/crates/bitwarden-cli/Cargo.toml +++ b/crates/bitwarden-cli/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" rust-version = "1.57" [dependencies] -clap = { version = "4.4.11", features = ["derive"] } +clap = { version = "4.4.12", features = ["derive"] } color-eyre = "0.6" inquire = "0.6.2" supports-color = "2.1.0" diff --git a/crates/bw/Cargo.toml b/crates/bw/Cargo.toml index d3739fc7d..059b853a5 100644 --- a/crates/bw/Cargo.toml +++ b/crates/bw/Cargo.toml @@ -13,7 +13,7 @@ Bitwarden Password Manager CLI keywords = ["bitwarden", "password-manager", "cli"] [dependencies] -clap = { version = "4.4.11", features = ["derive", "env"] } +clap = { version = "4.4.12", features = ["derive", "env"] } color-eyre = "0.6" env_logger = "0.10.1" inquire = "0.6.2" diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 98dfb668d..90c2cdb05 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -20,7 +20,7 @@ chrono = { version = "0.4.31", features = [ "clock", "std", ], default-features = false } -clap = { version = "4.4.11", features = ["derive", "env", "string"] } +clap = { version = "4.4.12", features = ["derive", "env", "string"] } clap_complete = "4.4.6" color-eyre = "0.6" comfy-table = "^7.1.0" From 68236d314a95f612d3b0bea1aa571596a8b3705f Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Fri, 5 Jan 2024 11:51:57 +0100 Subject: [PATCH 135/157] Add support for generating the users fingerprint (#474) Add support for generating the users fingerprint using the private key from EncSettings. --- crates/bitwarden-json/src/client.rs | 2 +- crates/bitwarden-uniffi/src/platform/mod.rs | 22 ++++++- crates/bitwarden-uniffi/src/uniffi_support.rs | 3 + crates/bitwarden/src/client/client.rs | 12 +--- .../src/client/encryption_settings.rs | 2 +- .../bitwarden/src/platform/client_platform.rs | 25 +++++++ .../src/platform/generate_fingerprint.rs | 66 ++++++++++++++++++- crates/bitwarden/src/platform/mod.rs | 2 +- languages/kotlin/doc.md | 40 ++++++++--- 9 files changed, 149 insertions(+), 25 deletions(-) create mode 100644 crates/bitwarden/src/platform/client_platform.rs diff --git a/crates/bitwarden-json/src/client.rs b/crates/bitwarden-json/src/client.rs index 416067b66..d484c7b50 100644 --- a/crates/bitwarden-json/src/client.rs +++ b/crates/bitwarden-json/src/client.rs @@ -58,7 +58,7 @@ impl Client { #[cfg(feature = "internal")] Command::Sync(req) => self.0.sync(&req).await.into_string(), #[cfg(feature = "internal")] - Command::Fingerprint(req) => self.0.fingerprint(&req).into_string(), + Command::Fingerprint(req) => self.0.platform().fingerprint(&req).into_string(), #[cfg(feature = "secrets")] Command::Secrets(cmd) => match cmd { diff --git a/crates/bitwarden-uniffi/src/platform/mod.rs b/crates/bitwarden-uniffi/src/platform/mod.rs index f03ed01e5..7200b6678 100644 --- a/crates/bitwarden-uniffi/src/platform/mod.rs +++ b/crates/bitwarden-uniffi/src/platform/mod.rs @@ -9,8 +9,26 @@ pub struct ClientPlatform(pub(crate) Arc); #[uniffi::export(async_runtime = "tokio")] impl ClientPlatform { - /// Fingerprint + /// Fingerprint (public key) pub async fn fingerprint(&self, req: FingerprintRequest) -> Result { - Ok(self.0 .0.read().await.fingerprint(&req)?.fingerprint) + Ok(self + .0 + .0 + .write() + .await + .platform() + .fingerprint(&req)? + .fingerprint) + } + + /// Fingerprint using logged in user's public key + pub async fn user_fingerprint(&self, fingerprint_material: String) -> Result { + Ok(self + .0 + .0 + .write() + .await + .platform() + .user_fingerprint(fingerprint_material)?) } } diff --git a/crates/bitwarden-uniffi/src/uniffi_support.rs b/crates/bitwarden-uniffi/src/uniffi_support.rs index 82cd343a7..72a67f698 100644 --- a/crates/bitwarden-uniffi/src/uniffi_support.rs +++ b/crates/bitwarden-uniffi/src/uniffi_support.rs @@ -1,3 +1,6 @@ +use bitwarden::crypto::EncString; + // Forward the type definitions to the main bitwarden crate type DateTime = chrono::DateTime; uniffi::ffi_converter_forward!(DateTime, bitwarden::UniFfiTag, crate::UniFfiTag); +uniffi::ffi_converter_forward!(EncString, bitwarden::UniFfiTag, crate::UniFfiTag); diff --git a/crates/bitwarden/src/client/client.rs b/crates/bitwarden/src/client/client.rs index 272d4210a..302e7ed75 100644 --- a/crates/bitwarden/src/client/client.rs +++ b/crates/bitwarden/src/client/client.rs @@ -4,6 +4,7 @@ use chrono::Utc; use reqwest::header::{self}; use uuid::Uuid; +use super::AccessToken; #[cfg(feature = "secrets")] use crate::auth::login::{AccessTokenLoginRequest, AccessTokenLoginResponse}; #[cfg(feature = "internal")] @@ -11,8 +12,8 @@ use crate::{ client::kdf::Kdf, crypto::{AsymmEncString, EncString}, platform::{ - generate_fingerprint, get_user_api_key, sync, FingerprintRequest, FingerprintResponse, - SecretVerificationRequest, SyncRequest, SyncResponse, UserApiKeyResponse, + get_user_api_key, sync, SecretVerificationRequest, SyncRequest, SyncResponse, + UserApiKeyResponse, }, }; use crate::{ @@ -24,8 +25,6 @@ use crate::{ error::{Error, Result}, }; -use super::AccessToken; - #[derive(Debug)] pub(crate) struct ApiConfigurations { pub identity: bitwarden_api_identity::apis::configuration::Configuration, @@ -285,9 +284,4 @@ impl Client { enc.set_org_keys(org_keys)?; Ok(self.encryption_settings.as_ref().unwrap()) } - - #[cfg(feature = "internal")] - pub fn fingerprint(&self, input: &FingerprintRequest) -> Result { - generate_fingerprint(input) - } } diff --git a/crates/bitwarden/src/client/encryption_settings.rs b/crates/bitwarden/src/client/encryption_settings.rs index d7321aeb4..ebfbba2b4 100644 --- a/crates/bitwarden/src/client/encryption_settings.rs +++ b/crates/bitwarden/src/client/encryption_settings.rs @@ -16,7 +16,7 @@ use crate::crypto::SymmetricCryptoKey; pub struct EncryptionSettings { user_key: SymmetricCryptoKey, - private_key: Option, + pub(crate) private_key: Option, org_keys: HashMap, } diff --git a/crates/bitwarden/src/platform/client_platform.rs b/crates/bitwarden/src/platform/client_platform.rs new file mode 100644 index 000000000..ada8b92b8 --- /dev/null +++ b/crates/bitwarden/src/platform/client_platform.rs @@ -0,0 +1,25 @@ +use super::{ + generate_fingerprint::{generate_fingerprint, generate_user_fingerprint}, + FingerprintRequest, FingerprintResponse, +}; +use crate::{error::Result, Client}; + +pub struct ClientPlatform<'a> { + pub(crate) client: &'a mut Client, +} + +impl<'a> ClientPlatform<'a> { + pub fn fingerprint(&self, input: &FingerprintRequest) -> Result { + generate_fingerprint(input) + } + + pub fn user_fingerprint(self, fingerprint_material: String) -> Result { + generate_user_fingerprint(self.client, fingerprint_material) + } +} + +impl<'a> Client { + pub fn platform(&'a mut self) -> ClientPlatform<'a> { + ClientPlatform { client: self } + } +} diff --git a/crates/bitwarden/src/platform/generate_fingerprint.rs b/crates/bitwarden/src/platform/generate_fingerprint.rs index e9562ab34..a8f45dd87 100644 --- a/crates/bitwarden/src/platform/generate_fingerprint.rs +++ b/crates/bitwarden/src/platform/generate_fingerprint.rs @@ -1,5 +1,6 @@ use base64::Engine; -use log::{debug, info}; +use log::info; +use rsa::pkcs8::EncodePublicKey; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -23,7 +24,6 @@ pub struct FingerprintResponse { pub(crate) fn generate_fingerprint(input: &FingerprintRequest) -> Result { info!("Generating fingerprint"); - debug!("{:?}", input); let key = BASE64_ENGINE.decode(&input.public_key)?; @@ -31,3 +31,65 @@ pub(crate) fn generate_fingerprint(input: &FingerprintRequest) -> Result Result { + info!("Generating fingerprint"); + + let enc_settings = client.get_encryption_settings()?; + let private_key = enc_settings + .private_key + .as_ref() + .ok_or("Missing private key")?; + + let public_key = private_key + .to_public_key() + .to_public_key_der() + .map_err(|_| "Invalid key")?; + + let fingerprint = fingerprint(&fingerprint_material, public_key.as_bytes())?; + + Ok(fingerprint) +} + +#[cfg(test)] +mod tests { + use std::num::NonZeroU32; + + use crate::{ + client::{kdf::Kdf, LoginMethod, UserLoginMethod}, + Client, + }; + + use super::*; + + #[test] + fn test_generate_user_fingerprint() { + let user_key = "2.oZg5RYpU2HjUAKI1DUQCkg==|PyRzI9kZpt66P2OedH8CHOeU0/lgKLkhIJiKDijdyFqIemBSIBoslhfQh/P1TK9xgZp0smgD6+5+yNbZfOpBaCVrsT3WWAO78xOWizduRe4=|xfDLDZSJ+yZAdh388flVg7SMDBJuMs0+CHTjutKs4uQ="; + let private_key = "2.tY6WsWKUbBwNU8wROuipiQ==|DNFL1d19xVojUKTTy2gxT+9J1VXbMQLcbMnx1HSeA6U3yZhsLR6DPaGibb3Bp8doIHtrsxzL/JeLb4gLDZ8RnDhFfE4iLRaPakX14kbBXrKH9/uW/zc7TqIVciWhI1PaeFlu8wnVuGt3e5Ysx6Y7Uw7RS8pRT5aE3sX3aDPGZTAdTutLn1VUfkShS5OK5HJl9CdiwV2wOcrf4w/WqtaNUUqGdsJ8C4ELlpBzHxqs+lEm+8pGPYmuGQIjVc0eOR9Tza9GTk3ih1XGc1znOCoKUZbtA29RfbwfmJy/yGi/3RLWZFQGCCij4cLC5OpldiX4JWL5Dhox44p/5IVF3rfxTVz3GCyDOoHevRG/06sUBq6nhbdCQf3lJvxwcQJhoQg4rsapM3rgol+u+TbXRiwWPbfswuLkRlvGFKtKUWMa4S57gj0CFYgSBPdTyhZTB44D7JQ2bd901Ur1dYWcDe4Kn3ZawpxL0cX2ZPlE3v8FXFJf2s8DJytL8yu73GasDzVmaGHxueWWVz7EHjh+pmB4oaAHARcY8d3LActAyl/+bcFRPYQJ68ae6DJhYYJGHIBWMImf2BifGgUX8vUFfUAYjne3D82lRyZQHs3xbl+ZxEPgWiPYRWUtxGXLLP4f9mbl+LeJdehtHNjC8kOduBL0CsP4gmugzNNUXI+Izc/9svno6kFr6SU0LA3MGrOU8ao7UCQbf/Pj/RKnG1gRmBDQqf7IMm6jOyTwdde9NpfQb32iH11PkuAKBvEtUuq9BeAKWjoZku+ycsN2jZH0hzd/QrU2c+E4+yHwX3wSxxorNOXt5EZkJbEDBlpRyE1zWoyy0wIYfcChYLvFN8QFHchlw5wmHxL+OOgdgndAtV/2DCx+NB6caY31qLictME+1GPPlQ7QvicMLgmpSWq83rs4ex/My6p3hCRSrJJiLvjEDZLYWKHHLd5tsPRAjX8ADNWB1VeIeiJrj1wpOCc1PbWpbljbbTsBmVPo6iKm/UDGAHBdQ//0j3FQg8f5w/j+McsoaMpDNHNTiLvjWERR+RBmsEA0lEL00wZz/DHlzOAYHLYYqFMT7GBCQD+Wk/l1TL+X2agUy7Irlk7QbZ4ivfdNIpSW8Ct9MGE6o4wV+nIpXURojgBBTcP85RTBLXXGrIprnK1G/VE8ONag3+nkqIyChjYyk5QMsxqOqSHsbiOxhCdXypbCbY4g9yKJtBJ/ADjxmELj0X7pqsTFqC0eRT7rk9qTBcYBBu6rwlAfq8AKjDB7WjNjzLaMi6lBoe4petBn1xcLkXD5hHra0TULxcYrq8MIb+Vk4CBZZdwwyVm/28SwSjHBIBpRysPAonDDsp3KlahwXEFvRDQR/oFww172GI7cx8SoPn93Qh0JfpTAAowsO3meR8bzUSyd7v3rmtaBPsWHE9zUXye/6nloMU5joEcD6uJaxd0kdaWWIoKLH++zHW1R776wJrS6u+TIWZgHqiIJoCd9fV25BnQcbZRKd6mnfNQkchJ6c6ozXKrFaa8DLdERdfh84+isw5mzW2zMJwHEwtKt6LUTyieC2exzPAwPxJT1+IMjuzuwiLnvGKOq+kwE/LWBSB0ZfGuCP/3jMM8OCfe7Hbpt1TfXcUxUzj6sSjkjQB6qBt+TINRdOFA=|fppguME86utsAOKrBYn6XU95q7daVbZ+3dD9OVkQlAw="; + let fingerprint_material = "a09726a0-9590-49d1-a5f5-afe300b6a515"; + + let mut client = Client::new(None); + client.set_login_method(LoginMethod::User(UserLoginMethod::Username { + client_id: "a09726a0-9590-49d1-a5f5-afe300b6a515".to_owned(), + email: "robb@stark.com".to_owned(), + kdf: Kdf::PBKDF2 { + iterations: NonZeroU32::new(600_000).unwrap(), + }, + })); + client + .initialize_user_crypto( + "asdfasdfasdf", + user_key.parse().unwrap(), + private_key.parse().unwrap(), + ) + .unwrap(); + + let fingerprint = + generate_user_fingerprint(&mut client, fingerprint_material.to_string()).unwrap(); + + assert_eq!(fingerprint, "turban-deftly-anime-chatroom-unselfish"); + } +} diff --git a/crates/bitwarden/src/platform/mod.rs b/crates/bitwarden/src/platform/mod.rs index 0e234f00b..b78cb27d4 100644 --- a/crates/bitwarden/src/platform/mod.rs +++ b/crates/bitwarden/src/platform/mod.rs @@ -1,10 +1,10 @@ +pub mod client_platform; mod domain; mod generate_fingerprint; mod get_user_api_key; mod secret_verification_request; mod sync; -pub(crate) use generate_fingerprint::generate_fingerprint; pub use generate_fingerprint::{FingerprintRequest, FingerprintResponse}; pub(crate) use get_user_api_key::get_user_api_key; pub use get_user_api_key::UserApiKeyResponse; diff --git a/languages/kotlin/doc.md b/languages/kotlin/doc.md index 9249751f0..27b376051 100644 --- a/languages/kotlin/doc.md +++ b/languages/kotlin/doc.md @@ -84,7 +84,7 @@ Test method, echoes back the input ### `satisfies_policy` -**API Draft:** Evaluate if the provided password satisfies the provided policy +Evaluate if the provided password satisfies the provided policy **Arguments**: @@ -333,6 +333,17 @@ Decrypt folder list **Output**: std::result::Result +### `username` + +**API Draft:** Generate Username + +**Arguments**: + +- self: +- settings: UsernameGeneratorRequest + +**Output**: std::result::Result + ## ClientPasswordHistory ### `encrypt` @@ -361,7 +372,7 @@ Decrypt password history ### `fingerprint` -Fingerprint +Fingerprint (public key) **Arguments**: @@ -370,6 +381,17 @@ Fingerprint **Output**: std::result::Result +### `user_fingerprint` + +Fingerprint using logged in user's public key + +**Arguments**: + +- self: +- fingerprint_material: String + +**Output**: std::result::Result + ## ClientSends ### `encrypt` @@ -800,7 +822,7 @@ implementations. id - string + string,null @@ -891,7 +913,7 @@ implementations. id - string + string,null @@ -916,7 +938,7 @@ implementations. id - string + string,null @@ -1296,12 +1318,12 @@ implementations. id - string + string,null accessId - string + string,null @@ -1386,12 +1408,12 @@ implementations. id - string + string,null accessId - string + string,null From 816799d4e6aa14cc7748f388ea839ca0ec44952b Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Fri, 5 Jan 2024 11:52:16 +0100 Subject: [PATCH 136/157] Change jwt parsing to use URL_SAFE_NO_PAD (#450) JWT should be url encoded and never use padding. --- crates/bitwarden/src/auth/jwt_token.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bitwarden/src/auth/jwt_token.rs b/crates/bitwarden/src/auth/jwt_token.rs index 17e0e13e9..f4d4692ee 100644 --- a/crates/bitwarden/src/auth/jwt_token.rs +++ b/crates/bitwarden/src/auth/jwt_token.rs @@ -1,8 +1,8 @@ use std::str::FromStr; -use base64::Engine; +use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; -use crate::{error::Result, util::BASE64_ENGINE}; +use crate::error::Result; /// A Bitwarden secrets manager JWT Token. /// @@ -31,7 +31,7 @@ impl FromStr for JWTToken { if split.len() != 3 { return Err("JWT token has an invalid number of parts".into()); } - let decoded = BASE64_ENGINE.decode(split[1])?; + let decoded = URL_SAFE_NO_PAD.decode(split[1])?; Ok(serde_json::from_slice(&decoded)?) } } From 0f4184ce6c7bb2fbaa3bab0600c7dfdaed4bc0a6 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Fri, 5 Jan 2024 15:18:29 +0100 Subject: [PATCH 137/157] Codecov ignore tools (#476) Ignore tools since they don't need to be tested. --- .github/codecov.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .github/codecov.yml diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 000000000..3228d009c --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,3 @@ +ignore: + - "crates/sdk-schemas" # Tool + - "crates/uniffi-bindgen" # Tool From fa09ad6932f64967cfad5fee10af592ca2be1266 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 09:57:35 +0100 Subject: [PATCH 138/157] [deps]: Update @types/node to v18.19.5 (#483) --- languages/js/sdk-client/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/languages/js/sdk-client/package-lock.json b/languages/js/sdk-client/package-lock.json index 76fc3e341..f1c72fa87 100644 --- a/languages/js/sdk-client/package-lock.json +++ b/languages/js/sdk-client/package-lock.json @@ -39,9 +39,9 @@ } }, "node_modules/@types/node": { - "version": "18.19.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.4.tgz", - "integrity": "sha512-xNzlUhzoHotIsnFoXmJB+yWmBvFZgKCI9TtPIEdYIMM1KWfwuY8zh7wvc1u1OAXlC7dlf6mZVx/s+Y5KfFz19A==", + "version": "18.19.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.5.tgz", + "integrity": "sha512-22MG6T02Hos2JWfa1o5jsIByn+bc5iOt1IS4xyg6OG68Bu+wMonVZzdrgCw693++rpLE9RUT/Bx15BeDzO0j+g==", "dev": true, "dependencies": { "undici-types": "~5.26.4" From bbdd18fbbdd4987da041d5d412e10418c26ef51c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 09:58:21 +0100 Subject: [PATCH 139/157] [deps]: Update Rust crate clap to 4.4.13 (#478) --- Cargo.lock | 4 ++-- crates/bitwarden-cli/Cargo.toml | 2 +- crates/bw/Cargo.toml | 2 +- crates/bws/Cargo.toml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 498aaf85b..b4aa123b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -666,9 +666,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.12" +version = "4.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfab8ba68f3668e89f6ff60f5b205cea56aa7b769451a59f34b8682f51c056d" +checksum = "52bdc885e4cacc7f7c9eedc1ef6da641603180c783c41a15c264944deeaab642" dependencies = [ "clap_builder", "clap_derive", diff --git a/crates/bitwarden-cli/Cargo.toml b/crates/bitwarden-cli/Cargo.toml index 08381a47c..217ac88c1 100644 --- a/crates/bitwarden-cli/Cargo.toml +++ b/crates/bitwarden-cli/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" rust-version = "1.57" [dependencies] -clap = { version = "4.4.12", features = ["derive"] } +clap = { version = "4.4.13", features = ["derive"] } color-eyre = "0.6" inquire = "0.6.2" supports-color = "2.1.0" diff --git a/crates/bw/Cargo.toml b/crates/bw/Cargo.toml index 059b853a5..d1298cf43 100644 --- a/crates/bw/Cargo.toml +++ b/crates/bw/Cargo.toml @@ -13,7 +13,7 @@ Bitwarden Password Manager CLI keywords = ["bitwarden", "password-manager", "cli"] [dependencies] -clap = { version = "4.4.12", features = ["derive", "env"] } +clap = { version = "4.4.13", features = ["derive", "env"] } color-eyre = "0.6" env_logger = "0.10.1" inquire = "0.6.2" diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 90c2cdb05..81994ad1f 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -20,7 +20,7 @@ chrono = { version = "0.4.31", features = [ "clock", "std", ], default-features = false } -clap = { version = "4.4.12", features = ["derive", "env", "string"] } +clap = { version = "4.4.13", features = ["derive", "env", "string"] } clap_complete = "4.4.6" color-eyre = "0.6" comfy-table = "^7.1.0" From abaf067cfff78478ea431108e62f13cd98cf481a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 10:00:13 +0100 Subject: [PATCH 140/157] [deps]: Update quicktype-core to v23.0.81 (#482) --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3aa3c5092..b2bda48e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@openapitools/openapi-generator-cli": "2.7.0", "handlebars": "^4.7.8", "prettier": "3.1.1", - "quicktype-core": "23.0.80", + "quicktype-core": "23.0.81", "rimraf": "5.0.5", "ts-node": "10.9.2", "typescript": "5.3.3" @@ -1494,9 +1494,9 @@ } }, "node_modules/quicktype-core": { - "version": "23.0.80", - "resolved": "https://registry.npmjs.org/quicktype-core/-/quicktype-core-23.0.80.tgz", - "integrity": "sha512-dd+aJRzAl3MzkaXJMjUu0j60y82gwX/RRr3EvW/aScQKycvkgwliNDN2tIiLB06EKBzjgC9mtlMqKyRg2rYKhQ==", + "version": "23.0.81", + "resolved": "https://registry.npmjs.org/quicktype-core/-/quicktype-core-23.0.81.tgz", + "integrity": "sha512-iJQpCEzSQIkffJPS5NC+0w+Rq9faGgz09L+WIbseu1toFfj+M/3KTG5jhzdY/uN88fWosAom2fMoEADA403+rQ==", "dev": true, "dependencies": { "@glideapps/ts-necessities": "2.1.3", diff --git a/package.json b/package.json index 683635f01..319086900 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "@openapitools/openapi-generator-cli": "2.7.0", "handlebars": "^4.7.8", "prettier": "3.1.1", - "quicktype-core": "23.0.80", + "quicktype-core": "23.0.81", "rimraf": "5.0.5", "ts-node": "10.9.2", "typescript": "5.3.3" From 3b220c5ef8a34bee5216e1e1e55fda2e70f309cf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 09:01:00 +0000 Subject: [PATCH 141/157] [deps]: Update Rust crate serde to v1.0.195 (#479) --- Cargo.lock | 8 ++++---- crates/bitwarden-wasm/Cargo.toml | 2 +- crates/bws/Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b4aa123b6..99c81ecb6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2793,18 +2793,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.193" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" dependencies = [ "proc-macro2", "quote", diff --git a/crates/bitwarden-wasm/Cargo.toml b/crates/bitwarden-wasm/Cargo.toml index 176edeaa9..4a4d7696c 100644 --- a/crates/bitwarden-wasm/Cargo.toml +++ b/crates/bitwarden-wasm/Cargo.toml @@ -12,7 +12,7 @@ console_error_panic_hook = "0.1.7" console_log = { version = "1.0.0", features = ["color"] } js-sys = "0.3.66" log = "0.4.20" -serde = { version = "1.0.193", features = ["derive"] } +serde = { version = "1.0.195", features = ["derive"] } wasm-bindgen = { version = "0.2.89", features = ["serde-serialize"] } wasm-bindgen-futures = "0.4.39" diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 81994ad1f..9466398e1 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -31,7 +31,7 @@ regex = { version = "1.10.2", features = [ "std", "perf", ], default-features = false } -serde = "^1.0.193" +serde = "^1.0.195" serde_json = "^1.0.108" serde_yaml = "0.9" supports-color = "2.1.0" From 2b561306c5a2070fc3722d8f415997828ee5873d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 10:08:32 +0100 Subject: [PATCH 142/157] [deps]: Update Rust crate serde_json to v1.0.111 (#480) --- Cargo.lock | 4 ++-- crates/bws/Cargo.toml | 2 +- crates/sdk-schemas/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 99c81ecb6..c53cd9086 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2824,9 +2824,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" dependencies = [ "itoa", "ryu", diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 9466398e1..821c6153c 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -32,7 +32,7 @@ regex = { version = "1.10.2", features = [ "perf", ], default-features = false } serde = "^1.0.195" -serde_json = "^1.0.108" +serde_json = "^1.0.111" serde_yaml = "0.9" supports-color = "2.1.0" thiserror = "1.0.56" diff --git a/crates/sdk-schemas/Cargo.toml b/crates/sdk-schemas/Cargo.toml index 7ae1874b4..05c52bffa 100644 --- a/crates/sdk-schemas/Cargo.toml +++ b/crates/sdk-schemas/Cargo.toml @@ -15,7 +15,7 @@ internal = [ anyhow = "1.0.79" itertools = "0.12.0" schemars = { version = "0.8.16", features = ["preserve_order"] } -serde_json = "1.0.108" +serde_json = "1.0.111" bitwarden = { path = "../bitwarden" } bitwarden-json = { path = "../bitwarden-json" } From e96a79faa628ae2e191228b584918f09c3545160 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 10:19:42 +0100 Subject: [PATCH 143/157] [deps]: Update pyo3 non-major to 0.20.2 (#481) --- Cargo.lock | 20 ++++++++++---------- crates/bitwarden-py/Cargo.toml | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c53cd9086..ebf999c2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2304,9 +2304,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.20.1" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82ad98ce1991c9c70c3464ba4187337b9c45fcbbb060d46dca15f0c075e14e2" +checksum = "9a89dc7a5850d0e983be1ec2a463a171d20990487c3cfcd68b5363f1ee3d6fe0" dependencies = [ "cfg-if", "indoc", @@ -2346,9 +2346,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.20.1" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5503d0b3aee2c7a8dbb389cd87cd9649f675d4c7f60ca33699a3e3859d81a891" +checksum = "07426f0d8fe5a601f26293f300afd1a7b1ed5e78b2a705870c5f30893c5163be" dependencies = [ "once_cell", "target-lexicon", @@ -2356,9 +2356,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.20.1" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a79e8d80486a00d11c0dcb27cd2aa17c022cc95c677b461f01797226ba8f41" +checksum = "dbb7dec17e17766b46bca4f1a4215a85006b4c2ecde122076c562dd058da6cf1" dependencies = [ "libc", "pyo3-build-config", @@ -2377,9 +2377,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.20.1" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4b0dc7eaa578604fab11c8c7ff8934c71249c61d4def8e272c76ed879f03d4" +checksum = "05f738b4e40d50b5711957f142878cfa0f28e054aa0ebdfc3fd137a843f74ed3" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -2389,9 +2389,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.20.1" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816a4f709e29ddab2e3cdfe94600d554c5556cad0ddfeea95c47b580c3247fa4" +checksum = "0fc910d4851847827daf9d6cdd4a823fbdaab5b8818325c5e97a86da79e8881f" dependencies = [ "heck", "proc-macro2", diff --git a/crates/bitwarden-py/Cargo.toml b/crates/bitwarden-py/Cargo.toml index 1d52d0b2b..ed7c5df5e 100644 --- a/crates/bitwarden-py/Cargo.toml +++ b/crates/bitwarden-py/Cargo.toml @@ -9,13 +9,13 @@ name = "bitwarden_py" crate-type = ["cdylib"] [dependencies] -pyo3 = { version = "0.20.1", features = ["extension-module"] } +pyo3 = { version = "0.20.2", features = ["extension-module"] } pyo3-log = "0.9.0" bitwarden-json = { path = "../bitwarden-json", features = ["secrets"] } [build-dependencies] -pyo3-build-config = { version = "0.20.1" } +pyo3-build-config = { version = "0.20.2" } [target.'cfg(not(target_arch="wasm32"))'.dependencies] tokio = { version = "1.35.1", features = ["rt-multi-thread", "macros"] } From f9986134a144cc55db7d2188e754d93250ea8246 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 8 Jan 2024 10:20:20 +0100 Subject: [PATCH 144/157] Update PULL_REQUEST_TEMPLATE.md (#475) Remove screenshot section. It's never used. --- .github/PULL_REQUEST_TEMPLATE.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e195cfd64..de0ba909c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -21,10 +21,6 @@ - **file.ext:** Description of what was changed and why -## Screenshots - - - ## Before you submit -- Please add **unit tests** where it makes sense to do so (encouraged but not required) +- Please add **unit tests** where it makes sense to do so From c45ad21e187ff59350ad560ba66c2b4dcbf2b093 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 8 Jan 2024 10:46:54 +0100 Subject: [PATCH 145/157] Convert totp secret to uppercase (#485) Some totp providers use lowercase secrets. This isn't valid base32 though and needs to be transformed into uppercase prior to parsing. --- crates/bitwarden/src/vault/totp.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/crates/bitwarden/src/vault/totp.rs b/crates/bitwarden/src/vault/totp.rs index 4adbe4dac..9bc92229b 100644 --- a/crates/bitwarden/src/vault/totp.rs +++ b/crates/bitwarden/src/vault/totp.rs @@ -129,7 +129,7 @@ impl FromStr for Totp { fn from_str(key: &str) -> Result { fn decode_secret(secret: &str) -> Result> { BASE32 - .decode(secret.as_bytes()) + .decode(secret.to_uppercase().as_bytes()) .map_err(|_| "Unable to decode secret".into()) } @@ -230,6 +230,20 @@ mod tests { assert_eq!(response.period, 30); } + #[test] + fn test_lowercase_secret() { + let key = "wqiq25brkzycjvyp".to_string(); + let time = Some( + DateTime::parse_from_rfc3339("2023-01-01T00:00:00.000Z") + .unwrap() + .with_timezone(&Utc), + ); + let response = generate_totp(key, time).unwrap(); + + assert_eq!(response.code, "194506".to_string()); + assert_eq!(response.period, 30); + } + #[test] fn test_generate_otpauth() { let key = "otpauth://totp/test-account?secret=WQIQ25BRKZYCJVYP".to_string(); From bd592bd7ac5f4cdbfa85196d80ff2f7fcaec9080 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Mon, 8 Jan 2024 12:38:53 +0100 Subject: [PATCH 146/157] Fix simplelogin auth (#486) ## Type of change ``` - [x] Bug fix - [ ] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Simplelogin was using the bearer Authorization header while it was supposed to use non-standard Authentication --- .../src/tool/generators/username_forwarders/simplelogin.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bitwarden/src/tool/generators/username_forwarders/simplelogin.rs b/crates/bitwarden/src/tool/generators/username_forwarders/simplelogin.rs index 3c09fc691..6c4f9dab4 100644 --- a/crates/bitwarden/src/tool/generators/username_forwarders/simplelogin.rs +++ b/crates/bitwarden/src/tool/generators/username_forwarders/simplelogin.rs @@ -31,7 +31,7 @@ async fn generate_with_api_url( let response = http .post(format!("{api_url}/api/alias/random/new{query}")) .header(CONTENT_TYPE, "application/json") - .bearer_auth(api_key) + .header("Authentication", api_key) .json(&Request { note }) .send() .await?; @@ -65,7 +65,7 @@ mod tests { .and(matchers::method("POST")) .and(matchers::query_param("hostname", "example.com")) .and(matchers::header("Content-Type", "application/json")) - .and(matchers::header("Authorization", "Bearer MY_TOKEN")) + .and(matchers::header("Authentication", "MY_TOKEN")) .and(matchers::body_json(json!({ "note": "Website: example.com. Generated by Bitwarden." }))) @@ -78,7 +78,7 @@ mod tests { .and(matchers::method("POST")) .and(matchers::query_param("hostname", "example.com")) .and(matchers::header("Content-Type", "application/json")) - .and(matchers::header("Authorization", "Bearer MY_FAKE_TOKEN")) + .and(matchers::header("Authentication", "MY_FAKE_TOKEN")) .and(matchers::body_json(json!({ "note": "Website: example.com. Generated by Bitwarden." }))) From fa4c2f7582a548a17a64085879c86ec3c103d592 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 8 Jan 2024 12:55:18 +0100 Subject: [PATCH 147/157] Fix SendView.key behaviour and fix types for SendFile and SendFileView (#484) --- crates/bitwarden/src/error.rs | 2 + crates/bitwarden/src/vault/send.rs | 181 ++++++++++++++++++++++++----- 2 files changed, 153 insertions(+), 30 deletions(-) diff --git a/crates/bitwarden/src/error.rs b/crates/bitwarden/src/error.rs index bb70adebf..48cb7da16 100644 --- a/crates/bitwarden/src/error.rs +++ b/crates/bitwarden/src/error.rs @@ -100,6 +100,8 @@ pub enum CryptoError { NoKeyForOrg, #[error("The value is not a valid UTF8 String")] InvalidUtf8String, + #[error("Missing key")] + MissingKey, } #[derive(Debug, Error)] diff --git a/crates/bitwarden/src/vault/send.rs b/crates/bitwarden/src/vault/send.rs index e732170c5..e0699ef9c 100644 --- a/crates/bitwarden/src/vault/send.rs +++ b/crates/bitwarden/src/vault/send.rs @@ -1,3 +1,4 @@ +use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; use bitwarden_api_api::models::{SendFileModel, SendResponseModel, SendTextModel}; use chrono::{DateTime, Utc}; use schemars::JsonSchema; @@ -10,29 +11,29 @@ use crate::{ derive_shareable_key, EncString, KeyDecryptable, KeyEncryptable, LocateKey, SymmetricCryptoKey, }, - error::{Error, Result}, + error::{CryptoError, Error, Result}, }; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct SendFile { - pub id: String, + pub id: Option, pub file_name: EncString, - pub size: String, + pub size: Option, /// Readable size, ex: "4.2 KB" or "1.43 GB" - pub size_name: String, + pub size_name: Option, } -#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[derive(Serialize, Deserialize, Debug, JsonSchema, PartialEq, Clone)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct SendFileView { - pub id: String, + pub id: Option, pub file_name: String, - pub size: String, + pub size: Option, /// Readable size, ex: "4.2 KB" or "1.43 GB" - pub size_name: String, + pub size_name: Option, } #[derive(Serialize, Deserialize, Debug, JsonSchema)] @@ -43,7 +44,7 @@ pub struct SendText { pub hidden: bool, } -#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[derive(Serialize, Deserialize, Debug, JsonSchema, PartialEq, Clone)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct SendTextView { @@ -51,7 +52,7 @@ pub struct SendTextView { pub hidden: bool, } -#[derive(Clone, Copy, Serialize_repr, Deserialize_repr, Debug, JsonSchema)] +#[derive(Clone, Copy, Serialize_repr, Deserialize_repr, Debug, JsonSchema, PartialEq)] #[repr(u8)] #[cfg_attr(feature = "mobile", derive(uniffi::Enum))] pub enum SendType { @@ -85,7 +86,7 @@ pub struct Send { pub expiration_date: Option>, } -#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[derive(Serialize, Deserialize, Debug, JsonSchema, PartialEq, Clone)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct SendView { @@ -94,7 +95,8 @@ pub struct SendView { pub name: String, pub notes: Option, - pub key: EncString, + /// Base64 encoded key + pub key: Option, pub password: Option, pub r#type: SendType, @@ -134,8 +136,12 @@ impl Send { enc_key: &SymmetricCryptoKey, ) -> Result { let key: Vec = send_key.decrypt_with_key(enc_key)?; - let key = derive_shareable_key(key.try_into().unwrap(), "send", Some("send")); - Ok(key) + Self::derive_shareable_key(&key) + } + + fn derive_shareable_key(key: &[u8]) -> Result { + let key = key.try_into().map_err(|_| CryptoError::InvalidKeyLen)?; + Ok(derive_shareable_key(key, "send", Some("send"))) } } @@ -184,7 +190,8 @@ impl KeyDecryptable for Send { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { // For sends, we first decrypt the send key with the user key, and stretch it to it's full size // For the rest of the fields, we ignore the provided SymmetricCryptoKey and the stretched key - let key = Send::get_key(&self.key, key)?; + let k: Vec = self.key.decrypt_with_key(key)?; + let key = Send::derive_shareable_key(&k)?; Ok(SendView { id: self.id, @@ -192,7 +199,7 @@ impl KeyDecryptable for Send { name: self.name.decrypt_with_key(&key)?, notes: self.notes.decrypt_with_key(&key)?, - key: self.key.clone(), + key: Some(URL_SAFE_NO_PAD.encode(k)), password: self.password.clone(), r#type: self.r#type, @@ -238,20 +245,23 @@ impl KeyEncryptable for SendView { fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { // For sends, we first decrypt the send key with the user key, and stretch it to it's full size // For the rest of the fields, we ignore the provided SymmetricCryptoKey and the stretched key - let key = Send::get_key(&self.key, key)?; + let k = URL_SAFE_NO_PAD + .decode(self.key.ok_or(CryptoError::MissingKey)?) + .map_err(|_| CryptoError::InvalidKey)?; + let send_key = Send::derive_shareable_key(&k)?; Ok(Send { id: self.id, access_id: self.access_id, - name: self.name.encrypt_with_key(&key)?, - notes: self.notes.encrypt_with_key(&key)?, - key: self.key.clone(), + name: self.name.encrypt_with_key(&send_key)?, + notes: self.notes.encrypt_with_key(&send_key)?, + key: k.encrypt_with_key(key)?, password: self.password.clone(), r#type: self.r#type, - file: self.file.encrypt_with_key(&key)?, - text: self.text.encrypt_with_key(&key)?, + file: self.file.encrypt_with_key(&send_key)?, + text: self.text.encrypt_with_key(&send_key)?, max_access_count: self.max_access_count, access_count: self.access_count, @@ -304,10 +314,10 @@ impl TryFrom for SendFile { fn try_from(file: SendFileModel) -> Result { Ok(SendFile { - id: file.id.ok_or(Error::MissingFields)?, + id: file.id, file_name: file.file_name.ok_or(Error::MissingFields)?.parse()?, - size: file.size.ok_or(Error::MissingFields)?.to_string(), - size_name: file.size_name.ok_or(Error::MissingFields)?, + size: file.size.map(|v| v.to_string()), + size_name: file.size_name, }) } } @@ -325,8 +335,12 @@ impl TryFrom for SendText { #[cfg(test)] mod tests { - use super::Send; - use crate::client::{encryption_settings::EncryptionSettings, kdf::Kdf, UserLoginMethod}; + use super::{Send, SendText, SendTextView, SendType}; + use crate::{ + client::{encryption_settings::EncryptionSettings, kdf::Kdf, UserLoginMethod}, + crypto::{KeyDecryptable, KeyEncryptable}, + vault::SendView, + }; #[test] fn test_get_send_key() { @@ -360,12 +374,12 @@ mod tests { password: None, r#type: super::SendType::File, file: Some(super::SendFile { - id: "7f129hzwu0umkmnmsghkt486w96p749c".into(), + id: Some("7f129hzwu0umkmnmsghkt486w96p749c".into()), file_name: "2.pnszM3slsCVlOIzuWrfCpA==|85zCg+X8GODvIAPf1Yt3K75YP+ub3wVAi1UvwOVXhPgUo9Gsu23FJgYSOkyKu3Vr|OvTrOugwRH7Mp2BWSuPlfxovoWt9oDRdi1Qo3xHUcdQ=" .parse() .unwrap(), - size: "1251825".into(), - size_name: "1.19 MB".into(), + size: Some("1251825".into()), + size_name: Some("1.19 MB".into()), }), text: None, max_access_count: None, @@ -382,4 +396,111 @@ mod tests { let send_key_b64 = send_key.to_base64(); assert_eq!(send_key_b64, "IR9ImHGm6rRuIjiN7csj94bcZR5WYTJj5GtNfx33zm6tJCHUl+QZlpNPba8g2yn70KnOHsAODLcR0um6E3MAlg=="); } + + fn build_encryption_settings() -> EncryptionSettings { + EncryptionSettings::new( + &UserLoginMethod::Username { + client_id: "test".into(), + email: "test@bitwarden.com".into(), + kdf: Kdf::PBKDF2 { + iterations: 600_000.try_into().unwrap(), + }, + }, + "asdfasdfasdf", + "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(), + "2.yN7l00BOlUE0Sb0M//Q53w==|EwKG/BduQRQ33Izqc/ogoBROIoI5dmgrxSo82sgzgAMIBt3A2FZ9vPRMY+GWT85JiqytDitGR3TqwnFUBhKUpRRAq4x7rA6A1arHrFp5Tp1p21O3SfjtvB3quiOKbqWk6ZaU1Np9HwqwAecddFcB0YyBEiRX3VwF2pgpAdiPbSMuvo2qIgyob0CUoC/h4Bz1be7Qa7B0Xw9/fMKkB1LpOm925lzqosyMQM62YpMGkjMsbZz0uPopu32fxzDWSPr+kekNNyLt9InGhTpxLmq1go/pXR2uw5dfpXc5yuta7DB0EGBwnQ8Vl5HPdDooqOTD9I1jE0mRyuBpWTTI3FRnu3JUh3rIyGBJhUmHqGZvw2CKdqHCIrQeQkkEYqOeJRJVdBjhv5KGJifqT3BFRwX/YFJIChAQpebNQKXe/0kPivWokHWwXlDB7S7mBZzhaAPidZvnuIhalE2qmTypDwHy22FyqV58T8MGGMchcASDi/QXI6kcdpJzPXSeU9o+NC68QDlOIrMVxKFeE7w7PvVmAaxEo0YwmuAzzKy9QpdlK0aab/xEi8V4iXj4hGepqAvHkXIQd+r3FNeiLfllkb61p6WTjr5urcmDQMR94/wYoilpG5OlybHdbhsYHvIzYoLrC7fzl630gcO6t4nM24vdB6Ymg9BVpEgKRAxSbE62Tqacxqnz9AcmgItb48NiR/He3n3ydGjPYuKk/ihZMgEwAEZvSlNxYONSbYrIGDtOY+8Nbt6KiH3l06wjZW8tcmFeVlWv+tWotnTY9IqlAfvNVTjtsobqtQnvsiDjdEVtNy/s2ci5TH+NdZluca2OVEr91Wayxh70kpM6ib4UGbfdmGgCo74gtKvKSJU0rTHakQ5L9JlaSDD5FamBRyI0qfL43Ad9qOUZ8DaffDCyuaVyuqk7cz9HwmEmvWU3VQ+5t06n/5kRDXttcw8w+3qClEEdGo1KeENcnXCB32dQe3tDTFpuAIMLqwXs6FhpawfZ5kPYvLPczGWaqftIs/RXJ/EltGc0ugw2dmTLpoQhCqrcKEBDoYVk0LDZKsnzitOGdi9mOWse7Se8798ib1UsHFUjGzISEt6upestxOeupSTOh0v4+AjXbDzRUyogHww3V+Bqg71bkcMxtB+WM+pn1XNbVTyl9NR040nhP7KEf6e9ruXAtmrBC2ah5cFEpLIot77VFZ9ilLuitSz+7T8n1yAh1IEG6xxXxninAZIzi2qGbH69O5RSpOJuJTv17zTLJQIIc781JwQ2TTwTGnx5wZLbffhCasowJKd2EVcyMJyhz6ru0PvXWJ4hUdkARJs3Xu8dus9a86N8Xk6aAPzBDqzYb1vyFIfBxP0oO8xFHgd30Cgmz8UrSE3qeWRrF8ftrI6xQnFjHBGWD/JWSvd6YMcQED0aVuQkuNW9ST/DzQThPzRfPUoiL10yAmV7Ytu4fR3x2sF0Yfi87YhHFuCMpV/DsqxmUizyiJuD938eRcH8hzR/VO53Qo3UIsqOLcyXtTv6THjSlTopQ+JOLOnHm1w8dzYbLN44OG44rRsbihMUQp+wUZ6bsI8rrOnm9WErzkbQFbrfAINdoCiNa6cimYIjvvnMTaFWNymqY1vZxGztQiMiHiHYwTfwHTXrb9j0uPM=|09J28iXv9oWzYtzK2LBT6Yht4IT4MijEkk0fwFdrVQ4=".parse().unwrap(), + ).unwrap() + } + + #[test] + pub fn test_decrypt() { + let enc = build_encryption_settings(); + let key = enc.get_key(&None).unwrap(); + + let send = Send { + id: "3d80dd72-2d14-4f26-812c-b0f0018aa144".parse().ok(), + access_id: Some("ct2APRQtJk-BLLDwAYqhRA".to_owned()), + r#type: SendType::Text, + name: "2.STIyTrfDZN/JXNDN9zNEMw==|NDLum8BHZpPNYhJo9ggSkg==|UCsCLlBO3QzdPwvMAWs2VVwuE6xwOx/vxOooPObqnEw=".parse() + .unwrap(), + notes: None, + file: None, + text: Some(SendText { + text: "2.2VPyLzk1tMLug0X3x7RkaQ==|mrMt9vbZsCJhJIj4eebKyg==|aZ7JeyndytEMR1+uEBupEvaZuUE69D/ejhfdJL8oKq0=".parse().ok(), + hidden: false, + }), + key: "2.KLv/j0V4Ebs0dwyPdtt4vw==|jcrFuNYN1Qb3onBlwvtxUV/KpdnR1LPRL4EsCoXNAt4=|gHSywGy4Rj/RsCIZFwze4s2AACYKBtqDXTrQXjkgtIE=".parse().unwrap(), + max_access_count: None, + access_count: 0, + password: None, + disabled: false, + revision_date: "2024-01-07T23:56:48.207363Z".parse().unwrap(), + expiration_date: None, + deletion_date: "2024-01-14T23:56:48Z".parse().unwrap(), + hide_email: false, + }; + + let view: SendView = send.decrypt_with_key(key).unwrap(); + + let expected = SendView { + id: "3d80dd72-2d14-4f26-812c-b0f0018aa144".parse().ok(), + access_id: Some("ct2APRQtJk-BLLDwAYqhRA".to_owned()), + name: "Test".to_string(), + notes: None, + key: Some("Pgui0FK85cNhBGWHAlBHBw".to_owned()), + password: None, + r#type: SendType::Text, + file: None, + text: Some(SendTextView { + text: Some("This is a test".to_owned()), + hidden: false, + }), + max_access_count: None, + access_count: 0, + disabled: false, + hide_email: false, + revision_date: "2024-01-07T23:56:48.207363Z".parse().unwrap(), + deletion_date: "2024-01-14T23:56:48Z".parse().unwrap(), + expiration_date: None, + }; + + assert_eq!(view, expected); + } + + #[test] + pub fn test_encrypt() { + let enc = build_encryption_settings(); + let key = enc.get_key(&None).unwrap(); + + let view = SendView { + id: "3d80dd72-2d14-4f26-812c-b0f0018aa144".parse().ok(), + access_id: Some("ct2APRQtJk-BLLDwAYqhRA".to_owned()), + name: "Test".to_string(), + notes: None, + key: Some("Pgui0FK85cNhBGWHAlBHBw".to_owned()), + password: None, + r#type: SendType::Text, + file: None, + text: Some(SendTextView { + text: Some("This is a test".to_owned()), + hidden: false, + }), + max_access_count: None, + access_count: 0, + disabled: false, + hide_email: false, + revision_date: "2024-01-07T23:56:48.207363Z".parse().unwrap(), + deletion_date: "2024-01-14T23:56:48Z".parse().unwrap(), + expiration_date: None, + }; + + // Re-encrypt and decrypt again to ensure encrypt works + let v: SendView = view + .clone() + .encrypt_with_key(key) + .unwrap() + .decrypt_with_key(key) + .unwrap(); + assert_eq!(v, view); + } } From 332293bade95676ab7434548b872e887d080d8c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Mon, 8 Jan 2024 13:13:04 +0100 Subject: [PATCH 148/157] Fix firefox forwarder when website isn't set (#487) ## Type of change ``` - [x] Bug fix - [ ] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective When the website parameter is None, the request would include a `generated_for: null` field, while the API expects the field to not be present. --- .../generators/username_forwarders/firefox.rs | 81 ++++++++++++++----- 1 file changed, 60 insertions(+), 21 deletions(-) diff --git a/crates/bitwarden/src/tool/generators/username_forwarders/firefox.rs b/crates/bitwarden/src/tool/generators/username_forwarders/firefox.rs index 2c45803a5..e53931358 100644 --- a/crates/bitwarden/src/tool/generators/username_forwarders/firefox.rs +++ b/crates/bitwarden/src/tool/generators/username_forwarders/firefox.rs @@ -22,6 +22,7 @@ async fn generate_with_api_url( #[derive(serde::Serialize)] struct Request { enabled: bool, + #[serde(skip_serializing_if = "Option::is_none")] generated_for: Option, description: String, } @@ -58,13 +59,13 @@ async fn generate_with_api_url( #[cfg(test)] mod tests { use serde_json::json; + #[tokio::test] - async fn test_mock_server() { + async fn test_mock_success() { use wiremock::{matchers, Mock, ResponseTemplate}; - let (server, _client) = crate::util::start_mock(vec![ - // Mock the request to the Firefox API, and verify that the correct request is made - Mock::given(matchers::path("/api/v1/relayaddresses/")) + let (server, _client) = + crate::util::start_mock(vec![Mock::given(matchers::path("/api/v1/relayaddresses/")) .and(matchers::method("POST")) .and(matchers::header("Content-Type", "application/json")) .and(matchers::header("Authorization", "Token MY_TOKEN")) @@ -76,33 +77,73 @@ mod tests { .respond_with(ResponseTemplate::new(201).set_body_json(json!({ "full_address": "ofuj4d4qw@mozmail.com" }))) - .expect(1), - // Mock an invalid API key - Mock::given(matchers::path("/api/v1/relayaddresses/")) + .expect(1)]) + .await; + + let address = super::generate_with_api_url( + &reqwest::Client::new(), + "MY_TOKEN".into(), + Some("example.com".into()), + format!("http://{}", server.address()), + ) + .await + .unwrap(); + assert_eq!(address, "ofuj4d4qw@mozmail.com"); + + server.verify().await; + } + + #[tokio::test] + async fn test_mock_without_website() { + use wiremock::{matchers, Mock, ResponseTemplate}; + + let (server, _client) = + crate::util::start_mock(vec![Mock::given(matchers::path("/api/v1/relayaddresses/")) .and(matchers::method("POST")) .and(matchers::header("Content-Type", "application/json")) - .and(matchers::header("Authorization", "Token MY_FAKE_TOKEN")) + .and(matchers::header("Authorization", "Token MY_OTHER_TOKEN")) .and(matchers::body_json(json!({ "enabled": true, - "generated_for": "example.com", - "description": "example.com - Generated by Bitwarden." + "description": "Generated by Bitwarden." }))) - .respond_with(ResponseTemplate::new(401)) - .expect(1), - ]) - .await; + .respond_with(ResponseTemplate::new(201).set_body_json(json!({ + "full_address": "856f7765@mozmail.com" + }))) + .expect(1)]) + .await; let address = super::generate_with_api_url( &reqwest::Client::new(), - "MY_TOKEN".into(), - Some("example.com".into()), + "MY_OTHER_TOKEN".into(), + None, format!("http://{}", server.address()), ) .await .unwrap(); - assert_eq!(address, "ofuj4d4qw@mozmail.com"); + assert_eq!(address, "856f7765@mozmail.com"); + + server.verify().await; + } + + #[tokio::test] + async fn test_mock_invalid_token() { + use wiremock::{matchers, Mock, ResponseTemplate}; + + let (server, _client) = + crate::util::start_mock(vec![Mock::given(matchers::path("/api/v1/relayaddresses/")) + .and(matchers::method("POST")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header("Authorization", "Token MY_FAKE_TOKEN")) + .and(matchers::body_json(json!({ + "enabled": true, + "generated_for": "example.com", + "description": "example.com - Generated by Bitwarden." + }))) + .respond_with(ResponseTemplate::new(401)) + .expect(1)]) + .await; - let fake_token_error = super::generate_with_api_url( + let error = super::generate_with_api_url( &reqwest::Client::new(), "MY_FAKE_TOKEN".into(), Some("example.com".into()), @@ -111,9 +152,7 @@ mod tests { .await .unwrap_err(); - assert!(fake_token_error - .to_string() - .contains("Invalid Firefox Relay API key")); + assert!(error.to_string().contains("Invalid Firefox Relay API key")); server.verify().await; } From 719f7e82a9b1ae2c90e8b1e9561c2c809f196826 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 09:37:20 -0500 Subject: [PATCH 149/157] [deps]: Update gh minor (#463) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-android.yml | 4 ++-- .github/workflows/build-cli.yml | 4 ++-- .github/workflows/build-dotnet.yml | 10 +++++----- .github/workflows/build-java.yml | 12 ++++++------ .github/workflows/build-napi.yml | 2 +- .github/workflows/publish-dotnet.yml | 10 +++++----- .github/workflows/publish-php.yml | 8 ++++---- .github/workflows/publish-ruby.yml | 12 ++++++------ .github/workflows/version-bump.yml | 2 +- 9 files changed, 32 insertions(+), 32 deletions(-) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 784beb168..f8bee2f56 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -86,7 +86,7 @@ jobs: java-version: 17 - name: Download Artifacts - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 - name: Move artifacts working-directory: languages/kotlin/sdk/src/main/jniLibs @@ -102,7 +102,7 @@ jobs: run: ./build-schemas.sh - name: Publish - uses: gradle/gradle-build-action@8cbcb9948b5892387aed077daf6f90e1f0ba5b27 # v2.11.0 + uses: gradle/gradle-build-action@982da8e78c05368c70dac0351bb82647a9e9a5d2 # v2.11.1 with: arguments: sdk:publish build-root-directory: languages/kotlin diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index 4732a1936..b0f3e7b63 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -118,12 +118,12 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Download x86_64-apple-darwin artifact - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: bws-x86_64-apple-darwin-${{ env._PACKAGE_VERSION }}.zip - name: Download aarch64-apple-darwin artifact - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: bws-aarch64-apple-darwin-${{ env._PACKAGE_VERSION }}.zip diff --git a/.github/workflows/build-dotnet.yml b/.github/workflows/build-dotnet.yml index a7cad42c0..904cea204 100644 --- a/.github/workflows/build-dotnet.yml +++ b/.github/workflows/build-dotnet.yml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Download C# schemas artifact - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: schemas.cs path: languages/csharp/Bitwarden.Sdk @@ -35,25 +35,25 @@ jobs: global-json-file: languages/csharp/global.json - name: Download x86_64-apple-darwin files - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: libbitwarden_c_files-x86_64-apple-darwin path: languages/csharp/Bitwarden.Sdk/macos-x64 - name: Download aarch64-apple-darwin files - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: libbitwarden_c_files-aarch64-apple-darwin path: languages/csharp/Bitwarden.Sdk/macos-arm64 - name: Download x86_64-unknown-linux-gnu files - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: libbitwarden_c_files-x86_64-unknown-linux-gnu path: languages/csharp/Bitwarden.Sdk/ubuntu-x64 - name: Download x86_64-pc-windows-msvc files - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: libbitwarden_c_files-x86_64-pc-windows-msvc path: languages/csharp/Bitwarden.Sdk/windows-x64 diff --git a/.github/workflows/build-java.yml b/.github/workflows/build-java.yml index a2c0938ec..d2bc049d1 100644 --- a/.github/workflows/build-java.yml +++ b/.github/workflows/build-java.yml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Download Java schemas artifact - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: sdk-schemas-java path: languages/java/src/main/java/bit/sdk/schema/ @@ -36,31 +36,31 @@ jobs: java-version: 17 - name: Download x86_64-apple-darwin files - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: libbitwarden_c_files-x86_64-apple-darwin path: languages/java/src/main/resources/darwin-x64 - name: Download aarch64-apple-darwin files - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: libbitwarden_c_files-aarch64-apple-darwin path: languages/java/src/main/resources/darwin-aarch64 - name: Download x86_64-unknown-linux-gnu files - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: libbitwarden_c_files-x86_64-unknown-linux-gnu path: languages/java/src/main/resources/ubuntu-x64 - name: Download x86_64-pc-windows-msvc files - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: libbitwarden_c_files-x86_64-pc-windows-msvc path: languages/java/src/main/resources/windows-x64 - name: Publish Maven - uses: gradle/gradle-build-action@8cbcb9948b5892387aed077daf6f90e1f0ba5b27 # v2.11.0 + uses: gradle/gradle-build-action@982da8e78c05368c70dac0351bb82647a9e9a5d2 # v2.11.1 with: arguments: publish build-root-directory: languages/java diff --git a/.github/workflows/build-napi.yml b/.github/workflows/build-napi.yml index 39674c2ea..ef72e7f1e 100644 --- a/.github/workflows/build-napi.yml +++ b/.github/workflows/build-napi.yml @@ -72,7 +72,7 @@ jobs: key: ${{ matrix.settings.target }}-cargo-${{ matrix.settings.os }} - name: Retrieve schemas - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: schemas.ts path: ${{ github.workspace }}/crates/bitwarden-napi/src-ts/bitwarden_client/ diff --git a/.github/workflows/publish-dotnet.yml b/.github/workflows/publish-dotnet.yml index e9b1076fe..a7a6a6e0e 100644 --- a/.github/workflows/publish-dotnet.yml +++ b/.github/workflows/publish-dotnet.yml @@ -26,7 +26,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Download C# schemas artifact - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: schemas.cs path: languages/csharp/Bitwarden.Sdk @@ -37,25 +37,25 @@ jobs: global-json-file: languages/csharp/global.json - name: Download x86_64-apple-darwin files - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: libbitwarden_c_files-x86_64-apple-darwin path: languages/csharp/Bitwarden.Sdk/macos-x64 - name: Download aarch64-apple-darwin files - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: libbitwarden_c_files-aarch64-apple-darwin path: languages/csharp/Bitwarden.Sdk/macos-arm64 - name: Download x86_64-unknown-linux-gnu files - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: libbitwarden_c_files-x86_64-unknown-linux-gnu path: languages/csharp/Bitwarden.Sdk/ubuntu-x64 - name: Download x86_64-pc-windows-msvc files - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: libbitwarden_c_files-x86_64-pc-windows-msvc path: languages/csharp/Bitwarden.Sdk/windows-x64 diff --git a/.github/workflows/publish-php.yml b/.github/workflows/publish-php.yml index c9d88a29e..6ed52537a 100644 --- a/.github/workflows/publish-php.yml +++ b/.github/workflows/publish-php.yml @@ -33,25 +33,25 @@ jobs: working-directory: languages/php/ - name: Download x86_64-apple-darwin files - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: libbitwarden_c_files-x86_64-apple-darwin path: temp/macos-x64 - name: Download aarch64-apple-darwin files - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: libbitwarden_c_files-aarch64-apple-darwin path: temp/macos-arm64 - name: Download x86_64-unknown-linux-gnu files - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: libbitwarden_c_files-x86_64-unknown-linux-gnu path: temp/ubuntu-x64 - name: Download x86_64-pc-windows-msvc files - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: libbitwarden_c_files-x86_64-pc-windows-msvc path: temp/windows-x64 diff --git a/.github/workflows/publish-ruby.yml b/.github/workflows/publish-ruby.yml index 01f6be58d..4315dea66 100644 --- a/.github/workflows/publish-ruby.yml +++ b/.github/workflows/publish-ruby.yml @@ -23,36 +23,36 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Set up Ruby - uses: ruby/setup-ruby@af848b40be8bb463a751551a1180d74782ba8a72 # v1.162.0 + uses: ruby/setup-ruby@360dc864d5da99d54fcb8e9148c14a84b90d3e88 # v1.165.1 with: ruby-version: 3.2 - name: Download Ruby schemas artifact - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: schemas.rb path: languages/ruby/bitwarden_sdk/lib - name: Download x86_64-apple-darwin files - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: libbitwarden_c_files-x86_64-apple-darwin path: temp/macos-x64 - name: Download aarch64-apple-darwin files - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: libbitwarden_c_files-aarch64-apple-darwin path: temp/macos-arm64 - name: Download x86_64-unknown-linux-gnu files - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: libbitwarden_c_files-x86_64-unknown-linux-gnu path: temp/ubuntu-x64 - name: Download x86_64-pc-windows-msvc files - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: libbitwarden_c_files-x86_64-pc-windows-msvc path: temp/windows-x64 diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index fae8b0bc5..b27a6a989 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -61,7 +61,7 @@ jobs: repository: bitwarden/sdk - name: Import GPG key - uses: crazy-max/ghaction-import-gpg@82a020f1f7f605c65dd2449b392a52c3fcfef7ef # v6.0.0 + uses: crazy-max/ghaction-import-gpg@01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4 # v6.1.0 with: gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }} passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }} From 679673096254e108fa0d71f65bf8fa28ba9679f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Mon, 8 Jan 2024 18:28:00 +0100 Subject: [PATCH 150/157] [PM-4269] Use rustls on non-wasm platforms (#374) ## Type of change ``` - [ ] Bug fix - [x] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Updated reqwest to use rustls on all platforms. We're also using rustls-platform-verifier where possible to load the CA certificates from the operating system store instead of including them in the binary. Note that WASM doesn't need a TLS stack as reqwest just uses the browser's `fetch` | | TLS Stack | CA Validator | Accepts self signed in OS root store | |---------|-----------|--------------------------|--------------------------------------| | Windows | RusTLS | ustls-platform-verifier | Yes | | Linux | RusTLS | rustls-platform-verifier (Native+WebPKI) | Yes | | Mac | RusTLS | rustls-platform-verifier (Native) | Yes | | Android | RusTLS | WebPKI | No | | iOS | RusTLS | rustls-platform-verifier (Native) | Yes | | WASM | fetch | fetch | Maybe, use browser config | --- Cargo.lock | 270 ++++++++++++++--------- about.toml | 5 + crates/bitwarden-api-api/Cargo.toml | 1 + crates/bitwarden-api-identity/Cargo.toml | 1 + crates/bitwarden-uniffi/Cargo.toml | 3 - crates/bitwarden/CHANGELOG.md | 4 + crates/bitwarden/Cargo.toml | 21 +- crates/bitwarden/src/client/client.rs | 14 +- crates/bws/CHANGELOG.md | 4 + crates/bws/Cargo.toml | 3 - crates/bws/Cross.toml | 6 - support/openapi-template/Cargo.mustache | 1 + 12 files changed, 213 insertions(+), 120 deletions(-) delete mode 100644 crates/bws/Cross.toml diff --git a/Cargo.lock b/Cargo.lock index ebf999c2c..4849ad02c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -351,6 +351,7 @@ dependencies = [ "rand_chacha 0.3.1", "reqwest", "rsa", + "rustls-platform-verifier", "schemars", "serde", "serde_json", @@ -454,7 +455,6 @@ dependencies = [ "bitwarden", "chrono", "env_logger", - "openssl", "schemars", "uniffi", ] @@ -546,7 +546,6 @@ dependencies = [ "directories", "env_logger", "log", - "openssl", "regex", "serde", "serde_json", @@ -633,6 +632,12 @@ dependencies = [ "libc", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.0" @@ -758,6 +763,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "combine" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "comfy-table" version = "7.1.0" @@ -1140,21 +1155,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.1" @@ -1522,16 +1522,17 @@ dependencies = [ ] [[package]] -name = "hyper-tls" -version = "0.5.0" +name = "hyper-rustls" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ - "bytes", + "futures-util", + "http", "hyper", - "native-tls", + "rustls", "tokio", - "tokio-native-tls", + "tokio-rustls", ] [[package]] @@ -1679,6 +1680,26 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "jni" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "js-sys" version = "0.3.66" @@ -1694,7 +1715,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" dependencies = [ - "spin", + "spin 0.5.2", ] [[package]] @@ -1900,24 +1921,6 @@ dependencies = [ "libloading", ] -[[package]] -name = "native-tls" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "newline-converter" version = "0.2.2" @@ -2071,60 +2074,12 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "openssl" -version = "0.10.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b8419dc8cc6d866deb801274bba2e6f8f6108c1bb7fcc10ee5ab864931dbb45" -dependencies = [ - "bitflags 2.4.1", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.47", -] - [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "openssl-src" -version = "300.2.1+3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fe476c29791a5ca0d1273c697e96085bbabbbea2ef7afd5617e78a4b40332d3" -dependencies = [ - "cc", -] - -[[package]] -name = "openssl-sys" -version = "0.9.97" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3eaad34cdd97d81de97964fc7f29e2d104f483840d906ef56daa1912338460b" -dependencies = [ - "cc", - "libc", - "openssl-src", - "pkg-config", - "vcpkg", -] - [[package]] name = "option-ext" version = "0.2.0" @@ -2573,27 +2528,29 @@ dependencies = [ "http", "http-body", "hyper", - "hyper-tls", + "hyper-rustls", "ipnet", "js-sys", "log", "mime", "mime_guess", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite", + "rustls", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "system-configuration", "tokio", - "tokio-native-tls", + "tokio-rustls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots", "winreg", ] @@ -2612,6 +2569,20 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "ring" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +dependencies = [ + "cc", + "getrandom 0.2.11", + "libc", + "spin 0.9.8", + "untrusted", + "windows-sys 0.48.0", +] + [[package]] name = "rsa" version = "0.9.6" @@ -2651,6 +2622,76 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.21.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.5", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c573e165e19be8c9fc0264ee041d66292d4ee0439949af61d71fa747bf5df082" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-roots", + "winapi", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84e217e7fdc8466b5b35d30f8c0a30febd29173df4a3a0c2115d306b9c4117ad" + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.14" @@ -2746,6 +2787,16 @@ dependencies = [ "syn 2.0.47", ] +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "sdk-schemas" version = "0.1.0" @@ -2769,6 +2820,7 @@ dependencies = [ "core-foundation", "core-foundation-sys", "libc", + "num-bigint", "security-framework-sys", ] @@ -3024,6 +3076,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "spki" version = "0.7.3" @@ -3291,12 +3349,12 @@ dependencies = [ ] [[package]] -name = "tokio-native-tls" -version = "0.3.1" +name = "tokio-rustls" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "native-tls", + "rustls", "tokio", ] @@ -3639,6 +3697,12 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.0" @@ -3672,12 +3736,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.4" @@ -3824,6 +3882,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" + [[package]] name = "weedle2" version = "4.0.0" diff --git a/about.toml b/about.toml index 2ed67c7cc..bd56148af 100644 --- a/about.toml +++ b/about.toml @@ -8,4 +8,9 @@ accepted = [ "MPL-2.0", "LGPL-3.0", "Unicode-DFS-2016", + "OpenSSL", ] + +# Ring has all the licenses combined into a single file, which causes cargo about to +# be confused about it. Thankfully it includes a workaround for this that we can enable. +workarounds = ["ring"] diff --git a/crates/bitwarden-api-api/Cargo.toml b/crates/bitwarden-api-api/Cargo.toml index e4ec902b1..5b8b0c773 100644 --- a/crates/bitwarden-api-api/Cargo.toml +++ b/crates/bitwarden-api-api/Cargo.toml @@ -22,5 +22,6 @@ uuid = { version = ">=1.3.3, <2", features = ["serde"] } [dependencies.reqwest] version = ">=0.11.18, <0.12" features = ["json", "multipart"] +default-features = false [dev-dependencies] diff --git a/crates/bitwarden-api-identity/Cargo.toml b/crates/bitwarden-api-identity/Cargo.toml index 929bbc6fa..4f4b46732 100644 --- a/crates/bitwarden-api-identity/Cargo.toml +++ b/crates/bitwarden-api-identity/Cargo.toml @@ -22,5 +22,6 @@ uuid = { version = ">=1.3.3, <2", features = ["serde"] } [dependencies.reqwest] version = ">=0.11.18, <0.12" features = ["json", "multipart"] +default-features = false [dev-dependencies] diff --git a/crates/bitwarden-uniffi/Cargo.toml b/crates/bitwarden-uniffi/Cargo.toml index ecde30a4b..4bf7b085b 100644 --- a/crates/bitwarden-uniffi/Cargo.toml +++ b/crates/bitwarden-uniffi/Cargo.toml @@ -25,6 +25,3 @@ bitwarden = { path = "../bitwarden", features = ["mobile", "internal"] } [build-dependencies] uniffi = { version = "=0.25.2", features = ["build"] } - -[target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies] -openssl = { version = "0.10", features = ["vendored"] } diff --git a/crates/bitwarden/CHANGELOG.md b/crates/bitwarden/CHANGELOG.md index b4a57cde1..ff47c19d8 100644 --- a/crates/bitwarden/CHANGELOG.md +++ b/crates/bitwarden/CHANGELOG.md @@ -7,6 +7,10 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed + +- Switched TLS backend to `rustls`, removing the dependency on `OpenSSL`. + ## [0.4.0] - 2023-12-21 ### Added diff --git a/crates/bitwarden/Cargo.toml b/crates/bitwarden/Cargo.toml index 5677a73d9..2fd1f32e6 100644 --- a/crates/bitwarden/Cargo.toml +++ b/crates/bitwarden/Cargo.toml @@ -46,7 +46,9 @@ num-bigint = ">=0.4, <0.5" num-traits = ">=0.2.15, <0.3" pbkdf2 = { version = ">=0.12.1, <0.13", default-features = false } rand = ">=0.8.5, <0.9" -reqwest = { version = ">=0.11, <0.12", features = ["json"] } +reqwest = { version = ">=0.11, <0.12", features = [ + "json", +], default-features = false } rsa = ">=0.9.2, <0.10" schemars = { version = ">=0.8.9, <0.9", features = ["uuid1", "chrono"] } serde = { version = ">=1.0, <2.0", features = ["derive"] } @@ -60,6 +62,23 @@ thiserror = ">=1.0.40, <2.0" uniffi = { version = "=0.25.2", optional = true, features = ["tokio"] } uuid = { version = ">=1.3.3, <2.0", features = ["serde"] } +[target.'cfg(all(not(target_os = "android"), not(target_arch="wasm32")))'.dependencies] +# By default, we use rustls as the TLS stack and rust-platform-verifier to support user-installed root certificates +# There are a few exceptions to this: +# - WASM doesn't require a TLS stack, as it just uses the browsers/node fetch +# - Android uses webpki-roots for the moment +reqwest = { version = "*", features = [ + "rustls-tls-manual-roots", +], default-features = false } +rustls-platform-verifier = "0.1.0" + +[target.'cfg(target_os = "android")'.dependencies] +# On android, the use of rustls-platform-verifier is more complicated and going through some changes at the moment, so we fall back to using webpki-roots +# This means that for the moment android won't support self-signed certificates, even if they are included in the OS trust store +reqwest = { version = "*", features = [ + "rustls-tls-webpki-roots", +], default-features = false } + [dev-dependencies] rand_chacha = "0.3.1" tokio = { version = "1.35.1", features = ["rt", "macros"] } diff --git a/crates/bitwarden/src/client/client.rs b/crates/bitwarden/src/client/client.rs index 302e7ed75..2c5d9b387 100644 --- a/crates/bitwarden/src/client/client.rs +++ b/crates/bitwarden/src/client/client.rs @@ -88,10 +88,16 @@ impl Client { let headers = header::HeaderMap::new(); - let client = reqwest::Client::builder() - .default_headers(headers) - .build() - .unwrap(); + #[allow(unused_mut)] + let mut client_builder = reqwest::Client::builder().default_headers(headers); + + #[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))] + { + client_builder = + client_builder.use_preconfigured_tls(rustls_platform_verifier::tls_config()); + } + + let client = client_builder.build().unwrap(); let identity = bitwarden_api_identity::apis::configuration::Configuration { base_path: settings.identity_url, diff --git a/crates/bws/CHANGELOG.md b/crates/bws/CHANGELOG.md index 007639800..d5ba27061 100644 --- a/crates/bws/CHANGELOG.md +++ b/crates/bws/CHANGELOG.md @@ -7,6 +7,10 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed + +- Switched TLS backend to `rustls`, removing the dependency on `OpenSSL`. + ## [0.4.0] - 2023-12-21 ### Added diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 821c6153c..980fe684e 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -44,6 +44,3 @@ bitwarden = { path = "../bitwarden", version = "0.4.0", features = ["secrets"] } [dev-dependencies] tempfile = "3.9.0" - -[target.'cfg(target_os = "linux")'.dependencies] -openssl = { version = "0.10", features = ["vendored"] } diff --git a/crates/bws/Cross.toml b/crates/bws/Cross.toml deleted file mode 100644 index 79b22e7f8..000000000 --- a/crates/bws/Cross.toml +++ /dev/null @@ -1,6 +0,0 @@ -# Install OpenSSL -[target.aarch64-unknown-linux-gnu] -pre-build = [ - "dpkg --add-architecture $CROSS_DEB_ARCH", - "apt-get update && apt-get install --assume-yes libssl-dev:$CROSS_DEB_ARCH", -] diff --git a/support/openapi-template/Cargo.mustache b/support/openapi-template/Cargo.mustache index 38885cb6f..3b89aa079 100644 --- a/support/openapi-template/Cargo.mustache +++ b/support/openapi-template/Cargo.mustache @@ -27,6 +27,7 @@ reqwest = "~0.9" [dependencies.reqwest] version = "^0.11" features = ["json", "multipart"] +default-features = false {{/supportAsync}} {{/reqwest}} {{#withAWSV4Signature}} From 37e4eaf9c66fdc2d54a88cbb4c5c25258b15ec1a Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 9 Jan 2024 11:17:51 +0100 Subject: [PATCH 151/157] Support generating a random send key if none is provided (#488) When creating a new send, we should generate a key for it. --- crates/bitwarden/src/crypto/mod.rs | 12 ++++ .../src/crypto/symmetric_crypto_key.rs | 5 +- crates/bitwarden/src/vault/send.rs | 60 +++++++++++++++++-- 3 files changed, 69 insertions(+), 8 deletions(-) diff --git a/crates/bitwarden/src/crypto/mod.rs b/crates/bitwarden/src/crypto/mod.rs index 84697e8a5..94db974d3 100644 --- a/crates/bitwarden/src/crypto/mod.rs +++ b/crates/bitwarden/src/crypto/mod.rs @@ -23,6 +23,10 @@ use aes::cipher::{generic_array::GenericArray, ArrayLength, Unsigned}; use hmac::digest::OutputSizeUser; +use rand::{ + distributions::{Distribution, Standard}, + Rng, +}; use crate::error::Result; @@ -73,3 +77,11 @@ fn hkdf_expand>(prk: &[u8], info: Option<&str>) -> Result() -> T +where + Standard: Distribution, +{ + rand::thread_rng().gen() +} diff --git a/crates/bitwarden/src/crypto/symmetric_crypto_key.rs b/crates/bitwarden/src/crypto/symmetric_crypto_key.rs index 5bce9e65d..741fe0559 100644 --- a/crates/bitwarden/src/crypto/symmetric_crypto_key.rs +++ b/crates/bitwarden/src/crypto/symmetric_crypto_key.rs @@ -4,7 +4,7 @@ use aes::cipher::{generic_array::GenericArray, typenum::U32}; use base64::Engine; use crate::{ - crypto::derive_shareable_key, + crypto::{derive_shareable_key, generate_random_bytes}, error::{CryptoError, Error}, util::BASE64_ENGINE, }; @@ -20,8 +20,7 @@ impl SymmetricCryptoKey { const MAC_LEN: usize = 32; pub fn generate(name: &str) -> Self { - use rand::Rng; - let secret: [u8; 16] = rand::thread_rng().gen(); + let secret: [u8; 16] = generate_random_bytes(); derive_shareable_key(secret, name, None) } diff --git a/crates/bitwarden/src/vault/send.rs b/crates/bitwarden/src/vault/send.rs index e0699ef9c..ce100db3e 100644 --- a/crates/bitwarden/src/vault/send.rs +++ b/crates/bitwarden/src/vault/send.rs @@ -8,8 +8,8 @@ use uuid::Uuid; use crate::{ crypto::{ - derive_shareable_key, EncString, KeyDecryptable, KeyEncryptable, LocateKey, - SymmetricCryptoKey, + derive_shareable_key, generate_random_bytes, EncString, KeyDecryptable, KeyEncryptable, + LocateKey, SymmetricCryptoKey, }, error::{CryptoError, Error, Result}, }; @@ -245,9 +245,19 @@ impl KeyEncryptable for SendView { fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { // For sends, we first decrypt the send key with the user key, and stretch it to it's full size // For the rest of the fields, we ignore the provided SymmetricCryptoKey and the stretched key - let k = URL_SAFE_NO_PAD - .decode(self.key.ok_or(CryptoError::MissingKey)?) - .map_err(|_| CryptoError::InvalidKey)?; + let k = match (self.key, self.id) { + // Existing send, decrypt key + (Some(k), _) => URL_SAFE_NO_PAD + .decode(k) + .map_err(|_| CryptoError::InvalidKey)?, + // New send, generate random key + (None, None) => { + let key: [u8; 16] = generate_random_bytes(); + key.to_vec() + } + // Existing send without key + _ => return Err(CryptoError::InvalidKey.into()), + }; let send_key = Send::derive_shareable_key(&k)?; Ok(Send { @@ -503,4 +513,44 @@ mod tests { .unwrap(); assert_eq!(v, view); } + + #[test] + pub fn test_encrypt_no_key() { + let enc = build_encryption_settings(); + let key = enc.get_key(&None).unwrap(); + + let view = SendView { + id: None, + access_id: Some("ct2APRQtJk-BLLDwAYqhRA".to_owned()), + name: "Test".to_string(), + notes: None, + key: None, + password: None, + r#type: SendType::Text, + file: None, + text: Some(SendTextView { + text: Some("This is a test".to_owned()), + hidden: false, + }), + max_access_count: None, + access_count: 0, + disabled: false, + hide_email: false, + revision_date: "2024-01-07T23:56:48.207363Z".parse().unwrap(), + deletion_date: "2024-01-14T23:56:48Z".parse().unwrap(), + expiration_date: None, + }; + + // Re-encrypt and decrypt again to ensure encrypt works + let v: SendView = view + .clone() + .encrypt_with_key(key) + .unwrap() + .decrypt_with_key(key) + .unwrap(); + + // Ignore key when comparing + let t = SendView { key: None, ..v }; + assert_eq!(t, view); + } } From 54b6759a2e7d53da38f64e7b025b0057aa305fc2 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 9 Jan 2024 14:47:26 +0100 Subject: [PATCH 152/157] [PM-5346] Implement password strength (#489) Break up `password.rs` into a module for clarity. Implement password strength along test cases that are replicated in the clients codebase. --- .vscode/settings.json | 3 +- Cargo.lock | 131 +++++++++- crates/bitwarden/Cargo.toml | 1 + crates/bitwarden/src/auth/password.rs | 245 ------------------ crates/bitwarden/src/auth/password/mod.rs | 7 + crates/bitwarden/src/auth/password/policy.rs | 176 +++++++++++++ .../bitwarden/src/auth/password/strength.rs | 78 ++++++ .../bitwarden/src/auth/password/validate.rs | 61 +++++ 8 files changed, 455 insertions(+), 247 deletions(-) delete mode 100644 crates/bitwarden/src/auth/password.rs create mode 100644 crates/bitwarden/src/auth/password/mod.rs create mode 100644 crates/bitwarden/src/auth/password/policy.rs create mode 100644 crates/bitwarden/src/auth/password/strength.rs create mode 100644 crates/bitwarden/src/auth/password/validate.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index d49fe884f..63c3dccaa 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,6 +18,7 @@ "reqwest", "schemars", "uniffi", - "wordlist" + "wordlist", + "zxcvbn" ] } diff --git a/Cargo.lock b/Cargo.lock index 4849ad02c..e04702e37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -315,6 +315,21 @@ dependencies = [ "serde", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.3.2" @@ -365,6 +380,7 @@ dependencies = [ "uniffi", "uuid", "wiremock", + "zxcvbn", ] [[package]] @@ -953,6 +969,41 @@ dependencies = [ "syn 2.0.47", ] +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + [[package]] name = "data-encoding" version = "2.5.0" @@ -998,6 +1049,37 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_builder" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder_macro" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" +dependencies = [ + "derive_builder_core", + "syn 1.0.109", +] + [[package]] name = "digest" version = "0.10.7" @@ -1124,6 +1206,16 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fancy-regex" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" +dependencies = [ + "bit-set", + "regex", +] + [[package]] name = "fastrand" version = "1.9.0" @@ -1558,6 +1650,12 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.5.0" @@ -1665,6 +1763,15 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.12.0" @@ -2354,6 +2461,12 @@ dependencies = [ "syn 2.0.47", ] +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quick-xml" version = "0.31.0" @@ -2805,7 +2918,7 @@ dependencies = [ "bitwarden", "bitwarden-json", "bitwarden-uniffi", - "itertools", + "itertools 0.12.0", "schemars", "serde_json", ] @@ -4189,3 +4302,19 @@ name = "zeroize" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" + +[[package]] +name = "zxcvbn" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "103fa851fff70ea29af380e87c25c48ff7faac5c530c70bd0e65366d4e0c94e4" +dependencies = [ + "derive_builder", + "fancy-regex", + "itertools 0.10.5", + "js-sys", + "lazy_static", + "quick-error", + "regex", + "time", +] diff --git a/crates/bitwarden/Cargo.toml b/crates/bitwarden/Cargo.toml index 2fd1f32e6..79910bca4 100644 --- a/crates/bitwarden/Cargo.toml +++ b/crates/bitwarden/Cargo.toml @@ -61,6 +61,7 @@ subtle = ">=2.5.0, <3.0" thiserror = ">=1.0.40, <2.0" uniffi = { version = "=0.25.2", optional = true, features = ["tokio"] } uuid = { version = ">=1.3.3, <2.0", features = ["serde"] } +zxcvbn = ">= 2.2.2, <3.0" [target.'cfg(all(not(target_os = "android"), not(target_arch="wasm32")))'.dependencies] # By default, we use rustls as the TLS stack and rust-platform-verifier to support user-installed root certificates diff --git a/crates/bitwarden/src/auth/password.rs b/crates/bitwarden/src/auth/password.rs deleted file mode 100644 index 94fe0faa0..000000000 --- a/crates/bitwarden/src/auth/password.rs +++ /dev/null @@ -1,245 +0,0 @@ -use schemars::JsonSchema; - -use super::determine_password_hash; -use crate::{ - client::{LoginMethod, UserLoginMethod}, - crypto::HashPurpose, - error::{Error, Result}, - Client, -}; - -pub(super) fn password_strength( - _password: String, - _email: String, - _additional_inputs: Vec, -) -> u8 { - 2 -} - -/// Validate the provided password passes the provided Master Password Requirements Policy. -pub(super) fn satisfies_policy( - password: String, - strength: u8, - policy: &MasterPasswordPolicyOptions, -) -> bool { - if policy.min_complexity > 0 && policy.min_complexity > strength { - return false; - } - - if policy.min_length > 0 && usize::from(policy.min_length) > password.len() { - return false; - } - - if policy.require_upper && password.to_lowercase() == password { - return false; - } - - if policy.require_lower && password.to_uppercase() == password { - return false; - } - - if policy.require_numbers && !password.chars().any(|c| c.is_numeric()) { - return false; - } - - if policy.require_special && !password.chars().any(|c| "!@#$%^&*".contains(c)) { - return false; - } - - true -} - -/// Validate if the provided password matches the password hash stored in the client. -pub(super) async fn validate_password( - client: &Client, - password: String, - password_hash: String, -) -> Result { - let login_method = client - .login_method - .as_ref() - .ok_or(Error::NotAuthenticated)?; - - if let LoginMethod::User(login_method) = login_method { - match login_method { - UserLoginMethod::Username { email, kdf, .. } - | UserLoginMethod::ApiKey { email, kdf, .. } => { - let hash = - determine_password_hash(email, kdf, &password, HashPurpose::LocalAuthorization) - .await?; - - Ok(hash == password_hash) - } - } - } else { - Err(Error::NotAuthenticated) - } -} - -#[derive(Debug, JsonSchema)] -#[cfg_attr(feature = "mobile", derive(uniffi::Record))] -#[allow(dead_code)] -pub struct MasterPasswordPolicyOptions { - min_complexity: u8, - min_length: u8, - require_upper: bool, - require_lower: bool, - require_numbers: bool, - require_special: bool, - - /// Flag to indicate if the policy should be enforced on login. - /// If true, and the user's password does not meet the policy requirements, - /// the user will be forced to update their password. - enforce_on_login: bool, -} - -#[cfg(test)] -mod tests { - mod satisfies_policy { - use crate::auth::password::{satisfies_policy, MasterPasswordPolicyOptions}; - - #[test] - fn satisfies_policy_gives_success() { - let password = "lkasfo!icbb$2323ALKJCO22".to_string(); - let options = MasterPasswordPolicyOptions { - min_complexity: 3, - min_length: 5, - require_upper: true, - require_lower: true, - require_numbers: true, - require_special: true, - enforce_on_login: false, - }; - - let result = satisfies_policy(password, 4, &options); - assert!(result); - } - - #[test] - fn satisfies_policy_evaluates_strength() { - let password = "password123".to_string(); - let options = MasterPasswordPolicyOptions { - min_complexity: 3, - min_length: 0, - require_upper: false, - require_lower: false, - require_numbers: false, - require_special: false, - enforce_on_login: false, - }; - - let result = satisfies_policy(password, 0, &options); - assert!(!result); - } - - #[test] - fn satisfies_policy_evaluates_length() { - let password = "password123".to_string(); - let options = MasterPasswordPolicyOptions { - min_complexity: 0, - min_length: 20, - require_upper: false, - require_lower: false, - require_numbers: false, - require_special: false, - enforce_on_login: false, - }; - - let result = satisfies_policy(password, 0, &options); - assert!(!result); - } - - #[test] - fn satisfies_policy_evaluates_upper() { - let password = "password123".to_string(); - let options = MasterPasswordPolicyOptions { - min_complexity: 0, - min_length: 0, - require_upper: true, - require_lower: false, - require_numbers: false, - require_special: false, - enforce_on_login: false, - }; - - let result = satisfies_policy(password, 0, &options); - assert!(!result); - } - - #[test] - fn satisfies_policy_evaluates_lower() { - let password = "ABCDEFG123".to_string(); - let options = MasterPasswordPolicyOptions { - min_complexity: 0, - min_length: 0, - require_upper: false, - require_lower: true, - require_numbers: false, - require_special: false, - enforce_on_login: false, - }; - - let result = satisfies_policy(password, 0, &options); - assert!(!result); - } - - #[test] - fn satisfies_policy_evaluates_numbers() { - let password = "password".to_string(); - let options = MasterPasswordPolicyOptions { - min_complexity: 0, - min_length: 0, - require_upper: false, - require_lower: false, - require_numbers: true, - require_special: false, - enforce_on_login: false, - }; - - let result = satisfies_policy(password, 0, &options); - assert!(!result); - } - - #[test] - fn satisfies_policy_evaluates_special() { - let password = "Password123".to_string(); - let options = MasterPasswordPolicyOptions { - min_complexity: 0, - min_length: 0, - require_upper: false, - require_lower: false, - require_numbers: false, - require_special: true, - enforce_on_login: false, - }; - - let result = satisfies_policy(password, 0, &options); - assert!(!result); - } - } - - #[cfg(feature = "mobile")] - #[tokio::test] - async fn test_validate_password() { - use std::num::NonZeroU32; - - use super::validate_password; - use crate::client::{kdf::Kdf, Client, LoginMethod, UserLoginMethod}; - - let mut client = Client::new(None); - client.set_login_method(LoginMethod::User(UserLoginMethod::Username { - email: "test@bitwarden.com".to_string(), - kdf: Kdf::PBKDF2 { - iterations: NonZeroU32::new(100_000).unwrap(), - }, - client_id: "1".to_string(), - })); - - let password = "password123".to_string(); - let password_hash = "7kTqkF1pY/3JeOu73N9kR99fDDe9O1JOZaVc7KH3lsU=".to_string(); - - let result = validate_password(&client, password, password_hash).await; - - assert!(result.unwrap()); - } -} diff --git a/crates/bitwarden/src/auth/password/mod.rs b/crates/bitwarden/src/auth/password/mod.rs new file mode 100644 index 000000000..b7833c7f8 --- /dev/null +++ b/crates/bitwarden/src/auth/password/mod.rs @@ -0,0 +1,7 @@ +mod policy; +pub(crate) use policy::satisfies_policy; +pub use policy::MasterPasswordPolicyOptions; +mod validate; +pub(crate) use validate::validate_password; +mod strength; +pub(crate) use strength::password_strength; diff --git a/crates/bitwarden/src/auth/password/policy.rs b/crates/bitwarden/src/auth/password/policy.rs new file mode 100644 index 000000000..dfd0e770d --- /dev/null +++ b/crates/bitwarden/src/auth/password/policy.rs @@ -0,0 +1,176 @@ +use schemars::JsonSchema; + +/// Validate the provided password passes the provided Master Password Requirements Policy. +pub(crate) fn satisfies_policy( + password: String, + strength: u8, + policy: &MasterPasswordPolicyOptions, +) -> bool { + if policy.min_complexity > 0 && policy.min_complexity > strength { + return false; + } + + if policy.min_length > 0 && usize::from(policy.min_length) > password.len() { + return false; + } + + if policy.require_upper && password.to_lowercase() == password { + return false; + } + + if policy.require_lower && password.to_uppercase() == password { + return false; + } + + if policy.require_numbers && !password.chars().any(|c| c.is_numeric()) { + return false; + } + + if policy.require_special && !password.chars().any(|c| "!@#$%^&*".contains(c)) { + return false; + } + + true +} + +#[derive(Debug, JsonSchema)] +#[cfg_attr(feature = "mobile", derive(uniffi::Record))] +#[allow(dead_code)] +pub struct MasterPasswordPolicyOptions { + min_complexity: u8, + min_length: u8, + require_upper: bool, + require_lower: bool, + require_numbers: bool, + require_special: bool, + + /// Flag to indicate if the policy should be enforced on login. + /// If true, and the user's password does not meet the policy requirements, + /// the user will be forced to update their password. + enforce_on_login: bool, +} + +#[cfg(test)] +mod tests { + + use super::{satisfies_policy, MasterPasswordPolicyOptions}; + + #[test] + fn satisfies_policy_gives_success() { + let password = "lkasfo!icbb$2323ALKJCO22".to_string(); + let options = MasterPasswordPolicyOptions { + min_complexity: 3, + min_length: 5, + require_upper: true, + require_lower: true, + require_numbers: true, + require_special: true, + enforce_on_login: false, + }; + + let result = satisfies_policy(password, 4, &options); + assert!(result); + } + + #[test] + fn satisfies_policy_evaluates_strength() { + let password = "password123".to_string(); + let options = MasterPasswordPolicyOptions { + min_complexity: 3, + min_length: 0, + require_upper: false, + require_lower: false, + require_numbers: false, + require_special: false, + enforce_on_login: false, + }; + + let result = satisfies_policy(password, 0, &options); + assert!(!result); + } + + #[test] + fn satisfies_policy_evaluates_length() { + let password = "password123".to_string(); + let options = MasterPasswordPolicyOptions { + min_complexity: 0, + min_length: 20, + require_upper: false, + require_lower: false, + require_numbers: false, + require_special: false, + enforce_on_login: false, + }; + + let result = satisfies_policy(password, 0, &options); + assert!(!result); + } + + #[test] + fn satisfies_policy_evaluates_upper() { + let password = "password123".to_string(); + let options = MasterPasswordPolicyOptions { + min_complexity: 0, + min_length: 0, + require_upper: true, + require_lower: false, + require_numbers: false, + require_special: false, + enforce_on_login: false, + }; + + let result = satisfies_policy(password, 0, &options); + assert!(!result); + } + + #[test] + fn satisfies_policy_evaluates_lower() { + let password = "ABCDEFG123".to_string(); + let options = MasterPasswordPolicyOptions { + min_complexity: 0, + min_length: 0, + require_upper: false, + require_lower: true, + require_numbers: false, + require_special: false, + enforce_on_login: false, + }; + + let result = satisfies_policy(password, 0, &options); + assert!(!result); + } + + #[test] + fn satisfies_policy_evaluates_numbers() { + let password = "password".to_string(); + let options = MasterPasswordPolicyOptions { + min_complexity: 0, + min_length: 0, + require_upper: false, + require_lower: false, + require_numbers: true, + require_special: false, + enforce_on_login: false, + }; + + let result = satisfies_policy(password, 0, &options); + assert!(!result); + } + + #[test] + fn satisfies_policy_evaluates_special() { + let password = "Password123".to_string(); + let options = MasterPasswordPolicyOptions { + min_complexity: 0, + min_length: 0, + require_upper: false, + require_lower: false, + require_numbers: false, + require_special: true, + enforce_on_login: false, + }; + + let result = satisfies_policy(password, 0, &options); + assert!(!result); + } +} diff --git a/crates/bitwarden/src/auth/password/strength.rs b/crates/bitwarden/src/auth/password/strength.rs new file mode 100644 index 000000000..66b5b5823 --- /dev/null +++ b/crates/bitwarden/src/auth/password/strength.rs @@ -0,0 +1,78 @@ +use zxcvbn::zxcvbn; + +const GLOBAL_INPUTS: [&str; 3] = ["bitwarden", "bit", "warden"]; + +pub(crate) fn password_strength( + password: String, + email: String, + additional_inputs: Vec, +) -> u8 { + let mut inputs = email_to_user_inputs(&email); + inputs.extend(additional_inputs); + + let mut arr: Vec<_> = inputs.iter().map(String::as_str).collect(); + arr.extend(GLOBAL_INPUTS); + + zxcvbn(&password, &arr).map_or(0, |e| e.score()) +} + +fn email_to_user_inputs(email: &str) -> Vec { + let parts = email.split_once('@'); + match parts { + Some((prefix, _)) => prefix + .trim() + .to_lowercase() + .split(|c: char| !c.is_alphanumeric()) + .map(str::to_owned) + .collect(), + None => vec![], + } +} + +#[cfg(test)] +mod tests { + use super::{email_to_user_inputs, password_strength}; + + #[test] + fn test_password_strength() { + let cases = vec![ + ("password", "random@bitwarden.com", 0), + ("password11", "random@bitwarden.com", 1), + ("Weakpass2", "random@bitwarden.com", 2), + ("GoodPass3!", "random@bitwarden.com", 3), + ("VeryStrong123@#", "random@bitwarden.com", 4), + ]; + + for (password, email, expected) in cases { + let result = password_strength(password.to_owned(), email.to_owned(), vec![]); + assert_eq!(expected, result, "{email}: {password}"); + } + } + + #[test] + fn test_penalize_email() { + let password = "asdfjkhkjwer!"; + + let result = password_strength( + password.to_owned(), + "random@bitwarden.com".to_owned(), + vec![], + ); + assert_eq!(result, 4); + + let result = password_strength( + password.to_owned(), + "asdfjkhkjwer@bitwarden.com".to_owned(), + vec![], + ); + assert_eq!(result, 1); + } + + #[test] + fn test_email_to_user_inputs() { + let email = "random@bitwarden.com"; + let result = email_to_user_inputs(email); + + assert_eq!(result, vec!["random"]); + } +} diff --git a/crates/bitwarden/src/auth/password/validate.rs b/crates/bitwarden/src/auth/password/validate.rs new file mode 100644 index 000000000..c86c1c40d --- /dev/null +++ b/crates/bitwarden/src/auth/password/validate.rs @@ -0,0 +1,61 @@ +use crate::{ + auth::determine_password_hash, + client::{LoginMethod, UserLoginMethod}, + crypto::HashPurpose, + error::{Error, Result}, + Client, +}; + +/// Validate if the provided password matches the password hash stored in the client. +pub(crate) async fn validate_password( + client: &Client, + password: String, + password_hash: String, +) -> Result { + let login_method = client + .login_method + .as_ref() + .ok_or(Error::NotAuthenticated)?; + + if let LoginMethod::User(login_method) = login_method { + match login_method { + UserLoginMethod::Username { email, kdf, .. } + | UserLoginMethod::ApiKey { email, kdf, .. } => { + let hash = + determine_password_hash(email, kdf, &password, HashPurpose::LocalAuthorization) + .await?; + + Ok(hash == password_hash) + } + } + } else { + Err(Error::NotAuthenticated) + } +} + +#[cfg(test)] +mod tests { + #[tokio::test] + async fn test_validate_password() { + use std::num::NonZeroU32; + + use super::validate_password; + use crate::client::{kdf::Kdf, Client, LoginMethod, UserLoginMethod}; + + let mut client = Client::new(None); + client.set_login_method(LoginMethod::User(UserLoginMethod::Username { + email: "test@bitwarden.com".to_string(), + kdf: Kdf::PBKDF2 { + iterations: NonZeroU32::new(100_000).unwrap(), + }, + client_id: "1".to_string(), + })); + + let password = "password123".to_string(); + let password_hash = "7kTqkF1pY/3JeOu73N9kR99fDDe9O1JOZaVc7KH3lsU=".to_string(); + + let result = validate_password(&client, password, password_hash).await; + + assert!(result.unwrap()); + } +} From fe7bce39c6ff7b0abe6f7212f6eb1ad29add0635 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 9 Jan 2024 16:48:10 +0100 Subject: [PATCH 153/157] Swap custom b64 config to standard (#451) Swap custom b64 config to standard --- crates/bitwarden/src/auth/api/request/mod.rs | 5 ++--- crates/bitwarden/src/auth/login/access_token.rs | 5 ++--- crates/bitwarden/src/client/access_token.rs | 4 ++-- crates/bitwarden/src/crypto/aes_ops.rs | 6 ++---- crates/bitwarden/src/crypto/enc_string/asymmetric.rs | 12 +++--------- crates/bitwarden/src/crypto/enc_string/mod.rs | 9 +++------ crates/bitwarden/src/crypto/enc_string/symmetric.rs | 8 ++------ crates/bitwarden/src/crypto/master_key.rs | 6 +++--- crates/bitwarden/src/crypto/rsa.rs | 5 ++--- crates/bitwarden/src/crypto/symmetric_crypto_key.rs | 9 +++------ .../bitwarden/src/platform/generate_fingerprint.rs | 6 +++--- crates/bitwarden/src/util.rs | 9 ++++----- 12 files changed, 31 insertions(+), 53 deletions(-) diff --git a/crates/bitwarden/src/auth/api/request/mod.rs b/crates/bitwarden/src/auth/api/request/mod.rs index 85b36a983..67796f2f3 100644 --- a/crates/bitwarden/src/auth/api/request/mod.rs +++ b/crates/bitwarden/src/auth/api/request/mod.rs @@ -9,7 +9,7 @@ mod renew_token_request; pub(crate) use access_token_request::*; #[cfg(feature = "internal")] pub(crate) use api_token_request::*; -use base64::Engine; +use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; #[cfg(feature = "internal")] pub(crate) use password_token_request::*; #[cfg(feature = "internal")] @@ -19,7 +19,6 @@ use crate::{ auth::api::response::{parse_identity_response, IdentityTokenResponse}, client::ApiConfigurations, error::Result, - util::BASE64_ENGINE, }; async fn send_identity_connect_request( @@ -46,7 +45,7 @@ async fn send_identity_connect_request( } if let Some(email) = email { - request = request.header("Auth-Email", BASE64_ENGINE.encode(email.as_bytes())); + request = request.header("Auth-Email", URL_SAFE_NO_PAD.encode(email.as_bytes())); } let response = request diff --git a/crates/bitwarden/src/auth/login/access_token.rs b/crates/bitwarden/src/auth/login/access_token.rs index 2e11035c6..43418d5f1 100644 --- a/crates/bitwarden/src/auth/login/access_token.rs +++ b/crates/bitwarden/src/auth/login/access_token.rs @@ -1,6 +1,6 @@ use std::path::{Path, PathBuf}; -use base64::Engine; +use base64::{engine::general_purpose::STANDARD, Engine}; use chrono::Utc; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -16,7 +16,6 @@ use crate::{ crypto::{EncString, KeyDecryptable, SymmetricCryptoKey}, error::{Error, Result}, secrets_manager::state::{self, ClientState}, - util::BASE64_ENGINE, Client, }; @@ -64,7 +63,7 @@ pub(crate) async fn login_access_token( } let payload: Payload = serde_json::from_slice(&decrypted_payload)?; - let encryption_key = BASE64_ENGINE.decode(payload.encryption_key.clone())?; + let encryption_key = STANDARD.decode(payload.encryption_key.clone())?; let encryption_key = SymmetricCryptoKey::try_from(encryption_key.as_slice())?; let access_token_obj: JWTToken = r.access_token.parse()?; diff --git a/crates/bitwarden/src/client/access_token.rs b/crates/bitwarden/src/client/access_token.rs index c3d6f2fea..854922c14 100644 --- a/crates/bitwarden/src/client/access_token.rs +++ b/crates/bitwarden/src/client/access_token.rs @@ -6,7 +6,7 @@ use uuid::Uuid; use crate::{ crypto::{derive_shareable_key, SymmetricCryptoKey}, error::AccessTokenInvalidError, - util::BASE64_ENGINE, + util::STANDARD_INDIFFERENT, }; pub struct AccessToken { @@ -45,7 +45,7 @@ impl FromStr for AccessToken { return Err(AccessTokenInvalidError::InvalidUuid.into()); }; - let encryption_key = BASE64_ENGINE + let encryption_key = STANDARD_INDIFFERENT .decode(encryption_key) .map_err(AccessTokenInvalidError::InvalidBase64)?; let encryption_key: [u8; 16] = encryption_key.try_into().map_err(|e: Vec<_>| { diff --git a/crates/bitwarden/src/crypto/aes_ops.rs b/crates/bitwarden/src/crypto/aes_ops.rs index 182986c3b..00e71a3fd 100644 --- a/crates/bitwarden/src/crypto/aes_ops.rs +++ b/crates/bitwarden/src/crypto/aes_ops.rs @@ -122,11 +122,9 @@ fn generate_mac(mac_key: &[u8], iv: &[u8], data: &[u8]) -> Result<[u8; 32]> { #[cfg(test)] mod tests { use aes::cipher::generic_array::sequence::GenericSequence; - use base64::Engine; + use base64::{engine::general_purpose::STANDARD, Engine}; use rand::SeedableRng; - use crate::util::BASE64_ENGINE; - use super::*; /// Helper function for generating a `GenericArray` of size 32 with each element being @@ -175,7 +173,7 @@ mod tests { let iv = generate_vec(16, 0, 1); let iv: &[u8; 16] = iv.as_slice().try_into().unwrap(); let key = generate_generic_array(0, 1); - let data = BASE64_ENGINE.decode("ByUF8vhyX4ddU9gcooznwA==").unwrap(); + let data = STANDARD.decode("ByUF8vhyX4ddU9gcooznwA==").unwrap(); let decrypted = decrypt_aes256(iv, data, key).unwrap(); diff --git a/crates/bitwarden/src/crypto/enc_string/asymmetric.rs b/crates/bitwarden/src/crypto/enc_string/asymmetric.rs index f90315afa..3c67c166a 100644 --- a/crates/bitwarden/src/crypto/enc_string/asymmetric.rs +++ b/crates/bitwarden/src/crypto/enc_string/asymmetric.rs @@ -1,14 +1,11 @@ use std::{fmt::Display, str::FromStr}; -use base64::Engine; +use base64::{engine::general_purpose::STANDARD, Engine}; #[cfg(feature = "internal")] use rsa::{Oaep, RsaPrivateKey}; use serde::Deserialize; -use crate::{ - error::{EncStringParseError, Error, Result}, - util::BASE64_ENGINE, -}; +use crate::error::{EncStringParseError, Error, Result}; #[cfg(feature = "internal")] use crate::error::CryptoError; @@ -135,10 +132,7 @@ impl Display for AsymmEncString { AsymmEncString::Rsa2048_OaepSha1_HmacSha256_B64 { data, mac } => vec![data, mac], }; - let encoded_parts: Vec = parts - .iter() - .map(|part| BASE64_ENGINE.encode(part)) - .collect(); + let encoded_parts: Vec = parts.iter().map(|part| STANDARD.encode(part)).collect(); write!(f, "{}.{}", self.enc_type(), encoded_parts.join("|"))?; diff --git a/crates/bitwarden/src/crypto/enc_string/mod.rs b/crates/bitwarden/src/crypto/enc_string/mod.rs index f8608638a..a998e6056 100644 --- a/crates/bitwarden/src/crypto/enc_string/mod.rs +++ b/crates/bitwarden/src/crypto/enc_string/mod.rs @@ -4,13 +4,10 @@ mod symmetric; use std::str::FromStr; pub use asymmetric::AsymmEncString; -use base64::Engine; +use base64::{engine::general_purpose::STANDARD, Engine}; pub use symmetric::EncString; -use crate::{ - error::{EncStringParseError, Result}, - util::BASE64_ENGINE, -}; +use crate::error::{EncStringParseError, Result}; #[cfg(feature = "mobile")] fn check_length(buf: &[u8], expected: usize) -> Result<()> { @@ -25,7 +22,7 @@ fn check_length(buf: &[u8], expected: usize) -> Result<()> { } fn from_b64_vec(s: &str) -> Result> { - Ok(BASE64_ENGINE + Ok(STANDARD .decode(s) .map_err(EncStringParseError::InvalidBase64)?) } diff --git a/crates/bitwarden/src/crypto/enc_string/symmetric.rs b/crates/bitwarden/src/crypto/enc_string/symmetric.rs index eed7c09ad..798994a1c 100644 --- a/crates/bitwarden/src/crypto/enc_string/symmetric.rs +++ b/crates/bitwarden/src/crypto/enc_string/symmetric.rs @@ -1,7 +1,7 @@ use std::{fmt::Display, str::FromStr}; use aes::cipher::{generic_array::GenericArray, typenum::U32}; -use base64::Engine; +use base64::{engine::general_purpose::STANDARD, Engine}; use serde::Deserialize; #[cfg(feature = "mobile")] @@ -10,7 +10,6 @@ use super::{from_b64, from_b64_vec, split_enc_string}; use crate::{ crypto::{decrypt_aes256_hmac, KeyDecryptable, KeyEncryptable, LocateKey, SymmetricCryptoKey}, error::{CryptoError, EncStringParseError, Error, Result}, - util::BASE64_ENGINE, }; /// # Encrypted string primitive @@ -181,10 +180,7 @@ impl Display for EncString { EncString::AesCbc256_HmacSha256_B64 { iv, mac, data } => vec![iv, data, mac], }; - let encoded_parts: Vec = parts - .iter() - .map(|part| BASE64_ENGINE.encode(part)) - .collect(); + let encoded_parts: Vec = parts.iter().map(|part| STANDARD.encode(part)).collect(); write!(f, "{}.{}", self.enc_type(), encoded_parts.join("|"))?; diff --git a/crates/bitwarden/src/crypto/master_key.rs b/crates/bitwarden/src/crypto/master_key.rs index 9786da3c2..e259ec334 100644 --- a/crates/bitwarden/src/crypto/master_key.rs +++ b/crates/bitwarden/src/crypto/master_key.rs @@ -1,5 +1,5 @@ use aes::cipher::{generic_array::GenericArray, typenum::U32}; -use base64::Engine; +use base64::{engine::general_purpose::STANDARD, Engine}; use rand::Rng; use schemars::JsonSchema; use sha2::Digest; @@ -8,7 +8,7 @@ use super::{ hkdf_expand, EncString, KeyDecryptable, PbkdfSha256Hmac, SymmetricCryptoKey, UserKey, PBKDF_SHA256_HMAC_OUT_SIZE, }; -use crate::{client::kdf::Kdf, error::Result, util::BASE64_ENGINE}; +use crate::{client::kdf::Kdf, error::Result}; #[derive(Copy, Clone, JsonSchema)] #[cfg_attr(feature = "mobile", derive(uniffi::Enum))] @@ -39,7 +39,7 @@ impl MasterKey { ) .expect("hash is a valid fixed size"); - Ok(BASE64_ENGINE.encode(hash)) + Ok(STANDARD.encode(hash)) } pub(crate) fn make_user_key(&self) -> Result<(UserKey, EncString)> { diff --git a/crates/bitwarden/src/crypto/rsa.rs b/crates/bitwarden/src/crypto/rsa.rs index 6feb6ef47..9641237fc 100644 --- a/crates/bitwarden/src/crypto/rsa.rs +++ b/crates/bitwarden/src/crypto/rsa.rs @@ -1,4 +1,4 @@ -use base64::Engine; +use base64::{engine::general_purpose::STANDARD, Engine}; use rsa::{ pkcs8::{EncodePrivateKey, EncodePublicKey}, RsaPrivateKey, RsaPublicKey, @@ -7,7 +7,6 @@ use rsa::{ use crate::{ crypto::{EncString, SymmetricCryptoKey}, error::Result, - util::BASE64_ENGINE, }; #[cfg_attr(feature = "mobile", derive(uniffi::Record))] @@ -28,7 +27,7 @@ pub(super) fn make_key_pair(key: &SymmetricCryptoKey) -> Result { .to_public_key_der() .map_err(|_| "unable to create public key")?; - let b64 = BASE64_ENGINE.encode(spki.as_bytes()); + let b64 = STANDARD.encode(spki.as_bytes()); let pkcs = priv_key .to_pkcs8_der() .map_err(|_| "unable to create private key")?; diff --git a/crates/bitwarden/src/crypto/symmetric_crypto_key.rs b/crates/bitwarden/src/crypto/symmetric_crypto_key.rs index 741fe0559..f6eda5e48 100644 --- a/crates/bitwarden/src/crypto/symmetric_crypto_key.rs +++ b/crates/bitwarden/src/crypto/symmetric_crypto_key.rs @@ -1,12 +1,11 @@ use std::str::FromStr; use aes::cipher::{generic_array::GenericArray, typenum::U32}; -use base64::Engine; +use base64::{engine::general_purpose::STANDARD, Engine}; use crate::{ crypto::{derive_shareable_key, generate_random_bytes}, error::{CryptoError, Error}, - util::BASE64_ENGINE, }; /// A symmetric encryption key. Used to encrypt and decrypt [`EncString`](crate::crypto::EncString) @@ -32,7 +31,7 @@ impl SymmetricCryptoKey { buf.extend_from_slice(&mac); } - BASE64_ENGINE.encode(&buf) + STANDARD.encode(&buf) } #[cfg(feature = "internal")] @@ -50,9 +49,7 @@ impl FromStr for SymmetricCryptoKey { type Err = Error; fn from_str(s: &str) -> Result { - let bytes = BASE64_ENGINE - .decode(s) - .map_err(|_| CryptoError::InvalidKey)?; + let bytes = STANDARD.decode(s).map_err(|_| CryptoError::InvalidKey)?; SymmetricCryptoKey::try_from(bytes.as_slice()) } } diff --git a/crates/bitwarden/src/platform/generate_fingerprint.rs b/crates/bitwarden/src/platform/generate_fingerprint.rs index a8f45dd87..cd763b765 100644 --- a/crates/bitwarden/src/platform/generate_fingerprint.rs +++ b/crates/bitwarden/src/platform/generate_fingerprint.rs @@ -1,10 +1,10 @@ -use base64::Engine; +use base64::{engine::general_purpose::STANDARD, Engine}; use log::info; use rsa::pkcs8::EncodePublicKey; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::{crypto::fingerprint, error::Result, util::BASE64_ENGINE}; +use crate::{crypto::fingerprint, error::Result}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -25,7 +25,7 @@ pub struct FingerprintResponse { pub(crate) fn generate_fingerprint(input: &FingerprintRequest) -> Result { info!("Generating fingerprint"); - let key = BASE64_ENGINE.decode(&input.public_key)?; + let key = STANDARD.decode(&input.public_key)?; Ok(FingerprintResponse { fingerprint: fingerprint(&input.fingerprint_material, &key)?, diff --git a/crates/bitwarden/src/util.rs b/crates/bitwarden/src/util.rs index 4dbe21ca6..038c3d7ee 100644 --- a/crates/bitwarden/src/util.rs +++ b/crates/bitwarden/src/util.rs @@ -21,12 +21,11 @@ pub fn default_argon2_parallelism() -> NonZeroU32 { NonZeroU32::new(4).unwrap() } -const BASE64_ENGINE_CONFIG: GeneralPurposeConfig = GeneralPurposeConfig::new() - .with_encode_padding(true) - .with_decode_padding_mode(DecodePaddingMode::Indifferent); +const INDIFFERENT: GeneralPurposeConfig = + GeneralPurposeConfig::new().with_decode_padding_mode(DecodePaddingMode::Indifferent); -pub const BASE64_ENGINE: GeneralPurpose = - GeneralPurpose::new(&alphabet::STANDARD, BASE64_ENGINE_CONFIG); +pub const STANDARD_INDIFFERENT: GeneralPurpose = + GeneralPurpose::new(&alphabet::STANDARD, INDIFFERENT); #[cfg(feature = "mobile")] pub(crate) fn capitalize_first_letter(s: &str) -> String { From f0c384e5b8fa162cd8aef42dd6b244ef1c84df7e Mon Sep 17 00:00:00 2001 From: Colton Hurst Date: Wed, 10 Jan 2024 11:01:06 -0500 Subject: [PATCH 154/157] SM-1066: Add back the Output option to the help menu in bws (#477) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Add back Output to the help menu in `bws` which was removed in [this](https://github.com/bitwarden/sdk/pull/103/commits/37428e215e672ec9ea5a993a4c605b4411181525) commit. ## Code changes - **main.rs:** Unhide Output option to the help menu ## Screenshots Screenshot 2024-01-05 at 12 05 22 ## Before you submit - Please add **unit tests** where it makes sense to do so (encouraged but not required) --- crates/bws/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bws/src/main.rs b/crates/bws/src/main.rs index 117186663..e55df5082 100644 --- a/crates/bws/src/main.rs +++ b/crates/bws/src/main.rs @@ -34,7 +34,7 @@ struct Cli { #[command(subcommand)] command: Option, - #[arg(short = 'o', long, global = true, value_enum, default_value_t = Output::JSON, help="Output format", hide = true)] + #[arg(short = 'o', long, global = true, value_enum, default_value_t = Output::JSON, help="Output format")] output: Output, #[arg(short = 'c', long, global = true, value_enum, default_value_t = Color::Auto, help="Use colors in the output")] From 339f45e0ac3b3bf9ab213208192664e0fdfd8fef Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Wed, 10 Jan 2024 17:06:59 +0100 Subject: [PATCH 155/157] Sanitize totp base32 and use no pad (#492) Fix difference in our totp handling in the sdk and clients repository. --- crates/bitwarden/src/vault/totp.rs | 46 ++++++++++++++++-------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/crates/bitwarden/src/vault/totp.rs b/crates/bitwarden/src/vault/totp.rs index 9bc92229b..6e863d57b 100644 --- a/crates/bitwarden/src/vault/totp.rs +++ b/crates/bitwarden/src/vault/totp.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, str::FromStr}; use crate::error::{Error, Result}; use chrono::{DateTime, Utc}; -use data_encoding::BASE32; +use data_encoding::BASE32_NOPAD; use hmac::{Hmac, Mac}; use reqwest::Url; use schemars::JsonSchema; @@ -12,6 +12,7 @@ type HmacSha1 = Hmac; type HmacSha256 = Hmac; type HmacSha512 = Hmac; +const BASE32_CHARS: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; const STEAM_CHARS: &str = "23456789BCDFGHJKMNPQRTVWXY"; const DEFAULT_ALGORITHM: Algorithm = Algorithm::Sha1; @@ -128,9 +129,16 @@ impl FromStr for Totp { /// - Steam URI fn from_str(key: &str) -> Result { fn decode_secret(secret: &str) -> Result> { - BASE32 - .decode(secret.to_uppercase().as_bytes()) - .map_err(|_| "Unable to decode secret".into()) + // Sanitize the secret to only contain allowed characters + let secret = secret + .to_uppercase() + .chars() + .filter(|c| BASE32_CHARS.contains(*c)) + .collect::(); + + BASE32_NOPAD + .decode(secret.as_bytes()) + .map_err(|e| e.to_string().into()) } let params = if key.starts_with("otpauth://") { @@ -218,30 +226,26 @@ mod tests { #[test] fn test_generate_totp() { - let key = "WQIQ25BRKZYCJVYP".to_string(); - let time = Some( - DateTime::parse_from_rfc3339("2023-01-01T00:00:00.000Z") - .unwrap() - .with_timezone(&Utc), - ); - let response = generate_totp(key, time).unwrap(); - - assert_eq!(response.code, "194506".to_string()); - assert_eq!(response.period, 30); - } + let cases = vec![ + ("WQIQ25BRKZYCJVYP", "194506"), // valid base32 + ("wqiq25brkzycjvyp", "194506"), // lowercase + ("PIUDISEQYA", "829846"), // non padded + ("PIUDISEQYA======", "829846"), // padded + ("PIUD1IS!EQYA=", "829846"), // sanitized + ]; - #[test] - fn test_lowercase_secret() { - let key = "wqiq25brkzycjvyp".to_string(); let time = Some( DateTime::parse_from_rfc3339("2023-01-01T00:00:00.000Z") .unwrap() .with_timezone(&Utc), ); - let response = generate_totp(key, time).unwrap(); - assert_eq!(response.code, "194506".to_string()); - assert_eq!(response.period, 30); + for (key, expected_code) in cases { + let response = generate_totp(key.to_string(), time).unwrap(); + + assert_eq!(response.code, expected_code, "wrong code for key: {key}"); + assert_eq!(response.period, 30); + } } #[test] From 99b0b622513c8bef75aa4738c59262cfb0ff6162 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 11 Jan 2024 13:22:13 +0100 Subject: [PATCH 156/157] Refactor make_user_key to call SymmetricCryptoKey::generate (#495) The `MasterKey` previously had intimate knowledge about `SymmetricCryptKey` in order to be able to generate one. This refactor extracts the knowledge back into `SymmetricCryptoKey` by creating a `SymmetricCryptoKey::generate`. --- .../src/crypto/enc_string/symmetric.rs | 6 ++-- crates/bitwarden/src/crypto/master_key.rs | 15 ++++----- crates/bitwarden/src/crypto/mod.rs | 2 ++ .../src/crypto/symmetric_crypto_key.rs | 33 ++++++++++++++----- 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/crates/bitwarden/src/crypto/enc_string/symmetric.rs b/crates/bitwarden/src/crypto/enc_string/symmetric.rs index 798994a1c..3fe0e8ef8 100644 --- a/crates/bitwarden/src/crypto/enc_string/symmetric.rs +++ b/crates/bitwarden/src/crypto/enc_string/symmetric.rs @@ -274,11 +274,13 @@ impl schemars::JsonSchema for EncString { #[cfg(test)] mod tests { use super::EncString; - use crate::crypto::{KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}; + use crate::crypto::{ + symmetric_crypto_key::derive_symmetric_key, KeyDecryptable, KeyEncryptable, + }; #[test] fn test_enc_string_roundtrip() { - let key = SymmetricCryptoKey::generate("test"); + let key = derive_symmetric_key("test"); let test_string = "encrypted_test_string".to_string(); let cipher = test_string.clone().encrypt_with_key(&key).unwrap(); diff --git a/crates/bitwarden/src/crypto/master_key.rs b/crates/bitwarden/src/crypto/master_key.rs index e259ec334..726759ac7 100644 --- a/crates/bitwarden/src/crypto/master_key.rs +++ b/crates/bitwarden/src/crypto/master_key.rs @@ -1,6 +1,5 @@ use aes::cipher::{generic_array::GenericArray, typenum::U32}; use base64::{engine::general_purpose::STANDARD, Engine}; -use rand::Rng; use schemars::JsonSchema; use sha2::Digest; @@ -69,10 +68,7 @@ fn make_user_key( mut rng: impl rand::RngCore, master_key: &MasterKey, ) -> Result<(UserKey, EncString)> { - let mut user_key = [0u8; 64]; - rng.fill(&mut user_key); - - let user_key = SymmetricCryptoKey::try_from(user_key.as_slice())?; + let user_key = SymmetricCryptoKey::generate(&mut rng); let protected = master_key.encrypt_user_key(&user_key)?; Ok((UserKey::new(user_key), protected)) } @@ -134,7 +130,10 @@ mod tests { use rand::SeedableRng; use super::{make_user_key, stretch_master_key, HashPurpose, MasterKey}; - use crate::{client::kdf::Kdf, crypto::SymmetricCryptoKey}; + use crate::{ + client::kdf::Kdf, + crypto::{symmetric_crypto_key::derive_symmetric_key, SymmetricCryptoKey}, + }; #[test] fn test_master_key_derive_pbkdf2() { @@ -292,9 +291,9 @@ mod tests { #[test] fn test_make_user_key2() { - let master_key = MasterKey(SymmetricCryptoKey::generate("test1")); + let master_key = MasterKey(derive_symmetric_key("test1")); - let user_key = SymmetricCryptoKey::generate("test2"); + let user_key = derive_symmetric_key("test2"); let encrypted = master_key.encrypt_user_key(&user_key).unwrap(); let decrypted = master_key.decrypt_user_key(encrypted).unwrap(); diff --git a/crates/bitwarden/src/crypto/mod.rs b/crates/bitwarden/src/crypto/mod.rs index 94db974d3..5de0cdd06 100644 --- a/crates/bitwarden/src/crypto/mod.rs +++ b/crates/bitwarden/src/crypto/mod.rs @@ -23,6 +23,7 @@ use aes::cipher::{generic_array::GenericArray, ArrayLength, Unsigned}; use hmac::digest::OutputSizeUser; +#[cfg(any(test, feature = "internal"))] use rand::{ distributions::{Distribution, Standard}, Rng, @@ -79,6 +80,7 @@ fn hkdf_expand>(prk: &[u8], info: Option<&str>) -> Result() -> T where Standard: Distribution, diff --git a/crates/bitwarden/src/crypto/symmetric_crypto_key.rs b/crates/bitwarden/src/crypto/symmetric_crypto_key.rs index f6eda5e48..5487dfd98 100644 --- a/crates/bitwarden/src/crypto/symmetric_crypto_key.rs +++ b/crates/bitwarden/src/crypto/symmetric_crypto_key.rs @@ -2,11 +2,9 @@ use std::str::FromStr; use aes::cipher::{generic_array::GenericArray, typenum::U32}; use base64::{engine::general_purpose::STANDARD, Engine}; +use rand::Rng; -use crate::{ - crypto::{derive_shareable_key, generate_random_bytes}, - error::{CryptoError, Error}, -}; +use crate::error::{CryptoError, Error}; /// A symmetric encryption key. Used to encrypt and decrypt [`EncString`](crate::crypto::EncString) pub struct SymmetricCryptoKey { @@ -18,9 +16,18 @@ impl SymmetricCryptoKey { const KEY_LEN: usize = 32; const MAC_LEN: usize = 32; - pub fn generate(name: &str) -> Self { - let secret: [u8; 16] = generate_random_bytes(); - derive_shareable_key(secret, name, None) + /// Generate a new random [SymmetricCryptoKey] + pub fn generate(mut rng: impl rand::RngCore) -> Self { + let mut key = [0u8; Self::KEY_LEN]; + let mut mac_key = [0u8; Self::MAC_LEN]; + + rng.fill(&mut key); + rng.fill(&mut mac_key); + + SymmetricCryptoKey { + key: key.into(), + mac_key: Some(mac_key.into()), + } } pub fn to_base64(&self) -> String { @@ -81,15 +88,23 @@ impl std::fmt::Debug for SymmetricCryptoKey { } } +#[cfg(test)] +pub fn derive_symmetric_key(name: &str) -> SymmetricCryptoKey { + use crate::crypto::{derive_shareable_key, generate_random_bytes}; + + let secret: [u8; 16] = generate_random_bytes(); + derive_shareable_key(secret, name, None) +} + #[cfg(test)] mod tests { use std::str::FromStr; - use super::SymmetricCryptoKey; + use super::{derive_symmetric_key, SymmetricCryptoKey}; #[test] fn test_symmetric_crypto_key() { - let key = SymmetricCryptoKey::generate("test"); + let key = derive_symmetric_key("test"); let key2 = SymmetricCryptoKey::from_str(&key.to_base64()).unwrap(); assert_eq!(key.key, key2.key); assert_eq!(key.mac_key, key2.mac_key); From b385d2db062a83fb7938edbb965abdd33d513d10 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 11 Jan 2024 15:05:59 +0100 Subject: [PATCH 157/157] Fix send password handling (#493) We should hash send passwords appropriately using pbkdf. Also changed how SendView handles passwords. It no longer provides the password but rather a boolean field `has_password` to prevent accidentally overriding the password when doing `send.decrypt().encrypt()`. --- crates/bitwarden/src/crypto/master_key.rs | 18 +--- crates/bitwarden/src/crypto/mod.rs | 5 + crates/bitwarden/src/vault/send.rs | 109 ++++++++++++++-------- 3 files changed, 78 insertions(+), 54 deletions(-) diff --git a/crates/bitwarden/src/crypto/master_key.rs b/crates/bitwarden/src/crypto/master_key.rs index 726759ac7..b22e11c6b 100644 --- a/crates/bitwarden/src/crypto/master_key.rs +++ b/crates/bitwarden/src/crypto/master_key.rs @@ -3,10 +3,7 @@ use base64::{engine::general_purpose::STANDARD, Engine}; use schemars::JsonSchema; use sha2::Digest; -use super::{ - hkdf_expand, EncString, KeyDecryptable, PbkdfSha256Hmac, SymmetricCryptoKey, UserKey, - PBKDF_SHA256_HMAC_OUT_SIZE, -}; +use super::{hkdf_expand, EncString, KeyDecryptable, SymmetricCryptoKey, UserKey}; use crate::{client::kdf::Kdf, error::Result}; #[derive(Copy, Clone, JsonSchema)] @@ -31,12 +28,7 @@ impl MasterKey { password: &[u8], purpose: HashPurpose, ) -> Result { - let hash = pbkdf2::pbkdf2_array::( - &self.0.key, - password, - purpose as u32, - ) - .expect("hash is a valid fixed size"); + let hash = super::pbkdf2(&self.0.key, password, purpose as u32); Ok(STANDARD.encode(hash)) } @@ -76,11 +68,7 @@ fn make_user_key( /// Derive a generic key from a secret and salt using the provided KDF. fn derive_key(secret: &[u8], salt: &[u8], kdf: &Kdf) -> Result { let hash = match kdf { - Kdf::PBKDF2 { iterations } => pbkdf2::pbkdf2_array::< - PbkdfSha256Hmac, - PBKDF_SHA256_HMAC_OUT_SIZE, - >(secret, salt, iterations.get()) - .unwrap(), + Kdf::PBKDF2 { iterations } => super::pbkdf2(secret, salt, iterations.get()), Kdf::Argon2id { iterations, diff --git a/crates/bitwarden/src/crypto/mod.rs b/crates/bitwarden/src/crypto/mod.rs index 5de0cdd06..5355a337a 100644 --- a/crates/bitwarden/src/crypto/mod.rs +++ b/crates/bitwarden/src/crypto/mod.rs @@ -87,3 +87,8 @@ where { rand::thread_rng().gen() } + +pub fn pbkdf2(password: &[u8], salt: &[u8], rounds: u32) -> [u8; PBKDF_SHA256_HMAC_OUT_SIZE] { + pbkdf2::pbkdf2_array::(password, salt, rounds) + .expect("hash is a valid fixed size") +} diff --git a/crates/bitwarden/src/vault/send.rs b/crates/bitwarden/src/vault/send.rs index ce100db3e..d8a18a8f6 100644 --- a/crates/bitwarden/src/vault/send.rs +++ b/crates/bitwarden/src/vault/send.rs @@ -1,4 +1,7 @@ -use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; +use base64::{ + engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD}, + Engine, +}; use bitwarden_api_api::models::{SendFileModel, SendResponseModel, SendTextModel}; use chrono::{DateTime, Utc}; use schemars::JsonSchema; @@ -14,6 +17,8 @@ use crate::{ error::{CryptoError, Error, Result}, }; +const SEND_ITERATIONS: u32 = 100_000; + #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] @@ -97,7 +102,13 @@ pub struct SendView { pub notes: Option, /// Base64 encoded key pub key: Option, - pub password: Option, + /// Replace or add a password to an existing send. The SDK will always return None when + /// decrypting a [Send] + /// TODO: We should revisit this, one variant is to have `[Create, Update]SendView` DTOs. + pub new_password: Option, + /// Denote if an existing send has a password. The SDK will ignore this value when creating or + /// updating sends. + pub has_password: bool, pub r#type: SendType, pub file: Option, @@ -200,7 +211,8 @@ impl KeyDecryptable for Send { name: self.name.decrypt_with_key(&key)?, notes: self.notes.decrypt_with_key(&key)?, key: Some(URL_SAFE_NO_PAD.encode(k)), - password: self.password.clone(), + new_password: None, + has_password: self.password.is_some(), r#type: self.r#type, file: self.file.decrypt_with_key(&key)?, @@ -267,7 +279,10 @@ impl KeyEncryptable for SendView { name: self.name.encrypt_with_key(&send_key)?, notes: self.notes.encrypt_with_key(&send_key)?, key: k.encrypt_with_key(key)?, - password: self.password.clone(), + password: self.new_password.map(|password| { + let password = crate::crypto::pbkdf2(password.as_bytes(), &k, SEND_ITERATIONS); + STANDARD.encode(password) + }), r#type: self.r#type, file: self.file.encrypt_with_key(&send_key)?, @@ -370,39 +385,12 @@ mod tests { let k = enc.get_key(&None).unwrap(); - // Create a send object, the only value we really care about here is the key - let send = Send { - id: Some("d7fb1e7f-9053-43c0-a02c-b0690098685a".parse().unwrap()), - access_id: Some("fx7711OQwEOgLLBpAJhoWg".into()), - name: "2.u5vXPAepUZ+4lI2vGGKiGg==|hEouC4SvCCb/ifzZzLcfSw==|E2VZUVffehczfIuRSlX2vnPRfflBtXef5tzsWvRrtfM=" - .parse() - .unwrap(), - notes: None, - key: "2.+1KUfOX8A83Xkwk1bumo/w==|Nczvv+DTkeP466cP/wMDnGK6W9zEIg5iHLhcuQG6s+M=|SZGsfuIAIaGZ7/kzygaVUau3LeOvJUlolENBOU+LX7g=" - .parse() - .unwrap(), - password: None, - r#type: super::SendType::File, - file: Some(super::SendFile { - id: Some("7f129hzwu0umkmnmsghkt486w96p749c".into()), - file_name: "2.pnszM3slsCVlOIzuWrfCpA==|85zCg+X8GODvIAPf1Yt3K75YP+ub3wVAi1UvwOVXhPgUo9Gsu23FJgYSOkyKu3Vr|OvTrOugwRH7Mp2BWSuPlfxovoWt9oDRdi1Qo3xHUcdQ=" - .parse() - .unwrap(), - size: Some("1251825".into()), - size_name: Some("1.19 MB".into()), - }), - text: None, - max_access_count: None, - access_count: 0, - disabled: false, - hide_email: false, - revision_date: "2023-08-25T09:14:53Z".parse().unwrap(), - deletion_date: "2023-09-25T09:14:53Z".parse().unwrap(), - expiration_date: None, - }; + let send_key = "2.+1KUfOX8A83Xkwk1bumo/w==|Nczvv+DTkeP466cP/wMDnGK6W9zEIg5iHLhcuQG6s+M=|SZGsfuIAIaGZ7/kzygaVUau3LeOvJUlolENBOU+LX7g=" + .parse() + .unwrap(); // Get the send key - let send_key = Send::get_key(&send.key, k).unwrap(); + let send_key = Send::get_key(&send_key, k).unwrap(); let send_key_b64 = send_key.to_base64(); assert_eq!(send_key_b64, "IR9ImHGm6rRuIjiN7csj94bcZR5WYTJj5GtNfx33zm6tJCHUl+QZlpNPba8g2yn70KnOHsAODLcR0um6E3MAlg=="); } @@ -458,7 +446,8 @@ mod tests { name: "Test".to_string(), notes: None, key: Some("Pgui0FK85cNhBGWHAlBHBw".to_owned()), - password: None, + new_password: None, + has_password: false, r#type: SendType::Text, file: None, text: Some(SendTextView { @@ -488,7 +477,8 @@ mod tests { name: "Test".to_string(), notes: None, key: Some("Pgui0FK85cNhBGWHAlBHBw".to_owned()), - password: None, + new_password: None, + has_password: false, r#type: SendType::Text, file: None, text: Some(SendTextView { @@ -515,7 +505,7 @@ mod tests { } #[test] - pub fn test_encrypt_no_key() { + pub fn test_create() { let enc = build_encryption_settings(); let key = enc.get_key(&None).unwrap(); @@ -525,7 +515,8 @@ mod tests { name: "Test".to_string(), notes: None, key: None, - password: None, + new_password: None, + has_password: false, r#type: SendType::Text, file: None, text: Some(SendTextView { @@ -553,4 +544,44 @@ mod tests { let t = SendView { key: None, ..v }; assert_eq!(t, view); } + + #[test] + pub fn test_create_password() { + let enc = build_encryption_settings(); + let key = enc.get_key(&None).unwrap(); + + let view = SendView { + id: None, + access_id: Some("ct2APRQtJk-BLLDwAYqhRA".to_owned()), + name: "Test".to_owned(), + notes: None, + key: Some("Pgui0FK85cNhBGWHAlBHBw".to_owned()), + new_password: Some("abc123".to_owned()), + has_password: false, + r#type: SendType::Text, + file: None, + text: Some(SendTextView { + text: Some("This is a test".to_owned()), + hidden: false, + }), + max_access_count: None, + access_count: 0, + disabled: false, + hide_email: false, + revision_date: "2024-01-07T23:56:48.207363Z".parse().unwrap(), + deletion_date: "2024-01-14T23:56:48Z".parse().unwrap(), + expiration_date: None, + }; + + let send: Send = view.encrypt_with_key(key).unwrap(); + + assert_eq!( + send.password, + Some("vTIDfdj3FTDbejmMf+mJWpYdMXsxfeSd1Sma3sjCtiQ=".to_owned()) + ); + + let v: SendView = send.decrypt_with_key(key).unwrap(); + assert_eq!(v.new_password, None); + assert!(v.has_password); + } }