diff --git a/.editorconfig b/.editorconfig index 28ef180e8..33fa272b4 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,10 +12,10 @@ insert_final_newline = true indent_style = space indent_size = 2 -[*.{ts}] -quote_type = single +[*.ts] +quote_type = double -[*.{rs}] +[*.rs] indent_style = space indent_size = 4 @@ -23,6 +23,6 @@ indent_size = 4 indent_style = space indent_size = 4 -[*.{xml}] +[*.xml] # VS Code XML extension removes the final newline insert_final_newline = false diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ef4561047..faf5a2eb4 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -16,3 +16,12 @@ .github/workflows/publish-rust-crates.yml @bitwarden/dept-bre .github/workflows/release-cpp.yml @bitwarden/dept-bre .github/workflows/publish-dotnet.yml @bitwarden/dept-bre + +# Secrets Manager team +crates/bitwarden-sm @bitwarden/team-secrets-manager-dev +crates/bws @bitwarden/team-secrets-manager-dev + +# BRE Automations +crates/bws/Cargo.toml +crates/bws/scripts/install.ps1 +crates/bws/scripts/install.sh diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index a5e6b294d..e43ad29df 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -6,10 +6,6 @@ -## 📸 Screenshots - - - ## ⏰ Reminders before review - Contributor guidelines followed diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 58a12f7f0..7607d530d 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -25,10 +25,10 @@ jobs: - target: i686-linux-android steps: - name: Checkout repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Install rust - uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # stable + uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable with: toolchain: stable @@ -46,7 +46,7 @@ jobs: run: cross build -p bitwarden-uniffi --release --target=${{ matrix.settings.target }} - name: Upload artifact - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: android-${{ matrix.settings.target }} path: ./target/${{ matrix.settings.target }}/release/libbitwarden_uniffi.so @@ -57,20 +57,20 @@ jobs: needs: build steps: - name: Checkout repo (PR) - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 if: github.event_name == 'pull_request' with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.ref }} - name: Checkout repo (Push) - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 if: github.event_name == 'push' with: fetch-depth: 0 - name: Install rust - uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # stable + uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable with: toolchain: stable @@ -80,13 +80,13 @@ jobs: key: cargo-combine-cache - name: Setup Java - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 + uses: actions/setup-java@b36c23c0d998641eff861008f374ee103c25ac73 # v4.4.0 with: distribution: temurin java-version: 17 - name: Download Artifacts - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - name: Move artifacts working-directory: languages/kotlin/sdk/src/main/jniLibs @@ -101,10 +101,11 @@ jobs: working-directory: languages/kotlin run: ./build-schemas.sh + - name: Setup gradle + uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0 + - name: Publish - uses: gradle/actions/setup-gradle@db19848a5fa7950289d3668fb053140cf3028d43 # v3.3.2 - with: - arguments: sdk:publish - build-root-directory: languages/kotlin + run: ./gradlew sdk:publish + working-directory: languages/kotlin env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/build-cli-docker.yml b/.github/workflows/build-cli-docker.yml index 375aa1bd9..83daabf2d 100644 --- a/.github/workflows/build-cli-docker.yml +++ b/.github/workflows/build-cli-docker.yml @@ -5,8 +5,6 @@ on: push: branches: - "main" - - "rc" - - "hotfix-rc" workflow_dispatch: pull_request: @@ -19,18 +17,12 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout Repository - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Check Branch to Publish - env: - PUBLISH_BRANCHES: "master,rc,hotfix-rc" id: publish-branch-check run: | - REF=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}} - - IFS="," read -a publish_branches <<< $PUBLISH_BRANCHES - - if [[ "${publish_branches[*]}" =~ "${REF}" ]]; then + if [[ "$GITHUB_REF" == "refs/heads/main" ]]; then echo "is_publish_branch=true" >> $GITHUB_ENV else echo "is_publish_branch=false" >> $GITHUB_ENV @@ -38,10 +30,10 @@ jobs: ########## Set up Docker ########## - name: Set up QEMU emulators - uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 + uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 + uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1 ########## Login to Docker registries ########## - name: Login to Azure - Prod Subscription @@ -77,10 +69,8 @@ jobs: run: | REF=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}} IMAGE_TAG=$(echo "${REF}" | sed "s#/#-#g") # slash safe branch name - if [[ "${IMAGE_TAG}" == "master" ]]; then + if [[ "${IMAGE_TAG}" == "main" ]]; then IMAGE_TAG=dev - elif [[ ("${IMAGE_TAG}" == "rc") || ("${IMAGE_TAG}" == "hotfix-rc") ]]; then - IMAGE_TAG=rc fi echo "image_tag=${IMAGE_TAG}" >> $GITHUB_OUTPUT @@ -89,16 +79,15 @@ jobs: id: tag-list env: IMAGE_TAG: ${{ steps.tag.outputs.image_tag }} - IS_PUBLISH_BRANCH: ${{ env.is_publish_branch }} run: | - if [[ ("${IMAGE_TAG}" == "dev" || "${IMAGE_TAG}" == "rc") && "${IS_PUBLISH_BRANCH}" == "true" ]]; then + if [[ "${IMAGE_TAG}" == "dev" ]]; then echo "tags=$_AZ_REGISTRY/bws:${IMAGE_TAG},bitwarden/bws:${IMAGE_TAG}" >> $GITHUB_OUTPUT else echo "tags=$_AZ_REGISTRY/bws:${IMAGE_TAG}" >> $GITHUB_OUTPUT fi - name: Build and push Docker image - uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0 + uses: docker/build-push-action@32945a339266b759abcbdc89316275140b0fc960 # v6.8.0 with: context: . file: crates/bws/Dockerfile @@ -123,10 +112,7 @@ jobs: needs: build-docker steps: - name: Check if any job failed - if: | - github.ref == 'refs/heads/master' - || github.ref == 'refs/heads/rc' - || github.ref == 'refs/heads/hotfix-rc' + if: github.ref == 'refs/heads/main' env: BUILD_DOCKER_STATUS: ${{ needs.build-docker.result }} run: | diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index 20e22632b..9ffb3bdea 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -6,8 +6,6 @@ on: push: branches: - "main" - - "rc" - - "hotfix-rc" workflow_dispatch: defaults: @@ -23,7 +21,7 @@ jobs: sign: ${{ steps.sign.outputs.sign }} steps: - name: Checkout repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Get Package Version id: retrieve-version @@ -58,10 +56,10 @@ jobs: target: aarch64-pc-windows-msvc steps: - name: Checkout repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Install rust - uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # stable + uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable with: toolchain: stable targets: ${{ matrix.settings.target }} @@ -74,7 +72,7 @@ jobs: - name: Build env: TARGET: ${{ matrix.settings.target }} - run: cargo build ${{ matrix.features }} -p bws --release --target=${{ matrix.settings.target }} + run: cargo build -p bws --release --target=${{ matrix.settings.target }} - name: Login to Azure if: ${{ needs.setup.outputs.sign == 'true' }} @@ -123,7 +121,7 @@ jobs: run: 7z a ./bws-${{ matrix.settings.target }}-%_PACKAGE_VERSION%.zip ./target/${{ matrix.settings.target }}/release/bws.exe - name: Upload artifact - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: bws-${{ matrix.settings.target }}-${{ env._PACKAGE_VERSION }}.zip path: ./bws-${{ matrix.settings.target }}-${{ env._PACKAGE_VERSION }}.zip @@ -132,8 +130,7 @@ jobs: build-macos: name: Building CLI for - ${{ matrix.settings.os }} - ${{ matrix.settings.target }} runs-on: ${{ matrix.settings.os || 'ubuntu-latest' }} - needs: - - setup + needs: setup env: _PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }} strategy: @@ -148,10 +145,10 @@ jobs: steps: - name: Checkout repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Install rust - uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # stable + uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable with: toolchain: stable targets: ${{ matrix.settings.target }} @@ -164,7 +161,7 @@ jobs: - name: Build env: TARGET: ${{ matrix.settings.target }} - run: cargo build ${{ matrix.features }} -p bws --release --target=${{ matrix.settings.target }} + run: cargo build -p bws --release --target=${{ matrix.settings.target }} - name: Login to Azure uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 @@ -233,7 +230,7 @@ jobs: xcrun notarytool submit ./bws-${{ matrix.settings.target }}-${{ env._PACKAGE_VERSION }}.zip --keychain-profile "notarytool-profile" --wait - name: Upload artifact - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: bws-${{ matrix.settings.target }}-${{ env._PACKAGE_VERSION }}.zip path: ./bws-${{ matrix.settings.target }}-${{ env._PACKAGE_VERSION }}.zip @@ -242,8 +239,7 @@ jobs: build-linux: name: Building CLI for - ${{ matrix.settings.os }} - ${{ matrix.settings.target }} runs-on: ${{ matrix.settings.os || 'ubuntu-latest' }} - needs: - - setup + needs: setup env: _PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }} strategy: @@ -263,15 +259,16 @@ jobs: target: aarch64-unknown-linux-gnu steps: - name: Checkout repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Install rust - uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # stable + uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable with: toolchain: stable targets: ${{ matrix.settings.target }} - - uses: goto-bus-stop/setup-zig@7ab2955eb728f5440978d5824358023be3a2802d # v2.2.0 + - name: Set up Zig + uses: goto-bus-stop/setup-zig@abea47f85e598557f500fa1fd2ab7464fcb39406 # v2.2.1 with: version: 0.12.0 @@ -286,13 +283,13 @@ jobs: - name: Build env: TARGET: ${{ matrix.settings.target }} - run: cargo zigbuild ${{ matrix.features }} -p bws --release --target=${{ matrix.settings.target }} + run: cargo zigbuild -p bws --release --target=${{ matrix.settings.target }} - name: Zip linux run: zip -j ./bws-${{ matrix.settings.target }}-${{ env._PACKAGE_VERSION }}.zip ./target/${{ matrix.settings.target }}/release/bws - name: Upload artifact - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: bws-${{ matrix.settings.target }}-${{ env._PACKAGE_VERSION }}.zip path: ./bws-${{ matrix.settings.target }}-${{ env._PACKAGE_VERSION }}.zip @@ -308,15 +305,15 @@ jobs: _PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }} steps: - name: Checkout repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Download x86_64-apple-darwin artifact - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: bws-x86_64-apple-darwin-${{ env._PACKAGE_VERSION }}.zip - name: Download aarch64-apple-darwin artifact - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: bws-aarch64-apple-darwin-${{ env._PACKAGE_VERSION }}.zip @@ -325,7 +322,7 @@ jobs: unzip bws-x86_64-apple-darwin-${{ env._PACKAGE_VERSION }}.zip -d ./bws-x86_64-apple-darwin unzip bws-aarch64-apple-darwin-${{ env._PACKAGE_VERSION }}.zip -d ./bws-aarch64-apple-darwin - - name: lipo create universal package + - name: Create universal package with lipo run: | mkdir ./bws-macos-universal @@ -375,7 +372,7 @@ jobs: - name: Sign binary env: MACOS_CERTIFICATE_NAME: ${{ steps.retrieve-secrets-macos.outputs.macos-bws-certificate-name }} - run: codesign --sign "$MACOS_CERTIFICATE_NAME" --verbose=3 --force --options=runtime --timestamp ./bws-aarch64-apple-darwin/bws + run: codesign --sign "$MACOS_CERTIFICATE_NAME" --verbose=3 --force --options=runtime --timestamp ./bws-macos-universal/bws - name: Notarize app env: @@ -389,7 +386,7 @@ jobs: xcrun notarytool store-credentials "notarytool-profile" --apple-id "$MACOS_NOTARIZATION_APPLE_ID" --team-id "$MACOS_NOTARIZATION_TEAM_ID" --password "$MACOS_NOTARIZATION_PWD" echo "Creating notarization archive" - zip -j ./bws-macos-universal-${{ env._PACKAGE_VERSION }}.zip ./bws-aarch64-apple-darwin/bws + zip -j ./bws-macos-universal-${{ env._PACKAGE_VERSION }}.zip ./bws-macos-universal/bws codesign --sign "$MACOS_CERTIFICATE_NAME" --verbose=3 --force --options=runtime --timestamp ./bws-macos-universal-${{ env._PACKAGE_VERSION }}.zip @@ -397,7 +394,7 @@ jobs: xcrun notarytool submit ./bws-macos-universal-${{ env._PACKAGE_VERSION }}.zip --keychain-profile "notarytool-profile" --wait - name: Upload artifact - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: bws-macos-universal-${{ env._PACKAGE_VERSION }}.zip path: ./bws-macos-universal-${{ env._PACKAGE_VERSION }}.zip @@ -410,10 +407,10 @@ jobs: - setup steps: - name: Checkout repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Install rust - uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # stable + uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable with: toolchain: stable @@ -432,7 +429,7 @@ jobs: sed -i.bak 's/\$NAME\$/Bitwarden Secrets Manager CLI/g' THIRDPARTY.html - name: Upload artifact - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: THIRDPARTY.html path: ./crates/bws/THIRDPARTY.html @@ -441,14 +438,13 @@ jobs: manpages: name: Generate manpages runs-on: ubuntu-22.04 - needs: - - setup + needs: setup steps: - name: Checkout repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Install rust - uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # stable + uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable with: toolchain: stable @@ -464,7 +460,7 @@ jobs: mv $OUT_DIR/manpages . - name: Upload artifact - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: manpages path: ./manpages/* diff --git a/.github/workflows/build-cpp.yml b/.github/workflows/build-cpp.yml index e5bf3ccfb..686369366 100644 --- a/.github/workflows/build-cpp.yml +++ b/.github/workflows/build-cpp.yml @@ -30,15 +30,18 @@ jobs: - os: macos-13 target: x86_64-apple-darwin - # - os: windows-2022 - # target: x86_64-pc-windows-msvc + - os: macos-13 + target: aarch64-apple-darwin + + - os: windows-2022 + target: x86_64-pc-windows-msvc - os: ubuntu-22.04 target: x86_64-unknown-linux-gnu steps: - name: Checkout Repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: install dependencies linux if: runner.os == 'Linux' @@ -52,16 +55,6 @@ jobs: brew install nlohmann-json brew install boost - - name: Cache vcpkg - if: runner.os == 'Windows' - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 - with: - path: C:/vcpkg/ - key: vcpkg-${{ runner.os }}-${{ matrix.settings.target }} - restore-keys: | - vcpkg-${{ runner.os }}- - vcpkg- - - name: Export GitHub Actions cache environment variables if: runner.os == 'Windows' uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 @@ -70,30 +63,14 @@ jobs: core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - - name: Install libraries for Windows - if: runner.os == 'Windows' && steps.cache.outputs.cache-hit != 'true' - env: - VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" - run: | - vcpkg install boost --binarysource="clear;x-gha,readwrite" - vcpkg install nlohmann-json --binarysource="clear;x-gha,readwrite" - shell: pwsh - - - name: Save cache - if: runner.os == 'Windows' - uses: actions/cache/save@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 - with: - path: C:/vcpkg/ - key: vcpkg-${{ runner.os }}-${{ matrix.settings.target }} - - name: Download schemas - uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: sdk-schemas-cpp path: languages/cpp/include - name: Download ${{ matrix.settings.target }} files - uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: libbitwarden_c_files-${{ matrix.settings.target }} path: languages/cpp/include @@ -114,6 +91,18 @@ jobs: Get-Acl languages/cpp/include/* | Format-List + - name: Ensure bitwarden-c is in include folder + working-directory: languages/cpp + shell: bash + run: | + if [[ '${{ runner.os }}' == 'macOS' || '${{ runner.os }}' == 'Linux' ]]; then + ls include/libbitwarden_c.* || { echo "Missing libbitwarden_c.*"; exit 1; } + fi + if [[ '${{ runner.os }}' == 'Windows' ]]; then + ls include/bitwarden_c.dll || { echo "Missing bitwarden_c.dll"; exit 1; } + ls include/bitwarden_c.dll.lib || { echo "Missing bitwarden_c.dll.lib"; exit 1; } + fi + - name: Build unix working-directory: languages/cpp if: runner.os == 'macOS' || runner.os == 'Linux' @@ -139,27 +128,31 @@ jobs: if: runner.os == 'Windows' working-directory: languages/cpp env: - BOOST_INCLUDE_DIR: C:\vcpkg\installed\x64-windows\include\boost - NLOHMANN_JSON_INCLUDE_DIR: C:\vcpkg\installed\x64-windows\include\nlohmann-json + VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" run: | mkdir build cd build - $DNLOHMANN_PATH="C:\vcpkg\installed\x64-windows\include\nlohmann-json" - $DBOOST_PATH="C:\vcpkg\installed\x64-windows\include\boost" - $DTARGET="include/libbitwarden_c.dll" - cmake .. -DNLOHMANN=$DNLOHMANN_PATH -DBOOST=$DBOOST_PATH -DTARGET="include/libbitwarden_c.dll" -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/languages/cpp/ -DBUILD_TESTING=OFF - cmake --build . + $env:DTARGET="include\bitwarden_c.dll.lib" + cmake .. -DTARGET="$env:DTARGET" -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT\scripts\buildsystems\vcpkg.cmake" + cmake --build . --config Release shell: pwsh - name: Copy artifacts working-directory: languages/cpp/build + shell: bash run: | mkdir artifacts - cp libbitwarden_c.* artifacts - cp libBitwardenClient.* artifacts + if [[ '${{ runner.os }}' == 'macOS' || '${{ runner.os }}' == 'Linux' ]]; then + cp libbitwarden_c.* artifacts + cp libBitwardenClient.* artifacts + fi + if [[ '${{ runner.os }}' == 'Windows' ]]; then + cp */BitwardenClient.* artifacts + cp ../include/bitwarden_c.{lib,dll.lib,dll} artifacts + fi - name: Upload C++ package for ${{ matrix.settings.target }} - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: libbitwarden_cpp-${{ matrix.settings.target }} path: languages/cpp/build/artifacts diff --git a/.github/workflows/build-dotnet.yml b/.github/workflows/build-dotnet.yml index 69987ab32..bb5d56b6b 100644 --- a/.github/workflows/build-dotnet.yml +++ b/.github/workflows/build-dotnet.yml @@ -23,7 +23,7 @@ jobs: version: ${{ steps.version.outputs.version }} steps: - name: Checkout repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Install xmllint run: sudo apt-get install -y libxml2-utils @@ -44,39 +44,39 @@ jobs: steps: - name: Checkout Repository - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Download C# schemas artifact - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: schemas.cs path: languages/csharp/Bitwarden.Sdk - name: Set up .NET Core - uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0 + uses: actions/setup-dotnet@6bd8b7f7774af54e05809fcc5431931b3eb1ddee # v4.0.1 with: global-json-file: languages/csharp/global.json - name: Download x86_64-apple-darwin files - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 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@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 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@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: libbitwarden_c_files-x86_64-unknown-linux-gnu path: languages/csharp/Bitwarden.Sdk/linux-x64 - name: Download x86_64-pc-windows-msvc files - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: libbitwarden_c_files-x86_64-pc-windows-msvc path: languages/csharp/Bitwarden.Sdk/windows-x64 @@ -92,7 +92,7 @@ jobs: working-directory: languages/csharp/Bitwarden.Sdk - name: Upload NuGet package - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden.Sdk.${{ needs.version.outputs.version }}.nupkg path: | diff --git a/.github/workflows/build-go.yaml b/.github/workflows/build-go.yaml index 58918aeec..ffb4d6693 100644 --- a/.github/workflows/build-go.yaml +++ b/.github/workflows/build-go.yaml @@ -11,7 +11,7 @@ on: env: GO111MODULE: on - GO_VERSION: "^1.18" + GO_VERSION: "^1.21" jobs: build: @@ -19,10 +19,10 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout Repository - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Setup Go environment - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: go-version: ${{ env.GO_VERSION }} diff --git a/.github/workflows/build-java.yml b/.github/workflows/build-java.yml index 5f71969d6..6994a5a61 100644 --- a/.github/workflows/build-java.yml +++ b/.github/workflows/build-java.yml @@ -1,11 +1,10 @@ +--- name: Build Java SDK on: push: branches: - main - - rc - - hotfix-rc pull_request: workflow_dispatch: @@ -25,40 +24,40 @@ jobs: steps: - name: Checkout Repository - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Download Java schemas artifact - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: sdk-schemas-java path: languages/java/src/main/java/bit/sdk/schema/ - name: Setup Java - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 + uses: actions/setup-java@b36c23c0d998641eff861008f374ee103c25ac73 # v4.4.0 with: distribution: temurin java-version: 17 - name: Download x86_64-apple-darwin files - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: libbitwarden_c_files-x86_64-apple-darwin path: languages/java/src/main/resources/darwin-x86-64 - name: Download aarch64-apple-darwin files - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 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@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: libbitwarden_c_files-x86_64-unknown-linux-gnu path: languages/java/src/main/resources/linux-x86-64 - name: Download x86_64-pc-windows-msvc files - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: libbitwarden_c_files-x86_64-pc-windows-msvc path: languages/java/src/main/resources/win32-x86-64 @@ -66,3 +65,24 @@ jobs: - name: Build Maven run: ./gradlew build working-directory: languages/java + + - name: Upload Java SDK Build + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + with: + name: build + path: ${{ github.workspace }}/languages/java/build + if-no-files-found: error + + - name: Upload Java SDK Build + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + with: + name: resources + path: ${{ github.workspace }}/languages/java/src/main/resources + if-no-files-found: error + + - name: Upload Java SDK Build + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + with: + name: schemas + path: languages/java/src/main/java/bit/sdk/schema + if-no-files-found: error diff --git a/.github/workflows/build-napi.yml b/.github/workflows/build-napi.yml index 87231d5f0..41ed1d17d 100644 --- a/.github/workflows/build-napi.yml +++ b/.github/workflows/build-napi.yml @@ -51,17 +51,17 @@ jobs: strip *.node steps: - name: Checkout repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Setup Node - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version: 18 cache: "npm" cache-dependency-path: crates/bitwarden-napi/package-lock.json - name: Install rust - uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # stable + uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable with: toolchain: stable targets: ${{ matrix.settings.target }} @@ -72,7 +72,7 @@ jobs: key: ${{ matrix.settings.target }}-cargo-${{ matrix.settings.os }} - name: Retrieve schemas - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: schemas.ts path: ${{ github.workspace }}/crates/bitwarden-napi/src-ts/bitwarden_client/ @@ -84,7 +84,7 @@ jobs: run: ${{ matrix.settings.build }} - name: Upload artifact - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: sdk-bitwarden-napi-${{ matrix.settings.target }} path: ${{ github.workspace }}/crates/bitwarden-napi/sdk-napi.*.node diff --git a/.github/workflows/build-python-wheels.yml b/.github/workflows/build-python-wheels.yml index bc718a430..b39195679 100644 --- a/.github/workflows/build-python-wheels.yml +++ b/.github/workflows/build-python-wheels.yml @@ -26,7 +26,7 @@ jobs: package_version: ${{ steps.retrieve-version.outputs.package_version }} steps: - name: Checkout repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Get Package Version id: retrieve-version @@ -63,15 +63,15 @@ jobs: steps: - name: Checkout repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Setup Node - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version: 18 - name: Install rust - uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # stable + uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable with: toolchain: stable targets: ${{ matrix.settings.target }} @@ -82,14 +82,14 @@ jobs: key: ${{ matrix.settings.target }}-cargo-${{ matrix.settings.os }} - name: Retrieve schemas - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: schemas.py path: ${{ github.workspace }}/languages/python/bitwarden_sdk - name: Build wheels if: ${{ matrix.settings.target != 'x86_64-unknown-linux-gnu' }} - uses: PyO3/maturin-action@52b28abb0c6729beb388babfc348bf6ff5aaff31 # v1.42.2 + uses: PyO3/maturin-action@2c5c1560848aaa364c3545136054932db5fa27b7 # v1.44.0 with: target: ${{ matrix.settings.target }} args: --release --find-interpreter --sdist @@ -99,7 +99,7 @@ jobs: - name: Build wheels (Linux - x86_64) if: ${{ matrix.settings.target == 'x86_64-unknown-linux-gnu' }} - uses: PyO3/maturin-action@52b28abb0c6729beb388babfc348bf6ff5aaff31 # v1.42.2 + uses: PyO3/maturin-action@2c5c1560848aaa364c3545136054932db5fa27b7 # v1.44.0 with: target: ${{ matrix.settings.target }} args: --release --find-interpreter --sdist @@ -109,14 +109,14 @@ jobs: working-directory: ${{ github.workspace }}/languages/python - name: Upload wheels - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: bitwarden_sdk-${{ env._PACKAGE_VERSION }}-${{ matrix.settings.target }} path: ${{ github.workspace }}/target/wheels/bitwarden_sdk*.whl - name: Upload sdists if: ${{ matrix.settings.target == 'x86_64-unknown-linux-gnu' }} # we only need one sdist - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: bitwarden_sdk-${{ env._PACKAGE_VERSION }}-sdist path: ${{ github.workspace }}/target/wheels/bitwarden_sdk-*.tar.gz diff --git a/.github/workflows/build-ruby.yml b/.github/workflows/build-ruby.yml new file mode 100644 index 000000000..5a3f1a016 --- /dev/null +++ b/.github/workflows/build-ruby.yml @@ -0,0 +1,95 @@ +--- +name: Build Ruby + +on: + pull_request: + push: + branches: + - "main" + workflow_dispatch: + +jobs: + build: + name: Build Ruby + runs-on: ubuntu-22.04 + steps: + - name: Checkout Repository + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Set up Ruby + uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999 # v1.191.0 + with: + ruby-version: 3.2 + + - name: Download artifacts + uses: bitwarden/gh-actions/download-artifacts@main + with: + workflow: generate_schemas.yml + path: languages/ruby/bitwarden_sdk_secrets/lib + workflow_conclusion: success + branch: ${{ github.ref_name }} + artifacts: schemas.rb + + - name: Download x86_64-apple-darwin artifact + uses: bitwarden/gh-actions/download-artifacts@main + with: + workflow: build-rust-cross-platform.yml + path: temp/macos-x64 + workflow_conclusion: success + branch: ${{ github.ref_name }} + artifacts: libbitwarden_c_files-x86_64-apple-darwin + + - name: Download aarch64-apple-darwin artifact + uses: bitwarden/gh-actions/download-artifacts@main + with: + workflow: build-rust-cross-platform.yml + workflow_conclusion: success + branch: ${{ github.ref_name }} + artifacts: libbitwarden_c_files-aarch64-apple-darwin + path: temp/macos-arm64 + + - name: Download x86_64-unknown-linux-gnu artifact + uses: bitwarden/gh-actions/download-artifacts@main + with: + workflow: build-rust-cross-platform.yml + workflow_conclusion: success + branch: ${{ github.ref_name }} + artifacts: libbitwarden_c_files-x86_64-unknown-linux-gnu + path: temp/linux-x64 + + - name: Download x86_64-pc-windows-msvc artifact + uses: bitwarden/gh-actions/download-artifacts@main + with: + workflow: build-rust-cross-platform.yml + workflow_conclusion: success + branch: ${{ github.ref_name }} + artifacts: libbitwarden_c_files-x86_64-pc-windows-msvc + path: temp/windows-x64 + + - name: Copy lib files + run: | + mkdir -p languages/ruby/bitwarden_sdk_secrets/lib/macos-arm64 + mkdir -p languages/ruby/bitwarden_sdk_secrets/lib/linux-x64 + mkdir -p languages/ruby/bitwarden_sdk_secrets/lib/macos-x64 + mkdir -p languages/ruby/bitwarden_sdk_secrets/lib/windows-x64 + + platforms=("macos-arm64" "linux-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_secrets/lib/${platforms[$i]}/${files[$i]}" + done + + - name: bundle install + run: bundle install + working-directory: languages/ruby/bitwarden_sdk_secrets + + - name: Build gem + run: gem build bitwarden-sdk-secrets.gemspec + working-directory: languages/ruby/bitwarden_sdk_secrets + + - name: Upload artifact + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + with: + path: bitwarden-sdk-secrets-*.gem + name: bitwarden-sdk-secrets diff --git a/.github/workflows/build-rust-crates.yml b/.github/workflows/build-rust-crates.yml index 60a982814..8400fcbf6 100644 --- a/.github/workflows/build-rust-crates.yml +++ b/.github/workflows/build-rust-crates.yml @@ -7,8 +7,6 @@ on: push: branches: - "main" - - "rc" - - "hotfix-rc" env: CARGO_TERM_COLOR: always @@ -17,7 +15,7 @@ jobs: build: name: Building ${{matrix.package}} for - ${{ matrix.os }} - runs-on: ${{ matrix.settings.os || 'ubuntu-latest' }} + runs-on: ${{ matrix.os || 'ubuntu-latest' }} strategy: fail-fast: false @@ -36,13 +34,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Install rust - uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # stable + uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable with: toolchain: stable - targets: ${{ matrix.settings.target }} - name: Cache cargo registry uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 @@ -52,27 +49,19 @@ jobs: env: RUSTFLAGS: "-D warnings" - - name: Build Internal - if: ${{ matrix.package == 'bitwarden' }} - run: cargo build -p ${{ matrix.package }} --features internal --release - env: - RUSTFLAGS: "-D warnings" - release-dry-run: name: Release dry-run runs-on: ubuntu-latest - if: ${{ github.ref == 'refs/head/main' || github.ref == 'refs/head/rc' || github.ref == 'refs/head/hotfix-rc' }} - needs: - - build + if: ${{ github.ref == 'refs/head/main' }} + needs: build steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Install rust - uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # stable + uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable with: toolchain: stable - targets: ${{ matrix.settings.target }} - name: Cache cargo registry uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 diff --git a/.github/workflows/build-rust-cross-platform.yml b/.github/workflows/build-rust-cross-platform.yml index ab56a47e8..1748661b9 100644 --- a/.github/workflows/build-rust-cross-platform.yml +++ b/.github/workflows/build-rust-cross-platform.yml @@ -26,6 +26,9 @@ jobs: target: x86_64-pc-windows-msvc - os: windows-2022 target: x86_64-pc-windows-gnu + # caution: updating the linux runner OS version for GNU + # targets will likely break libbitwarden_c for older OS versions. + # prefer using oldest supported runner for for these targets - os: ubuntu-22.04 target: x86_64-unknown-linux-gnu - os: ubuntu-22.04 @@ -35,17 +38,17 @@ jobs: steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Install rust - uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # stable + uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable with: toolchain: stable - name: Cache cargo registry uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 - - uses: goto-bus-stop/setup-zig@7ab2955eb728f5440978d5824358023be3a2802d # v2.2.0 + - uses: goto-bus-stop/setup-zig@abea47f85e598557f500fa1fd2ab7464fcb39406 # v2.2.1 if: ${{ contains(matrix.settings.target, 'musl') }} with: version: 0.12.0 @@ -57,21 +60,36 @@ jobs: - name: Add build architecture run: rustup target add ${{ matrix.settings.target }} + # Build Rust for musl - name: Build Rust for - ${{ matrix.settings.target }} if: ${{ contains(matrix.settings.target, 'musl') }} env: RUSTFLAGS: "-D warnings" run: cargo zigbuild -p bitwarden-c --target ${{ matrix.settings.target }} --release + # Build Rust for windows-gnu - name: Build Rust for - ${{ matrix.settings.target }} - if: ${{ !contains(matrix.settings.target, 'musl') }} + if: ${{ matrix.settings.target == 'x86_64-pc-windows-gnu' }} env: RUSTFLAGS: "-D warnings" + run: cargo build -p bitwarden-c --target ${{ matrix.settings.target }} --profile=release-windows + + # Build Rust for !musl && !windows-gnu + - name: Build Rust for - ${{ matrix.settings.target }} + if: ${{ !contains(matrix.settings.target, 'musl') && matrix.settings.target != 'x86_64-pc-windows-gnu' }} + env: + RUSTFLAGS: "-D warnings" + MACOSX_DEPLOYMENT_TARGET: "10.14" # allows using new macos runner versions while still supporting older systems run: cargo build -p bitwarden-c --target ${{ matrix.settings.target }} --release - name: Upload Artifact - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + with: + name: libbitwarden_c_files-${{ matrix.settings.target }} + path: target/${{ matrix.settings.target }}/release/*bitwarden_c* + + - name: Upload Artifact + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: libbitwarden_c_files-${{ matrix.settings.target }} - path: | - target/${{ matrix.settings.target }}/release/*bitwarden_c* + path: target/${{ matrix.settings.target }}/release-windows/*bitwarden_c* diff --git a/.github/workflows/build-swift.yml b/.github/workflows/build-swift.yml new file mode 100644 index 000000000..372241348 --- /dev/null +++ b/.github/workflows/build-swift.yml @@ -0,0 +1,115 @@ +--- +name: Build Swift Package + +on: + push: + branches: + - "rc" + - "hotfix-rc" + - "main" + workflow_dispatch: + +jobs: + version: + name: Get Version + runs-on: ubuntu-22.04 + outputs: + package_version: ${{ steps.retrieve-version.outputs.package_version }} + steps: + - name: Checkout repo + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + + - name: Get Package Version + id: retrieve-version + run: | + VERSION=$(grep -o '^version = ".*"' Cargo.toml | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+") + echo "package_version=$VERSION" >> $GITHUB_OUTPUT + + build: + name: Build + runs-on: macos-13 + needs: version + env: + _VERSION: ${{ needs.version.outputs.package_version }} + steps: + - name: Checkout repo + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + + - name: Install rust + uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable + with: + toolchain: stable + + - name: Cache cargo registry + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 + with: + key: sdk-swift-cargo + + - name: Rustup target + run: | + rustup target install aarch64-apple-ios-sim + rustup target install aarch64-apple-ios + rustup target install x86_64-apple-ios + + - name: Build + shell: bash + working-directory: languages/swift + id: build + run: | + ./build.sh + + # SHA Short + echo "short-sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + + - name: Zip BitwardenFFI.xcframework + run: | + mkdir artifacts + cp -rf languages/swift/BitwardenFFI.xcframework artifacts + + - name: Upload BitwardenFFI.xcframework artifact + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + with: + name: BitwardenFFI-${{ env._VERSION }}-${{ steps.build.outputs.short-sha }}.xcframework + path: artifacts + if-no-files-found: error + + - name: Upload BitwardenSdk sources + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + with: + name: BitwardenSdk-${{ env._VERSION }}-${{ steps.build.outputs.short-sha }}-sources + path: languages/swift/Sources/BitwardenSdk + if-no-files-found: error + + trigger-swift-release: + name: Trigger Swift release + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-22.04 + needs: build + steps: + - name: Login to Azure - CI Subscription + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 + with: + creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + + - name: Retrieve github PAT secrets + id: retrieve-secret-pat + uses: bitwarden/gh-actions/get-keyvault-secrets@main + with: + keyvault: "bitwarden-ci" + secrets: "github-pat-bitwarden-devops-bot-repo-scope" + + - name: Trigger Swift release + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + github-token: ${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }} + script: | + await github.rest.actions.createWorkflowDispatch({ + owner: 'bitwarden', + repo: 'sdk', + workflow_id: 'release-swift.yml', + ref: 'main', + inputs: { + 'build-run-id': '${{ github.run_id }}', + 'pre-release': 'true' + } + }) diff --git a/.github/workflows/build-wasm-internal.yml b/.github/workflows/build-wasm-internal.yml new file mode 100644 index 000000000..0be6b29c1 --- /dev/null +++ b/.github/workflows/build-wasm-internal.yml @@ -0,0 +1,59 @@ +--- +name: Build @bitwarden/sdk-internal + +on: + pull_request: + push: + branches: + - "main" + - "rc" + - "hotfix-rc" + workflow_dispatch: + +defaults: + run: + shell: bash + working-directory: crates/bitwarden-wasm-internal + +jobs: + build: + name: Building @bitwarden/sdk-wasm-internal + runs-on: ubuntu-22.04 + + steps: + - name: Checkout repo + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Setup Node + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 + with: + node-version: 20 + registry-url: "https://npm.pkg.github.com" + cache: "npm" + + - name: Install dependencies + run: npm i -g binaryen + + - name: Install rust + uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable + with: + toolchain: stable + targets: wasm32-unknown-unknown + + - name: Cache cargo registry + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 + with: + key: wasm-cargo-cache + + - name: Install wasm-bindgen-cli + run: cargo install wasm-bindgen-cli --version 0.2.95 + + - name: Build + run: ./build.sh -r + + - name: Upload artifact + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + with: + name: sdk-internal + path: ${{ github.workspace }}/languages/js/sdk-internal/* + if-no-files-found: error diff --git a/.github/workflows/build-wasm.yml b/.github/workflows/build-wasm.yml index 760e83254..117bbf2c7 100644 --- a/.github/workflows/build-wasm.yml +++ b/.github/workflows/build-wasm.yml @@ -22,10 +22,10 @@ jobs: steps: - name: Checkout repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Setup Node - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version: 18 registry-url: "https://npm.pkg.github.com" @@ -35,7 +35,7 @@ jobs: run: npm i -g binaryen - name: Install rust - uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # stable + uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable with: toolchain: stable targets: wasm32-unknown-unknown @@ -46,13 +46,13 @@ jobs: key: wasm-cargo-cache - name: Install wasm-bindgen-cli - run: cargo install wasm-bindgen-cli + run: cargo install wasm-bindgen-cli --version 0.2.95 - name: Build run: ./build.sh -r - name: Upload artifact - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: sdk-bitwarden-wasm path: ${{ github.workspace }}/languages/js/wasm/* diff --git a/.github/workflows/cloc.yml b/.github/workflows/cloc.yml index bba74c1dc..644dc0bfd 100644 --- a/.github/workflows/cloc.yml +++ b/.github/workflows/cloc.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set up cloc run: | diff --git a/.github/workflows/direct-minimal-versions.yml b/.github/workflows/direct-minimal-versions.yml index c9d3900ee..22feb04cf 100644 --- a/.github/workflows/direct-minimal-versions.yml +++ b/.github/workflows/direct-minimal-versions.yml @@ -36,10 +36,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Install rust - uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # stable + uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable with: toolchain: nightly targets: ${{ matrix.settings.target }} diff --git a/.github/workflows/generate_schemas.yml b/.github/workflows/generate_schemas.yml index 008ee31e9..434174c7b 100644 --- a/.github/workflows/generate_schemas.yml +++ b/.github/workflows/generate_schemas.yml @@ -19,15 +19,15 @@ jobs: steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Install rust - uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # stable + uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable with: toolchain: stable - name: Set up Node - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: cache: "npm" cache-dependency-path: "package-lock.json" @@ -43,55 +43,55 @@ jobs: run: npm run schemas - name: Upload ts schemas artifact - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.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@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.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@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.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@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: schemas.rb path: ${{ github.workspace }}/languages/ruby/bitwarden_sdk_secrets/lib/schemas.rb if-no-files-found: error - name: Upload json schemas artifact - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.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@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: schemas.go path: ${{ github.workspace }}/languages/go/schema.go - name: Upload java schemas artifact - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: sdk-schemas-java path: ${{ github.workspace }}/languages/java/src/main/java/com/bitwarden/sdk/schema/* if-no-files-found: error - name: Upload cpp schemas artifact - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: sdk-schemas-cpp path: ${{ github.workspace }}/languages/cpp/include/schemas.hpp diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b9b6d4d73..a12e263c4 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,10 +17,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Install rust - uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # stable + uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable with: toolchain: stable @@ -45,12 +45,12 @@ jobs: RUSTFLAGS: "-D warnings" - name: Upload Clippy results to GitHub - uses: github/codeql-action/upload-sarif@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 + uses: github/codeql-action/upload-sarif@461ef6c76dfe95d5c364de2f431ddbd31a417628 # v3.26.9 with: sarif_file: clippy_result.sarif - name: Set up Node - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: cache: "npm" cache-dependency-path: "package-lock.json" diff --git a/.github/workflows/memory-testing.yml b/.github/workflows/memory-testing.yml index c0a9fbecd..41b158536 100644 --- a/.github/workflows/memory-testing.yml +++ b/.github/workflows/memory-testing.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Check out repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set up gdb run: | @@ -30,7 +30,7 @@ jobs: sudo apt -y install gdb - name: Install rust - uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # stable + uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable with: toolchain: stable diff --git a/.github/workflows/minimum-rust-version.yml b/.github/workflows/minimum-rust-version.yml index af309ceed..fa95eb0bb 100644 --- a/.github/workflows/minimum-rust-version.yml +++ b/.github/workflows/minimum-rust-version.yml @@ -27,14 +27,14 @@ jobs: steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Install rust - uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # stable + uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable with: # Important: When updating this, make sure to update the Readme file # and also the `rust-version` field in all the `Cargo.toml`. - toolchain: 1.71.0 + toolchain: 1.75.0 targets: ${{ matrix.settings.target }} - name: Cache cargo registry diff --git a/.github/workflows/release-cli.yml b/.github/workflows/publish-bws.yml similarity index 55% rename from .github/workflows/release-cli.yml rename to .github/workflows/publish-bws.yml index df9715bb1..dc939a5d6 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/publish-bws.yml @@ -1,6 +1,6 @@ --- -name: Release CLI -run-name: Release CLI ${{ inputs.release_type }} +name: Publish bws CLI +run-name: Publish bws CLI ${{ inputs.release_type }} on: workflow_dispatch: @@ -13,10 +13,11 @@ on: options: - Release - Dry Run - -defaults: - run: - shell: bash + version: + description: "Version to publish (default: latest bws cli release)" + required: true + type: string + default: latest env: _AZ_REGISTRY: bitwardenprod.azurecr.io @@ -26,113 +27,66 @@ jobs: name: Setup runs-on: ubuntu-22.04 outputs: - release-version: ${{ steps.version.outputs.version }} + release-version: ${{ steps.version-output.outputs.version }} + release-tag: ${{ steps.version-output.outputs.tag_name }} + deployment-id: ${{ steps.deployment.outputs.deployment_id }} steps: - name: Checkout repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Branch check - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} run: | - if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc-cli" ]]; then + if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then echo "===================================" - echo "[!] Can only release from the 'rc' or 'hotfix-rc-cli' branches" + echo "[!] Can only release from the 'main' branch" echo "===================================" exit 1 fi - - name: Check Release Version - id: version + - name: Version output + id: version-output run: | - VERSION=$(grep -o '^version = ".*"' crates/bws/Cargo.toml | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+") - echo "version=$VERSION" >> $GITHUB_OUTPUT + if [[ "${{ inputs.version }}" == "latest" || "${{ inputs.version }}" == "" ]]; then + TAG_NAME=$(curl "https://api.github.com/repos/bitwarden/sdk/releases" | jq -c '.[] | select(.tag_name | contains("bws")) | .tag_name' | head -1) + VERSION=$(echo $TAG_NAME | grep -ohE '20[0-9]{2}\.([1-9]|1[0-2])\.[0-9]+') + echo "Latest Released Version: $VERSION" + echo "version=$VERSION" >> $GITHUB_OUTPUT + + echo "Latest Released Tag name: $TAG_NAME" + echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT + else + echo "Release Version: ${{ inputs.version }}" + echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT + fi - name: Create GitHub deployment - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7 id: deployment with: token: "${{ secrets.GITHUB_TOKEN }}" initial-status: "in_progress" - environment: "CLI - Production" - description: "Deployment ${{ steps.version.outputs.version }} from branch ${{ github.ref_name }}" + environment: "bws CLI - Production" + description: "Deployment ${{ steps.version-output.outputs.version }} from branch ${{ github.ref_name }}" task: release - - name: Download all Release artifacts - if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@main - with: - workflow: build-cli.yml - path: packages - workflow_conclusion: success - branch: ${{ github.ref_name }} - - - name: Dry Run - Download all artifacts - if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@main - with: - workflow: build-cli.yml - path: packages - workflow_conclusion: success - branch: main - - - name: Get checksum files - uses: bitwarden/gh-actions/get-checksum@main - with: - packages_dir: "packages" - file_path: "packages/bws-sha256-checksums-${{ steps.version.outputs.version }}.txt" - - - name: Create release - if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 - env: - PKG_VERSION: ${{ steps.version.outputs.version }} - with: - artifacts: "packages/bws-x86_64-apple-darwin-${{ env.PKG_VERSION }}.zip, - packages/bws-aarch64-apple-darwin-${{ env.PKG_VERSION }}.zip, - packages/bws-macos-universal-${{ env.PKG_VERSION }}.zip, - packages/bws-x86_64-pc-windows-msvc-${{ env.PKG_VERSION }}.zip, - packages/bws-aarch64-pc-windows-msvc-${{ env.PKG_VERSION }}.zip, - packages/bws-x86_64-unknown-linux-gnu-${{ env.PKG_VERSION }}.zip, - packages/bws-aarch64-unknown-linux-gnu-${{ env.PKG_VERSION }}.zip, - packages/THIRDPARTY.html, - packages/bws-sha256-checksums-${{ env.PKG_VERSION }}.txt" - commit: ${{ github.sha }} - tag: bws-v${{ env.PKG_VERSION }} - name: bws CLI v${{ env.PKG_VERSION }} - body: "" - token: ${{ secrets.GITHUB_TOKEN }} - draft: true - - - name: Update deployment status to Success - if: ${{ github.event.inputs.release_type != 'Dry Run' && success() }} - uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 - with: - token: "${{ secrets.GITHUB_TOKEN }}" - state: "success" - deployment-id: ${{ steps.deployment.outputs.deployment_id }} - - - name: Update deployment status to Failure - if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }} - uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 - with: - token: "${{ secrets.GITHUB_TOKEN }}" - state: "failure" - deployment-id: ${{ steps.deployment.outputs.deployment_id }} - publish: name: Publish bws to crates.io runs-on: ubuntu-22.04 - needs: - - setup + needs: setup + env: + _VERSION: ${{ needs.setup.outputs.release-version }} + _TAG_NAME: ${{ needs.setup.outputs.release-tag }} steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Login to Azure uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + ref: ${{ env._TAG_NAME }} - name: Retrieve secrets id: retrieve-secrets @@ -142,7 +96,7 @@ jobs: secrets: "cratesio-api-token" - name: Install rust - uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # stable + uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable with: toolchain: stable @@ -153,7 +107,7 @@ jobs: run: cargo install cargo-release - name: Cargo release - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} env: PUBLISH_GRACE_SLEEP: 10 CARGO_REGISTRY_TOKEN: ${{ steps.retrieve-secrets.outputs.cratesio-api-token }} @@ -165,7 +119,9 @@ jobs: needs: setup steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + with: + ref: ${{ env._TAG_NAME }} - name: Generate tag list id: tag-list @@ -183,10 +139,10 @@ jobs: ########## Set up Docker ########## - name: Set up QEMU emulators - uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 + uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 + uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1 ########## Login to Docker registries ########## - name: Login to Azure - Prod Subscription @@ -216,7 +172,7 @@ jobs: azure-keyvault-name: "bitwarden-ci" - name: Build and push Docker image - uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0 + uses: docker/build-push-action@32945a339266b759abcbdc89316275140b0fc960 # v6.8.0 with: context: . file: crates/bws/Dockerfile @@ -229,7 +185,31 @@ jobs: "GH_PAT=${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }}" - name: Log out of Docker and disable Docker Notary - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} run: | docker logout echo "DOCKER_CONTENT_TRUST=0" >> $GITHUB_ENV + + update_release_status: + name: Update GitHub deployment status + runs-on: ubuntu-22.04 + needs: setup + if: ${{ inputs.release_type != 'Dry Run' }} + env: + _DEPLOYMENT_ID: ${{ needs.setup.outputs.deployment-id }} + steps: + - name: Update deployment status to Success + if: ${{ inputs.release_type != 'Dry Run' && success() }} + uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 + with: + token: "${{ secrets.GITHUB_TOKEN }}" + state: "success" + deployment-id: ${{ env._DEPLOYMENT_ID }} + + - name: Update deployment status to Failure + if: ${{ inputs.release_type != 'Dry Run' && failure() }} + uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 + with: + token: "${{ secrets.GITHUB_TOKEN }}" + state: "failure" + deployment-id: ${{ env._DEPLOYMENT_ID }} diff --git a/.github/workflows/publish-dotnet.yml b/.github/workflows/publish-dotnet.yml index 7a6573b05..1e2fc06f4 100644 --- a/.github/workflows/publish-dotnet.yml +++ b/.github/workflows/publish-dotnet.yml @@ -12,6 +12,10 @@ on: options: - Release - Dry Run + version: + description: "Release Version" + required: false + default: "latest" env: _KEY_VAULT: "bitwarden-ci" @@ -21,43 +25,58 @@ jobs: name: Setup runs-on: ubuntu-22.04 outputs: - version: ${{ steps.version.outputs.version }} + version: ${{ steps.version-output.outputs.version }} steps: - name: Checkout repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Branch check if: ${{ inputs.release_type != 'Dry Run' }} run: | - if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then + if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then echo "===================================" - echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches" + echo "[!] Can only release from the 'main' branch" echo "===================================" exit 1 fi - - name: Install xmllint - run: sudo apt-get install -y libxml2-utils - - - name: Get version - id: version + - name: Version output + id: version-output run: | - VERSION=$(xmllint --xpath 'string(/Project/PropertyGroup/Version)' languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj) - echo "version=$VERSION" >> $GITHUB_OUTPUT + if [[ "${{ inputs.version }}" == "latest" || "${{ inputs.version }}" == "" ]]; then + TAG_NAME=$(curl "https://api.github.com/repos/bitwarden/sdk/releases" | jq -c '.[] | select(.tag_name | contains("dotnet")) | .tag_name' | head -1) + VERSION=$(echo $TAG_NAME | grep -ohE '20[0-9]{2}\.([1-9]|1[0-2])\.[0-9]+') + echo "Latest Released Version: $VERSION" + echo "version=$VERSION" >> $GITHUB_OUTPUT + + echo "Latest Released Tag name: $TAG_NAME" + echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT + else + echo "Release Version: ${{ inputs.version }}" + echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT + fi deploy: name: Deploy runs-on: ubuntu-22.04 needs: validate steps: - - name: Download NuGet package - uses: bitwarden/gh-actions/download-artifacts@main + - name: Create GitHub deployment + if: ${{ inputs.release_type != 'Dry Run' }} + uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7 + id: deployment with: - workflow: build-dotnet.yml - workflow_conclusion: success - branch: ${{ inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} - artifacts: Bitwarden.Sdk.${{ needs.validate.outputs.version }}.nupkg - path: ./nuget-output + token: "${{ secrets.GITHUB_TOKEN }}" + initial-status: "in_progress" + environment: "dotnet - Production" + description: "Deployment ${{ needs.validate.outputs.version }} from branch ${{ github.ref_name }}" + task: release + + - name: Download artifact + run: | + mkdir -p nuget-output + cd nuget-output + wget https://github.com/bitwarden/sdk/releases/download/dotnet-v${{ needs.validate.outputs.version }}/Bitwarden.Sdk.${{ needs.validate.outputs.version }}.nupkg - name: Login to Azure - Prod Subscription uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 @@ -76,3 +95,19 @@ jobs: env: NUGET_API_KEY: ${{ steps.retrieve-secrets.outputs.nuget-api-key }} run: dotnet nuget push ./nuget-output/*.nupkg -k ${{ env.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json + + - name: Update deployment status to Success + if: ${{ inputs.release_type != 'Dry Run' && success() }} + uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 + with: + token: "${{ secrets.GITHUB_TOKEN }}" + state: "success" + deployment-id: ${{ steps.deployment.outputs.deployment_id }} + + - name: Update deployment status to Failure + if: ${{ inputs.release_type != 'Dry Run' && failure() }} + uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 + with: + token: "${{ secrets.GITHUB_TOKEN }}" + state: "failure" + deployment-id: ${{ steps.deployment.outputs.deployment_id }} diff --git a/.github/workflows/publish-internal.yml b/.github/workflows/publish-internal.yml new file mode 100644 index 000000000..13f3a9d97 --- /dev/null +++ b/.github/workflows/publish-internal.yml @@ -0,0 +1,94 @@ +--- +name: Publish @bitwarden/sdk-internal +run-name: Publish @bitwarden/sdk-internal ${{ inputs.release_type }} + +on: + workflow_dispatch: + inputs: + release_type: + description: "Release Options" + required: true + default: "Release" + type: choice + options: + - Release + - Dry Run + version: + description: "Release Version" + required: true + +defaults: + run: + working-directory: languages/js/sdk-internal + +jobs: + setup: + name: Setup + runs-on: ubuntu-22.04 + steps: + - name: Checkout repo + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Branch check + if: ${{ inputs.release_type != 'Dry Run' }} + run: | + if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then + echo "===================================" + echo "[!] Can only release from the 'main' branch" + echo "===================================" + exit 1 + fi + + npm: + name: Publish NPM + runs-on: ubuntu-22.04 + needs: setup + steps: + - name: Checkout repo + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Setup Node + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + with: + node-version: 20 + + - name: Login to Azure + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 + with: + creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + + - name: Retrieve secrets + id: retrieve-secrets + uses: bitwarden/gh-actions/get-keyvault-secrets@main + with: + keyvault: "bitwarden-ci" + secrets: "npm-api-key" + + - name: Download artifact + uses: bitwarden/gh-actions/download-artifacts@main + with: + workflow: build-wasm-internal.yml + workflow_conclusion: success + branch: ${{ inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} + artifacts: sdk-internal + path: languages/js/sdk-internal + + - name: Set version + run: | + npm version --no-git-tag-version ${{ inputs.version }} + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup NPM + run: | + echo 'registry="https://registry.npmjs.org/"' > ./.npmrc + echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ./.npmrc + + echo 'registry="https://registry.npmjs.org/"' > ~/.npmrc + echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc + env: + NPM_TOKEN: ${{ steps.retrieve-secrets.outputs.npm-api-key }} + + - name: Publish NPM + if: ${{ inputs.release_type != 'Dry Run' }} + run: npm publish --access public --registry=https://registry.npmjs.org/ --userconfig=./.npmrc diff --git a/.github/workflows/publish-java.yml b/.github/workflows/publish-java.yml index 99df6aeda..002a61ee4 100644 --- a/.github/workflows/publish-java.yml +++ b/.github/workflows/publish-java.yml @@ -1,3 +1,4 @@ +--- name: Publish Java SDK run-name: Publish Java SDK ${{ inputs.release_type }} @@ -12,6 +13,15 @@ on: options: - Release - Dry Run + version: + description: "Release Version" + required: false + default: "latest" + +defaults: + run: + shell: bash + working-directory: languages/java env: _KEY_VAULT: "bitwarden-ci" @@ -21,26 +31,37 @@ jobs: name: Setup runs-on: ubuntu-22.04 outputs: - version: ${{ steps.version.outputs.version }} + version: ${{ steps.version-output.outputs.version }} + tag_name: ${{ steps.version-output.outputs.tag_name }} steps: - name: Checkout repo - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Branch check if: ${{ inputs.release_type != 'Dry Run' }} run: | - if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then + if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then echo "===================================" - echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches" + echo "[!] Can only release from the 'main' branch" echo "===================================" exit 1 fi - - name: Get version - id: version + - name: Version output + id: version-output run: | - VERSION=$(cat languages/java/build.gradle | grep -Eo 'version = "[0-9]+\.[0-9]+\.[0-9]+"' | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+') - echo "version=$VERSION" >> $GITHUB_OUTPUT + if [[ "${{ inputs.version }}" == "latest" || "${{ inputs.version }}" == "" ]]; then + TAG_NAME=$(curl "https://api.github.com/repos/bitwarden/sdk/releases" | jq -c '.[] | select(.tag_name | contains("java")) | .tag_name' | head -1) + VERSION=$(echo $TAG_NAME | grep -ohE '20[0-9]{2}\.([1-9]|1[0-2])\.[0-9]+') + echo "Latest Released Version: $VERSION" + echo "version=$VERSION" >> $GITHUB_OUTPUT + + echo "Latest Released Tag name: $TAG_NAME" + echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT + else + echo "Release Version: ${{ inputs.version }}" + echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT + fi publish: name: Publish @@ -48,7 +69,9 @@ jobs: needs: validate steps: - name: Checkout Repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + with: + ref: ${{ needs.validate.outputs.tag_name }} - name: Azure login uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 @@ -64,13 +87,37 @@ jobs: maven-sonartype-ossrh-password" - name: Setup java - uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0 + uses: actions/setup-java@b36c23c0d998641eff861008f374ee103c25ac73 # v4.4.0 with: distribution: temurin java-version: 17 - name: Setup Gradle - uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0 + uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0 + + - name: Download Java SDK Build + uses: bitwarden/gh-actions/download-artifacts@main + with: + workflow: build-java.yml + workflow_conclusion: success + artifacts: build + path: languages/java/build + + - name: Download Java Resources + uses: bitwarden/gh-actions/download-artifacts@main + with: + workflow: build-java.yml + workflow_conclusion: success + artifacts: resources + path: languages/java/src/main/resources + + - name: Download Java Resources + uses: bitwarden/gh-actions/download-artifacts@main + with: + workflow: build-java.yml + workflow_conclusion: success + artifacts: schemas + path: languages/java/src/main/java/bit/sdk/schema - name: Publish package to GitHub Packages if: ${{ inputs.release_type != 'Dry Run' }} diff --git a/.github/workflows/publish-napi.yml b/.github/workflows/publish-napi.yml new file mode 100644 index 000000000..6a284d4a4 --- /dev/null +++ b/.github/workflows/publish-napi.yml @@ -0,0 +1,155 @@ +--- +name: Publish @bitwarden/sdk-napi +run-name: Publish @bitwarden/sdk-napi ${{ inputs.release_type }} + +on: + workflow_dispatch: + inputs: + release_type: + description: "Release Options" + required: true + default: "Release" + type: choice + options: + - Release + - Dry Run + version: + description: "Release Version" + required: false + default: "latest" + +defaults: + run: + working-directory: crates/bitwarden-napi + +jobs: + setup: + name: Setup + runs-on: ubuntu-22.04 + outputs: + release-version: ${{ steps.version-output.outputs.version }} + tag-name: ${{ steps.version-output.outputs.tag_name }} + steps: + - name: Checkout repo + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Branch check + if: ${{ inputs.release_type != 'Dry Run' }} + run: | + if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then + echo "===================================" + echo "[!] Can only release from the 'main' branch" + echo "===================================" + exit 1 + fi + + - name: Version output + id: version-output + run: | + if [[ "${{ inputs.version }}" == "latest" || "${{ inputs.version }}" == "" ]]; then + TAG_NAME=$(curl "https://api.github.com/repos/bitwarden/sdk/releases" | jq -c '.[] | select(.tag_name | contains("napi")) | .tag_name' | head -1) + VERSION=$(echo $TAG_NAME | grep -ohE '20[0-9]{2}\.([1-9]|1[0-2])\.[0-9]+') + echo "Latest Released Version: $VERSION" + echo "version=$VERSION" >> $GITHUB_OUTPUT + + echo "Latest Released Tag name: $TAG_NAME" + echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT + else + echo "Release Version: ${{ inputs.version }}" + echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT + fi + + npm: + name: Publish NPM + runs-on: ubuntu-22.04 + needs: setup + env: + _PKG_VERSION: ${{ needs.setup.outputs.release-version }} + _TAG_NAME: ${{ needs.setup.outputs.tag-name }} + steps: + - name: Checkout repo + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: ${{ env._TAG_NAME }} + + - name: Create GitHub deployment + if: ${{ inputs.release_type != 'Dry Run' }} + uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7 + id: deployment + with: + token: "${{ secrets.GITHUB_TOKEN }}" + initial-status: "in_progress" + environment: "Bitwarden SDK NAPI - Production" + description: "Deployment ${{ env._PKG_VERSION }} from branch ${{ github.ref_name }}" + task: release + + - name: Setup Node + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + with: + node-version: 18 + cache: "npm" + cache-dependency-path: crates/bitwarden-napi/package-lock.json + + - name: Download schemas.ts artifact + run: | + wget https://github.com/bitwarden/sdk/releases/download/napi-v${{ env._PKG_VERSION }}/schemas.ts + mv schemas.ts ${{ github.workspace }}/crates/bitwarden-napi/src-ts/bitwarden_client/schemas.ts + + - name: Install dependencies + run: npm ci + + - name: Run tsc + run: npm run tsc + + - name: Login to Azure + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 + with: + creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + + - name: Retrieve secrets + id: retrieve-secrets + uses: bitwarden/gh-actions/get-keyvault-secrets@main + with: + keyvault: "bitwarden-ci" + secrets: "npm-api-key" + + - name: Download sdk-napi artifacts + run: | + wget https://github.com/bitwarden/sdk/releases/download/napi-v${{ env._PKG_VERSION }}/sdk-napi.darwin-arm64.node + wget https://github.com/bitwarden/sdk/releases/download/napi-v${{ env._PKG_VERSION }}/sdk-napi.darwin-x64.node + wget https://github.com/bitwarden/sdk/releases/download/napi-v${{ env._PKG_VERSION }}/sdk-napi.win32-x64-msvc.node + wget https://github.com/bitwarden/sdk/releases/download/napi-v${{ env._PKG_VERSION }}/sdk-napi.linux-x64-gnu.node + mv sdk-napi.*.node ${{ github.workspace }}/crates/bitwarden-napi/artifacts + + - name: Move artifacts + run: npm run artifacts + + - name: Setup NPM + run: | + echo 'registry="https://registry.npmjs.org/"' > ./.npmrc + echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ./.npmrc + + echo 'registry="https://registry.npmjs.org/"' > ~/.npmrc + echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc + env: + NPM_TOKEN: ${{ steps.retrieve-secrets.outputs.npm-api-key }} + + - name: Publish NPM + if: ${{ inputs.release_type != 'Dry Run' }} + run: npm publish --access public --registry=https://registry.npmjs.org/ --userconfig=./.npmrc + + - name: Update deployment status to Success + if: ${{ inputs.release_type != 'Dry Run' && success() }} + uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 + with: + token: "${{ secrets.GITHUB_TOKEN }}" + state: "success" + deployment-id: ${{ steps.deployment.outputs.deployment_id }} + + - name: Update deployment status to Failure + if: ${{ inputs.release_type != 'Dry Run' && failure() }} + uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 + with: + token: "${{ secrets.GITHUB_TOKEN }}" + state: "failure" + deployment-id: ${{ steps.deployment.outputs.deployment_id }} diff --git a/.github/workflows/publish-php.yml b/.github/workflows/publish-php.yml index 5323cffa0..986923ab5 100644 --- a/.github/workflows/publish-php.yml +++ b/.github/workflows/publish-php.yml @@ -1,3 +1,4 @@ +--- name: Publish PHP SDK run-name: Publish PHP SDK ${{ inputs.release_type }} @@ -24,14 +25,14 @@ jobs: version: ${{ steps.version.outputs.version }} steps: - name: Checkout repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Branch check if: ${{ inputs.release_type != 'Dry Run' }} run: | - if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then + if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then echo "===================================" - echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches" + echo "[!] Can only release from the 'main' branch" echo "===================================" exit 1 fi @@ -47,10 +48,10 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout Repository - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Setup PHP with PECL extension - uses: shivammathur/setup-php@c665c7a15b5295c2488ac8a87af9cb806cd72198 # 2.30.4 + uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 # 2.31.1 with: php-version: "8.0" tools: composer @@ -75,7 +76,7 @@ jobs: _PKG_VERSION: ${{ needs.validate.outputs.version }} steps: - name: Checkout SDK repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: path: sdk @@ -92,7 +93,7 @@ jobs: secrets: "github-pat-bitwarden-devops-bot-repo-scope" - name: Checkout SDK-PHP repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: repository: bitwarden/sm-sdk-php path: sm-sdk-php @@ -123,7 +124,7 @@ jobs: working-directory: sm-sdk-php run: | git add . - git commit -m "Update Go SDK to ${{ github.sha }}" + git commit -m "Update PHP SDK to ${{ github.sha }}" if [[ "${{ inputs.release_type }}" == "Dry Run" ]]; then echo "===================================" @@ -135,7 +136,7 @@ jobs: git push origin main fi - - name: Create release tag on SDK Go repo + - name: Create release tag on PHP SDK repo if: ${{ inputs.release_type != 'Dry Run' }} working-directory: sm-sdk-php run: | @@ -177,7 +178,7 @@ jobs: with: workflow: build-rust-cross-platform.yml workflow_conclusion: success - branch: ${{ inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} + branch: main artifacts: libbitwarden_c_files-x86_64-apple-darwin skip_unpack: true @@ -186,7 +187,7 @@ jobs: with: workflow: build-rust-cross-platform.yml workflow_conclusion: success - branch: ${{ inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} + branch: main artifacts: libbitwarden_c_files-aarch64-apple-darwin skip_unpack: true @@ -195,7 +196,7 @@ jobs: with: workflow: build-rust-cross-platform.yml workflow_conclusion: success - branch: ${{ inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} + branch: main artifacts: libbitwarden_c_files-x86_64-unknown-linux-gnu skip_unpack: true @@ -204,7 +205,7 @@ jobs: with: workflow: build-rust-cross-platform.yml workflow_conclusion: success - branch: ${{ inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} + branch: main artifacts: libbitwarden_c_files-x86_64-pc-windows-msvc skip_unpack: true @@ -254,7 +255,7 @@ jobs: packagist-key" - name: Checkout SDK-PHP repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: repository: bitwarden/sm-sdk-php path: sm-sdk-php diff --git a/.github/workflows/publish-python.yml b/.github/workflows/publish-python.yml index 8eaa28757..7e3395093 100644 --- a/.github/workflows/publish-python.yml +++ b/.github/workflows/publish-python.yml @@ -13,6 +13,10 @@ on: options: - Release - Dry Run + version: + description: "Release Version" + required: false + default: "latest" defaults: run: @@ -22,42 +26,71 @@ jobs: setup: name: Setup runs-on: ubuntu-22.04 + outputs: + version: ${{ steps.version-output.outputs.version }} + tag_name: ${{ steps.version-output.outputs.tag_name }} steps: - name: Checkout repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Branch check - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} run: | - if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then + if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then echo "===================================" - echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches" + echo "[!] Can only release from the 'main' branch" echo "===================================" exit 1 fi + - name: Version output + id: version-output + run: | + if [[ "${{ inputs.version }}" == "latest" || "${{ inputs.version }}" == "" ]]; then + TAG_NAME=$(curl "https://api.github.com/repos/bitwarden/sdk/releases" | jq -c '.[] | select(.tag_name | contains("python")) | .tag_name' | head -1) + VERSION=$(echo $TAG_NAME | grep -ohE '20[0-9]{2}\.([1-9]|1[0-2])\.[0-9]+') + echo "Latest Released Version: $VERSION" + echo "version=$VERSION" >> $GITHUB_OUTPUT + + echo "Latest Released Tag name: $TAG_NAME" + echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT + else + echo "Release Version: ${{ inputs.version }}" + echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT + fi + publish: name: Publish runs-on: ubuntu-22.04 needs: setup steps: + - name: Checkout repo + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: ${{ needs.setup.outputs.tag_name }} + - name: Install Python - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 with: python-version: "3.9" - name: Install twine run: pip install twine - - name: Download artifacts - uses: bitwarden/gh-actions/download-artifacts@main - with: - workflow: build-python-wheels.yml - path: ${{ github.workspace }}/target/wheels/dist - workflow_conclusion: success - branch: ${{ github.event.inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} - name: bitwarden_sdk(.*) - name_is_regexp: true + - name: Get release assets + working-directory: ${{ github.workspace }}/target/wheels/dist + run: | + ARTIFACT_URLS=$(curl -sSL https://api.github.com/repos/bitwarden/sdk/releases/tags/${{ needs.setup.outputs.tag_name }} | jq -r '.assets[].browser_download_url') + for url in $ARTIFACT_URLS; do + wget $url + done + + - name: Unpack release assets + working-directory: ${{ github.workspace }}/target/wheels/dist + run: | + for file in *.zip; do + unzip $file + done - name: Move files working-directory: ${{ github.workspace }}/target/wheels/dist @@ -83,7 +116,7 @@ jobs: run: twine check dist/* - name: Publish - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} working-directory: ${{ github.workspace }}/target/wheels env: TWINE_USERNAME: __token__ @@ -91,7 +124,7 @@ jobs: run: twine upload --repository pypi dist/* - name: Dry Run - Publish - if: ${{ github.event.inputs.release_type == 'Dry Run' }} + if: ${{ inputs.release_type == 'Dry Run' }} working-directory: ${{ github.workspace }}/target/wheels env: TWINE_USERNAME: __token__ diff --git a/.github/workflows/publish-ruby.yml b/.github/workflows/publish-ruby.yml index 51d1cc765..875def064 100644 --- a/.github/workflows/publish-ruby.yml +++ b/.github/workflows/publish-ruby.yml @@ -12,6 +12,10 @@ on: options: - Release - Dry Run + version: + description: "Release Version" + required: false + default: "latest" permissions: contents: read @@ -21,91 +25,62 @@ jobs: setup: name: Setup runs-on: ubuntu-22.04 + outputs: + release-version: ${{ steps.version-output.outputs.version }} + tag-name: ${{ steps.version-output.outputs.tag_name }} steps: - - name: Checkout repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - name: Checkout Repository + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Branch check - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} run: | - if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then + if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then echo "===================================" - echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches" + echo "[!] Can only release from the 'main' branch" echo "===================================" exit 1 fi - publish_ruby: - name: Publish Ruby + - name: Version output + id: version-output + run: | + if [[ "${{ inputs.version }}" == "latest" || "${{ inputs.version }}" == "" ]]; then + TAG_NAME=$(curl "https://api.github.com/repos/bitwarden/sdk/releases" | jq -c '.[] | select(.tag_name | contains("ruby")) | .tag_name' | head -1) + VERSION=$(echo $TAG_NAME | grep -ohE '20[0-9]{2}\.([1-9]|1[0-2])\.[0-9]+') + echo "Latest Released Version: $VERSION" + echo "version=$VERSION" >> $GITHUB_OUTPUT + + echo "Latest Released Tag name: $TAG_NAME" + echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT + else + echo "Release Version: ${{ inputs.version }}" + echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT + fi + + publish: + name: Publish runs-on: ubuntu-22.04 needs: setup + env: + _VERSION: ${{ needs.setup.outputs.release-version }} + _TAG_NAME: ${{ needs.setup.outputs.tag-name }} steps: - name: Checkout Repository - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - - - name: Set up Ruby - uses: ruby/setup-ruby@cacc9f1c0b3f4eb8a16a6bb0ed10897b43b9de49 # v1.176.0 - with: - ruby-version: 3.2 - - - name: Download artifacts - uses: bitwarden/gh-actions/download-artifacts@main - with: - workflow: generate_schemas.yml - path: languages/ruby/bitwarden_sdk_secrets/lib - workflow_conclusion: success - branch: ${{ github.event.inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} - artifacts: schemas.rb - - - name: Download x86_64-apple-darwin artifact - uses: bitwarden/gh-actions/download-artifacts@main - with: - workflow: build-rust-cross-platform.yml - path: temp/macos-x64 - workflow_conclusion: success - branch: ${{ github.event.inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} - artifacts: libbitwarden_c_files-x86_64-apple-darwin - - - name: Download aarch64-apple-darwin artifact - uses: bitwarden/gh-actions/download-artifacts@main - with: - workflow: build-rust-cross-platform.yml - workflow_conclusion: success - branch: ${{ github.event.inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} - artifacts: libbitwarden_c_files-aarch64-apple-darwin - path: temp/macos-arm64 - - - name: Download x86_64-unknown-linux-gnu artifact - uses: bitwarden/gh-actions/download-artifacts@main + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: - workflow: build-rust-cross-platform.yml - workflow_conclusion: success - branch: ${{ github.event.inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} - artifacts: libbitwarden_c_files-x86_64-unknown-linux-gnu - path: temp/linux-x64 + ref: ${{ env._TAG_NAME }} - - name: Download x86_64-pc-windows-msvc artifact - uses: bitwarden/gh-actions/download-artifacts@main + - name: Create GitHub deployment + if: ${{ inputs.release_type != 'Dry Run' }} + uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7 + id: deployment with: - workflow: build-rust-cross-platform.yml - workflow_conclusion: success - branch: ${{ github.event.inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} - artifacts: libbitwarden_c_files-x86_64-pc-windows-msvc - path: temp/windows-x64 - - - name: Copy lib files - run: | - mkdir -p languages/ruby/bitwarden_sdk_secrets/lib/macos-arm64 - mkdir -p languages/ruby/bitwarden_sdk_secrets/lib/linux-x64 - mkdir -p languages/ruby/bitwarden_sdk_secrets/lib/macos-x64 - mkdir -p languages/ruby/bitwarden_sdk_secrets/lib/windows-x64 - - platforms=("macos-arm64" "linux-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_secrets/lib/${platforms[$i]}/${files[$i]}" - done + token: "${{ secrets.GITHUB_TOKEN }}" + initial-status: "in_progress" + environment: "Bitwarden Ruby SDK - Production" + description: "Deployment ${{ env._VERSION }} from branch ${{ github.ref_name }}" + task: release - name: Login to Azure uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 @@ -119,15 +94,11 @@ jobs: keyvault: "bitwarden-ci" secrets: "rubygem-api-key" - - name: bundle install - run: bundle install - working-directory: languages/ruby/bitwarden_sdk_secrets - - - name: Build gem - run: gem build bitwarden-sdk-secrets.gemspec - working-directory: languages/ruby/bitwarden_sdk_secrets + - name: Download ruby artifact + run: wget https://github.com/bitwarden/sdk/releases/download/ruby-v${{ env._VERSION }}/bitwarden-sdk-secrets-${{ env._VERSION }}.gem - name: Push gem to Rubygems + if: ${{ inputs.release_type != 'Dry Run' }} run: | mkdir -p $HOME/.gem touch $HOME/.gem/credentials @@ -137,3 +108,19 @@ jobs: env: GEM_HOST_API_KEY: ${{ steps.retrieve-secrets.outputs.rubygem-api-key }} working-directory: languages/ruby/bitwarden_sdk_secrets + + - name: Update deployment status to Success + if: ${{ inputs.release_type != 'Dry Run' && success() }} + uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 + with: + token: "${{ secrets.GITHUB_TOKEN }}" + state: "success" + deployment-id: ${{ steps.deployment.outputs.deployment_id }} + + - name: Update deployment status to Failure + if: ${{ inputs.release_type != 'Dry Run' && failure() }} + uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 + with: + token: "${{ secrets.GITHUB_TOKEN }}" + state: "failure" + deployment-id: ${{ steps.deployment.outputs.deployment_id }} diff --git a/.github/workflows/publish-rust-crates.yml b/.github/workflows/publish-rust-crates.yml index 399810d9b..bb79aad24 100644 --- a/.github/workflows/publish-rust-crates.yml +++ b/.github/workflows/publish-rust-crates.yml @@ -14,137 +14,58 @@ on: - Initial Release - Redeploy - Dry Run - publish_bitwarden: - description: "Publish bitwarden crate" + version: + description: "Version to publish (default: latest rust crates release)" required: true - default: true - type: boolean - publish_bitwarden-api-api: - description: "Publish bitwarden-api-api crate" - required: true - default: true - type: boolean - publish_bitwarden-api-identity: - description: "Publish bitwarden-api-identity crate" - required: true - default: true - type: boolean - publish_bitwarden-crypto: - description: "Publish bitwarden-crypto crate" - required: true - default: true - type: boolean - publish_bitwarden-cli: - description: "Publish bitwarden-cli crate" - required: true - default: true - type: boolean - publish_bitwarden-generators: - description: "Publish bitwarden-generators crate" - required: true - default: true - type: boolean - publish_bitwarden-exporters: - description: "Publish bitwarden-exporters crate" - required: true - default: true - type: boolean - -defaults: - run: - shell: bash + type: string + default: latest jobs: setup: - name: Setup + name: setup runs-on: ubuntu-22.04 outputs: - packages_list: ${{ steps.packages-list.outputs.packages_list }} - packages_command: ${{ steps.packages-list.outputs.packages_command }} + release-version: ${{ steps.version-output.outputs.version }} + release-tag: ${{ steps.version-output.outputs.tag_name }} steps: - - name: Checkout repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - name: Checkout + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Branch check - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} run: | - if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then + if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then echo "===================================" - echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches" + echo "[!] Can only release from the 'main' branch" echo "===================================" exit 1 fi - - name: Prepare packages list - id: packages-list - env: - PUBLISH_BITWARDEN: ${{ github.event.inputs.publish_bitwarden }} - PUBLISH_BITWARDEN_API_API: ${{ github.event.inputs.publish_bitwarden-api-api }} - PUBLISH_BITWARDEN_API_IDENTITY: ${{ github.event.inputs.publish_bitwarden-api-identity }} - PUBLISH_BITWARDEN_CRYPTO: ${{ github.event.inputs.publish_bitwarden-crypto }} - PUBLISH_BITWARDEN_CLI: ${{ github.event.inputs.publish_bitwarden-cli }} - PUBLISH_BITWARDEN_GENERATORS: ${{ github.event.inputs.publish_bitwarden-generators }} - PUBLISH_BITWARDEN_EXPORTERS: ${{ github.event.inputs.publish_bitwarden-exporters }} + - name: Version output + id: version-output run: | - if [[ "$PUBLISH_BITWARDEN" == "false" ]] && [[ "$PUBLISH_BITWARDEN_API_API" == "false" ]] && [[ "$PUBLISH_BITWARDEN_API_IDENTITY" == "false" ]]; then - echo "===================================" - echo "[!] You need to specify at least one crate for release!" - echo "===================================" - exit 1 + if [[ "${{ inputs.version }}" == "latest" || "${{ inputs.version }}" == "" ]]; then + TAG_NAME=$(curl "https://api.github.com/repos/bitwarden/sdk/releases" | jq -c '.[] | select(.tag_name | contains("rust")) | .tag_name' | head -1) + VERSION=$(echo $TAG_NAME | grep -ohE '20[0-9]{2}\.([1-9]|1[0-2])\.[0-9]+') + echo "Latest Released Version: $VERSION" + echo "version=$VERSION" >> $GITHUB_OUTPUT + + echo "Latest Released Tag name: $TAG_NAME" + echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT + else + echo "Release Version: ${{ inputs.version }}" + echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT fi - PACKAGES_COMMAND="" - PACKAGES_LIST="" - - if [[ "$PUBLISH_BITWARDEN" == "true" ]] ; then - PACKAGES_COMMAND="$PACKAGES_COMMAND -p bitwarden" - PACKAGES_LIST="$PACKAGES_LIST bitwarden" - fi - - if [[ "$PUBLISH_BITWARDEN_API_API" == "true" ]]; then - PACKAGES_COMMAND="$PACKAGES_COMMAND -p bitwarden-api-api" - PACKAGES_LIST="$PACKAGES_LIST bitwarden-api-api" - fi - - if [[ "$PUBLISH_BITWARDEN_API_IDENTITY" == "true" ]]; then - PACKAGES_COMMAND="$PACKAGES_COMMAND -p bitwarden-api-identity" - PACKAGES_LIST="$PACKAGES_LIST bitwarden-api-identity" - fi - - if [[ "$PUBLISH_BITWARDEN_CRYPTO" == "true" ]]; then - PACKAGES_COMMAND="$PACKAGES_COMMAND -p bitwarden-crypto" - PACKAGES_LIST="$PACKAGES_LIST bitwarden-crypto" - fi - - if [[ "$PUBLISH_BITWARDEN_CLI" == "true" ]]; then - PACKAGES_COMMAND="$PACKAGES_COMMAND -p bitwarden-cli" - PACKAGES_LIST="$PACKAGES_LIST bitwarden-cli" - fi - - if [[ "$PUBLISH_BITWARDEN_GENERATORS" == "true" ]]; then - PACKAGES_COMMAND="$PACKAGES_COMMAND -p bitwarden-generators" - PACKAGES_LIST="$PACKAGES_LIST bitwarden-generators" - fi - - if [[ "$PUBLISH_BITWARDEN_EXPORTERS" == "true" ]]; then - PACKAGES_COMMAND="$PACKAGES_COMMAND -p bitwarden-exporters" - PACKAGES_LIST="$PACKAGES_LIST bitwarden-exporters" - fi - - echo "Packages command: " $PACKAGES_COMMAND - echo "Packages list: " $PACKAGES_LIST - - echo "packages_list=$PACKAGES_LIST" >> $GITHUB_OUTPUT - echo "packages_command=$PACKAGES_COMMAND" >> $GITHUB_OUTPUT - publish: - name: Publish ${{ needs.setup.outputs.packages_list }} - runs-on: ubuntu-latest - needs: - - setup + name: Publish + runs-on: ubuntu-22.04 + needs: setup steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: ${{ needs.setup.outputs.release-tag }} - name: Login to Azure uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 @@ -159,7 +80,7 @@ jobs: secrets: "cratesio-api-token" - name: Install rust - uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # stable + uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable with: toolchain: stable @@ -170,26 +91,25 @@ jobs: run: cargo install cargo-release - name: Create GitHub deployment - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7 id: deployment with: token: "${{ secrets.GITHUB_TOKEN }}" initial-status: "in_progress" - environment: "Bitwarden SDK to crates.io: ${{ needs.setup.outputs.packages_list }}" + environment: "Bitwarden SDK" description: "Deployment from branch ${{ github.ref_name }}" task: release - name: Cargo release - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} env: PUBLISH_GRACE_SLEEP: 10 - PACKAGES_PUBLISH: ${{ needs.setup.outputs.packages_command }} CARGO_REGISTRY_TOKEN: ${{ steps.retrieve-secrets.outputs.cratesio-api-token }} - run: cargo-release release publish $PACKAGES_PUBLISH --execute --no-confirm + run: cargo-release release publish --exclude bw --exclude bws --execute --no-confirm - name: Update deployment status to Success - if: ${{ github.event.inputs.release_type != 'Dry Run' && success() }} + if: ${{ inputs.release_type != 'Dry Run' && success() }} uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 with: token: "${{ secrets.GITHUB_TOKEN }}" @@ -197,7 +117,7 @@ jobs: deployment-id: ${{ steps.deployment.outputs.deployment_id }} - name: Update deployment status to Failure - if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }} + if: ${{ inputs.release_type != 'Dry Run' && failure() }} uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 with: token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/publish-wasm.yml b/.github/workflows/publish-wasm.yml new file mode 100644 index 000000000..95a86a0c4 --- /dev/null +++ b/.github/workflows/publish-wasm.yml @@ -0,0 +1,138 @@ +--- +name: Publish @bitwarden/sdk-wasm +run-name: Publish @bitwarden/sdk-wasm ${{ inputs.release_type }} + +on: + workflow_dispatch: + inputs: + release_type: + description: "Release Options" + required: true + default: "Release" + type: choice + options: + - Release + - Dry Run + version: + description: "Release Version" + required: false + default: "latest" + +defaults: + run: + working-directory: languages/js/wasm + +jobs: + setup: + name: Setup + runs-on: ubuntu-22.04 + outputs: + release-version: ${{ steps.version-output.outputs.version }} + tag_name: ${{ steps.version-output.outputs.tag_name }} + steps: + - name: Checkout repo + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Branch check + if: ${{ inputs.release_type != 'Dry Run' }} + run: | + if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then + echo "===================================" + echo "[!] Can only release from the 'main' branch" + echo "===================================" + exit 1 + fi + + - name: Version output + id: version-output + run: | + if [[ "${{ inputs.version }}" == "latest" || "${{ inputs.version }}" == "" ]]; then + TAG_NAME=$(curl "https://api.github.com/repos/bitwarden/sdk/releases" | jq -c '.[] | select(.tag_name | contains("napi")) | .tag_name' | head -1) + VERSION=$(echo $TAG_NAME | grep -ohE '20[0-9]{2}\.([1-9]|1[0-2])\.[0-9]+') + echo "Latest Released Version: $VERSION" + echo "version=$VERSION" >> $GITHUB_OUTPUT + + echo "Latest Released Tag name: $TAG_NAME" + echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT + else + echo "Release Version: ${{ inputs.version }}" + echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT + fi + + npm: + name: Publish NPM + runs-on: ubuntu-22.04 + needs: setup + env: + _VERSION: ${{ needs.setup.outputs.release-version }} + steps: + - name: Checkout repo + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: ${{ needs.setup.outputs.tag_name }} + + - name: Setup Node + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + with: + node-version: 18 + cache: "npm" + + - name: Login to Azure + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 + with: + creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + + - name: Retrieve secrets + id: retrieve-secrets + uses: bitwarden/gh-actions/get-keyvault-secrets@main + with: + keyvault: "bitwarden-ci" + secrets: "npm-api-key" + + - name: Download artifact + run: | + cd ${{ github.workspace }}/languages/js/wasm + wget https://github.com/bitwarden/sdk/releases/download/wasm-v${{ env._VERSION }}/sdk-bitwarden-wasm.zip + unzip sdk-bitwarden-wasm.zip + rm sdk-bitwarden-wasm.zip + + - name: Create GitHub deployment + if: ${{ inputs.release_type != 'Dry Run' }} + uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7 + id: deployment + with: + token: "${{ secrets.GITHUB_TOKEN }}" + initial-status: "in_progress" + environment: "Bitwarden SDK WASM - Production" + description: "Deployment ${{ env._VERSION }} from branch ${{ github.ref_name }}" + task: release + + - name: Setup NPM + run: | + echo 'registry="https://registry.npmjs.org/"' > ./.npmrc + echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ./.npmrc + + echo 'registry="https://registry.npmjs.org/"' > ~/.npmrc + echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc + env: + NPM_TOKEN: ${{ steps.retrieve-secrets.outputs.npm-api-key }} + + - name: Publish NPM + if: ${{ inputs.release_type != 'Dry Run' }} + run: npm publish --access public --registry=https://registry.npmjs.org/ --userconfig=./.npmrc + + - name: Update deployment status to Success + if: ${{ inputs.release_type != 'Dry Run' && success() }} + uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 + with: + token: "${{ secrets.GITHUB_TOKEN }}" + state: "success" + deployment-id: ${{ steps.deployment.outputs.deployment_id }} + + - name: Update deployment status to Failure + if: ${{ inputs.release_type != 'Dry Run' && failure() }} + uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 + with: + token: "${{ secrets.GITHUB_TOKEN }}" + state: "failure" + deployment-id: ${{ steps.deployment.outputs.deployment_id }} diff --git a/.github/workflows/release-bws.yml b/.github/workflows/release-bws.yml new file mode 100644 index 000000000..92a8544b8 --- /dev/null +++ b/.github/workflows/release-bws.yml @@ -0,0 +1,77 @@ +--- +name: Release bws CLI +run-name: Release bws CLI ${{ inputs.release_type }} + +on: + workflow_dispatch: + inputs: + release_type: + description: "Release Options" + required: true + default: "Release" + type: choice + options: + - Release + - Dry Run + +jobs: + setup: + name: Setup + runs-on: ubuntu-22.04 + outputs: + release-version: ${{ steps.version.outputs.version }} + steps: + - name: Checkout repo + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Branch check + if: ${{ inputs.release_type != 'Dry Run' }} + run: | + if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then + echo "===================================" + echo "[!] Can only release from the 'main' branch" + echo "===================================" + exit 1 + fi + + - name: Check Release Version + id: version + run: | + VERSION=$(grep -o '^version = ".*"' crates/bws/Cargo.toml | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+") + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Download all Release artifacts + uses: bitwarden/gh-actions/download-artifacts@main + with: + workflow: build-cli.yml + path: packages + workflow_conclusion: success + branch: ${{ github.ref_name }} + + - name: Get checksum files + uses: bitwarden/gh-actions/get-checksum@main + with: + packages_dir: "packages" + file_path: "packages/bws-sha256-checksums-${{ steps.version.outputs.version }}.txt" + + - name: Create release + if: ${{ inputs.release_type != 'Dry Run' }} + uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 + env: + PKG_VERSION: ${{ steps.version.outputs.version }} + with: + artifacts: "packages/bws-x86_64-apple-darwin-${{ env.PKG_VERSION }}.zip, + packages/bws-aarch64-apple-darwin-${{ env.PKG_VERSION }}.zip, + packages/bws-macos-universal-${{ env.PKG_VERSION }}.zip, + packages/bws-x86_64-pc-windows-msvc-${{ env.PKG_VERSION }}.zip, + packages/bws-aarch64-pc-windows-msvc-${{ env.PKG_VERSION }}.zip, + packages/bws-x86_64-unknown-linux-gnu-${{ env.PKG_VERSION }}.zip, + packages/bws-aarch64-unknown-linux-gnu-${{ env.PKG_VERSION }}.zip, + packages/THIRDPARTY.html, + packages/bws-sha256-checksums-${{ env.PKG_VERSION }}.txt" + commit: ${{ github.sha }} + tag: bws-v${{ env.PKG_VERSION }} + name: bws CLI v${{ env.PKG_VERSION }} + body: "" + token: ${{ secrets.GITHUB_TOKEN }} + draft: true diff --git a/.github/workflows/release-cpp.yml b/.github/workflows/release-cpp.yml index 65517eb39..47199a99f 100644 --- a/.github/workflows/release-cpp.yml +++ b/.github/workflows/release-cpp.yml @@ -24,14 +24,14 @@ jobs: version: ${{ steps.version.outputs.version }} steps: - name: Checkout repo - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Branch check if: ${{ inputs.release_type != 'Dry Run' }} run: | - if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then + if [[ "$GITHUB_REF" != "refs/heads/main" ]] ; then echo "===================================" - echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches" + echo "[!] Can only release from the 'main' branches" echo "===================================" exit 1 fi @@ -45,14 +45,12 @@ jobs: github-release: name: GitHub Release runs-on: ubuntu-22.04 - needs: - - repo-sync - - validate + needs: validate env: _PKG_VERSION: ${{ needs.validate.outputs.version }} steps: - name: Login to Azure - Prod Subscription - uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 with: creds: ${{ secrets.AZURE_CI_SERVICE_PRINCIPAL }} @@ -151,7 +149,7 @@ jobs: - name: Create release if: ${{ inputs.release_type != 'Dry Run' }} - uses: ncipollo/release-action@6c75be85e571768fa31b40abf38de58ba0397db5 # v1.13.0 + uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 with: tag: cpp-sdk-v${{ env._PKG_VERSION }} name: "C++ SDK v${{ env._PKG_VERSION }}" diff --git a/.github/workflows/release-dotnet.yml b/.github/workflows/release-dotnet.yml new file mode 100644 index 000000000..dc8539271 --- /dev/null +++ b/.github/workflows/release-dotnet.yml @@ -0,0 +1,75 @@ +name: Release .NET NuGet +run-name: Release .NET NuGet Package ${{ inputs.release_type }} + +on: + workflow_dispatch: + inputs: + release_type: + description: "Release Options" + required: true + default: "Release" + type: choice + options: + - Release + - Dry Run + +jobs: + setup: + name: Setup + runs-on: ubuntu-22.04 + outputs: + version: ${{ steps.version.outputs.version }} + steps: + - name: Checkout repo + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Branch check + if: ${{ inputs.release_type != 'Dry Run' }} + run: | + if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then + echo "===================================" + echo "[!] Can only release from the 'main' branch" + echo "===================================" + exit 1 + fi + + - name: Install xmllint + run: sudo apt-get install -y libxml2-utils + + - name: Get version + id: version + run: | + VERSION=$(xmllint --xpath 'string(/Project/PropertyGroup/Version)' languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj) + echo "version=$VERSION" >> $GITHUB_OUTPUT + + release: + name: Create GitHub release + runs-on: ubuntu-22.04 + needs: setup + steps: + - name: Checkout Repository + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Download NuGet package + uses: bitwarden/gh-actions/download-artifacts@main + with: + workflow: build-dotnet.yml + workflow_conclusion: success + branch: main + artifacts: Bitwarden.Sdk.${{ needs.setup.outputs.version }}.nupkg + path: ./nuget-output + + - name: Create release + if: ${{ inputs.release_type != 'Dry Run' }} + uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 + env: + PKG_VERSION: ${{ needs.setup.outputs.version }} + with: + commit: ${{ github.sha }} + tag: dotnet-v${{ env.PKG_VERSION }} + name: .NET NuGet v${{ env.PKG_VERSION }} + body: "" + token: ${{ secrets.GITHUB_TOKEN }} + draft: true + artifacts: | + ./nuget-output/Bitwarden.Sdk.${{ needs.setup.outputs.version }}.nupkg diff --git a/.github/workflows/release-go.yml b/.github/workflows/release-go.yml index d73536f38..f88e52af5 100644 --- a/.github/workflows/release-go.yml +++ b/.github/workflows/release-go.yml @@ -15,7 +15,7 @@ on: env: GO111MODULE: on - GO_VERSION: "^1.18" + GO_VERSION: "^1.21" _KEY_VAULT: "bitwarden-ci" jobs: @@ -26,7 +26,7 @@ jobs: version: ${{ steps.version.outputs.version }} steps: - name: Checkout repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Branch check if: ${{ inputs.release_type != 'Dry Run' }} @@ -55,7 +55,7 @@ jobs: steps: - name: Checkout SDK repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: path: sdk @@ -72,7 +72,7 @@ jobs: secrets: "github-pat-bitwarden-devops-bot-repo-scope" - name: Checkout SDK-Go repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: repository: bitwarden/sdk-go path: sdk-go diff --git a/.github/workflows/release-java.yml b/.github/workflows/release-java.yml new file mode 100644 index 000000000..6898932d5 --- /dev/null +++ b/.github/workflows/release-java.yml @@ -0,0 +1,61 @@ +name: Release Java SDK +run-name: Release Java SDK ${{ inputs.release_type }} + +on: + workflow_dispatch: + inputs: + release_type: + description: "Release Options" + required: true + default: "Release" + type: choice + options: + - Release + - Dry Run + +jobs: + setup: + name: Setup + runs-on: ubuntu-22.04 + outputs: + version: ${{ steps.version.outputs.version }} + steps: + - name: Checkout repo + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Branch check + if: ${{ inputs.release_type != 'Dry Run' }} + run: | + if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then + echo "===================================" + echo "[!] Can only release from the 'main' branch" + echo "===================================" + exit 1 + fi + + - name: Get version + id: version + run: | + VERSION=$(cat languages/java/build.gradle | grep -Eo 'version = "[0-9]+\.[0-9]+\.[0-9]+"' | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+') + echo "version=$VERSION" >> $GITHUB_OUTPUT + + release: + name: Release + runs-on: ubuntu-22.04 + needs: setup + steps: + - name: Checkout Repository + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Create release + if: ${{ inputs.release_type != 'Dry Run' }} + uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 + env: + PKG_VERSION: ${{ needs.setup.outputs.version }} + with: + commit: ${{ github.sha }} + tag: java-v${{ env.PKG_VERSION }} + name: Java SDK v${{ env.PKG_VERSION }} + body: "" + token: ${{ secrets.GITHUB_TOKEN }} + draft: true diff --git a/.github/workflows/release-napi.yml b/.github/workflows/release-napi.yml index f0f1bd667..761515c73 100644 --- a/.github/workflows/release-napi.yml +++ b/.github/workflows/release-napi.yml @@ -14,11 +14,6 @@ on: - Initial Release - Redeploy - Dry Run - npm_publish: - description: "Publish to NPM registry" - required: true - default: true - type: boolean defaults: run: @@ -30,17 +25,17 @@ jobs: name: Setup runs-on: ubuntu-22.04 outputs: - release-version: ${{ steps.version.outputs.version }} + version: ${{ steps.version.outputs.version }} steps: - name: Checkout repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Branch check - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} run: | - if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then + if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then echo "===================================" - echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches" + echo "[!] Can only release from the 'main' branch" echo "===================================" exit 1 fi @@ -49,125 +44,53 @@ jobs: id: version uses: bitwarden/gh-actions/release-version-check@main with: - release-type: ${{ github.event.inputs.release_type }} + release-type: ${{ inputs.release_type }} project-type: ts file: crates/bitwarden-napi/package.json monorepo: false - - name: Create GitHub deployment - if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7 - id: deployment - with: - token: "${{ secrets.GITHUB_TOKEN }}" - initial-status: "in_progress" - environment: "Bitwarden SDK NAPI - Production" - description: "Deployment ${{ steps.version.outputs.version }} from branch ${{ github.ref_name }}" - task: release - - - name: Update deployment status to Success - if: ${{ github.event.inputs.release_type != 'Dry Run' && success() }} - uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 - with: - token: "${{ secrets.GITHUB_TOKEN }}" - state: "success" - deployment-id: ${{ steps.deployment.outputs.deployment_id }} - - - name: Update deployment status to Failure - if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }} - uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 - with: - token: "${{ secrets.GITHUB_TOKEN }}" - state: "failure" - deployment-id: ${{ steps.deployment.outputs.deployment_id }} - - npm: - name: Publish NPM + release: + name: Create GitHub release runs-on: ubuntu-22.04 needs: setup - if: inputs.npm_publish - env: - _PKG_VERSION: ${{ needs.setup.outputs.release-version }} steps: - - name: Checkout repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - - - name: Setup Node - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 - with: - node-version: 18 - cache: "npm" - cache-dependency-path: crates/bitwarden-napi/package-lock.json - - - name: Download schemas - if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@main - with: - workflow: build-napi.yml - artifacts: schemas.ts - path: ${{ github.workspace }}/crates/bitwarden-napi/src-ts/bitwarden_client/ - workflow_conclusion: success - branch: ${{ github.ref_name }} - - - name: Dry Run - Download schemas - if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@main - with: - workflow: build-napi.yml - artifacts: schemas.ts - path: ${{ github.workspace }}/crates/bitwarden-napi/src-ts/bitwarden_client/ - workflow_conclusion: success - branch: main - - - name: Install dependencies - run: npm ci - - - name: Run tsc - run: npm run tsc - - - name: Login to Azure - uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 - with: - creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - - - name: Retrieve secrets - id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@main - with: - keyvault: "bitwarden-ci" - secrets: "npm-api-key" + - name: Checkout Repository + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Download artifacts - if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@main + uses: dawidd6/action-download-artifact@bf251b5aa9c2f7eeb574a96ee720e24f801b7c11 # v6 with: workflow: build-napi.yml - path: ${{ github.workspace }}/crates/bitwarden-napi/artifacts - workflow_conclusion: success - branch: ${{ github.ref_name }} - - - name: Dry Run - Download artifacts - if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@main - with: - workflow: build-napi.yml - path: ${{ github.workspace }}/crates/bitwarden-napi/artifacts workflow_conclusion: success branch: main + name: sdk-bitwarden-napi-(.*)|schemas.ts + name_is_regexp: true + path: dist - - name: Move artifacts - run: npm run artifacts - - - name: Setup NPM + - name: Move artifact files to single directory run: | - echo 'registry="https://registry.npmjs.org/"' > ./.npmrc - echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ./.npmrc - - echo 'registry="https://registry.npmjs.org/"' > ~/.npmrc - echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc + ls -alhR + shopt -s globstar + mv **/*.node . + mv schemas.ts/ schemas/ + mv schemas/schemas.ts . + working-directory: dist + + - name: Create release + if: ${{ inputs.release_type != 'Dry Run' }} + uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 env: - NPM_TOKEN: ${{ steps.retrieve-secrets.outputs.npm-api-key }} - - - name: Publish NPM - if: ${{ github.event.inputs.release_type != 'Dry Run' }} - run: npm publish --access public --registry=https://registry.npmjs.org/ --userconfig=./.npmrc + _VERSION: ${{ needs.setup.outputs.version }} + with: + commit: ${{ github.sha }} + tag: napi-v${{ env._VERSION }} + name: napi v${{ env._VERSION }} + body: "" + token: ${{ secrets.GITHUB_TOKEN }} + draft: true + artifacts: | + dist/sdk-napi.darwin-arm64.node + dist/sdk-napi.darwin-x64.node + dist/sdk-napi.win32-x64-msvc.node + dist/sdk-napi.linux-x64-gnu.node + dist/schemas.ts diff --git a/.github/workflows/release-python.yml b/.github/workflows/release-python.yml new file mode 100644 index 000000000..5be1a12fc --- /dev/null +++ b/.github/workflows/release-python.yml @@ -0,0 +1,80 @@ +--- +name: Release Python SDK +run-name: Release Python SDK ${{ inputs.release_type }} + +on: + workflow_dispatch: + inputs: + release_type: + description: "Release Options" + required: true + default: "Release" + type: choice + options: + - Release + - Dry Run + +jobs: + setup: + name: Setup + runs-on: ubuntu-22.04 + outputs: + version: ${{ steps.version.outputs.version }} + steps: + - name: Checkout repo + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Branch check + if: ${{ inputs.release_type != 'Dry Run' }} + run: | + if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then + echo "===================================" + echo "[!] Can only release from the 'main' branch" + echo "===================================" + exit 1 + fi + + - name: Get version + id: version + run: | + VERSION=$(cat languages/python/pyproject.toml | grep -Eo 'version = "[0-9]+\.[0-9]+\.[0-9]+"' | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+') + echo "version=$VERSION" >> $GITHUB_OUTPUT + + release: + name: Release + runs-on: ubuntu-22.04 + needs: setup + steps: + - name: Checkout repo + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Download artifacts + uses: dawidd6/action-download-artifact@bf251b5aa9c2f7eeb574a96ee720e24f801b7c11 # v6 + with: + workflow: build-python-wheels.yml + path: ${{ github.workspace }}/target/wheels/dist + workflow_conclusion: success + branch: main + name: bitwarden_sdk(.*) + name_is_regexp: true + + - name: Move all whl files to single directory + run: | + shopt -s globstar + mv **/*.whl . + working-directory: ${{ github.workspace }}/target/wheels/dist + + - name: Create GitHub release + if: ${{ inputs.release_type != 'Dry Run' }} + uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 + env: + PKG_VERSION: ${{ needs.setup.outputs.version }} + with: + commit: ${{ github.sha }} + tag: python-v${{ env.PKG_VERSION }} + name: Python v${{ env.PKG_VERSION }} + body: "" + token: ${{ secrets.GITHUB_TOKEN }} + draft: true + artifacts: | + ${{ github.workspace }}/target/wheels/dist/bitwarden_sdk-*.whl diff --git a/.github/workflows/release-ruby.yml b/.github/workflows/release-ruby.yml new file mode 100644 index 000000000..9c3e82b77 --- /dev/null +++ b/.github/workflows/release-ruby.yml @@ -0,0 +1,71 @@ +name: Release Ruby SDK +run-name: Release Ruby SDK ${{ inputs.release_type }} + +on: + workflow_dispatch: + inputs: + release_type: + description: "Release Options" + required: true + default: "Release" + type: choice + options: + - Release + - Dry Run + +jobs: + setup: + name: Setup + runs-on: ubuntu-22.04 + outputs: + version: ${{ steps.version.outputs.version }} + steps: + - name: Checkout repo + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Branch check + if: ${{ inputs.release_type != 'Dry Run' }} + run: | + if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then + echo "===================================" + echo "[!] Can only release from the 'main' branch" + echo "===================================" + exit 1 + fi + + - name: Get version + id: version + run: | + VERSION=$(cat languages/ruby/lib/version.rb | grep -Eo 'VERSION = "[0-9]+\.[0-9]+\.[0-9]+"' | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+') + echo "version=$VERSION" >> $GITHUB_OUTPUT + + release: + name: Create GitHub release + runs-on: ubuntu-22.04 + needs: setup + steps: + - name: Checkout Repository + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Download ruby artifact + uses: bitwarden/gh-actions/download-artifacts@main + with: + workflow: build-ruby.yml + workflow_conclusion: success + branch: main + artifacts: bitwarden-sdk-secrets + + - name: Create release + if: ${{ inputs.release_type != 'Dry Run' }} + uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 + env: + PKG_VERSION: ${{ needs.setup.outputs.version }} + with: + commit: ${{ github.sha }} + tag: ruby-v${{ env.PKG_VERSION }} + name: Ruby v${{ env.PKG_VERSION }} + body: "" + token: ${{ secrets.GITHUB_TOKEN }} + draft: true + artifacts: | + bitwarden-sdk-secrets-${{ env.PKG_VERSION }}.gem diff --git a/.github/workflows/release-rust-crates.yml b/.github/workflows/release-rust-crates.yml new file mode 100644 index 000000000..54845e148 --- /dev/null +++ b/.github/workflows/release-rust-crates.yml @@ -0,0 +1,53 @@ +name: Release Rust crates +run-name: Release Rust crates ${{ inputs.release_type }} + +on: + workflow_dispatch: + inputs: + release_type: + description: "Release Options" + required: true + default: "Release" + type: choice + options: + - Release + - Dry Run + +jobs: + setup: + name: Setup + runs-on: ubuntu-22.04 + outputs: + release-version: ${{ steps.version.outputs.version }} + steps: + - name: Checkout repo + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Branch check + if: ${{ inputs.release_type != 'Dry Run' }} + run: | + if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then + echo "===================================" + echo "[!] Can only release from the 'main' branch" + echo "===================================" + exit 1 + fi + + - name: Get version + id: version + run: | + VERSION=$(grep -o '^version = ".*"' Cargo.toml | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+") + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Create release + if: ${{ inputs.release_type != 'Dry Run' }} + uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 + env: + PKG_VERSION: ${{ steps.version.outputs.version }} + with: + commit: ${{ github.sha }} + tag: rust-v${{ env.PKG_VERSION }} + name: Rust crates v${{ env.PKG_VERSION }} + body: "" + token: ${{ secrets.GITHUB_TOKEN }} + draft: true diff --git a/.github/workflows/release-swift.yml b/.github/workflows/release-swift.yml new file mode 100644 index 000000000..351581a7a --- /dev/null +++ b/.github/workflows/release-swift.yml @@ -0,0 +1,263 @@ +--- +name: Release Swift Package + +on: + workflow_call: + inputs: + build-run-id: + description: "Workflow Run ID to use for artifact download. If not provided the latest build from the selected branch will be used." + type: string + required: false + pre-release: + description: "Create a pre-release" + type: boolean + required: false + default: false + + workflow_dispatch: + inputs: + build-run-id: + description: "Workflow Run ID to use for artifact download. If not provided the latest build from the selected branch will be used." + type: string + required: false + pre-release: + description: "Create a pre-release" + type: boolean + required: false + default: false + +env: + _KEY_VAULT: "bitwarden-ci" + +jobs: + validate: + name: Set Version and SHA + runs-on: ubuntu-22.04 + outputs: + version: ${{ steps.version.outputs.version }} + sha: ${{ steps.set-sha.outputs.sha }} + short_sha: ${{ steps.set-sha.outputs.short_sha }} + release_name: ${{ steps.set-release-name.outputs.release_name }} + run_id: ${{ steps.get-run-id.outputs.build-run-id }} + swift_checksum: ${{ steps.calculate-swift-checksum.outputs.checksum }} + steps: + - name: Checkout repo + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + + - name: Get version + id: version + run: | + VERSION=$(grep -o '^version = ".*"' Cargo.toml | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+") + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Get run id + id: get-run-id + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OWNER: ${{ github.repository_owner }} + REPO: ${{ github.event.repository.name }} + run: | + if [ -z ${{ inputs.build-run-id }} ]; then + BRANCH=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}} + RUN_ID=$(gh api \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + /repos/$OWNER/$REPO/actions/workflows/build-swift.yml/runs \ + | jq -r "[.workflow_runs[] | select(.head_branch == \"$BRANCH\").id ] | first") + else + RUN_ID=${{ inputs.build-run-id }} + fi + + echo "build-run-id=$RUN_ID" >> $GITHUB_OUTPUT + + - name: Download BitwardenEFI artifact + uses: bitwarden/gh-actions/download-artifacts@main + id: download-artifact + with: + workflow: build-swift.yml + workflow_conclusion: success + skip_unpack: true + run_id: ${{ steps.get-run-id.outputs.build-run-id }} + + - name: Set SHA + id: set-sha + run: | + echo "sha=${{ steps.download-artifact.outputs.artifact-build-commit }}" >> $GITHUB_OUTPUT + echo "short_sha=$(echo ${{ steps.download-artifact.outputs.artifact-build-commit }} | cut -c1-7)" >> $GITHUB_OUTPUT + + - name: Set release name + id: set-release-name + run: | + if [[ ${{ inputs.pre-release }} == true ]]; then + echo "release_name=${{ steps.version.outputs.version }}-unstable-${{ steps.set-sha.outputs.short_sha }}" >> $GITHUB_OUTPUT + else + echo "release_name=${{ steps.version.outputs.version }}" >> $GITHUB_OUTPUT + fi + + - name: Calculate swift file checksum + id: calculate-swift-checksum + run: | + CHECKSUM=$(swift package compute-checksum BitwardenFFI-${{ steps.version.outputs.version }}-${{ steps.set-sha.outputs.short_sha }}.xcframework.zip) + echo "checksum=$CHECKSUM" >> $GITHUB_OUTPUT + + repo-sync: + name: Push changed files to SDK Swift repo + runs-on: ubuntu-22.04 + needs: validate + env: + _BOT_EMAIL: 106330231+bitwarden-devops-bot@users.noreply.github.com + _BOT_NAME: bitwarden-devops-bot + _PKG_VERSION: ${{ needs.validate.outputs.version }} + _PRE_RELEASE: ${{ inputs.pre-release }} + _RELEASE_NAME: ${{ needs.validate.outputs.release_name }} + _SWIFT_CHECKSUM: ${{ needs.validate.outputs.swift_checksum }} + _BUILD_RUN_ID: ${{ needs.validate.outputs.run_id }} + steps: + - name: Checkout SDK repo + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + with: + path: sdk + + - name: Get ref from SDK repo + id: get-ref + run: | + if [[ $_PRE_RELEASE == true ]]; then + echo "ref=unstable" >> $GITHUB_OUTPUT + else + echo "ref=main" >> $GITHUB_OUTPUT + fi + + - name: Login to Azure - Prod Subscription + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 + with: + creds: ${{ secrets.AZURE_CI_SERVICE_PRINCIPAL }} + + - name: Retrieve secrets + id: retrieve-secrets + uses: bitwarden/gh-actions/get-keyvault-secrets@main + with: + keyvault: "bitwarden-ci" + secrets: "github-gpg-private-key, + github-gpg-private-key-passphrase, + github-pat-bitwarden-devops-bot-repo-scope" + + - name: Checkout SDK-Swift repo + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + with: + repository: bitwarden/sdk-swift + path: sdk-swift + ref: ${{ steps.get-ref.outputs.ref }} + token: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }} + + - name: Import GPG key + 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 }} + git_user_signingkey: true + git_commit_gpgsign: true + workdir: sdk-swift + + - name: Setup Git + working-directory: sdk-swift + run: | + git config --local user.email "${{ env._BOT_EMAIL }}" + git config --local user.name "${{ env._BOT_NAME }}" + + - name: Download BitwardenSdk sources artifact + uses: bitwarden/gh-actions/download-artifacts@main + id: download-artifact + with: + workflow: build-swift.yml + workflow_conclusion: success + artifacts: "BitwardenSdk-${{ env._PKG_VERSION }}-${{ needs.validate.outputs.short_sha }}-sources" + run_id: ${{ env._BUILD_RUN_ID }} + path: sdk/languages/swift/Sources/BitwardenSdk + + - name: Install Swift formatter + run: | + git clone https://github.com/nicklockwood/SwiftFormat + cd SwiftFormat + swift build -c release + cp -f .build/release/swiftformat /usr/local/bin/swiftformat + + - name: Update files + run: | + # Update BitwardenFFI path + sed -i 's|.binaryTarget(name: "BitwardenFFI", path: "BitwardenFFI.xcframework")|.binaryTarget(\ + name: "BitwardenFFI",\ + url: "https://github.com/bitwarden/sdk-swift/releases/download/v${{ env._RELEASE_NAME }}/BitwardenFFI-${{ env._PKG_VERSION }}-${{ needs.validate.outputs.short_sha }}.xcframework.zip",\ + checksum: "${{ env._SWIFT_CHECKSUM }}" )|' sdk/languages/swift/Package.swift + + # Run swiftformat + swiftformat sdk/languages/swift/Package.swift + + find sdk/languages/swift/Sources/ -name ".gitignore" -exec rm -f {} \; + + rm -rf sdk-swift/Sources + rm -rf sdk-swift/Tests + + # Copy files to local sdk-swift repo path + cp --verbose -rf sdk/languages/swift/README.md sdk-swift/README.md + cp --verbose -rf sdk/languages/swift/Package.swift sdk-swift/Package.swift + cp --verbose -rf sdk/languages/swift/Sources sdk-swift + cp --verbose -rf sdk/languages/swift/Tests sdk-swift + + - name: Push changes + working-directory: sdk-swift + run: | + git add . + git commit -m "Update Swift SDK to ${{ needs.validate.outputs.sha }}" + git push origin ${{ steps.get-ref.outputs.ref }} + + - name: Create release tag on SDK Swift repo + working-directory: sdk-swift + run: | + git tag v${{ env._RELEASE_NAME }} + git push origin v${{ env._RELEASE_NAME }} + + github-release: + name: GitHub Release + runs-on: ubuntu-22.04 + needs: + - validate + - repo-sync + env: + _PKG_VERSION: ${{ needs.validate.outputs.version }} + _RELEASE_NAME: ${{ needs.validate.outputs.release_name }} + _BUILD_RUN_ID: ${{ needs.validate.outputs.run_id }} + steps: + - name: Login to Azure - Prod Subscription + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 + with: + creds: ${{ secrets.AZURE_CI_SERVICE_PRINCIPAL }} + + - name: Retrieve secrets + id: retrieve-secrets + uses: bitwarden/gh-actions/get-keyvault-secrets@main + with: + keyvault: ${{ env._KEY_VAULT }} + secrets: "github-pat-bitwarden-devops-bot-repo-scope" + + - name: Download BitwardenEFI artifact + uses: bitwarden/gh-actions/download-artifacts@main + id: download-artifact + with: + workflow: build-swift.yml + workflow_conclusion: success + artifacts: "BitwardenFFI-${{ env._PKG_VERSION }}-${{ needs.validate.outputs.short_sha }}.xcframework" + run_id: ${{ env._BUILD_RUN_ID }} + skip_unpack: true + + - name: Create release + uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 + with: + tag: v${{ env._RELEASE_NAME }} + name: v${{ env._RELEASE_NAME }} + body: "" + token: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }} + draft: false + repo: sdk-swift + owner: bitwarden + artifacts: "BitwardenFFI-${{ env._PKG_VERSION }}-${{ needs.validate.outputs.short_sha }}.xcframework.zip" + prerelease: ${{ inputs.pre-release }} diff --git a/.github/workflows/release-wasm.yml b/.github/workflows/release-wasm.yml index 1511dd7be..97b2c34dd 100644 --- a/.github/workflows/release-wasm.yml +++ b/.github/workflows/release-wasm.yml @@ -13,15 +13,9 @@ on: options: - Release - Dry Run - npm_publish: - description: "Publish to NPM registry" - required: true - default: true - type: boolean defaults: run: - shell: bash working-directory: languages/js/wasm jobs: @@ -32,14 +26,14 @@ jobs: release-version: ${{ steps.version.outputs.version }} steps: - name: Checkout repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Branch check - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} run: | - if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then + if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then echo "===================================" - echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches" + echo "[!] Can only release from the 'main' branch" echo "===================================" exit 1 fi @@ -48,85 +42,37 @@ jobs: id: version uses: bitwarden/gh-actions/release-version-check@main with: - release-type: ${{ github.event.inputs.release_type }} + release-type: ${{ inputs.release_type }} project-type: ts file: languages/js/wasm/package.json monorepo: false - - name: Create GitHub deployment - if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7 - id: deployment - with: - token: "${{ secrets.GITHUB_TOKEN }}" - initial-status: "in_progress" - environment: "Bitwarden SDK WASM - Production" - description: "Deployment ${{ steps.version.outputs.version }} from branch ${{ github.ref_name }}" - task: release - - - name: Update deployment status to Success - if: ${{ github.event.inputs.release_type != 'Dry Run' && success() }} - uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 - with: - token: "${{ secrets.GITHUB_TOKEN }}" - state: "success" - deployment-id: ${{ steps.deployment.outputs.deployment_id }} - - - name: Update deployment status to Failure - if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }} - uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 - with: - token: "${{ secrets.GITHUB_TOKEN }}" - state: "failure" - deployment-id: ${{ steps.deployment.outputs.deployment_id }} - - npm: - name: Publish NPM + release: + name: Release runs-on: ubuntu-22.04 needs: setup - if: inputs.npm_publish - env: - _PKG_VERSION: ${{ needs.setup.outputs.release-version }} steps: - name: Checkout repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - - - name: Setup Node - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 - with: - node-version: 18 - cache: "npm" - - - name: Login to Azure - uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 - with: - creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - - - name: Retrieve secrets - id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@main - with: - keyvault: "bitwarden-ci" - secrets: "npm-api-key" + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Download artifacts - uses: bitwarden/gh-actions/download-artifacts@main + uses: dawidd6/action-download-artifact@bf251b5aa9c2f7eeb574a96ee720e24f801b7c11 # v6 with: workflow: build-wasm.yml - path: ${{ github.workspace }}/languages/js/wasm + skip_unpack: true workflow_conclusion: success - branch: ${{ github.event.inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} + branch: main - - name: Setup NPM - run: | - echo 'registry="https://registry.npmjs.org/"' > ./.npmrc - echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ./.npmrc - - echo 'registry="https://registry.npmjs.org/"' > ~/.npmrc - echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc + - name: Create GitHub release + if: ${{ inputs.release_type != 'Dry Run' }} + uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 env: - NPM_TOKEN: ${{ steps.retrieve-secrets.outputs.npm-api-key }} - - - name: Publish NPM - if: ${{ github.event.inputs.release_type != 'Dry Run' }} - run: npm publish --access public --registry=https://registry.npmjs.org/ --userconfig=./.npmrc + PKG_VERSION: ${{ needs.setup.outputs.release-version }} + with: + commit: ${{ github.sha }} + tag: wasm-v${{ env.PKG_VERSION }} + name: WASM v${{ env.PKG_VERSION }} + body: "" + token: ${{ secrets.GITHUB_TOKEN }} + draft: true + artifacts: sdk-bitwarden-wasm.zip diff --git a/.github/workflows/rust-test.yml b/.github/workflows/rust-test.yml index 8aa844293..5e7703e84 100644 --- a/.github/workflows/rust-test.yml +++ b/.github/workflows/rust-test.yml @@ -5,8 +5,6 @@ on: push: branches: - "main" - - "rc" - - "hotfix-rc" pull_request: env: @@ -23,7 +21,7 @@ jobs: - run: exit 0 test: - name: ${{ matrix.os }} / ${{matrix.target || 'default' }} + name: ${{ matrix.os }} / default runs-on: ${{ matrix.os || 'ubuntu-22.04' }} @@ -36,10 +34,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Install rust - uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # stable + uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable with: toolchain: stable @@ -47,7 +45,8 @@ jobs: uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 - name: Test - run: cargo test --all-features + # Termporarily exclude NAPI due to a test error on windows + run: cargo test --workspace --exclude bitwarden-napi --all-features coverage: name: Coverage @@ -55,10 +54,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Install rust - uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # stable + uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable with: toolchain: stable components: llvm-tools @@ -73,7 +72,7 @@ jobs: 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@5ecb98a3c6b747ed38dc09f787459979aebb39be # v4.3.1 + uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} @@ -84,10 +83,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Install rust - uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # stable + uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable with: toolchain: stable targets: wasm32-unknown-unknown diff --git a/.github/workflows/rustdoc.yml b/.github/workflows/rustdoc.yml new file mode 100644 index 000000000..45ab6d07d --- /dev/null +++ b/.github/workflows/rustdoc.yml @@ -0,0 +1,50 @@ +name: Rustdoc + +on: + push: + branches: ["main"] + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + rustdoc: + name: Rustdoc + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + + - name: Install rust + uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable + with: + toolchain: nightly + + - name: Build documentation + env: + RUSTDOCFLAGS: "--enable-index-page -Zunstable-options" + run: cargo +nightly doc --no-deps --all-features --document-private-items + + - name: Deploy to GitHub Pages + uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1 + with: + path: ./target/doc + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: rustdoc + name: Deploy + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml index 11fd47651..193c9fcf6 100644 --- a/.github/workflows/scan.yml +++ b/.github/workflows/scan.yml @@ -26,12 +26,12 @@ jobs: steps: - name: Check out repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: ref: ${{ github.event.pull_request.head.sha }} - name: Scan with Checkmarx - uses: checkmarx/ast-github-action@dd0f9365942f29a99c3be5bdb308958ede8f906b # 2.0.25 + uses: checkmarx/ast-github-action@9fda5a4a2c297608117a5a56af424502a9192e57 # 2.0.34 env: INCREMENTAL: "${{ contains(github.event_name, 'pull_request') && '--sast-incremental' || '' }}" with: @@ -46,7 +46,7 @@ jobs: --output-path . ${{ env.INCREMENTAL }} - name: Upload Checkmarx results to GitHub - uses: github/codeql-action/upload-sarif@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 + uses: github/codeql-action/upload-sarif@461ef6c76dfe95d5c364de2f431ddbd31a417628 # v3.26.9 with: sarif_file: cx_result.sarif @@ -60,13 +60,13 @@ jobs: steps: - name: Check out repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} - name: Scan with SonarCloud - uses: sonarsource/sonarcloud-github-action@49e6cd3b187936a73b8280d59ffd9da69df63ec9 # v2.1.1 + uses: sonarsource/sonarcloud-github-action@eb211723266fe8e83102bac7361f0a05c3ac1d1b # v3.0.0 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index 406f3b927..7053181eb 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -1,6 +1,6 @@ --- name: Version Bump -run-name: Version Bump - v${{ inputs.version_number }} +run-name: Version Bump - ${{ inputs.project }} - v${{ inputs.version_number }} on: workflow_dispatch: @@ -25,7 +25,7 @@ on: required: true cut_rc_branch: description: "Cut RC branch?" - default: true + default: false type: boolean jobs: @@ -34,7 +34,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Install rust - uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a # stable + uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable with: toolchain: stable @@ -59,7 +59,7 @@ jobs: github-pat-bitwarden-devops-bot-repo-scope" - name: Checkout Branch - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: ref: main repository: bitwarden/sdk @@ -103,16 +103,19 @@ jobs: run: cargo set-version -p bitwarden-napi ${{ inputs.version_number }} ### bitwarden - - name: Bump bitwarden crate Version if: ${{ inputs.project == 'bitwarden' }} run: cargo set-version -p bitwarden ${{ inputs.version_number }} ### bws - - name: Bump bws Version if: ${{ inputs.project == 'bws' }} - run: cargo set-version -p bws ${{ inputs.version_number }} + run: | + cargo set-version -p bws ${{ inputs.version_number }} + # bump the version in install.sh + sed -i 's/DEFAULT_BWS_VERSION="[0-9]\+\.[0-9]\+\.[0-9]\+"/DEFAULT_BWS_VERSION="${{ inputs.version_number }}"/' ./crates/bws/scripts/install.sh + # bump the version in install.ps1 + sed -i 's/\$defaultBwsVersion = "[0-9]\+\.[0-9]\+\.[0-9]\+"/\$defaultBwsVersion = "${{ inputs.version_number }}"/' ./crates/bws/scripts/install.ps1 ### python - name: Bump python-sdk Version @@ -195,7 +198,7 @@ jobs: env: 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 }}" + TITLE: "Bump ${{ inputs.project }} version to ${{ inputs.version_number }}" run: | PR_URL=$(gh pr create --title "$TITLE" \ --base "main" \ @@ -233,7 +236,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout Branch - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: ref: main diff --git a/.gitignore b/.gitignore index b13651d19..4a1d79fca 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,6 @@ languages/java/src/main/java/com/bitwarden/sdk/schema languages/js/sdk-client/src/schemas.ts languages/python/bitwarden_sdk/schemas.py support/schemas + +# Cmake build files +languages/cpp/cmake-build-debug diff --git a/.prettierignore b/.prettierignore index 36c418776..16243942f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,5 +1,9 @@ target languages/* +!languages/js +languages/js/* +!languages/js/sdk-internal +languages/js/sdk-internal/bitwarden_wasm_internal_bg.wasm.js schemas /crates/bitwarden-napi/src-ts/bitwarden_client/schemas.ts about.hbs diff --git a/.vscode/settings.json b/.vscode/settings.json index dcef4bc17..f75a2e3f5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,6 +5,7 @@ "Cdecl", "chrono", "cloc", + "dealloc", "decryptable", "dylib", "encryptable", @@ -22,6 +23,7 @@ "uniffi", "wordlist", "Zeroize", + "Zeroizing", "zxcvbn" ], "rust-analyzer.cargo.targetDir": true diff --git a/Cargo.lock b/Cargo.lock index 6864413c1..e9b7646b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "aes" version = "0.8.4" @@ -52,14 +58,13 @@ checksum = "5ecc8056bf6ab9892dcd53216c83d1597487d7dacac16c8df6b877d127df9937" [[package]] name = "android_logger" -version = "0.13.3" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c494134f746c14dc653a35a4ea5aca24ac368529da5370ecf41fe0341c35772f" +checksum = "05b07e8e73d720a1f2e4b6014766e6039fd2e96a4fa44e2a78d0e1fa2ff49826" dependencies = [ "android_log-sys", - "env_logger 0.10.2", + "env_filter", "log", - "once_cell", ] [[package]] @@ -79,18 +84,18 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "ansi_colours" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a1558bd2075d341b9ca698ec8eb6fcc55a746b1fc4255585aad5b141d918a80" +checksum = "14eec43e0298190790f41679fe69ef7a829d2a2ddd78c8c00339e84710e435fe" dependencies = [ "rgb", ] [[package]] name = "anstream" -version = "0.6.14" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", @@ -103,33 +108,33 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.3" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -137,9 +142,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "arc-swap" @@ -183,7 +188,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -213,9 +218,9 @@ dependencies = [ [[package]] name = "async-compat" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f68a707c1feb095d8c07f8a65b9f506b117d30af431cab89374357de7c11461b" +checksum = "7bab94bde396a3f7b4962e396fdad640e241ed797d4d8d77fc8c237d14c58fc0" dependencies = [ "futures-core", "futures-io", @@ -224,33 +229,28 @@ dependencies = [ "tokio", ] -[[package]] -name = "async-lock" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" -dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", -] - [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" @@ -262,7 +262,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.7.4", "object", "rustc-demangle", ] @@ -273,12 +273,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -360,52 +354,24 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bitwarden" -version = "0.5.0" +version = "1.0.0" dependencies = [ - "async-trait", - "base64 0.22.1", - "bitwarden-api-api", - "bitwarden-api-identity", - "bitwarden-crypto", - "bitwarden-exporters", + "bitwarden-core", "bitwarden-generators", - "chrono", - "coset", - "getrandom", - "hmac", - "log", - "p256", - "passkey", - "rand", - "rand_chacha", - "reqwest", - "rustls-platform-verifier", - "schemars", - "security-framework", - "serde", - "serde_json", - "serde_qs", - "serde_repr", - "sha1", - "sha2", + "bitwarden-sm", "thiserror", - "tokio", - "uniffi", "uuid", - "wiremock", - "zeroize", - "zxcvbn", ] [[package]] name = "bitwarden-api-api" -version = "0.5.0" +version = "1.0.0" dependencies = [ "reqwest", "serde", @@ -418,7 +384,7 @@ dependencies = [ [[package]] name = "bitwarden-api-identity" -version = "0.5.0" +version = "1.0.0" dependencies = [ "reqwest", "serde", @@ -434,13 +400,13 @@ name = "bitwarden-c" version = "0.1.0" dependencies = [ "bitwarden-json", - "env_logger 0.11.3", + "env_logger", "tokio", ] [[package]] name = "bitwarden-cli" -version = "0.5.0" +version = "1.0.0" dependencies = [ "clap", "color-eyre", @@ -448,13 +414,48 @@ dependencies = [ "supports-color", ] +[[package]] +name = "bitwarden-core" +version = "1.0.0" +dependencies = [ + "base64", + "bitwarden-api-api", + "bitwarden-api-identity", + "bitwarden-crypto", + "chrono", + "getrandom", + "hmac", + "log", + "rand", + "rand_chacha", + "reqwest", + "rustls-platform-verifier", + "schemars", + "serde", + "serde_json", + "serde_qs", + "serde_repr", + "sha1", + "sha2", + "thiserror", + "tokio", + "tsify-next", + "uniffi", + "uuid", + "validator", + "wasm-bindgen", + "wiremock", + "zeroize", + "zxcvbn", +] + [[package]] name = "bitwarden-crypto" -version = "0.5.0" +version = "1.0.0" dependencies = [ "aes", "argon2", - "base64 0.22.1", + "base64", "cbc", "criterion", "generic-array", @@ -474,29 +475,61 @@ dependencies = [ "sha2", "subtle", "thiserror", + "tsify-next", "uniffi", "uuid", + "wasm-bindgen", "zeroize", ] [[package]] name = "bitwarden-exporters" -version = "0.5.0" +version = "1.0.0" dependencies = [ - "base64 0.22.1", + "base64", + "bitwarden-core", "bitwarden-crypto", + "bitwarden-vault", "chrono", "csv", + "schemars", + "serde", + "serde_json", + "thiserror", + "uniffi", + "uuid", +] + +[[package]] +name = "bitwarden-fido" +version = "1.0.0" +dependencies = [ + "async-trait", + "base64", + "bitwarden-core", + "bitwarden-crypto", + "bitwarden-vault", + "chrono", + "coset", + "itertools 0.13.0", + "log", + "p256", + "passkey", + "passkey-client", + "reqwest", + "schemars", "serde", "serde_json", "thiserror", + "uniffi", "uuid", ] [[package]] name = "bitwarden-generators" -version = "0.5.0" +version = "1.0.0" dependencies = [ + "bitwarden-core", "bitwarden-crypto", "rand", "rand_chacha", @@ -514,7 +547,6 @@ dependencies = [ name = "bitwarden-json" version = "0.3.0" dependencies = [ - "async-lock", "bitwarden", "log", "schemars", @@ -524,10 +556,10 @@ dependencies = [ [[package]] name = "bitwarden-napi" -version = "0.3.1" +version = "1.0.0" dependencies = [ "bitwarden-json", - "env_logger 0.11.3", + "env_logger", "log", "napi", "napi-build", @@ -540,29 +572,96 @@ version = "0.1.0" dependencies = [ "bitwarden-json", "pyo3", - "pyo3-asyncio", "pyo3-build-config", "pyo3-log", "tokio", ] +[[package]] +name = "bitwarden-send" +version = "1.0.0" +dependencies = [ + "base64", + "bitwarden-api-api", + "bitwarden-core", + "bitwarden-crypto", + "chrono", + "schemars", + "serde", + "serde_repr", + "thiserror", + "uniffi", + "uuid", + "zeroize", +] + +[[package]] +name = "bitwarden-sm" +version = "1.0.0" +dependencies = [ + "bitwarden-api-api", + "bitwarden-core", + "bitwarden-crypto", + "chrono", + "schemars", + "serde", + "serde_json", + "thiserror", + "tokio", + "uuid", + "validator", +] + [[package]] name = "bitwarden-uniffi" version = "0.1.0" dependencies = [ "android_logger", - "async-lock", "async-trait", - "bitwarden", + "bitwarden-core", "bitwarden-crypto", + "bitwarden-exporters", + "bitwarden-fido", "bitwarden-generators", + "bitwarden-send", + "bitwarden-vault", "chrono", - "env_logger 0.11.3", + "env_logger", + "jni", + "libloading", "log", + "oslog", + "rustls-platform-verifier", + "schemars", + "thiserror", + "uniffi", + "uuid", +] + +[[package]] +name = "bitwarden-vault" +version = "1.0.0" +dependencies = [ + "base64", + "bitwarden-api-api", + "bitwarden-core", + "bitwarden-crypto", + "chrono", + "hmac", + "rand", + "reqwest", "schemars", + "serde", + "serde_json", + "serde_repr", + "sha1", + "sha2", "thiserror", + "tokio", + "tsify-next", "uniffi", "uuid", + "wasm-bindgen", ] [[package]] @@ -571,6 +670,7 @@ version = "0.1.0" dependencies = [ "argon2", "bitwarden-json", + "chrono", "console_error_panic_hook", "console_log", "js-sys", @@ -581,6 +681,22 @@ dependencies = [ "wasm-bindgen-test", ] +[[package]] +name = "bitwarden-wasm-internal" +version = "0.1.0" +dependencies = [ + "bitwarden-core", + "bitwarden-crypto", + "bitwarden-vault", + "console_error_panic_hook", + "console_log", + "js-sys", + "log", + "serde_json", + "wasm-bindgen", + "wasm-bindgen-futures", +] + [[package]] name = "blake2" version = "0.10.6" @@ -610,9 +726,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" dependencies = [ "memchr", "serde", @@ -628,12 +744,14 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" name = "bw" version = "0.0.2" dependencies = [ - "bitwarden", "bitwarden-cli", + "bitwarden-core", "bitwarden-crypto", + "bitwarden-generators", + "bitwarden-vault", "clap", "color-eyre", - "env_logger 0.11.3", + "env_logger", "inquire", "log", "tempfile", @@ -642,7 +760,7 @@ dependencies = [ [[package]] name = "bws" -version = "0.5.0" +version = "1.0.0" dependencies = [ "bat", "bitwarden", @@ -654,7 +772,8 @@ dependencies = [ "color-eyre", "comfy-table", "directories", - "env_logger 0.11.3", + "env_logger", + "itertools 0.13.0", "log", "regex", "serde", @@ -664,15 +783,16 @@ dependencies = [ "tempfile", "thiserror", "tokio", - "toml 0.8.12", + "toml 0.8.19", "uuid", + "which", ] [[package]] name = "bytemuck" -version = "1.15.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" +checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" [[package]] name = "byteorder" @@ -682,9 +802,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.0" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "bytesize" @@ -694,9 +814,9 @@ checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" [[package]] name = "camino" -version = "1.1.6" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" dependencies = [ "serde", ] @@ -741,9 +861,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.97" +version = "1.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" +checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" +dependencies = [ + "shlex", +] [[package]] name = "cesu8" @@ -769,7 +892,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -812,9 +935,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.4" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", "clap_derive", @@ -822,48 +945,48 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.1", + "strsim", ] [[package]] name = "clap_complete" -version = "4.5.2" +version = "4.5.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79504325bf38b10165b02e89b4347300f855f273c4cb30c4a3209e6583275e" +checksum = "9646e2e245bf62f45d39a0f3f36f1171ad1ea0d6967fd114bca72cb02a8fcdfb" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "clap_mangen" -version = "0.2.20" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1dd95b5ebb5c1c54581dd6346f3ed6a79a3eef95dd372fc2ac13d535535300e" +checksum = "fbae9cbfdc5d4fa8711c09bd7b83f644cb48281ac35bf97af3e47b0675864bdf" dependencies = [ "clap", "roff", @@ -910,9 +1033,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "combine" @@ -931,20 +1054,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7" dependencies = [ "crossterm 0.27.0", - "strum 0.26.2", - "strum_macros 0.26.2", + "strum 0.26.3", + "strum_macros 0.26.4", "unicode-width", ] -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "console" version = "0.15.8" @@ -1015,15 +1129,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "coset" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff8aad850c1f86daa47e812913051eb5a26c4d9fb4242a89178bf99b946e4e3c" +checksum = "f4c8cc80f631f8307b887faca24dcc3abc427cd0367f6eb6188f6e8f5b7ad8fb" dependencies = [ "ciborium", "ciborium-io", @@ -1031,18 +1145,18 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] @@ -1104,9 +1218,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crossterm" @@ -1117,7 +1231,7 @@ dependencies = [ "bitflags 1.3.2", "crossterm_winapi", "libc", - "mio", + "mio 0.8.11", "parking_lot", "signal-hook", "signal-hook-mio", @@ -1130,7 +1244,7 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "crossterm_winapi", "libc", "parking_lot", @@ -1202,77 +1316,55 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" dependencies = [ "quote", - "syn 2.0.63", -] - -[[package]] -name = "darling" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" -dependencies = [ - "darling_core 0.14.4", - "darling_macro 0.14.4", + "syn 2.0.79", ] [[package]] name = "darling" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ - "darling_core 0.20.9", - "darling_macro 0.20.9", + "darling_core", + "darling_macro", ] [[package]] name = "darling_core" -version = "0.14.4" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim 0.10.0", - "syn 1.0.109", -] - -[[package]] -name = "darling_core" -version = "0.20.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.11.1", - "syn 2.0.63", + "strsim", + "syn 2.0.79", ] [[package]] name = "darling_macro" -version = "0.14.4" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ - "darling_core 0.14.4", + "darling_core", "quote", - "syn 1.0.109", + "syn 2.0.79", ] [[package]] -name = "darling_macro" -version = "0.20.9" +name = "dashmap" +version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ - "darling_core 0.20.9", - "quote", - "syn 2.0.63", + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", ] [[package]] @@ -1322,33 +1414,33 @@ dependencies = [ [[package]] name = "derive_builder" -version = "0.12.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" dependencies = [ "derive_builder_macro", ] [[package]] name = "derive_builder_core" -version = "0.12.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" dependencies = [ - "darling 0.14.4", + "darling", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.79", ] [[package]] name = "derive_builder_macro" -version = "0.12.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 1.0.109", + "syn 2.0.79", ] [[package]] @@ -1406,9 +1498,9 @@ dependencies = [ [[package]] name = "either" -version = "1.11.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "elliptic-curve" @@ -1450,19 +1542,9 @@ dependencies = [ [[package]] name = "env_filter" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_logger" -version = "0.10.2" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" dependencies = [ "log", "regex", @@ -1470,9 +1552,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.3" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" dependencies = [ "anstream", "anstyle", @@ -1497,27 +1579,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "event-listener" -version = "4.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" -dependencies = [ - "event-listener", - "pin-project-lite", -] - [[package]] name = "eyre" version = "0.6.12" @@ -1530,19 +1591,20 @@ dependencies = [ [[package]] name = "fancy-regex" -version = "0.11.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" +checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2" dependencies = [ "bit-set", - "regex", + "regex-automata", + "regex-syntax", ] [[package]] name = "fastrand" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "ff" @@ -1556,12 +1618,12 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.30" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.8.0", ] [[package]] @@ -1590,9 +1652,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -1605,9 +1667,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -1615,15 +1677,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -1632,38 +1694,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -1733,9 +1795,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" dependencies = [ "aho-corasick", "bstr", @@ -1768,17 +1830,17 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" dependencies = [ + "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "futures-util", "http", - "indexmap 2.2.6", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -1807,6 +1869,12 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + [[package]] name = "heck" version = "0.4.1" @@ -1825,6 +1893,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "hex" version = "0.4.3" @@ -1871,9 +1945,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", @@ -1881,12 +1955,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", - "futures-core", + "futures-util", "http", "http-body", "pin-project-lite", @@ -1894,9 +1968,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -1912,9 +1986,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "1.3.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ "bytes", "futures-channel", @@ -1933,9 +2007,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.26.0" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http", @@ -1950,9 +2024,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.3" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" dependencies = [ "bytes", "futures-channel", @@ -1963,16 +2037,15 @@ dependencies = [ "pin-project-lite", "socket2", "tokio", - "tower", "tower-service", "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -2026,12 +2099,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.0", "serde", ] @@ -2057,7 +2130,7 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "crossterm 0.25.0", "dyn-clone", "fuzzy-matcher", @@ -2070,17 +2143,17 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "is-terminal" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ - "hermit-abi", + "hermit-abi 0.4.0", "libc", "windows-sys 0.52.0", ] @@ -2093,9 +2166,9 @@ checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" [[package]] name = "is_terminal_polyfill" -version = "1.70.0" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" @@ -2108,9 +2181,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] @@ -2143,36 +2216,36 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin 0.5.2", + "spin", ] [[package]] name = "libc" -version = "0.2.154" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libloading" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -2187,21 +2260,15 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "libc", ] -[[package]] -name = "line-wrap" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd1bc4d24ad230d21fb898d1116b1801d7adfc449d42026475862ab48b11e70e" - [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" @@ -2215,15 +2282,15 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memoffset" @@ -2254,14 +2321,24 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ "mime", "unicase", ] +[[package]] +name = "minicov" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c71e683cd655513b99affab7d317deb690528255a0d5f717f1024093c12b169" +dependencies = [ + "cc", + "walkdir", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -2270,13 +2347,22 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + [[package]] name = "mio" version = "0.8.11" @@ -2289,13 +2375,25 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "wasi", + "windows-sys 0.52.0", +] + [[package]] name = "napi" -version = "2.16.6" +version = "2.16.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc300228808a0e6aea5a58115c82889240bcf8dab16fc25ad675b33e454b368" +checksum = "3a84fdaf64da2b2d86b1be5db1b81963353bf00f7bef4b9e2668bbe6f72e8eb3" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "ctor", "napi-derive", "napi-sys", @@ -2311,23 +2409,23 @@ checksum = "e1c0f5d67ee408a4685b61f5ab7e58605c8ae3f2b4189f0127d804ff13d5560a" [[package]] name = "napi-derive" -version = "2.16.5" +version = "2.16.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0e034ddf6155192cf83f267ede763fe6c164dfa9971585436b16173718d94c4" +checksum = "17435f7a00bfdab20b0c27d9c56f58f6499e418252253081bfff448099da31d1" dependencies = [ "cfg-if", "convert_case", "napi-derive-backend", "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] name = "napi-derive-backend" -version = "1.0.67" +version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff2c00437f3b3266391eb5e6aa25d0029187daf5caf05b8e3271468fb5ae73e" +checksum = "967c485e00f0bf3b1bdbe510a38a4606919cf1d34d9a37ad41f25a81aa077abe" dependencies = [ "convert_case", "once_cell", @@ -2335,7 +2433,7 @@ dependencies = [ "quote", "regex", "semver", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -2377,9 +2475,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", @@ -2444,7 +2542,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", ] @@ -2459,9 +2557,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "onig" @@ -2487,9 +2585,9 @@ dependencies = [ [[package]] name = "oorandom" -version = "11.1.3" +version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] name = "openssl-probe" @@ -2503,6 +2601,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "oslog" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d2043d1f61d77cb2f4b1f7b7b2295f40507f5f8e9d1c8bf10a1ca5f97a3969" +dependencies = [ + "cc", + "dashmap", + "log", +] + [[package]] name = "owo-colors" version = "3.5.0" @@ -2521,17 +2630,11 @@ dependencies = [ "sha2", ] -[[package]] -name = "parking" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" - [[package]] name = "parking_lot" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -2547,13 +2650,13 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] name = "passkey" -version = "0.3.1" -source = "git+https://github.com/bitwarden/passkey-rs?rev=c48c2ddfd6b884b2d754432576c66cb2b1985a3a#c48c2ddfd6b884b2d754432576c66cb2b1985a3a" +version = "0.2.0" +source = "git+https://github.com/bitwarden/passkey-rs?rev=ff757604cd7b4e8f321ed1616fef7e40e21ac5df#ff757604cd7b4e8f321ed1616fef7e40e21ac5df" dependencies = [ "passkey-authenticator", "passkey-client", @@ -2563,8 +2666,8 @@ dependencies = [ [[package]] name = "passkey-authenticator" -version = "0.3.0" -source = "git+https://github.com/bitwarden/passkey-rs?rev=c48c2ddfd6b884b2d754432576c66cb2b1985a3a#c48c2ddfd6b884b2d754432576c66cb2b1985a3a" +version = "0.2.0" +source = "git+https://github.com/bitwarden/passkey-rs?rev=ff757604cd7b4e8f321ed1616fef7e40e21ac5df#ff757604cd7b4e8f321ed1616fef7e40e21ac5df" dependencies = [ "async-trait", "coset", @@ -2576,43 +2679,42 @@ dependencies = [ [[package]] name = "passkey-client" -version = "0.3.1" -source = "git+https://github.com/bitwarden/passkey-rs?rev=c48c2ddfd6b884b2d754432576c66cb2b1985a3a#c48c2ddfd6b884b2d754432576c66cb2b1985a3a" +version = "0.2.0" +source = "git+https://github.com/bitwarden/passkey-rs?rev=ff757604cd7b4e8f321ed1616fef7e40e21ac5df#ff757604cd7b4e8f321ed1616fef7e40e21ac5df" dependencies = [ "ciborium", "coset", "idna", + "nom", "passkey-authenticator", "passkey-types", "public-suffix", "serde", "serde_json", - "typeshare", "url", ] [[package]] name = "passkey-transports" version = "0.1.0" -source = "git+https://github.com/bitwarden/passkey-rs?rev=c48c2ddfd6b884b2d754432576c66cb2b1985a3a#c48c2ddfd6b884b2d754432576c66cb2b1985a3a" +source = "git+https://github.com/bitwarden/passkey-rs?rev=ff757604cd7b4e8f321ed1616fef7e40e21ac5df#ff757604cd7b4e8f321ed1616fef7e40e21ac5df" [[package]] name = "passkey-types" version = "0.2.1" -source = "git+https://github.com/bitwarden/passkey-rs?rev=c48c2ddfd6b884b2d754432576c66cb2b1985a3a#c48c2ddfd6b884b2d754432576c66cb2b1985a3a" +source = "git+https://github.com/bitwarden/passkey-rs?rev=ff757604cd7b4e8f321ed1616fef7e40e21ac5df#ff757604cd7b4e8f321ed1616fef7e40e21ac5df" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "ciborium", "coset", "data-encoding", "getrandom", - "indexmap 2.2.6", + "indexmap 2.6.0", "rand", "serde", "serde_json", "sha2", "strum 0.25.0", - "typeshare", ] [[package]] @@ -2665,26 +2767,6 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "pin-project" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.63", -] - [[package]] name = "pin-project-lite" version = "0.2.14" @@ -2720,9 +2802,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "plain" @@ -2732,13 +2814,12 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "plist" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9d34169e64b3c7a80c8621a48adaf44e0cf62c78a9b25dd9dd35f1881a17cf9" +checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" dependencies = [ - "base64 0.21.7", - "indexmap 2.2.6", - "line-wrap", + "base64", + "indexmap 2.6.0", "quick-xml", "serde", "time", @@ -2746,9 +2827,9 @@ dependencies = [ [[package]] name = "plotters" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits", "plotters-backend", @@ -2759,24 +2840,24 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] [[package]] name = "portable-atomic" -version = "1.6.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] name = "powerfmt" @@ -2786,9 +2867,12 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "primeorder" @@ -2799,11 +2883,35 @@ dependencies = [ "elliptic-curve", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" -version = "1.0.82" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" dependencies = [ "unicode-ident", ] @@ -2811,19 +2919,19 @@ dependencies = [ [[package]] name = "public-suffix" version = "0.1.1" -source = "git+https://github.com/bitwarden/passkey-rs?rev=c48c2ddfd6b884b2d754432576c66cb2b1985a3a#c48c2ddfd6b884b2d754432576c66cb2b1985a3a" +source = "git+https://github.com/bitwarden/passkey-rs?rev=ff757604cd7b4e8f321ed1616fef7e40e21ac5df#ff757604cd7b4e8f321ed1616fef7e40e21ac5df" [[package]] name = "pyo3" -version = "0.20.3" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53bdbb96d49157e65d45cc287af5f32ffadd5f4761438b527b055fb0d4bb8233" +checksum = "00e89ce2565d6044ca31a3eb79a334c3a79a841120a98f64eea9f579564cb691" dependencies = [ "cfg-if", "indoc", "libc", "memoffset", - "parking_lot", + "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", @@ -2831,36 +2939,11 @@ dependencies = [ "unindent", ] -[[package]] -name = "pyo3-asyncio" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea6b68e93db3622f3bb3bf363246cf948ed5375afe7abff98ccbdd50b184995" -dependencies = [ - "futures", - "once_cell", - "pin-project-lite", - "pyo3", - "pyo3-asyncio-macros", - "tokio", -] - -[[package]] -name = "pyo3-asyncio-macros" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c467178e1da6252c95c29ecf898b133f742e9181dca5def15dc24e19d45a39" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "pyo3-build-config" -version = "0.20.3" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deaa5745de3f5231ce10517a1f5dd97d53e5a2fd77aa6b5842292085831d48d7" +checksum = "d8afbaf3abd7325e08f35ffb8deb5892046fcb2608b703db6a583a5ba4cea01e" dependencies = [ "once_cell", "target-lexicon", @@ -2868,9 +2951,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.20.3" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b42531d03e08d4ef1f6e85a2ed422eb678b8cd62b762e53891c05faf0d4afa" +checksum = "ec15a5ba277339d04763f4c23d85987a5b08cbb494860be141e6a10a8eb88022" dependencies = [ "libc", "pyo3-build-config", @@ -2878,9 +2961,9 @@ dependencies = [ [[package]] name = "pyo3-log" -version = "0.9.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c10808ee7250403bedb24bc30c32493e93875fef7ba3e4292226fe924f398bd" +checksum = "3ac84e6eec1159bc2a575c9ae6723baa6ee9d45873e9bebad1e3ad7e8d28a443" dependencies = [ "arc-swap", "log", @@ -2889,49 +2972,91 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.20.3" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7305c720fa01b8055ec95e484a6eca7a83c841267f0dd5280f0c8b8551d2c158" +checksum = "15e0f01b5364bcfbb686a52fc4181d412b708a68ed20c330db9fc8d2c2bf5a43" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.63", + "syn 2.0.79", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a09b550200e1e5ed9176976d0060cbc2ea82dc8515da07885e7b8153a85caacb" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "quick-xml" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" +dependencies = [ + "memchr", ] [[package]] -name = "pyo3-macros-backend" -version = "0.20.3" +name = "quinn" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c7e9b68bb9c3149c5b0cade5d07f953d6d125eb4337723c4ccdb665f1f96185" +checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" dependencies = [ - "heck 0.4.1", - "proc-macro2", - "pyo3-build-config", - "quote", - "syn 2.0.63", + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", ] [[package]] -name = "quick-error" -version = "2.0.1" +name = "quinn-proto" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" +checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" +dependencies = [ + "bytes", + "rand", + "ring", + "rustc-hash", + "rustls", + "slab", + "thiserror", + "tinyvec", + "tracing", +] [[package]] -name = "quick-xml" -version = "0.31.0" +name = "quinn-udp" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" dependencies = [ - "memchr", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.59.0", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -2988,18 +3113,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.1" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", ] [[package]] name = "redox_users" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", @@ -3008,9 +3133,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.4" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", @@ -3020,9 +3145,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -3031,17 +3156,17 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.4" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-core", "futures-util", @@ -3060,6 +3185,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "quinn", "rustls", "rustls-pemfile", "rustls-pki-types", @@ -3074,8 +3200,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", - "winreg", + "windows-registry", ] [[package]] @@ -3090,9 +3215,9 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.37" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" dependencies = [ "bytemuck", ] @@ -3107,16 +3232,16 @@ dependencies = [ "cfg-if", "getrandom", "libc", - "spin 0.9.8", + "spin", "untrusted", "windows-sys 0.52.0", ] [[package]] name = "roff" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" +checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3" [[package]] name = "rsa" @@ -3144,13 +3269,19 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -3159,11 +3290,11 @@ dependencies = [ [[package]] name = "rustls" -version = "0.22.4" +version = "0.23.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8" dependencies = [ - "log", + "once_cell", "ring", "rustls-pki-types", "rustls-webpki", @@ -3173,9 +3304,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" dependencies = [ "openssl-probe", "rustls-pemfile", @@ -3186,25 +3317,24 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.7.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" [[package]] name = "rustls-platform-verifier" -version = "0.2.0" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c35b9a497e588f1fb2e1d18a0d46a6d057710f34c3da7084b27353b319453cc" +checksum = "afbb878bdfdf63a336a5e63561b1835e7a8c91524f51621db870169eac84b490" dependencies = [ "core-foundation", "core-foundation-sys", @@ -3223,15 +3353,15 @@ dependencies = [ [[package]] name = "rustls-platform-verifier-android" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84e217e7fdc8466b5b35d30f8c0a30febd29173df4a3a0c2115d306b9c4117ad" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.102.3" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3bce581c0dd41bce533ce695a1437fa16a7ab5ac3ccfa99fe1a620a7885eabf" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", @@ -3240,9 +3370,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "092474d1a01ea8278f69e6a358998405fae5b8b963ddaeb2b0b04a128bf1dfb0" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "ryu" @@ -3261,11 +3391,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3292,7 +3422,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -3324,7 +3454,7 @@ checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -3335,7 +3465,7 @@ dependencies = [ "bitwarden", "bitwarden-json", "bitwarden-uniffi", - "itertools 0.12.1", + "itertools 0.13.0", "schemars", "serde_json", ] @@ -3357,11 +3487,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.10.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -3371,9 +3501,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" dependencies = [ "core-foundation-sys", "libc", @@ -3390,43 +3520,55 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.203" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] name = "serde_derive_internals" -version = "0.29.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.6.0", "itoa", + "memchr", "ryu", "serde", ] @@ -3450,14 +3592,14 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] name = "serde_spanned" -version = "0.6.5" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -3476,15 +3618,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.8.1" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" +checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" dependencies = [ - "base64 0.22.1", + "base64", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.2.6", + "indexmap 2.6.0", "serde", "serde_derive", "serde_json", @@ -3494,14 +3636,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.8.1" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" +checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" dependencies = [ - "darling 0.20.9", + "darling", "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -3510,7 +3652,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.6.0", "itoa", "ryu", "serde", @@ -3558,6 +3700,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook" version = "0.3.17" @@ -3570,12 +3718,12 @@ dependencies = [ [[package]] name = "signal-hook-mio" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", - "mio", + "mio 0.8.11", "signal-hook", ] @@ -3635,12 +3783,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - [[package]] name = "spin" version = "0.9.8" @@ -3669,12 +3811,6 @@ version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8207e78455ffdf55661170876f88daf85356e4edd54e0a3dbc79586ca1e50cbe" -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "strsim" version = "0.11.1" @@ -3692,9 +3828,9 @@ dependencies = [ [[package]] name = "strum" -version = "0.26.2" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" [[package]] name = "strum_macros" @@ -3706,33 +3842,33 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] name = "strum_macros" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "proc-macro2", "quote", "rustversion", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "supports-color" -version = "3.0.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9829b314621dfc575df4e409e79f9d6a66a3bd707ab73f23cb4aa3a854ac854f" +checksum = "8775305acf21c96926c900ad056abeef436701108518cf890020387236ac5a77" dependencies = [ "is_ci", ] @@ -3744,15 +3880,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", - "quote", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.63" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -3761,9 +3896,12 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "0.1.2" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] [[package]] name = "syntect" @@ -3787,20 +3925,21 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.14" +version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.10.1" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand", + "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3810,28 +3949,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" dependencies = [ "smawk", - "unicode-linebreak", - "unicode-width", ] [[package]] name = "thiserror" -version = "1.0.60" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.60" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -3887,9 +4024,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -3902,37 +4039,36 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.37.0" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", "libc", - "mio", - "num_cpus", + "mio 1.0.2", "pin-project-lite", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] name = "tokio-rustls" -version = "0.25.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ "rustls", "rustls-pki-types", @@ -3941,9 +4077,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", @@ -3963,9 +4099,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.12" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", @@ -3975,53 +4111,31 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.12" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime", "winnow", ] -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tokio", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-layer" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" - [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -4029,7 +4143,6 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "log", "pin-project-lite", "tracing-core", ] @@ -4072,33 +4185,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "typeshare" -version = "1.0.3" +name = "tsify-next" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f17399b76c2e743d58eac0635d7686e9c00f48cd4776f00695d9882a7d3187" +checksum = "2f4a645dca4ee0800f5ab60ce166deba2db6a0315de795a2691e138a3d55d756" dependencies = [ - "chrono", "serde", - "serde_json", - "typeshare-annotation", + "serde-wasm-bindgen", + "tsify-next-macros", + "wasm-bindgen", ] [[package]] -name = "typeshare-annotation" -version = "1.0.4" +name = "tsify-next-macros" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a615d6c2764852a2e88a4f16e9ce1ea49bb776b5872956309e170d63a042a34f" +checksum = "0d5c06f8a51d759bb58129e30b2631739e7e1e4579fad1f30ac09a6c88e488a6" dependencies = [ + "proc-macro2", "quote", - "syn 2.0.63", + "serde_derive_internals", + "syn 2.0.79", ] +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicase" version = "2.7.0" @@ -4110,51 +4225,46 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "unicode-linebreak" -version = "0.1.5" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "uniffi" -version = "0.27.2" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab38ff7ce5037772ca9bf7667e4e8535d110f11c6e2ec8cc9c1a7fc66938650c" +checksum = "2db87def739fe4183947f8419d572d1849a4a09355eba4e988a2105cfd0ac6a7" dependencies = [ "anyhow", "camino", + "cargo_metadata", "clap", "uniffi_bindgen", "uniffi_build", @@ -4171,34 +4281,32 @@ dependencies = [ [[package]] name = "uniffi_bindgen" -version = "0.27.2" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "480597c3b4074ab2faa39158f45f87f3ac33ccfd7bc7943ff0877372d9d8db97" +checksum = "7a112599c9556d1581e4a3d72019a74c2c3e122cc27f4af12577a429c4d5e614" dependencies = [ "anyhow", "askama", "camino", "cargo_metadata", - "clap", "fs-err", "glob", "goblin", - "heck 0.4.1", + "heck 0.5.0", "once_cell", "paste", "serde", "textwrap", "toml 0.5.11", "uniffi_meta", - "uniffi_testing", "uniffi_udl", ] [[package]] name = "uniffi_build" -version = "0.27.2" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497391e423074ed5dbd828a2860d6203a333123519a285560c5ae1fd78075de4" +checksum = "e2b12684401d2a8508ca9c72a95bbc45906417e42fc80942abaf033bbf01aa33" dependencies = [ "anyhow", "camino", @@ -4207,19 +4315,19 @@ dependencies = [ [[package]] name = "uniffi_checksum_derive" -version = "0.27.2" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95e86ccd44c138ba12b9132decbabeed84bf686ebe4b6538a5e489a243a7c2c9" +checksum = "d2c801f0f05b06df456a2da4c41b9c2c4fdccc6b9916643c6c67275c4c9e4d07" dependencies = [ "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] name = "uniffi_core" -version = "0.27.2" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52fcb15ab907c37fe50163f05f97d497bc4400d8bfbdb7ef56b3a9ef777188d4" +checksum = "5a0c35aaad30e3a9e6d4fe34e358d64dbc92ee09045b48591b05fc9f12e0905b" dependencies = [ "anyhow", "async-compat", @@ -4233,9 +4341,9 @@ dependencies = [ [[package]] name = "uniffi_macros" -version = "0.27.2" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "865e2144b19552516c288e7c0425553c64724a8e4862bcb0c169355008e0ff0d" +checksum = "db66474c5c61b0f7afc3b4995fecf9b72b340daa5ca0ef3da7778d75eb5482ea" dependencies = [ "bincode", "camino", @@ -4244,16 +4352,16 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.63", + "syn 2.0.79", "toml 0.5.11", "uniffi_meta", ] [[package]] name = "uniffi_meta" -version = "0.27.2" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7968bda370d74b9bffb9af1e9cdc9a354ce027dc313963860f26dcf6c8efcecf" +checksum = "d898893f102e0e39b8bcb7e3d2188f4156ba280db32db9e8af1f122d057e9526" dependencies = [ "anyhow", "bytes", @@ -4263,9 +4371,9 @@ dependencies = [ [[package]] name = "uniffi_testing" -version = "0.27.2" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cfe857c83a2655412745e31929c05486d02b340336b595b7044eff342cf6c91" +checksum = "2c6aa4f0cf9d12172d84fc00a35a6c1f3522b526daad05ae739f709f6941b9b6" dependencies = [ "anyhow", "camino", @@ -4276,9 +4384,9 @@ dependencies = [ [[package]] name = "uniffi_udl" -version = "0.27.2" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af11dd5dd1a60d9af5ef30cd37f37090999d998be0c9d34d5ddaf6cee138ed4a" +checksum = "6b044e9c519e0bb51e516ab6f6d8f4f4dcf900ce30d5ad07c03f924e2824f28e" dependencies = [ "anyhow", "textwrap", @@ -4307,9 +4415,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", @@ -4318,20 +4426,50 @@ dependencies = [ [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ "getrandom", "serde", ] +[[package]] +name = "validator" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db79c75af171630a3148bd3e6d7c4f42b6a9a014c2945bc5ed0020cbb8d9478e" +dependencies = [ + "idna", + "once_cell", + "regex", + "serde", + "serde_derive", + "serde_json", + "url", + "validator_derive", +] + +[[package]] +name = "validator_derive" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0bcf92720c40105ac4b2dda2a4ea3aa717d4d6a862cc217da653a4bd5c6b10" +dependencies = [ + "darling", + "once_cell", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "valuable" version = "0.1.0" @@ -4340,9 +4478,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" @@ -4371,11 +4509,12 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", + "once_cell", "serde", "serde_json", "wasm-bindgen-macro", @@ -4383,24 +4522,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if", "js-sys", @@ -4410,9 +4549,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4420,31 +4559,32 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "wasm-bindgen-test" -version = "0.3.42" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bf62a58e0780af3e852044583deee40983e5886da43a271dd772379987667b" +checksum = "d381749acb0943d357dcbd8f0b100640679883fcdeeef04def49daf8d33a5426" dependencies = [ "console_error_panic_hook", "js-sys", + "minicov", "scoped-tls", "wasm-bindgen", "wasm-bindgen-futures", @@ -4453,20 +4593,20 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.42" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0" +checksum = "c97b2ef2c8d627381e51c071c2ab328eac606d3f69dd82bcbca20a9e389d95f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" dependencies = [ "js-sys", "wasm-bindgen", @@ -4474,9 +4614,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.1" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" dependencies = [ "rustls-pki-types", ] @@ -4490,6 +4630,18 @@ dependencies = [ "nom", ] +[[package]] +name = "which" +version = "6.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" +dependencies = [ + "either", + "home", + "rustix", + "winsafe", +] + [[package]] name = "winapi" version = "0.3.9" @@ -4508,11 +4660,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4527,7 +4679,37 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", ] [[package]] @@ -4545,7 +4727,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -4565,18 +4756,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -4587,9 +4778,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -4599,9 +4790,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -4611,15 +4802,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -4629,9 +4820,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -4641,9 +4832,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -4653,9 +4844,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -4665,38 +4856,34 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.8" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] [[package]] -name = "winreg" -version = "0.52.0" +name = "winsafe" +version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" [[package]] name = "wiremock" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec874e1eef0df2dcac546057fe5e29186f09c378181cd7b635b4b7bcc98e9d81" +checksum = "7fff469918e7ca034884c7fd8f93fe27bacb7fcb599fd879df6c7b429a29b646" dependencies = [ "assert-json-diff", "async-trait", - "base64 0.21.7", + "base64", "deadpool", "futures", "http", @@ -4712,11 +4899,32 @@ dependencies = [ "url", ] +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" dependencies = [ "serde", "zeroize_derive", @@ -4730,21 +4938,22 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] name = "zxcvbn" -version = "2.2.2" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "103fa851fff70ea29af380e87c25c48ff7faac5c530c70bd0e65366d4e0c94e4" +checksum = "ad76e35b00ad53688d6b90c431cabe3cbf51f7a4a154739e04b63004ab1c736c" dependencies = [ + "chrono", "derive_builder", "fancy-regex", - "itertools 0.10.5", - "js-sys", + "itertools 0.13.0", "lazy_static", - "quick-error", "regex", "time", + "wasm-bindgen", + "web-sys", ] diff --git a/Cargo.toml b/Cargo.toml index 1f32654dc..ce01a7b7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,11 +5,11 @@ members = ["crates/*"] # Global settings for all crates should be defined here [workspace.package] # Update using `cargo set-version -p bitwarden ` -version = "0.5.0" +version = "1.0.0" authors = ["Bitwarden Inc"] edition = "2021" # Note: Changing rust-version should be considered a breaking change -rust-version = "1.71" +rust-version = "1.75" homepage = "https://bitwarden.com" repository = "https://github.com/bitwarden/sdk" license-file = "LICENSE" @@ -17,15 +17,49 @@ keywords = ["bitwarden"] # Define dependencies that are expected to be consistent across all crates [workspace.dependencies] -bitwarden = { path = "crates/bitwarden", version = "=0.5.0" } -bitwarden-api-api = { path = "crates/bitwarden-api-api", version = "=0.5.0" } -bitwarden-api-identity = { path = "crates/bitwarden-api-identity", version = "=0.5.0" } -bitwarden-cli = { path = "crates/bitwarden-cli", version = "=0.5.0" } -bitwarden-crypto = { path = "crates/bitwarden-crypto", version = "=0.5.0" } -bitwarden-exporters = { path = "crates/bitwarden-exporters", version = "=0.5.0" } -bitwarden-generators = { path = "crates/bitwarden-generators", version = "=0.5.0" } +bitwarden = { path = "crates/bitwarden", version = "=1.0.0" } +bitwarden-api-api = { path = "crates/bitwarden-api-api", version = "=1.0.0" } +bitwarden-api-identity = { path = "crates/bitwarden-api-identity", version = "=1.0.0" } +bitwarden-cli = { path = "crates/bitwarden-cli", version = "=1.0.0" } +bitwarden-core = { path = "crates/bitwarden-core", version = "=1.0.0" } +bitwarden-crypto = { path = "crates/bitwarden-crypto", version = "=1.0.0" } +bitwarden-exporters = { path = "crates/bitwarden-exporters", version = "=1.0.0" } +bitwarden-fido = { path = "crates/bitwarden-fido", version = "=1.0.0" } +bitwarden-generators = { path = "crates/bitwarden-generators", version = "=1.0.0" } +bitwarden-send = { path = "crates/bitwarden-send", version = "=1.0.0" } +bitwarden-sm = { path = "crates/bitwarden-sm", version = "=1.0.0" } +bitwarden-vault = { path = "crates/bitwarden-vault", version = "=1.0.0" } + +# External crates that are expected to maintain a consistent version across all crates +chrono = { version = ">=0.4.26, <0.5", features = [ + "clock", + "serde", + "std", +], default-features = false } +log = "0.4.20" +reqwest = { version = ">=0.12.5, <0.13", features = [ + "json", + "multipart", + "http2", +], default-features = false } +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.14" +serde_repr = ">=0.1.12, <0.2" +thiserror = ">=1.0.40, <2.0" +tokio = { version = "1.36.0", features = ["macros"] } +tsify-next = { version = ">=0.5.4, <0.6", features = [ + "js", +], default-features = false } +uniffi = "=0.28.1" +uuid = { version = ">=1.3.3, <2.0", features = ["serde", "v4"] } +validator = { version = "0.18.1", features = ["derive"] } +wasm-bindgen = { version = ">=0.2.91, <0.3", features = ["serde-serialize"] } +wasm-bindgen-futures = "0.4.41" [workspace.lints.clippy] +unused_async = "deny" unwrap_used = "deny" # Compile all dependencies with some optimizations when building this crate on debug @@ -43,6 +77,13 @@ opt-level = 1 [profile.release] lto = "thin" codegen-units = 1 + +# Turn off LTO on release mode for windows +# This is a workaround until this is fixed: https://github.com/rustls/rustls-platform-verifier/issues/141 +[profile.release-windows] +inherits = "release" +lto = "off" + # Stripping the binary reduces the size by ~30%, but the stacktraces won't be usable anymore. # This is fine as long as we don't have any unhandled panics, but let's keep it disabled for now # strip = true diff --git a/README.md b/README.md index ed3093448..14ca1f667 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,34 @@ are currently open as well as what it's like to work at Bitwarden. ## Getting Started +### Linux / Mac / Windows + ```bash cargo build ``` +### Windows on ARM + +To build, you will need the following in your PATH: + +- [Python](https://www.python.org) +- [Clang](https://clang.llvm.org) + - We recommend installing this via the + [Visual Studio Build Tools](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2022) + +## Documentation + +Please refer to our [Contributing Docs](https://contributing.bitwarden.com/) for +[getting started](https://contributing.bitwarden.com/getting-started/sdk/) instructions and +[architectural documentation](https://contributing.bitwarden.com/architecture/sdk/). + +You can also browse the latest published documentation: + +- [docs.rs](https://docs.rs/bitwarden/latest/bitwarden/) for the public SDK. +- Or for developers of the SDK, view the internal + [API documentation](https://sdk-api-docs.bitwarden.com/bitwarden/index.html) which includes + private items. + ## Crates The project is structured as a monorepo using cargo workspaces. Some of the more noteworthy crates diff --git a/crates/bitwarden-api-api/.openapi-generator/FILES b/crates/bitwarden-api-api/.openapi-generator/FILES index e9cae26d1..7ff73f0b3 100644 --- a/crates/bitwarden-api-api/.openapi-generator/FILES +++ b/crates/bitwarden-api-api/.openapi-generator/FILES @@ -9,6 +9,7 @@ src/apis/ciphers_api.rs src/apis/collections_api.rs src/apis/config_api.rs src/apis/configuration.rs +src/apis/counts_api.rs src/apis/devices_api.rs src/apis/emergency_access_api.rs src/apis/events_api.rs @@ -38,6 +39,7 @@ src/apis/provider_organizations_api.rs src/apis/provider_users_api.rs src/apis/providers_api.rs src/apis/push_api.rs +src/apis/request_sm_access_api.rs src/apis/secrets_api.rs src/apis/secrets_manager_events_api.rs src/apis/secrets_manager_porting_api.rs @@ -46,6 +48,7 @@ src/apis/self_hosted_organization_sponsorships_api.rs src/apis/sends_api.rs src/apis/service_accounts_api.rs src/apis/settings_api.rs +src/apis/stripe_api.rs src/apis/sync_api.rs src/apis/trash_api.rs src/apis/two_factor_api.rs @@ -84,7 +87,6 @@ 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 -src/models/billing_response_model.rs src/models/billing_source.rs src/models/billing_subscription.rs src/models/billing_subscription_item.rs @@ -178,14 +180,15 @@ src/models/get_secrets_request_model.rs src/models/global_domains.rs src/models/global_equivalent_domains_type.rs src/models/granted_access_policy_request.rs +src/models/granted_project_access_policy_permission_details_response_model.rs +src/models/granted_project_access_policy_response_model.rs src/models/group.rs +src/models/group_access_policy_response_model.rs src/models/group_bulk_request_model.rs src/models/group_details_response_model.rs src/models/group_details_response_model_list_response_model.rs -src/models/group_project_access_policy_response_model.rs src/models/group_request_model.rs src/models/group_response_model.rs -src/models/group_service_account_access_policy_response_model.rs src/models/import_ciphers_request_model.rs src/models/import_organization_ciphers_request_model.rs src/models/import_organization_users_request_model.rs @@ -212,12 +215,14 @@ src/models/organization_api_key_information.rs src/models/organization_api_key_information_list_response_model.rs src/models/organization_api_key_request_model.rs src/models/organization_api_key_type.rs +src/models/organization_auth_request_update_many_request_model.rs src/models/organization_auto_enroll_status_response_model.rs src/models/organization_billing_status_response_model.rs src/models/organization_collection_management_update_request_model.rs src/models/organization_connection_request_model.rs src/models/organization_connection_response_model.rs src/models/organization_connection_type.rs +src/models/organization_counts_response_model.rs src/models/organization_create_request_model.rs src/models/organization_domain_request_model.rs src/models/organization_domain_response_model.rs @@ -255,6 +260,7 @@ src/models/organization_user_invite_request_model.rs src/models/organization_user_public_key_response_model.rs src/models/organization_user_public_key_response_model_list_response_model.rs src/models/organization_user_reset_password_details_response_model.rs +src/models/organization_user_reset_password_details_response_model_list_response_model.rs src/models/organization_user_reset_password_enrollment_request_model.rs src/models/organization_user_reset_password_request_model.rs src/models/organization_user_status_type.rs @@ -285,14 +291,13 @@ src/models/policy_response_model_list_response_model.rs src/models/policy_type.rs src/models/potential_grantee_response_model.rs src/models/potential_grantee_response_model_list_response_model.rs -src/models/prelogin_request_model.rs -src/models/prelogin_response_model.rs -src/models/product_type.rs +src/models/product_tier_type.rs src/models/profile_organization_response_model.rs src/models/profile_organization_response_model_list_response_model.rs src/models/profile_provider_organization_response_model.rs src/models/profile_provider_response_model.rs src/models/profile_response_model.rs +src/models/project_counts_response_model.rs src/models/project_create_request_model.rs src/models/project_people_access_policies_response_model.rs src/models/project_response_model.rs @@ -337,14 +342,15 @@ src/models/push_registration_request_model.rs src/models/push_send_request_model.rs src/models/push_type.rs src/models/push_update_request_model.rs -src/models/register_request_model.rs -src/models/register_response_model.rs +src/models/request_sm_access_request_model.rs src/models/reset_password_with_org_id_request_model.rs src/models/response_data.rs src/models/revoke_access_tokens_request.rs src/models/saml2_binding_type.rs src/models/saml2_name_id_format.rs src/models/saml2_signing_behavior.rs +src/models/secret_access_policies_requests_model.rs +src/models/secret_access_policies_response_model.rs src/models/secret_create_request_model.rs src/models/secret_response_inner_project.rs src/models/secret_response_model.rs @@ -371,12 +377,12 @@ src/models/send_text_model.rs src/models/send_type.rs src/models/send_with_id_request_model.rs src/models/server_config_response_model.rs +src/models/service_account_access_policy_response_model.rs +src/models/service_account_counts_response_model.rs src/models/service_account_create_request_model.rs src/models/service_account_granted_policies_permission_details_response_model.rs src/models/service_account_granted_policies_request_model.rs src/models/service_account_people_access_policies_response_model.rs -src/models/service_account_project_access_policy_permission_details_response_model.rs -src/models/service_account_project_access_policy_response_model.rs src/models/service_account_response_model.rs src/models/service_account_secrets_details_response_model.rs src/models/service_account_secrets_details_response_model_list_response_model.rs @@ -395,9 +401,11 @@ src/models/subscription_response_model.rs src/models/sync_response_model.rs src/models/tax_info_response_model.rs src/models/tax_info_update_request_model.rs +src/models/tax_information_request_body.rs src/models/tax_rate_response_model.rs src/models/tax_rate_response_model_list_response_model.rs src/models/transaction_type.rs +src/models/two_factor_authenticator_disable_request_model.rs src/models/two_factor_authenticator_response_model.rs src/models/two_factor_duo_response_model.rs src/models/two_factor_email_request_model.rs @@ -418,6 +426,7 @@ 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 +src/models/update_tde_offboarding_password_request_model.rs src/models/update_temp_password_request_model.rs src/models/update_two_factor_authenticator_request_model.rs src/models/update_two_factor_duo_request_model.rs @@ -425,10 +434,9 @@ src/models/update_two_factor_email_request_model.rs src/models/update_two_factor_yubico_otp_request_model.rs src/models/uri_match_type.rs src/models/user.rs +src/models/user_access_policy_response_model.rs src/models/user_key_response_model.rs src/models/user_license.rs -src/models/user_project_access_policy_response_model.rs -src/models/user_service_account_access_policy_response_model.rs src/models/user_verification_requirement.rs src/models/verify_delete_recover_request_model.rs src/models/verify_email_request_model.rs @@ -439,4 +447,5 @@ src/models/web_authn_credential_response_model_list_response_model.rs src/models/web_authn_login_assertion_options_response_model.rs src/models/web_authn_login_credential_create_request_model.rs src/models/web_authn_login_credential_update_request_model.rs +src/models/web_authn_login_rotate_key_request_model.rs src/models/web_authn_prf_status.rs diff --git a/crates/bitwarden-api-api/Cargo.toml b/crates/bitwarden-api-api/Cargo.toml index 7c6de8278..43ec79ff0 100644 --- a/crates/bitwarden-api-api/Cargo.toml +++ b/crates/bitwarden-api-api/Cargo.toml @@ -13,10 +13,14 @@ license-file.workspace = true keywords.workspace = true [dependencies] -serde = { version = ">=1.0.163, <2", features = ["derive"] } -serde_with = { version = ">=3.8, <4", default-features = false, features = ["base64", "std", "macros"] } -serde_json = ">=1.0.96, <2" -serde_repr = ">=0.1.12, <0.2" +serde = { workspace = true } +serde_with = { version = ">=3.8, <4", default-features = false, features = [ + "base64", + "std", + "macros", +] } +serde_json = { workspace = true } +serde_repr = { workspace = true } url = ">=2.5, <3" -uuid = { version = ">=1.3.3, <2", features = ["serde", "v4"] } -reqwest = { version = ">=0.12, <0.13", features = ["json", "multipart", "http2"], default-features = false } +uuid = { workspace = true } +reqwest = { workspace = true } diff --git a/crates/bitwarden-api-api/README.md b/crates/bitwarden-api-api/README.md index 84b3e478a..c3a05f846 100644 --- a/crates/bitwarden-api-api/README.md +++ b/crates/bitwarden-api-api/README.md @@ -27,8 +27,8 @@ bitwarden-api-api = { path = "./bitwarden-api-api" } All URIs are relative to _http://localhost_ -| Class | Method | HTTP request | Description | -| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Class | Method | HTTP request | Description | +| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | | _AccessPoliciesApi_ | [**organizations_id_access_policies_people_potential_grantees_get**](docs/AccessPoliciesApi.md#organizations_id_access_policies_people_potential_grantees_get) | **GET** /organizations/{id}/access-policies/people/potential-grantees | | _AccessPoliciesApi_ | [**organizations_id_access_policies_projects_potential_grantees_get**](docs/AccessPoliciesApi.md#organizations_id_access_policies_projects_potential_grantees_get) | **GET** /organizations/{id}/access-policies/projects/potential-grantees | | _AccessPoliciesApi_ | [**organizations_id_access_policies_service_accounts_potential_grantees_get**](docs/AccessPoliciesApi.md#organizations_id_access_policies_service_accounts_potential_grantees_get) | **GET** /organizations/{id}/access-policies/service-accounts/potential-grantees | @@ -36,6 +36,7 @@ All URIs are relative to _http://localhost_ | _AccessPoliciesApi_ | [**projects_id_access_policies_people_put**](docs/AccessPoliciesApi.md#projects_id_access_policies_people_put) | **PUT** /projects/{id}/access-policies/people | | _AccessPoliciesApi_ | [**projects_id_access_policies_service_accounts_get**](docs/AccessPoliciesApi.md#projects_id_access_policies_service_accounts_get) | **GET** /projects/{id}/access-policies/service-accounts | | _AccessPoliciesApi_ | [**projects_id_access_policies_service_accounts_put**](docs/AccessPoliciesApi.md#projects_id_access_policies_service_accounts_put) | **PUT** /projects/{id}/access-policies/service-accounts | +| _AccessPoliciesApi_ | [**secrets_secret_id_access_policies_get**](docs/AccessPoliciesApi.md#secrets_secret_id_access_policies_get) | **GET** /secrets/{secretId}/access-policies | | _AccessPoliciesApi_ | [**service_accounts_id_access_policies_people_get**](docs/AccessPoliciesApi.md#service_accounts_id_access_policies_people_get) | **GET** /service-accounts/{id}/access-policies/people | | _AccessPoliciesApi_ | [**service_accounts_id_access_policies_people_put**](docs/AccessPoliciesApi.md#service_accounts_id_access_policies_people_put) | **PUT** /service-accounts/{id}/access-policies/people | | _AccessPoliciesApi_ | [**service_accounts_id_granted_policies_get**](docs/AccessPoliciesApi.md#service_accounts_id_granted_policies_get) | **GET** /service-accounts/{id}/granted-policies | @@ -60,12 +61,10 @@ All URIs are relative to _http://localhost_ | _AccountsApi_ | [**accounts_password_hint_post**](docs/AccountsApi.md#accounts_password_hint_post) | **POST** /accounts/password-hint | | _AccountsApi_ | [**accounts_password_post**](docs/AccountsApi.md#accounts_password_post) | **POST** /accounts/password | | _AccountsApi_ | [**accounts_payment_post**](docs/AccountsApi.md#accounts_payment_post) | **POST** /accounts/payment | -| _AccountsApi_ | [**accounts_prelogin_post**](docs/AccountsApi.md#accounts_prelogin_post) | **POST** /accounts/prelogin | | _AccountsApi_ | [**accounts_premium_post**](docs/AccountsApi.md#accounts_premium_post) | **POST** /accounts/premium | | _AccountsApi_ | [**accounts_profile_get**](docs/AccountsApi.md#accounts_profile_get) | **GET** /accounts/profile | | _AccountsApi_ | [**accounts_profile_post**](docs/AccountsApi.md#accounts_profile_post) | **POST** /accounts/profile | | _AccountsApi_ | [**accounts_profile_put**](docs/AccountsApi.md#accounts_profile_put) | **PUT** /accounts/profile | -| _AccountsApi_ | [**accounts_register_post**](docs/AccountsApi.md#accounts_register_post) | **POST** /accounts/register | | _AccountsApi_ | [**accounts_reinstate_premium_post**](docs/AccountsApi.md#accounts_reinstate_premium_post) | **POST** /accounts/reinstate-premium | | _AccountsApi_ | [**accounts_request_otp_post**](docs/AccountsApi.md#accounts_request_otp_post) | **POST** /accounts/request-otp | | _AccountsApi_ | [**accounts_revision_date_get**](docs/AccountsApi.md#accounts_revision_date_get) | **GET** /accounts/revision-date | @@ -79,6 +78,7 @@ All URIs are relative to _http://localhost_ | _AccountsApi_ | [**accounts_subscription_get**](docs/AccountsApi.md#accounts_subscription_get) | **GET** /accounts/subscription | | _AccountsApi_ | [**accounts_tax_get**](docs/AccountsApi.md#accounts_tax_get) | **GET** /accounts/tax | | _AccountsApi_ | [**accounts_tax_put**](docs/AccountsApi.md#accounts_tax_put) | **PUT** /accounts/tax | +| _AccountsApi_ | [**accounts_update_tde_offboarding_password_put**](docs/AccountsApi.md#accounts_update_tde_offboarding_password_put) | **PUT** /accounts/update-tde-offboarding-password | | _AccountsApi_ | [**accounts_update_temp_password_put**](docs/AccountsApi.md#accounts_update_temp_password_put) | **PUT** /accounts/update-temp-password | | _AccountsApi_ | [**accounts_verify_email_post**](docs/AccountsApi.md#accounts_verify_email_post) | **POST** /accounts/verify-email | | _AccountsApi_ | [**accounts_verify_email_token_post**](docs/AccountsApi.md#accounts_verify_email_token_post) | **POST** /accounts/verify-email-token | @@ -103,7 +103,6 @@ All URIs are relative to _http://localhost_ | _CiphersApi_ | [**ciphers_delete_post**](docs/CiphersApi.md#ciphers_delete_post) | **POST** /ciphers/delete | | _CiphersApi_ | [**ciphers_delete_put**](docs/CiphersApi.md#ciphers_delete_put) | **PUT** /ciphers/delete | | _CiphersApi_ | [**ciphers_get**](docs/CiphersApi.md#ciphers_get) | **GET** /ciphers | -| _CiphersApi_ | [**ciphers_has_unassigned_ciphers_get**](docs/CiphersApi.md#ciphers_has_unassigned_ciphers_get) | **GET** /ciphers/has-unassigned-ciphers | Returns true if the user is an admin or owner of an organization with unassigned ciphers (i.e. ciphers that are not assigned to a collection). | | _CiphersApi_ | [**ciphers_id_admin_delete**](docs/CiphersApi.md#ciphers_id_admin_delete) | **DELETE** /ciphers/{id}/admin | | _CiphersApi_ | [**ciphers_id_admin_get**](docs/CiphersApi.md#ciphers_id_admin_get) | **GET** /ciphers/{id}/admin | | _CiphersApi_ | [**ciphers_id_admin_post**](docs/CiphersApi.md#ciphers_id_admin_post) | **POST** /ciphers/{id}/admin | @@ -169,6 +168,9 @@ 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 | +| _CountsApi_ | [**organizations_organization_id_sm_counts_get**](docs/CountsApi.md#organizations_organization_id_sm_counts_get) | **GET** /organizations/{organizationId}/sm-counts | +| _CountsApi_ | [**projects_project_id_sm_counts_get**](docs/CountsApi.md#projects_project_id_sm_counts_get) | **GET** /projects/{projectId}/sm-counts | +| _CountsApi_ | [**service_accounts_service_account_id_sm_counts_get**](docs/CountsApi.md#service_accounts_service_account_id_sm_counts_get) | **GET** /service-accounts/{serviceAccountId}/sm-counts | | _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 | @@ -185,6 +187,7 @@ All URIs are relative to _http://localhost_ | _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_lost_trust_post**](docs/DevicesApi.md#devices_lost_trust_post) | **POST** /devices/lost-trust | | _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 | @@ -241,14 +244,16 @@ All URIs are relative to _http://localhost_ | _InfoApi_ | [**version_get**](docs/InfoApi.md#version_get) | **GET** /version | | _InstallationsApi_ | [**installations_id_get**](docs/InstallationsApi.md#installations_id_get) | **GET** /installations/{id} | | _InstallationsApi_ | [**installations_post**](docs/InstallationsApi.md#installations_post) | **POST** /installations | -| _LicensesApi_ | [**licenses_organization_id_get**](docs/LicensesApi.md#licenses_organization_id_get) | **GET** /licenses/organization/{id} | Used by self-hosted installations to get an updated license file | +| _LicensesApi_ | [**licenses_organization_id_get**](docs/LicensesApi.md#licenses_organization_id_get) | **GET** /licenses/organization/{id} | Used by self-hosted installations to get an updated license file | | _LicensesApi_ | [**licenses_user_id_get**](docs/LicensesApi.md#licenses_user_id_get) | **GET** /licenses/user/{id} | | _MiscApi_ | [**bitpay_invoice_post**](docs/MiscApi.md#bitpay_invoice_post) | **POST** /bitpay-invoice | | _MiscApi_ | [**setup_payment_post**](docs/MiscApi.md#setup_payment_post) | **POST** /setup-payment | | _OrganizationAuthRequestsApi_ | [**organizations_org_id_auth_requests_deny_post**](docs/OrganizationAuthRequestsApi.md#organizations_org_id_auth_requests_deny_post) | **POST** /organizations/{orgId}/auth-requests/deny | | _OrganizationAuthRequestsApi_ | [**organizations_org_id_auth_requests_get**](docs/OrganizationAuthRequestsApi.md#organizations_org_id_auth_requests_get) | **GET** /organizations/{orgId}/auth-requests | +| _OrganizationAuthRequestsApi_ | [**organizations_org_id_auth_requests_post**](docs/OrganizationAuthRequestsApi.md#organizations_org_id_auth_requests_post) | **POST** /organizations/{orgId}/auth-requests | | _OrganizationAuthRequestsApi_ | [**organizations_org_id_auth_requests_request_id_post**](docs/OrganizationAuthRequestsApi.md#organizations_org_id_auth_requests_request_id_post) | **POST** /organizations/{orgId}/auth-requests/{requestId} | | _OrganizationBillingApi_ | [**organizations_organization_id_billing_get**](docs/OrganizationBillingApi.md#organizations_organization_id_billing_get) | **GET** /organizations/{organizationId}/billing | +| _OrganizationBillingApi_ | [**organizations_organization_id_billing_history_get**](docs/OrganizationBillingApi.md#organizations_organization_id_billing_history_get) | **GET** /organizations/{organizationId}/billing/history | | _OrganizationBillingApi_ | [**organizations_organization_id_billing_metadata_get**](docs/OrganizationBillingApi.md#organizations_organization_id_billing_metadata_get) | **GET** /organizations/{organizationId}/billing/metadata | | _OrganizationConnectionsApi_ | [**organizations_connections_enabled_get**](docs/OrganizationConnectionsApi.md#organizations_connections_enabled_get) | **GET** /organizations/connections/enabled | | _OrganizationConnectionsApi_ | [**organizations_connections_organization_connection_id_delete**](docs/OrganizationConnectionsApi.md#organizations_connections_organization_connection_id_delete) | **DELETE** /organizations/connections/{organizationConnectionId} | @@ -274,6 +279,7 @@ All URIs are relative to _http://localhost_ | _OrganizationSponsorshipsApi_ | [**organization_sponsorship_sponsoring_organization_id_delete_post**](docs/OrganizationSponsorshipsApi.md#organization_sponsorship_sponsoring_organization_id_delete_post) | **POST** /organization/sponsorship/{sponsoringOrganizationId}/delete | | _OrganizationSponsorshipsApi_ | [**organization_sponsorship_sync_post**](docs/OrganizationSponsorshipsApi.md#organization_sponsorship_sync_post) | **POST** /organization/sponsorship/sync | | _OrganizationSponsorshipsApi_ | [**organization_sponsorship_validate_token_post**](docs/OrganizationSponsorshipsApi.md#organization_sponsorship_validate_token_post) | **POST** /organization/sponsorship/validate-token | +| _OrganizationUsersApi_ | [**organizations_org_id_users_account_recovery_details_post**](docs/OrganizationUsersApi.md#organizations_org_id_users_account_recovery_details_post) | **POST** /organizations/{orgId}/users/account-recovery-details | | _OrganizationUsersApi_ | [**organizations_org_id_users_confirm_post**](docs/OrganizationUsersApi.md#organizations_org_id_users_confirm_post) | **POST** /organizations/{orgId}/users/confirm | | _OrganizationUsersApi_ | [**organizations_org_id_users_delete**](docs/OrganizationUsersApi.md#organizations_org_id_users_delete) | **DELETE** /organizations/{orgId}/users | | _OrganizationUsersApi_ | [**organizations_org_id_users_delete_post**](docs/OrganizationUsersApi.md#organizations_org_id_users_delete_post) | **POST** /organizations/{orgId}/users/delete | @@ -313,7 +319,6 @@ All URIs are relative to _http://localhost_ | _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_delete_recover_token_post**](docs/OrganizationsApi.md#organizations_id_delete_recover_token_post) | **POST** /organizations/{id}/delete-recover-token | -| _OrganizationsApi_ | [**organizations_id_enable_collection_enhancements_post**](docs/OrganizationsApi.md#organizations_id_enable_collection_enhancements_post) | **POST** /organizations/{id}/enable-collection-enhancements | Migrates user, collection, and group data to the new Flexible Collections permissions scheme, then sets organization.FlexibleCollections to true to enable these new features for the organization. This is irreversible. | | _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 | @@ -353,7 +358,10 @@ All URIs are relative to _http://localhost_ | _ProjectsApi_ | [**projects_delete_post**](docs/ProjectsApi.md#projects_delete_post) | **POST** /projects/delete | | _ProjectsApi_ | [**projects_id_get**](docs/ProjectsApi.md#projects_id_get) | **GET** /projects/{id} | | _ProjectsApi_ | [**projects_id_put**](docs/ProjectsApi.md#projects_id_put) | **PUT** /projects/{id} | +| _ProviderBillingApi_ | [**providers_provider_id_billing_invoices_get**](docs/ProviderBillingApi.md#providers_provider_id_billing_invoices_get) | **GET** /providers/{providerId}/billing/invoices | +| _ProviderBillingApi_ | [**providers_provider_id_billing_invoices_invoice_id_get**](docs/ProviderBillingApi.md#providers_provider_id_billing_invoices_invoice_id_get) | **GET** /providers/{providerId}/billing/invoices/{invoiceId} | | _ProviderBillingApi_ | [**providers_provider_id_billing_subscription_get**](docs/ProviderBillingApi.md#providers_provider_id_billing_subscription_get) | **GET** /providers/{providerId}/billing/subscription | +| _ProviderBillingApi_ | [**providers_provider_id_billing_tax_information_put**](docs/ProviderBillingApi.md#providers_provider_id_billing_tax_information_put) | **PUT** /providers/{providerId}/billing/tax-information | | _ProviderClientsApi_ | [**providers_provider_id_clients_post**](docs/ProviderClientsApi.md#providers_provider_id_clients_post) | **POST** /providers/{providerId}/clients | | _ProviderClientsApi_ | [**providers_provider_id_clients_provider_organization_id_put**](docs/ProviderClientsApi.md#providers_provider_id_clients_provider_organization_id_put) | **PUT** /providers/{providerId}/clients/{providerOrganizationId} | | _ProviderOrganizationsApi_ | [**providers_provider_id_organizations_add_post**](docs/ProviderOrganizationsApi.md#providers_provider_id_organizations_add_post) | **POST** /providers/{providerId}/organizations/add | @@ -388,6 +396,7 @@ All URIs are relative to _http://localhost_ | _PushApi_ | [**push_delete_post**](docs/PushApi.md#push_delete_post) | **POST** /push/delete | | _PushApi_ | [**push_register_post**](docs/PushApi.md#push_register_post) | **POST** /push/register | | _PushApi_ | [**push_send_post**](docs/PushApi.md#push_send_post) | **POST** /push/send | +| _RequestSmAccessApi_ | [**request_access_request_sm_access_post**](docs/RequestSmAccessApi.md#request_access_request_sm_access_post) | **POST** /request-access/request-sm-access | | _SecretsApi_ | [**organizations_organization_id_secrets_get**](docs/SecretsApi.md#organizations_organization_id_secrets_get) | **GET** /organizations/{organizationId}/secrets | | _SecretsApi_ | [**organizations_organization_id_secrets_post**](docs/SecretsApi.md#organizations_organization_id_secrets_post) | **POST** /organizations/{organizationId}/secrets | | _SecretsApi_ | [**organizations_organization_id_secrets_sync_get**](docs/SecretsApi.md#organizations_organization_id_secrets_sync_get) | **GET** /organizations/{organizationId}/secrets/sync | @@ -429,6 +438,8 @@ All URIs are relative to _http://localhost_ | _SettingsApi_ | [**settings_domains_get**](docs/SettingsApi.md#settings_domains_get) | **GET** /settings/domains | | _SettingsApi_ | [**settings_domains_post**](docs/SettingsApi.md#settings_domains_post) | **POST** /settings/domains | | _SettingsApi_ | [**settings_domains_put**](docs/SettingsApi.md#settings_domains_put) | **PUT** /settings/domains | +| _StripeApi_ | [**setup_intent_bank_account_post**](docs/StripeApi.md#setup_intent_bank_account_post) | **POST** /setup-intent/bank-account | +| _StripeApi_ | [**setup_intent_card_post**](docs/StripeApi.md#setup_intent_card_post) | **POST** /setup-intent/card | | _SyncApi_ | [**sync_get**](docs/SyncApi.md#sync_get) | **GET** /sync | | _TrashApi_ | [**secrets_organization_id_trash_empty_post**](docs/TrashApi.md#secrets_organization_id_trash_empty_post) | **POST** /secrets/{organizationId}/trash/empty | | _TrashApi_ | [**secrets_organization_id_trash_get**](docs/TrashApi.md#secrets_organization_id_trash_get) | **GET** /secrets/{organizationId}/trash | @@ -439,6 +450,7 @@ All URIs are relative to _http://localhost_ | _TwoFactorApi_ | [**organizations_id_two_factor_duo_put**](docs/TwoFactorApi.md#organizations_id_two_factor_duo_put) | **PUT** /organizations/{id}/two-factor/duo | | _TwoFactorApi_ | [**organizations_id_two_factor_get**](docs/TwoFactorApi.md#organizations_id_two_factor_get) | **GET** /organizations/{id}/two-factor | | _TwoFactorApi_ | [**organizations_id_two_factor_get_duo_post**](docs/TwoFactorApi.md#organizations_id_two_factor_get_duo_post) | **POST** /organizations/{id}/two-factor/get-duo | +| _TwoFactorApi_ | [**two_factor_authenticator_delete**](docs/TwoFactorApi.md#two_factor_authenticator_delete) | **DELETE** /two-factor/authenticator | | _TwoFactorApi_ | [**two_factor_authenticator_post**](docs/TwoFactorApi.md#two_factor_authenticator_post) | **POST** /two-factor/authenticator | | _TwoFactorApi_ | [**two_factor_authenticator_put**](docs/TwoFactorApi.md#two_factor_authenticator_put) | **PUT** /two-factor/authenticator | | _TwoFactorApi_ | [**two_factor_device_verification_settings_put**](docs/TwoFactorApi.md#two_factor_device_verification_settings_put) | **PUT** /two-factor/device-verification-settings | @@ -506,7 +518,6 @@ All URIs are relative to _http://localhost_ - [BillingHistoryResponseModel](docs/BillingHistoryResponseModel.md) - [BillingInvoice](docs/BillingInvoice.md) - [BillingPaymentResponseModel](docs/BillingPaymentResponseModel.md) -- [BillingResponseModel](docs/BillingResponseModel.md) - [BillingSource](docs/BillingSource.md) - [BillingSubscription](docs/BillingSubscription.md) - [BillingSubscriptionItem](docs/BillingSubscriptionItem.md) @@ -600,14 +611,15 @@ All URIs are relative to _http://localhost_ - [GlobalDomains](docs/GlobalDomains.md) - [GlobalEquivalentDomainsType](docs/GlobalEquivalentDomainsType.md) - [GrantedAccessPolicyRequest](docs/GrantedAccessPolicyRequest.md) +- [GrantedProjectAccessPolicyPermissionDetailsResponseModel](docs/GrantedProjectAccessPolicyPermissionDetailsResponseModel.md) +- [GrantedProjectAccessPolicyResponseModel](docs/GrantedProjectAccessPolicyResponseModel.md) - [Group](docs/Group.md) +- [GroupAccessPolicyResponseModel](docs/GroupAccessPolicyResponseModel.md) - [GroupBulkRequestModel](docs/GroupBulkRequestModel.md) - [GroupDetailsResponseModel](docs/GroupDetailsResponseModel.md) - [GroupDetailsResponseModelListResponseModel](docs/GroupDetailsResponseModelListResponseModel.md) -- [GroupProjectAccessPolicyResponseModel](docs/GroupProjectAccessPolicyResponseModel.md) - [GroupRequestModel](docs/GroupRequestModel.md) - [GroupResponseModel](docs/GroupResponseModel.md) -- [GroupServiceAccountAccessPolicyResponseModel](docs/GroupServiceAccountAccessPolicyResponseModel.md) - [ImportCiphersRequestModel](docs/ImportCiphersRequestModel.md) - [ImportOrganizationCiphersRequestModel](docs/ImportOrganizationCiphersRequestModel.md) - [ImportOrganizationUsersRequestModel](docs/ImportOrganizationUsersRequestModel.md) @@ -633,12 +645,14 @@ All URIs are relative to _http://localhost_ - [OrganizationApiKeyInformationListResponseModel](docs/OrganizationApiKeyInformationListResponseModel.md) - [OrganizationApiKeyRequestModel](docs/OrganizationApiKeyRequestModel.md) - [OrganizationApiKeyType](docs/OrganizationApiKeyType.md) +- [OrganizationAuthRequestUpdateManyRequestModel](docs/OrganizationAuthRequestUpdateManyRequestModel.md) - [OrganizationAutoEnrollStatusResponseModel](docs/OrganizationAutoEnrollStatusResponseModel.md) - [OrganizationBillingStatusResponseModel](docs/OrganizationBillingStatusResponseModel.md) - [OrganizationCollectionManagementUpdateRequestModel](docs/OrganizationCollectionManagementUpdateRequestModel.md) - [OrganizationConnectionRequestModel](docs/OrganizationConnectionRequestModel.md) - [OrganizationConnectionResponseModel](docs/OrganizationConnectionResponseModel.md) - [OrganizationConnectionType](docs/OrganizationConnectionType.md) +- [OrganizationCountsResponseModel](docs/OrganizationCountsResponseModel.md) - [OrganizationCreateRequestModel](docs/OrganizationCreateRequestModel.md) - [OrganizationDomainRequestModel](docs/OrganizationDomainRequestModel.md) - [OrganizationDomainResponseModel](docs/OrganizationDomainResponseModel.md) @@ -676,6 +690,7 @@ All URIs are relative to _http://localhost_ - [OrganizationUserPublicKeyResponseModel](docs/OrganizationUserPublicKeyResponseModel.md) - [OrganizationUserPublicKeyResponseModelListResponseModel](docs/OrganizationUserPublicKeyResponseModelListResponseModel.md) - [OrganizationUserResetPasswordDetailsResponseModel](docs/OrganizationUserResetPasswordDetailsResponseModel.md) +- [OrganizationUserResetPasswordDetailsResponseModelListResponseModel](docs/OrganizationUserResetPasswordDetailsResponseModelListResponseModel.md) - [OrganizationUserResetPasswordEnrollmentRequestModel](docs/OrganizationUserResetPasswordEnrollmentRequestModel.md) - [OrganizationUserResetPasswordRequestModel](docs/OrganizationUserResetPasswordRequestModel.md) - [OrganizationUserStatusType](docs/OrganizationUserStatusType.md) @@ -706,14 +721,13 @@ All URIs are relative to _http://localhost_ - [PolicyType](docs/PolicyType.md) - [PotentialGranteeResponseModel](docs/PotentialGranteeResponseModel.md) - [PotentialGranteeResponseModelListResponseModel](docs/PotentialGranteeResponseModelListResponseModel.md) -- [PreloginRequestModel](docs/PreloginRequestModel.md) -- [PreloginResponseModel](docs/PreloginResponseModel.md) -- [ProductType](docs/ProductType.md) +- [ProductTierType](docs/ProductTierType.md) - [ProfileOrganizationResponseModel](docs/ProfileOrganizationResponseModel.md) - [ProfileOrganizationResponseModelListResponseModel](docs/ProfileOrganizationResponseModelListResponseModel.md) - [ProfileProviderOrganizationResponseModel](docs/ProfileProviderOrganizationResponseModel.md) - [ProfileProviderResponseModel](docs/ProfileProviderResponseModel.md) - [ProfileResponseModel](docs/ProfileResponseModel.md) +- [ProjectCountsResponseModel](docs/ProjectCountsResponseModel.md) - [ProjectCreateRequestModel](docs/ProjectCreateRequestModel.md) - [ProjectPeopleAccessPoliciesResponseModel](docs/ProjectPeopleAccessPoliciesResponseModel.md) - [ProjectResponseModel](docs/ProjectResponseModel.md) @@ -758,14 +772,15 @@ All URIs are relative to _http://localhost_ - [PushSendRequestModel](docs/PushSendRequestModel.md) - [PushType](docs/PushType.md) - [PushUpdateRequestModel](docs/PushUpdateRequestModel.md) -- [RegisterRequestModel](docs/RegisterRequestModel.md) -- [RegisterResponseModel](docs/RegisterResponseModel.md) +- [RequestSmAccessRequestModel](docs/RequestSmAccessRequestModel.md) - [ResetPasswordWithOrgIdRequestModel](docs/ResetPasswordWithOrgIdRequestModel.md) - [ResponseData](docs/ResponseData.md) - [RevokeAccessTokensRequest](docs/RevokeAccessTokensRequest.md) - [Saml2BindingType](docs/Saml2BindingType.md) - [Saml2NameIdFormat](docs/Saml2NameIdFormat.md) - [Saml2SigningBehavior](docs/Saml2SigningBehavior.md) +- [SecretAccessPoliciesRequestsModel](docs/SecretAccessPoliciesRequestsModel.md) +- [SecretAccessPoliciesResponseModel](docs/SecretAccessPoliciesResponseModel.md) - [SecretCreateRequestModel](docs/SecretCreateRequestModel.md) - [SecretResponseInnerProject](docs/SecretResponseInnerProject.md) - [SecretResponseModel](docs/SecretResponseModel.md) @@ -792,12 +807,12 @@ All URIs are relative to _http://localhost_ - [SendType](docs/SendType.md) - [SendWithIdRequestModel](docs/SendWithIdRequestModel.md) - [ServerConfigResponseModel](docs/ServerConfigResponseModel.md) +- [ServiceAccountAccessPolicyResponseModel](docs/ServiceAccountAccessPolicyResponseModel.md) +- [ServiceAccountCountsResponseModel](docs/ServiceAccountCountsResponseModel.md) - [ServiceAccountCreateRequestModel](docs/ServiceAccountCreateRequestModel.md) - [ServiceAccountGrantedPoliciesPermissionDetailsResponseModel](docs/ServiceAccountGrantedPoliciesPermissionDetailsResponseModel.md) - [ServiceAccountGrantedPoliciesRequestModel](docs/ServiceAccountGrantedPoliciesRequestModel.md) - [ServiceAccountPeopleAccessPoliciesResponseModel](docs/ServiceAccountPeopleAccessPoliciesResponseModel.md) -- [ServiceAccountProjectAccessPolicyPermissionDetailsResponseModel](docs/ServiceAccountProjectAccessPolicyPermissionDetailsResponseModel.md) -- [ServiceAccountProjectAccessPolicyResponseModel](docs/ServiceAccountProjectAccessPolicyResponseModel.md) - [ServiceAccountResponseModel](docs/ServiceAccountResponseModel.md) - [ServiceAccountSecretsDetailsResponseModel](docs/ServiceAccountSecretsDetailsResponseModel.md) - [ServiceAccountSecretsDetailsResponseModelListResponseModel](docs/ServiceAccountSecretsDetailsResponseModelListResponseModel.md) @@ -816,9 +831,11 @@ All URIs are relative to _http://localhost_ - [SyncResponseModel](docs/SyncResponseModel.md) - [TaxInfoResponseModel](docs/TaxInfoResponseModel.md) - [TaxInfoUpdateRequestModel](docs/TaxInfoUpdateRequestModel.md) +- [TaxInformationRequestBody](docs/TaxInformationRequestBody.md) - [TaxRateResponseModel](docs/TaxRateResponseModel.md) - [TaxRateResponseModelListResponseModel](docs/TaxRateResponseModelListResponseModel.md) - [TransactionType](docs/TransactionType.md) +- [TwoFactorAuthenticatorDisableRequestModel](docs/TwoFactorAuthenticatorDisableRequestModel.md) - [TwoFactorAuthenticatorResponseModel](docs/TwoFactorAuthenticatorResponseModel.md) - [TwoFactorDuoResponseModel](docs/TwoFactorDuoResponseModel.md) - [TwoFactorEmailRequestModel](docs/TwoFactorEmailRequestModel.md) @@ -839,6 +856,7 @@ All URIs are relative to _http://localhost_ - [UpdateDomainsRequestModel](docs/UpdateDomainsRequestModel.md) - [UpdateKeyRequestModel](docs/UpdateKeyRequestModel.md) - [UpdateProfileRequestModel](docs/UpdateProfileRequestModel.md) +- [UpdateTdeOffboardingPasswordRequestModel](docs/UpdateTdeOffboardingPasswordRequestModel.md) - [UpdateTempPasswordRequestModel](docs/UpdateTempPasswordRequestModel.md) - [UpdateTwoFactorAuthenticatorRequestModel](docs/UpdateTwoFactorAuthenticatorRequestModel.md) - [UpdateTwoFactorDuoRequestModel](docs/UpdateTwoFactorDuoRequestModel.md) @@ -846,10 +864,9 @@ All URIs are relative to _http://localhost_ - [UpdateTwoFactorYubicoOtpRequestModel](docs/UpdateTwoFactorYubicoOtpRequestModel.md) - [UriMatchType](docs/UriMatchType.md) - [User](docs/User.md) +- [UserAccessPolicyResponseModel](docs/UserAccessPolicyResponseModel.md) - [UserKeyResponseModel](docs/UserKeyResponseModel.md) - [UserLicense](docs/UserLicense.md) -- [UserProjectAccessPolicyResponseModel](docs/UserProjectAccessPolicyResponseModel.md) -- [UserServiceAccountAccessPolicyResponseModel](docs/UserServiceAccountAccessPolicyResponseModel.md) - [UserVerificationRequirement](docs/UserVerificationRequirement.md) - [VerifyDeleteRecoverRequestModel](docs/VerifyDeleteRecoverRequestModel.md) - [VerifyEmailRequestModel](docs/VerifyEmailRequestModel.md) @@ -860,6 +877,7 @@ All URIs are relative to _http://localhost_ - [WebAuthnLoginAssertionOptionsResponseModel](docs/WebAuthnLoginAssertionOptionsResponseModel.md) - [WebAuthnLoginCredentialCreateRequestModel](docs/WebAuthnLoginCredentialCreateRequestModel.md) - [WebAuthnLoginCredentialUpdateRequestModel](docs/WebAuthnLoginCredentialUpdateRequestModel.md) +- [WebAuthnLoginRotateKeyRequestModel](docs/WebAuthnLoginRotateKeyRequestModel.md) - [WebAuthnPrfStatus](docs/WebAuthnPrfStatus.md) To get access to the crate's generated documentation, use: diff --git a/crates/bitwarden-api-api/src/apis/access_policies_api.rs b/crates/bitwarden-api-api/src/apis/access_policies_api.rs index 5ad67157c..e97a9d567 100644 --- a/crates/bitwarden-api-api/src/apis/access_policies_api.rs +++ b/crates/bitwarden-api-api/src/apis/access_policies_api.rs @@ -66,6 +66,13 @@ pub enum ProjectsIdAccessPoliciesServiceAccountsPutError { UnknownValue(serde_json::Value), } +/// struct for typed errors of method [`secrets_secret_id_access_policies_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum SecretsSecretIdAccessPoliciesGetError { + UnknownValue(serde_json::Value), +} + /// struct for typed errors of method [`service_accounts_id_access_policies_people_get`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -432,6 +439,51 @@ pub async fn projects_id_access_policies_service_accounts_put( } } +pub async fn secrets_secret_id_access_policies_get( + configuration: &configuration::Configuration, + secret_id: uuid::Uuid, +) -> Result> +{ + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/secrets/{secretId}/access-policies", + local_var_configuration.base_path, + secretId = crate::apis::urlencode(secret_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 service_accounts_id_access_policies_people_get( configuration: &configuration::Configuration, id: uuid::Uuid, diff --git a/crates/bitwarden-api-api/src/apis/accounts_api.rs b/crates/bitwarden-api-api/src/apis/accounts_api.rs index da44cffea..d4a5321f1 100644 --- a/crates/bitwarden-api-api/src/apis/accounts_api.rs +++ b/crates/bitwarden-api-api/src/apis/accounts_api.rs @@ -154,13 +154,6 @@ pub enum AccountsPaymentPostError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`accounts_prelogin_post`] -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum AccountsPreloginPostError { - UnknownValue(serde_json::Value), -} - /// struct for typed errors of method [`accounts_premium_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -189,13 +182,6 @@ pub enum AccountsProfilePutError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`accounts_register_post`] -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum AccountsRegisterPostError { - UnknownValue(serde_json::Value), -} - /// struct for typed errors of method [`accounts_reinstate_premium_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -287,6 +273,13 @@ pub enum AccountsTaxPutError { UnknownValue(serde_json::Value), } +/// struct for typed errors of method [`accounts_update_tde_offboarding_password_put`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum AccountsUpdateTdeOffboardingPasswordPutError { + UnknownValue(serde_json::Value), +} + /// struct for typed errors of method [`accounts_update_temp_password_put`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -1156,47 +1149,6 @@ pub async fn accounts_payment_post( } } -pub async fn accounts_prelogin_post( - configuration: &configuration::Configuration, - prelogin_request_model: Option, -) -> Result> { - let local_var_configuration = configuration; - - let local_var_client = &local_var_configuration.client; - - let local_var_uri_str = format!("{}/accounts/prelogin", 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(&prelogin_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 accounts_premium_post( configuration: &configuration::Configuration, payment_method_type: models::PaymentMethodType, @@ -1384,47 +1336,6 @@ pub async fn accounts_profile_put( } } -pub async fn accounts_register_post( - configuration: &configuration::Configuration, - register_request_model: Option, -) -> Result> { - let local_var_configuration = configuration; - - let local_var_client = &local_var_configuration.client; - - let local_var_uri_str = format!("{}/accounts/register", 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(®ister_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 accounts_reinstate_premium_post( configuration: &configuration::Configuration, ) -> Result<(), Error> { @@ -1973,6 +1884,53 @@ pub async fn accounts_tax_put( } } +pub async fn accounts_update_tde_offboarding_password_put( + configuration: &configuration::Configuration, + update_tde_offboarding_password_request_model: Option< + models::UpdateTdeOffboardingPasswordRequestModel, + >, +) -> Result<(), Error> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/accounts/update-tde-offboarding-password", + local_var_configuration.base_path + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::PUT, 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_tde_offboarding_password_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 accounts_update_temp_password_put( configuration: &configuration::Configuration, update_temp_password_request_model: Option, diff --git a/crates/bitwarden-api-api/src/apis/ciphers_api.rs b/crates/bitwarden-api-api/src/apis/ciphers_api.rs index ee9cb11b7..e0a522560 100644 --- a/crates/bitwarden-api-api/src/apis/ciphers_api.rs +++ b/crates/bitwarden-api-api/src/apis/ciphers_api.rs @@ -91,13 +91,6 @@ pub enum CiphersGetError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`ciphers_has_unassigned_ciphers_get`] -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum CiphersHasUnassignedCiphersGetError { - UnknownValue(serde_json::Value), -} - /// struct for typed errors of method [`ciphers_id_admin_delete`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -883,48 +876,6 @@ pub async fn ciphers_get( } } -pub async fn ciphers_has_unassigned_ciphers_get( - configuration: &configuration::Configuration, -) -> Result> { - let local_var_configuration = configuration; - - let local_var_client = &local_var_configuration.client; - - let local_var_uri_str = format!( - "{}/ciphers/has-unassigned-ciphers", - local_var_configuration.base_path - ); - 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 ciphers_id_admin_delete( configuration: &configuration::Configuration, id: &str, diff --git a/crates/bitwarden-api-api/src/apis/counts_api.rs b/crates/bitwarden-api-api/src/apis/counts_api.rs new file mode 100644 index 000000000..5331d91fa --- /dev/null +++ b/crates/bitwarden-api-api/src/apis/counts_api.rs @@ -0,0 +1,174 @@ +/* + * 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 + */ + +use reqwest; +use serde::{Deserialize, Serialize}; + +use super::{configuration, Error}; +use crate::{apis::ResponseContent, models}; + +/// struct for typed errors of method [`organizations_organization_id_sm_counts_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum OrganizationsOrganizationIdSmCountsGetError { + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`projects_project_id_sm_counts_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum ProjectsProjectIdSmCountsGetError { + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`service_accounts_service_account_id_sm_counts_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum ServiceAccountsServiceAccountIdSmCountsGetError { + UnknownValue(serde_json::Value), +} + +pub async fn organizations_organization_id_sm_counts_get( + configuration: &configuration::Configuration, + organization_id: uuid::Uuid, +) -> Result< + models::OrganizationCountsResponseModel, + Error, +> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/organizations/{organizationId}/sm-counts", + local_var_configuration.base_path, + organizationId = crate::apis::urlencode(organization_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 projects_project_id_sm_counts_get( + configuration: &configuration::Configuration, + project_id: uuid::Uuid, +) -> Result> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/projects/{projectId}/sm-counts", + local_var_configuration.base_path, + projectId = crate::apis::urlencode(project_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 service_accounts_service_account_id_sm_counts_get( + configuration: &configuration::Configuration, + service_account_id: uuid::Uuid, +) -> Result< + models::ServiceAccountCountsResponseModel, + Error, +> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/service-accounts/{serviceAccountId}/sm-counts", + local_var_configuration.base_path, + serviceAccountId = crate::apis::urlencode(service_account_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)) + } +} diff --git a/crates/bitwarden-api-api/src/apis/devices_api.rs b/crates/bitwarden-api-api/src/apis/devices_api.rs index c7f3d29a0..4b3b65980 100644 --- a/crates/bitwarden-api-api/src/apis/devices_api.rs +++ b/crates/bitwarden-api-api/src/apis/devices_api.rs @@ -126,6 +126,13 @@ pub enum DevicesKnowndeviceGetError { UnknownValue(serde_json::Value), } +/// struct for typed errors of method [`devices_lost_trust_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum DevicesLostTrustPostError { + UnknownValue(serde_json::Value), +} + /// struct for typed errors of method [`devices_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -856,6 +863,45 @@ pub async fn devices_knowndevice_get( } } +pub async fn devices_lost_trust_post( + configuration: &configuration::Configuration, +) -> Result<(), Error> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!("{}/devices/lost-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()); + }; + + 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 devices_post( configuration: &configuration::Configuration, device_request_model: Option, diff --git a/crates/bitwarden-api-api/src/apis/mod.rs b/crates/bitwarden-api-api/src/apis/mod.rs index a7c22cd4e..0b0e68fde 100644 --- a/crates/bitwarden-api-api/src/apis/mod.rs +++ b/crates/bitwarden-api-api/src/apis/mod.rs @@ -98,6 +98,7 @@ pub mod auth_requests_api; pub mod ciphers_api; pub mod collections_api; pub mod config_api; +pub mod counts_api; pub mod devices_api; pub mod emergency_access_api; pub mod events_api; @@ -126,6 +127,7 @@ pub mod provider_organizations_api; pub mod provider_users_api; pub mod providers_api; pub mod push_api; +pub mod request_sm_access_api; pub mod secrets_api; pub mod secrets_manager_events_api; pub mod secrets_manager_porting_api; @@ -134,6 +136,7 @@ pub mod self_hosted_organization_sponsorships_api; pub mod sends_api; pub mod service_accounts_api; pub mod settings_api; +pub mod stripe_api; pub mod sync_api; pub mod trash_api; pub mod two_factor_api; diff --git a/crates/bitwarden-api-api/src/apis/organization_auth_requests_api.rs b/crates/bitwarden-api-api/src/apis/organization_auth_requests_api.rs index 946b79da0..44e4ca9e9 100644 --- a/crates/bitwarden-api-api/src/apis/organization_auth_requests_api.rs +++ b/crates/bitwarden-api-api/src/apis/organization_auth_requests_api.rs @@ -28,6 +28,13 @@ pub enum OrganizationsOrgIdAuthRequestsGetError { UnknownValue(serde_json::Value), } +/// struct for typed errors of method [`organizations_org_id_auth_requests_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum OrganizationsOrgIdAuthRequestsPostError { + UnknownValue(serde_json::Value), +} + /// struct for typed errors of method [`organizations_org_id_auth_requests_request_id_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -130,6 +137,55 @@ pub async fn organizations_org_id_auth_requests_get( } } +pub async fn organizations_org_id_auth_requests_post( + configuration: &configuration::Configuration, + org_id: uuid::Uuid, + organization_auth_request_update_many_request_model: Option< + Vec, + >, +) -> Result<(), Error> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/organizations/{orgId}/auth-requests", + local_var_configuration.base_path, + orgId = crate::apis::urlencode(org_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_auth_request_update_many_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_org_id_auth_requests_request_id_post( configuration: &configuration::Configuration, org_id: uuid::Uuid, diff --git a/crates/bitwarden-api-api/src/apis/organization_billing_api.rs b/crates/bitwarden-api-api/src/apis/organization_billing_api.rs index b8c2c959d..5ae42d921 100644 --- a/crates/bitwarden-api-api/src/apis/organization_billing_api.rs +++ b/crates/bitwarden-api-api/src/apis/organization_billing_api.rs @@ -21,6 +21,13 @@ pub enum OrganizationsOrganizationIdBillingGetError { UnknownValue(serde_json::Value), } +/// struct for typed errors of method [`organizations_organization_id_billing_history_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum OrganizationsOrganizationIdBillingHistoryGetError { + UnknownValue(serde_json::Value), +} + /// struct for typed errors of method [`organizations_organization_id_billing_metadata_get`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -31,7 +38,7 @@ pub enum OrganizationsOrganizationIdBillingMetadataGetError { pub async fn organizations_organization_id_billing_get( configuration: &configuration::Configuration, organization_id: uuid::Uuid, -) -> Result> { +) -> Result<(), Error> { let local_var_configuration = configuration; let local_var_client = &local_var_configuration.client; @@ -59,7 +66,7 @@ pub async fn organizations_organization_id_billing_get( 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(); @@ -72,6 +79,50 @@ pub async fn organizations_organization_id_billing_get( } } +pub async fn organizations_organization_id_billing_history_get( + configuration: &configuration::Configuration, + organization_id: uuid::Uuid, +) -> Result<(), Error> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/organizations/{organizationId}/billing/history", + local_var_configuration.base_path, + organizationId = crate::apis::urlencode(organization_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() { + 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_organization_id_billing_metadata_get( configuration: &configuration::Configuration, organization_id: uuid::Uuid, 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 3ede841fd..c5eafb9c9 100644 --- a/crates/bitwarden-api-api/src/apis/organization_users_api.rs +++ b/crates/bitwarden-api-api/src/apis/organization_users_api.rs @@ -14,6 +14,13 @@ use serde::{Deserialize, Serialize}; use super::{configuration, Error}; use crate::{apis::ResponseContent, models}; +/// struct for typed errors of method [`organizations_org_id_users_account_recovery_details_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum OrganizationsOrgIdUsersAccountRecoveryDetailsPostError { + UnknownValue(serde_json::Value), +} + /// struct for typed errors of method [`organizations_org_id_users_confirm_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -227,6 +234,55 @@ pub enum OrganizationsOrgIdUsersUserIdResetPasswordEnrollmentPutError { UnknownValue(serde_json::Value), } +pub async fn organizations_org_id_users_account_recovery_details_post( + configuration: &configuration::Configuration, + org_id: uuid::Uuid, + organization_user_bulk_request_model: Option, +) -> Result< + models::OrganizationUserResetPasswordDetailsResponseModelListResponseModel, + Error, +> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/organizations/{orgId}/users/account-recovery-details", + local_var_configuration.base_path, + orgId = crate::apis::urlencode(org_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_user_bulk_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_org_id_users_confirm_post( configuration: &configuration::Configuration, org_id: &str, diff --git a/crates/bitwarden-api-api/src/apis/organizations_api.rs b/crates/bitwarden-api-api/src/apis/organizations_api.rs index 026d37b10..fd88331dd 100644 --- a/crates/bitwarden-api-api/src/apis/organizations_api.rs +++ b/crates/bitwarden-api-api/src/apis/organizations_api.rs @@ -77,13 +77,6 @@ pub enum OrganizationsIdDeleteRecoverTokenPostError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organizations_id_enable_collection_enhancements_post`] -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum OrganizationsIdEnableCollectionEnhancementsPostError { - UnknownValue(serde_json::Value), -} - /// struct for typed errors of method [`organizations_id_get`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -679,50 +672,6 @@ pub async fn organizations_id_delete_recover_token_post( } } -pub async fn organizations_id_enable_collection_enhancements_post( - configuration: &configuration::Configuration, - id: uuid::Uuid, -) -> Result<(), Error> { - let local_var_configuration = configuration; - - let local_var_client = &local_var_configuration.client; - - let local_var_uri_str = format!( - "{}/organizations/{id}/enable-collection-enhancements", - 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()); - }; - - 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, diff --git a/crates/bitwarden-api-api/src/apis/provider_billing_api.rs b/crates/bitwarden-api-api/src/apis/provider_billing_api.rs index 2c56c5759..b34b885f8 100644 --- a/crates/bitwarden-api-api/src/apis/provider_billing_api.rs +++ b/crates/bitwarden-api-api/src/apis/provider_billing_api.rs @@ -14,6 +14,20 @@ use serde::{Deserialize, Serialize}; use super::{configuration, Error}; use crate::{apis::ResponseContent, models}; +/// struct for typed errors of method [`providers_provider_id_billing_invoices_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum ProvidersProviderIdBillingInvoicesGetError { + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`providers_provider_id_billing_invoices_invoice_id_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum ProvidersProviderIdBillingInvoicesInvoiceIdGetError { + UnknownValue(serde_json::Value), +} + /// struct for typed errors of method [`providers_provider_id_billing_subscription_get`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -21,6 +35,103 @@ pub enum ProvidersProviderIdBillingSubscriptionGetError { UnknownValue(serde_json::Value), } +/// struct for typed errors of method [`providers_provider_id_billing_tax_information_put`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum ProvidersProviderIdBillingTaxInformationPutError { + UnknownValue(serde_json::Value), +} + +pub async fn providers_provider_id_billing_invoices_get( + configuration: &configuration::Configuration, + provider_id: uuid::Uuid, +) -> Result<(), Error> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/providers/{providerId}/billing/invoices", + local_var_configuration.base_path, + providerId = crate::apis::urlencode(provider_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() { + 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 providers_provider_id_billing_invoices_invoice_id_get( + configuration: &configuration::Configuration, + provider_id: uuid::Uuid, + invoice_id: &str, +) -> Result<(), Error> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/providers/{providerId}/billing/invoices/{invoiceId}", + local_var_configuration.base_path, + providerId = crate::apis::urlencode(provider_id.to_string()), + invoiceId = crate::apis::urlencode(invoice_id) + ); + 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() { + 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 providers_provider_id_billing_subscription_get( configuration: &configuration::Configuration, provider_id: uuid::Uuid, @@ -64,3 +175,49 @@ pub async fn providers_provider_id_billing_subscription_get( Err(Error::ResponseError(local_var_error)) } } + +pub async fn providers_provider_id_billing_tax_information_put( + configuration: &configuration::Configuration, + provider_id: uuid::Uuid, + tax_information_request_body: Option, +) -> Result<(), Error> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/providers/{providerId}/billing/tax-information", + local_var_configuration.base_path, + providerId = crate::apis::urlencode(provider_id.to_string()) + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::PUT, 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(&tax_information_request_body); + + 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/request_sm_access_api.rs b/crates/bitwarden-api-api/src/apis/request_sm_access_api.rs new file mode 100644 index 000000000..c67de851d --- /dev/null +++ b/crates/bitwarden-api-api/src/apis/request_sm_access_api.rs @@ -0,0 +1,66 @@ +/* + * 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 + */ + +use reqwest; +use serde::{Deserialize, Serialize}; + +use super::{configuration, Error}; +use crate::{apis::ResponseContent, models}; + +/// struct for typed errors of method [`request_access_request_sm_access_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum RequestAccessRequestSmAccessPostError { + UnknownValue(serde_json::Value), +} + +pub async fn request_access_request_sm_access_post( + configuration: &configuration::Configuration, + request_sm_access_request_model: Option, +) -> Result<(), Error> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/request-access/request-sm-access", + 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(&request_sm_access_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/stripe_api.rs b/crates/bitwarden-api-api/src/apis/stripe_api.rs new file mode 100644 index 000000000..8b186fbfb --- /dev/null +++ b/crates/bitwarden-api-api/src/apis/stripe_api.rs @@ -0,0 +1,110 @@ +/* + * 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 + */ + +use reqwest; +use serde::{Deserialize, Serialize}; + +use super::{configuration, Error}; +use crate::{apis::ResponseContent, models}; + +/// struct for typed errors of method [`setup_intent_bank_account_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum SetupIntentBankAccountPostError { + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`setup_intent_card_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum SetupIntentCardPostError { + UnknownValue(serde_json::Value), +} + +pub async fn setup_intent_bank_account_post( + configuration: &configuration::Configuration, +) -> Result<(), Error> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/setup-intent/bank-account", + 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()); + }; + + 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 setup_intent_card_post( + configuration: &configuration::Configuration, +) -> Result<(), Error> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!("{}/setup-intent/card", 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()); + }; + + 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/two_factor_api.rs b/crates/bitwarden-api-api/src/apis/two_factor_api.rs index 298dcb486..f012036dc 100644 --- a/crates/bitwarden-api-api/src/apis/two_factor_api.rs +++ b/crates/bitwarden-api-api/src/apis/two_factor_api.rs @@ -56,6 +56,13 @@ pub enum OrganizationsIdTwoFactorGetDuoPostError { UnknownValue(serde_json::Value), } +/// struct for typed errors of method [`two_factor_authenticator_delete`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum TwoFactorAuthenticatorDeleteError { + UnknownValue(serde_json::Value), +} + /// struct for typed errors of method [`two_factor_authenticator_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -510,6 +517,53 @@ pub async fn organizations_id_two_factor_get_duo_post( } } +pub async fn two_factor_authenticator_delete( + configuration: &configuration::Configuration, + two_factor_authenticator_disable_request_model: Option< + models::TwoFactorAuthenticatorDisableRequestModel, + >, +) -> Result> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/two-factor/authenticator", + local_var_configuration.base_path + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::DELETE, 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(&two_factor_authenticator_disable_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 two_factor_authenticator_post( configuration: &configuration::Configuration, update_two_factor_authenticator_request_model: Option< diff --git a/crates/bitwarden-api-api/src/models/billing_response_model.rs b/crates/bitwarden-api-api/src/models/billing_response_model.rs deleted file mode 100644 index 0adc96a5c..000000000 --- a/crates/bitwarden-api-api/src/models/billing_response_model.rs +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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 - */ - -use serde::{Deserialize, Serialize}; - -use crate::models; - -#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] -pub struct BillingResponseModel { - #[serde(rename = "object", skip_serializing_if = "Option::is_none")] - pub object: Option, - #[serde(rename = "balance", skip_serializing_if = "Option::is_none")] - pub balance: Option, - #[serde(rename = "paymentSource", skip_serializing_if = "Option::is_none")] - pub payment_source: Option>, - #[serde(rename = "invoices", skip_serializing_if = "Option::is_none")] - pub invoices: Option>, - #[serde(rename = "transactions", skip_serializing_if = "Option::is_none")] - pub transactions: Option>, -} - -impl BillingResponseModel { - pub fn new() -> BillingResponseModel { - BillingResponseModel { - object: None, - balance: None, - payment_source: None, - invoices: None, - transactions: None, - } - } -} diff --git a/crates/bitwarden-api-api/src/models/bit_pay_invoice_request_model.rs b/crates/bitwarden-api-api/src/models/bit_pay_invoice_request_model.rs index 6e761baf9..42e71a66f 100644 --- a/crates/bitwarden-api-api/src/models/bit_pay_invoice_request_model.rs +++ b/crates/bitwarden-api-api/src/models/bit_pay_invoice_request_model.rs @@ -18,6 +18,8 @@ pub struct BitPayInvoiceRequestModel { pub user_id: Option, #[serde(rename = "organizationId", skip_serializing_if = "Option::is_none")] pub organization_id: Option, + #[serde(rename = "providerId", skip_serializing_if = "Option::is_none")] + pub provider_id: Option, #[serde(rename = "credit", skip_serializing_if = "Option::is_none")] pub credit: Option, #[serde(rename = "amount")] @@ -35,6 +37,7 @@ impl BitPayInvoiceRequestModel { BitPayInvoiceRequestModel { user_id: None, organization_id: None, + provider_id: None, credit: None, amount, return_url: None, diff --git a/crates/bitwarden-api-api/src/models/event_type.rs b/crates/bitwarden-api-api/src/models/event_type.rs index 366ed6c18..e4294452b 100644 --- a/crates/bitwarden-api-api/src/models/event_type.rs +++ b/crates/bitwarden-api-api/src/models/event_type.rs @@ -38,6 +38,7 @@ pub enum EventType { User_UpdatedTempPassword = 1008, User_MigratedKeyToKeyConnector = 1009, User_RequestedDeviceApproval = 1010, + User_TdeOffboardingPasswordSet = 1011, Cipher_Created = 1100, Cipher_Updated = 1101, Cipher_Deleted = 1102, @@ -117,6 +118,7 @@ impl std::fmt::Display for EventType { Self::User_UpdatedTempPassword => write!(f, "1008"), Self::User_MigratedKeyToKeyConnector => write!(f, "1009"), Self::User_RequestedDeviceApproval => write!(f, "1010"), + Self::User_TdeOffboardingPasswordSet => write!(f, "1011"), Self::Cipher_Created => write!(f, "1100"), Self::Cipher_Updated => write!(f, "1101"), Self::Cipher_Deleted => write!(f, "1102"), diff --git a/crates/bitwarden-api-api/src/models/service_account_project_access_policy_permission_details_response_model.rs b/crates/bitwarden-api-api/src/models/granted_project_access_policy_permission_details_response_model.rs similarity index 66% rename from crates/bitwarden-api-api/src/models/service_account_project_access_policy_permission_details_response_model.rs rename to crates/bitwarden-api-api/src/models/granted_project_access_policy_permission_details_response_model.rs index 1545c95b0..0f957d42f 100644 --- a/crates/bitwarden-api-api/src/models/service_account_project_access_policy_permission_details_response_model.rs +++ b/crates/bitwarden-api-api/src/models/granted_project_access_policy_permission_details_response_model.rs @@ -13,18 +13,18 @@ use serde::{Deserialize, Serialize}; use crate::models; #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] -pub struct ServiceAccountProjectAccessPolicyPermissionDetailsResponseModel { +pub struct GrantedProjectAccessPolicyPermissionDetailsResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, #[serde(rename = "accessPolicy", skip_serializing_if = "Option::is_none")] - pub access_policy: Option>, + pub access_policy: Option>, #[serde(rename = "hasPermission", skip_serializing_if = "Option::is_none")] pub has_permission: Option, } -impl ServiceAccountProjectAccessPolicyPermissionDetailsResponseModel { - pub fn new() -> ServiceAccountProjectAccessPolicyPermissionDetailsResponseModel { - ServiceAccountProjectAccessPolicyPermissionDetailsResponseModel { +impl GrantedProjectAccessPolicyPermissionDetailsResponseModel { + pub fn new() -> GrantedProjectAccessPolicyPermissionDetailsResponseModel { + GrantedProjectAccessPolicyPermissionDetailsResponseModel { object: None, access_policy: None, has_permission: None, diff --git a/crates/bitwarden-api-api/src/models/service_account_project_access_policy_response_model.rs b/crates/bitwarden-api-api/src/models/granted_project_access_policy_response_model.rs similarity index 52% rename from crates/bitwarden-api-api/src/models/service_account_project_access_policy_response_model.rs rename to crates/bitwarden-api-api/src/models/granted_project_access_policy_response_model.rs index 29369402d..d098fccea 100644 --- a/crates/bitwarden-api-api/src/models/service_account_project_access_policy_response_model.rs +++ b/crates/bitwarden-api-api/src/models/granted_project_access_policy_response_model.rs @@ -13,40 +13,25 @@ use serde::{Deserialize, Serialize}; use crate::models; #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] -pub struct ServiceAccountProjectAccessPolicyResponseModel { +pub struct GrantedProjectAccessPolicyResponseModel { #[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 = "read", skip_serializing_if = "Option::is_none")] pub read: Option, #[serde(rename = "write", skip_serializing_if = "Option::is_none")] pub write: 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 = "serviceAccountId", skip_serializing_if = "Option::is_none")] - pub service_account_id: Option, - #[serde(rename = "serviceAccountName", skip_serializing_if = "Option::is_none")] - pub service_account_name: Option, #[serde(rename = "grantedProjectId", skip_serializing_if = "Option::is_none")] pub granted_project_id: Option, #[serde(rename = "grantedProjectName", skip_serializing_if = "Option::is_none")] pub granted_project_name: Option, } -impl ServiceAccountProjectAccessPolicyResponseModel { - pub fn new() -> ServiceAccountProjectAccessPolicyResponseModel { - ServiceAccountProjectAccessPolicyResponseModel { +impl GrantedProjectAccessPolicyResponseModel { + pub fn new() -> GrantedProjectAccessPolicyResponseModel { + GrantedProjectAccessPolicyResponseModel { object: None, - id: None, read: None, write: None, - creation_date: None, - revision_date: None, - service_account_id: None, - service_account_name: None, granted_project_id: None, granted_project_name: None, } diff --git a/crates/bitwarden-api-api/src/models/group_project_access_policy_response_model.rs b/crates/bitwarden-api-api/src/models/group_access_policy_response_model.rs similarity index 60% rename from crates/bitwarden-api-api/src/models/group_project_access_policy_response_model.rs rename to crates/bitwarden-api-api/src/models/group_access_policy_response_model.rs index bfd7aec3e..943f2e11d 100644 --- a/crates/bitwarden-api-api/src/models/group_project_access_policy_response_model.rs +++ b/crates/bitwarden-api-api/src/models/group_access_policy_response_model.rs @@ -13,42 +13,30 @@ use serde::{Deserialize, Serialize}; use crate::models; #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] -pub struct GroupProjectAccessPolicyResponseModel { +pub struct GroupAccessPolicyResponseModel { #[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 = "read", skip_serializing_if = "Option::is_none")] pub read: Option, #[serde(rename = "write", skip_serializing_if = "Option::is_none")] pub write: 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 = "groupId", skip_serializing_if = "Option::is_none")] pub group_id: Option, #[serde(rename = "groupName", skip_serializing_if = "Option::is_none")] pub group_name: Option, #[serde(rename = "currentUserInGroup", skip_serializing_if = "Option::is_none")] pub current_user_in_group: Option, - #[serde(rename = "grantedProjectId", skip_serializing_if = "Option::is_none")] - pub granted_project_id: Option, } -impl GroupProjectAccessPolicyResponseModel { - pub fn new() -> GroupProjectAccessPolicyResponseModel { - GroupProjectAccessPolicyResponseModel { +impl GroupAccessPolicyResponseModel { + pub fn new() -> GroupAccessPolicyResponseModel { + GroupAccessPolicyResponseModel { object: None, - id: None, read: None, write: None, - creation_date: None, - revision_date: None, group_id: None, group_name: None, current_user_in_group: None, - granted_project_id: None, } } } diff --git a/crates/bitwarden-api-api/src/models/group_details_response_model.rs b/crates/bitwarden-api-api/src/models/group_details_response_model.rs index 7fc890d14..e73cec876 100644 --- a/crates/bitwarden-api-api/src/models/group_details_response_model.rs +++ b/crates/bitwarden-api-api/src/models/group_details_response_model.rs @@ -22,8 +22,6 @@ pub struct GroupDetailsResponseModel { pub organization_id: Option, #[serde(rename = "name", skip_serializing_if = "Option::is_none")] pub name: Option, - #[serde(rename = "accessAll", skip_serializing_if = "Option::is_none")] - pub access_all: Option, #[serde(rename = "externalId", skip_serializing_if = "Option::is_none")] pub external_id: Option, #[serde(rename = "collections", skip_serializing_if = "Option::is_none")] @@ -37,7 +35,6 @@ impl GroupDetailsResponseModel { id: None, organization_id: None, name: None, - access_all: None, external_id: None, collections: None, } diff --git a/crates/bitwarden-api-api/src/models/group_request_model.rs b/crates/bitwarden-api-api/src/models/group_request_model.rs index d986ebe13..e218a3fe6 100644 --- a/crates/bitwarden-api-api/src/models/group_request_model.rs +++ b/crates/bitwarden-api-api/src/models/group_request_model.rs @@ -16,8 +16,6 @@ use crate::models; pub struct GroupRequestModel { #[serde(rename = "name")] pub name: String, - #[serde(rename = "accessAll")] - pub access_all: bool, #[serde(rename = "collections", skip_serializing_if = "Option::is_none")] pub collections: Option>, #[serde(rename = "users", skip_serializing_if = "Option::is_none")] @@ -25,10 +23,9 @@ pub struct GroupRequestModel { } impl GroupRequestModel { - pub fn new(name: String, access_all: bool) -> GroupRequestModel { + pub fn new(name: String) -> GroupRequestModel { GroupRequestModel { name, - access_all, collections: None, users: None, } diff --git a/crates/bitwarden-api-api/src/models/group_response_model.rs b/crates/bitwarden-api-api/src/models/group_response_model.rs index 4e9da78ee..a3d6d6fd3 100644 --- a/crates/bitwarden-api-api/src/models/group_response_model.rs +++ b/crates/bitwarden-api-api/src/models/group_response_model.rs @@ -22,8 +22,6 @@ pub struct GroupResponseModel { pub organization_id: Option, #[serde(rename = "name", skip_serializing_if = "Option::is_none")] pub name: Option, - #[serde(rename = "accessAll", skip_serializing_if = "Option::is_none")] - pub access_all: Option, #[serde(rename = "externalId", skip_serializing_if = "Option::is_none")] pub external_id: Option, } @@ -35,7 +33,6 @@ impl GroupResponseModel { id: None, organization_id: None, name: None, - access_all: None, external_id: None, } } diff --git a/crates/bitwarden-api-api/src/models/group_service_account_access_policy_response_model.rs b/crates/bitwarden-api-api/src/models/group_service_account_access_policy_response_model.rs deleted file mode 100644 index 9fcc03bea..000000000 --- a/crates/bitwarden-api-api/src/models/group_service_account_access_policy_response_model.rs +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 - */ - -use serde::{Deserialize, Serialize}; - -use crate::models; - -#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] -pub struct GroupServiceAccountAccessPolicyResponseModel { - #[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 = "read", skip_serializing_if = "Option::is_none")] - pub read: Option, - #[serde(rename = "write", skip_serializing_if = "Option::is_none")] - pub write: 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 = "groupId", skip_serializing_if = "Option::is_none")] - pub group_id: Option, - #[serde(rename = "groupName", skip_serializing_if = "Option::is_none")] - pub group_name: Option, - #[serde( - rename = "grantedServiceAccountId", - skip_serializing_if = "Option::is_none" - )] - pub granted_service_account_id: Option, - #[serde(rename = "currentUserInGroup", skip_serializing_if = "Option::is_none")] - pub current_user_in_group: Option, -} - -impl GroupServiceAccountAccessPolicyResponseModel { - pub fn new() -> GroupServiceAccountAccessPolicyResponseModel { - GroupServiceAccountAccessPolicyResponseModel { - object: None, - id: None, - read: None, - write: None, - creation_date: None, - revision_date: None, - group_id: None, - group_name: None, - granted_service_account_id: None, - current_user_in_group: None, - } - } -} diff --git a/crates/bitwarden-api-api/src/models/mod.rs b/crates/bitwarden-api-api/src/models/mod.rs index 86365cd28..91998fb0c 100644 --- a/crates/bitwarden-api-api/src/models/mod.rs +++ b/crates/bitwarden-api-api/src/models/mod.rs @@ -62,8 +62,6 @@ pub mod billing_invoice; pub use self::billing_invoice::BillingInvoice; pub mod billing_payment_response_model; pub use self::billing_payment_response_model::BillingPaymentResponseModel; -pub mod billing_response_model; -pub use self::billing_response_model::BillingResponseModel; pub mod billing_source; pub use self::billing_source::BillingSource; pub mod billing_subscription; @@ -250,22 +248,24 @@ pub mod global_equivalent_domains_type; pub use self::global_equivalent_domains_type::GlobalEquivalentDomainsType; pub mod granted_access_policy_request; pub use self::granted_access_policy_request::GrantedAccessPolicyRequest; +pub mod granted_project_access_policy_permission_details_response_model; +pub use self::granted_project_access_policy_permission_details_response_model::GrantedProjectAccessPolicyPermissionDetailsResponseModel; +pub mod granted_project_access_policy_response_model; +pub use self::granted_project_access_policy_response_model::GrantedProjectAccessPolicyResponseModel; pub mod group; pub use self::group::Group; +pub mod group_access_policy_response_model; +pub use self::group_access_policy_response_model::GroupAccessPolicyResponseModel; pub mod group_bulk_request_model; pub use self::group_bulk_request_model::GroupBulkRequestModel; pub mod group_details_response_model; pub use self::group_details_response_model::GroupDetailsResponseModel; pub mod group_details_response_model_list_response_model; pub use self::group_details_response_model_list_response_model::GroupDetailsResponseModelListResponseModel; -pub mod group_project_access_policy_response_model; -pub use self::group_project_access_policy_response_model::GroupProjectAccessPolicyResponseModel; pub mod group_request_model; pub use self::group_request_model::GroupRequestModel; pub mod group_response_model; pub use self::group_response_model::GroupResponseModel; -pub mod group_service_account_access_policy_response_model; -pub use self::group_service_account_access_policy_response_model::GroupServiceAccountAccessPolicyResponseModel; pub mod import_ciphers_request_model; pub use self::import_ciphers_request_model::ImportCiphersRequestModel; pub mod import_organization_ciphers_request_model; @@ -316,6 +316,8 @@ pub mod organization_api_key_request_model; pub use self::organization_api_key_request_model::OrganizationApiKeyRequestModel; pub mod organization_api_key_type; pub use self::organization_api_key_type::OrganizationApiKeyType; +pub mod organization_auth_request_update_many_request_model; +pub use self::organization_auth_request_update_many_request_model::OrganizationAuthRequestUpdateManyRequestModel; pub mod organization_auto_enroll_status_response_model; pub use self::organization_auto_enroll_status_response_model::OrganizationAutoEnrollStatusResponseModel; pub mod organization_billing_status_response_model; @@ -328,6 +330,8 @@ pub mod organization_connection_response_model; pub use self::organization_connection_response_model::OrganizationConnectionResponseModel; pub mod organization_connection_type; pub use self::organization_connection_type::OrganizationConnectionType; +pub mod organization_counts_response_model; +pub use self::organization_counts_response_model::OrganizationCountsResponseModel; pub mod organization_create_request_model; pub use self::organization_create_request_model::OrganizationCreateRequestModel; pub mod organization_domain_request_model; @@ -402,6 +406,8 @@ pub mod organization_user_public_key_response_model_list_response_model; pub use self::organization_user_public_key_response_model_list_response_model::OrganizationUserPublicKeyResponseModelListResponseModel; pub mod organization_user_reset_password_details_response_model; pub use self::organization_user_reset_password_details_response_model::OrganizationUserResetPasswordDetailsResponseModel; +pub mod organization_user_reset_password_details_response_model_list_response_model; +pub use self::organization_user_reset_password_details_response_model_list_response_model::OrganizationUserResetPasswordDetailsResponseModelListResponseModel; pub mod organization_user_reset_password_enrollment_request_model; pub use self::organization_user_reset_password_enrollment_request_model::OrganizationUserResetPasswordEnrollmentRequestModel; pub mod organization_user_reset_password_request_model; @@ -462,12 +468,8 @@ pub mod potential_grantee_response_model; pub use self::potential_grantee_response_model::PotentialGranteeResponseModel; pub mod potential_grantee_response_model_list_response_model; pub use self::potential_grantee_response_model_list_response_model::PotentialGranteeResponseModelListResponseModel; -pub mod prelogin_request_model; -pub use self::prelogin_request_model::PreloginRequestModel; -pub mod prelogin_response_model; -pub use self::prelogin_response_model::PreloginResponseModel; -pub mod product_type; -pub use self::product_type::ProductType; +pub mod product_tier_type; +pub use self::product_tier_type::ProductTierType; pub mod profile_organization_response_model; pub use self::profile_organization_response_model::ProfileOrganizationResponseModel; pub mod profile_organization_response_model_list_response_model; @@ -478,6 +480,8 @@ pub mod profile_provider_response_model; pub use self::profile_provider_response_model::ProfileProviderResponseModel; pub mod profile_response_model; pub use self::profile_response_model::ProfileResponseModel; +pub mod project_counts_response_model; +pub use self::project_counts_response_model::ProjectCountsResponseModel; pub mod project_create_request_model; pub use self::project_create_request_model::ProjectCreateRequestModel; pub mod project_people_access_policies_response_model; @@ -566,10 +570,8 @@ pub mod push_type; pub use self::push_type::PushType; pub mod push_update_request_model; pub use self::push_update_request_model::PushUpdateRequestModel; -pub mod register_request_model; -pub use self::register_request_model::RegisterRequestModel; -pub mod register_response_model; -pub use self::register_response_model::RegisterResponseModel; +pub mod request_sm_access_request_model; +pub use self::request_sm_access_request_model::RequestSmAccessRequestModel; pub mod reset_password_with_org_id_request_model; pub use self::reset_password_with_org_id_request_model::ResetPasswordWithOrgIdRequestModel; pub mod response_data; @@ -582,6 +584,10 @@ pub mod saml2_name_id_format; pub use self::saml2_name_id_format::Saml2NameIdFormat; pub mod saml2_signing_behavior; pub use self::saml2_signing_behavior::Saml2SigningBehavior; +pub mod secret_access_policies_requests_model; +pub use self::secret_access_policies_requests_model::SecretAccessPoliciesRequestsModel; +pub mod secret_access_policies_response_model; +pub use self::secret_access_policies_response_model::SecretAccessPoliciesResponseModel; pub mod secret_create_request_model; pub use self::secret_create_request_model::SecretCreateRequestModel; pub mod secret_response_inner_project; @@ -634,6 +640,10 @@ pub mod send_with_id_request_model; pub use self::send_with_id_request_model::SendWithIdRequestModel; pub mod server_config_response_model; pub use self::server_config_response_model::ServerConfigResponseModel; +pub mod service_account_access_policy_response_model; +pub use self::service_account_access_policy_response_model::ServiceAccountAccessPolicyResponseModel; +pub mod service_account_counts_response_model; +pub use self::service_account_counts_response_model::ServiceAccountCountsResponseModel; pub mod service_account_create_request_model; pub use self::service_account_create_request_model::ServiceAccountCreateRequestModel; pub mod service_account_granted_policies_permission_details_response_model; @@ -642,10 +652,6 @@ pub mod service_account_granted_policies_request_model; pub use self::service_account_granted_policies_request_model::ServiceAccountGrantedPoliciesRequestModel; pub mod service_account_people_access_policies_response_model; pub use self::service_account_people_access_policies_response_model::ServiceAccountPeopleAccessPoliciesResponseModel; -pub mod service_account_project_access_policy_permission_details_response_model; -pub use self::service_account_project_access_policy_permission_details_response_model::ServiceAccountProjectAccessPolicyPermissionDetailsResponseModel; -pub mod service_account_project_access_policy_response_model; -pub use self::service_account_project_access_policy_response_model::ServiceAccountProjectAccessPolicyResponseModel; pub mod service_account_response_model; pub use self::service_account_response_model::ServiceAccountResponseModel; pub mod service_account_secrets_details_response_model; @@ -682,12 +688,16 @@ pub mod tax_info_response_model; pub use self::tax_info_response_model::TaxInfoResponseModel; pub mod tax_info_update_request_model; pub use self::tax_info_update_request_model::TaxInfoUpdateRequestModel; +pub mod tax_information_request_body; +pub use self::tax_information_request_body::TaxInformationRequestBody; pub mod tax_rate_response_model; pub use self::tax_rate_response_model::TaxRateResponseModel; pub mod tax_rate_response_model_list_response_model; pub use self::tax_rate_response_model_list_response_model::TaxRateResponseModelListResponseModel; pub mod transaction_type; pub use self::transaction_type::TransactionType; +pub mod two_factor_authenticator_disable_request_model; +pub use self::two_factor_authenticator_disable_request_model::TwoFactorAuthenticatorDisableRequestModel; pub mod two_factor_authenticator_response_model; pub use self::two_factor_authenticator_response_model::TwoFactorAuthenticatorResponseModel; pub mod two_factor_duo_response_model; @@ -728,6 +738,8 @@ pub mod update_key_request_model; pub use self::update_key_request_model::UpdateKeyRequestModel; pub mod update_profile_request_model; pub use self::update_profile_request_model::UpdateProfileRequestModel; +pub mod update_tde_offboarding_password_request_model; +pub use self::update_tde_offboarding_password_request_model::UpdateTdeOffboardingPasswordRequestModel; pub mod update_temp_password_request_model; pub use self::update_temp_password_request_model::UpdateTempPasswordRequestModel; pub mod update_two_factor_authenticator_request_model; @@ -742,14 +754,12 @@ pub mod uri_match_type; pub use self::uri_match_type::UriMatchType; pub mod user; pub use self::user::User; +pub mod user_access_policy_response_model; +pub use self::user_access_policy_response_model::UserAccessPolicyResponseModel; pub mod user_key_response_model; pub use self::user_key_response_model::UserKeyResponseModel; pub mod user_license; pub use self::user_license::UserLicense; -pub mod user_project_access_policy_response_model; -pub use self::user_project_access_policy_response_model::UserProjectAccessPolicyResponseModel; -pub mod user_service_account_access_policy_response_model; -pub use self::user_service_account_access_policy_response_model::UserServiceAccountAccessPolicyResponseModel; pub mod user_verification_requirement; pub use self::user_verification_requirement::UserVerificationRequirement; pub mod verify_delete_recover_request_model; @@ -770,5 +780,7 @@ pub mod web_authn_login_credential_create_request_model; pub use self::web_authn_login_credential_create_request_model::WebAuthnLoginCredentialCreateRequestModel; pub mod web_authn_login_credential_update_request_model; pub use self::web_authn_login_credential_update_request_model::WebAuthnLoginCredentialUpdateRequestModel; +pub mod web_authn_login_rotate_key_request_model; +pub use self::web_authn_login_rotate_key_request_model::WebAuthnLoginRotateKeyRequestModel; pub mod web_authn_prf_status; pub use self::web_authn_prf_status::WebAuthnPrfStatus; diff --git a/crates/bitwarden-api-api/src/models/organization_auth_request_update_many_request_model.rs b/crates/bitwarden-api-api/src/models/organization_auth_request_update_many_request_model.rs new file mode 100644 index 000000000..cccb1b9ed --- /dev/null +++ b/crates/bitwarden-api-api/src/models/organization_auth_request_update_many_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 + */ + +use serde::{Deserialize, Serialize}; + +use crate::models; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct OrganizationAuthRequestUpdateManyRequestModel { + #[serde(rename = "id", skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(rename = "key", skip_serializing_if = "Option::is_none")] + pub key: Option, + #[serde(rename = "approved", skip_serializing_if = "Option::is_none")] + pub approved: Option, +} + +impl OrganizationAuthRequestUpdateManyRequestModel { + pub fn new() -> OrganizationAuthRequestUpdateManyRequestModel { + OrganizationAuthRequestUpdateManyRequestModel { + id: None, + key: None, + approved: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/organization_counts_response_model.rs b/crates/bitwarden-api-api/src/models/organization_counts_response_model.rs new file mode 100644 index 000000000..4ba9203c0 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/organization_counts_response_model.rs @@ -0,0 +1,36 @@ +/* + * 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 + */ + +use serde::{Deserialize, Serialize}; + +use crate::models; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct OrganizationCountsResponseModel { + #[serde(rename = "object", skip_serializing_if = "Option::is_none")] + pub object: Option, + #[serde(rename = "projects", skip_serializing_if = "Option::is_none")] + pub projects: Option, + #[serde(rename = "secrets", skip_serializing_if = "Option::is_none")] + pub secrets: Option, + #[serde(rename = "serviceAccounts", skip_serializing_if = "Option::is_none")] + pub service_accounts: Option, +} + +impl OrganizationCountsResponseModel { + pub fn new() -> OrganizationCountsResponseModel { + OrganizationCountsResponseModel { + object: None, + projects: None, + secrets: None, + service_accounts: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/organization_response_model.rs b/crates/bitwarden-api-api/src/models/organization_response_model.rs index b696cce7e..31aed69c4 100644 --- a/crates/bitwarden-api-api/src/models/organization_response_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_response_model.rs @@ -112,11 +112,6 @@ pub struct OrganizationResponseModel { skip_serializing_if = "Option::is_none" )] pub allow_admin_access_to_all_collection_items: Option, - #[serde( - rename = "flexibleCollections", - skip_serializing_if = "Option::is_none" - )] - pub flexible_collections: Option, } impl OrganizationResponseModel { @@ -162,7 +157,6 @@ impl OrganizationResponseModel { max_autoscale_sm_service_accounts: None, limit_collection_creation_deletion: None, allow_admin_access_to_all_collection_items: None, - flexible_collections: 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 4eb316ba0..b2c3acb0a 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 @@ -112,11 +112,6 @@ pub struct OrganizationSubscriptionResponseModel { skip_serializing_if = "Option::is_none" )] pub allow_admin_access_to_all_collection_items: Option, - #[serde( - rename = "flexibleCollections", - skip_serializing_if = "Option::is_none" - )] - pub flexible_collections: Option, #[serde(rename = "storageName", skip_serializing_if = "Option::is_none")] pub storage_name: Option, #[serde(rename = "storageGb", skip_serializing_if = "Option::is_none")] @@ -181,7 +176,6 @@ impl OrganizationSubscriptionResponseModel { max_autoscale_sm_service_accounts: None, limit_collection_creation_deletion: None, allow_admin_access_to_all_collection_items: None, - flexible_collections: None, storage_name: None, storage_gb: None, customer_discount: None, diff --git a/crates/bitwarden-api-api/src/models/organization_user_details_response_model.rs b/crates/bitwarden-api-api/src/models/organization_user_details_response_model.rs index f10c7b10b..b9ea3c152 100644 --- a/crates/bitwarden-api-api/src/models/organization_user_details_response_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_user_details_response_model.rs @@ -24,8 +24,6 @@ pub struct OrganizationUserDetailsResponseModel { pub r#type: Option, #[serde(rename = "status", skip_serializing_if = "Option::is_none")] pub status: Option, - #[serde(rename = "accessAll", skip_serializing_if = "Option::is_none")] - pub access_all: Option, #[serde(rename = "externalId", skip_serializing_if = "Option::is_none")] pub external_id: Option, #[serde( @@ -58,7 +56,6 @@ impl OrganizationUserDetailsResponseModel { user_id: None, r#type: None, status: None, - access_all: None, external_id: None, access_secrets_manager: None, permissions: None, diff --git a/crates/bitwarden-api-api/src/models/organization_user_invite_request_model.rs b/crates/bitwarden-api-api/src/models/organization_user_invite_request_model.rs index 2e66bd7f2..0a6f2d4c7 100644 --- a/crates/bitwarden-api-api/src/models/organization_user_invite_request_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_user_invite_request_model.rs @@ -18,8 +18,6 @@ pub struct OrganizationUserInviteRequestModel { pub emails: Vec, #[serde(rename = "type")] pub r#type: models::OrganizationUserType, - #[serde(rename = "accessAll", skip_serializing_if = "Option::is_none")] - pub access_all: Option, #[serde( rename = "accessSecretsManager", skip_serializing_if = "Option::is_none" @@ -41,7 +39,6 @@ impl OrganizationUserInviteRequestModel { OrganizationUserInviteRequestModel { emails, r#type, - access_all: None, access_secrets_manager: None, permissions: None, collections: None, diff --git a/crates/bitwarden-api-api/src/models/organization_user_reset_password_details_response_model.rs b/crates/bitwarden-api-api/src/models/organization_user_reset_password_details_response_model.rs index eb999f06c..1dabdd642 100644 --- a/crates/bitwarden-api-api/src/models/organization_user_reset_password_details_response_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_user_reset_password_details_response_model.rs @@ -16,6 +16,8 @@ use crate::models; pub struct OrganizationUserResetPasswordDetailsResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, + #[serde(rename = "organizationUserId", skip_serializing_if = "Option::is_none")] + pub organization_user_id: Option, #[serde(rename = "kdf", skip_serializing_if = "Option::is_none")] pub kdf: Option, #[serde(rename = "kdfIterations", skip_serializing_if = "Option::is_none")] @@ -37,6 +39,7 @@ impl OrganizationUserResetPasswordDetailsResponseModel { pub fn new() -> OrganizationUserResetPasswordDetailsResponseModel { OrganizationUserResetPasswordDetailsResponseModel { object: None, + organization_user_id: None, kdf: None, kdf_iterations: None, kdf_memory: None, diff --git a/crates/bitwarden-api-api/src/models/organization_user_reset_password_details_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/organization_user_reset_password_details_response_model_list_response_model.rs new file mode 100644 index 000000000..600973567 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/organization_user_reset_password_details_response_model_list_response_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 + */ + +use serde::{Deserialize, Serialize}; + +use crate::models; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct OrganizationUserResetPasswordDetailsResponseModelListResponseModel { + #[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 OrganizationUserResetPasswordDetailsResponseModelListResponseModel { + pub fn new() -> OrganizationUserResetPasswordDetailsResponseModelListResponseModel { + OrganizationUserResetPasswordDetailsResponseModelListResponseModel { + object: None, + data: None, + continuation_token: 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 edd45ea80..a61256ca3 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 @@ -16,12 +16,15 @@ use crate::models; pub struct OrganizationUserResetPasswordEnrollmentRequestModel { #[serde(rename = "resetPasswordKey", skip_serializing_if = "Option::is_none")] pub reset_password_key: Option, + #[serde(rename = "masterPasswordHash", skip_serializing_if = "Option::is_none")] + pub master_password_hash: Option, } impl OrganizationUserResetPasswordEnrollmentRequestModel { pub fn new() -> OrganizationUserResetPasswordEnrollmentRequestModel { OrganizationUserResetPasswordEnrollmentRequestModel { reset_password_key: None, + master_password_hash: None, } } } diff --git a/crates/bitwarden-api-api/src/models/organization_user_type.rs b/crates/bitwarden-api-api/src/models/organization_user_type.rs index a87ea0960..cf79c2a38 100644 --- a/crates/bitwarden-api-api/src/models/organization_user_type.rs +++ b/crates/bitwarden-api-api/src/models/organization_user_type.rs @@ -30,7 +30,6 @@ pub enum OrganizationUserType { Owner = 0, Admin = 1, User = 2, - Manager = 3, Custom = 4, } @@ -40,7 +39,6 @@ impl std::fmt::Display for OrganizationUserType { Self::Owner => write!(f, "0"), Self::Admin => write!(f, "1"), Self::User => write!(f, "2"), - Self::Manager => write!(f, "3"), Self::Custom => write!(f, "4"), } } diff --git a/crates/bitwarden-api-api/src/models/organization_user_update_request_model.rs b/crates/bitwarden-api-api/src/models/organization_user_update_request_model.rs index 1d6fa833f..983177357 100644 --- a/crates/bitwarden-api-api/src/models/organization_user_update_request_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_user_update_request_model.rs @@ -16,8 +16,6 @@ use crate::models; pub struct OrganizationUserUpdateRequestModel { #[serde(rename = "type")] pub r#type: models::OrganizationUserType, - #[serde(rename = "accessAll", skip_serializing_if = "Option::is_none")] - pub access_all: Option, #[serde( rename = "accessSecretsManager", skip_serializing_if = "Option::is_none" @@ -35,7 +33,6 @@ impl OrganizationUserUpdateRequestModel { pub fn new(r#type: models::OrganizationUserType) -> OrganizationUserUpdateRequestModel { OrganizationUserUpdateRequestModel { r#type, - access_all: None, access_secrets_manager: None, permissions: None, collections: None, diff --git a/crates/bitwarden-api-api/src/models/organization_user_user_details_response_model.rs b/crates/bitwarden-api-api/src/models/organization_user_user_details_response_model.rs index 60cc46053..37b04edde 100644 --- a/crates/bitwarden-api-api/src/models/organization_user_user_details_response_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_user_user_details_response_model.rs @@ -24,8 +24,6 @@ pub struct OrganizationUserUserDetailsResponseModel { pub r#type: Option, #[serde(rename = "status", skip_serializing_if = "Option::is_none")] pub status: Option, - #[serde(rename = "accessAll", skip_serializing_if = "Option::is_none")] - pub access_all: Option, #[serde(rename = "externalId", skip_serializing_if = "Option::is_none")] pub external_id: Option, #[serde( @@ -68,7 +66,6 @@ impl OrganizationUserUserDetailsResponseModel { user_id: None, r#type: None, status: None, - access_all: None, external_id: None, access_secrets_manager: None, permissions: None, diff --git a/crates/bitwarden-api-api/src/models/password_manager_plan_features_response_model.rs b/crates/bitwarden-api-api/src/models/password_manager_plan_features_response_model.rs index 618a85cb0..d7e9514dd 100644 --- a/crates/bitwarden-api-api/src/models/password_manager_plan_features_response_model.rs +++ b/crates/bitwarden-api-api/src/models/password_manager_plan_features_response_model.rs @@ -18,10 +18,20 @@ pub struct PasswordManagerPlanFeaturesResponseModel { pub stripe_plan_id: Option, #[serde(rename = "stripeSeatPlanId", skip_serializing_if = "Option::is_none")] pub stripe_seat_plan_id: Option, + #[serde( + rename = "stripeProviderPortalSeatPlanId", + skip_serializing_if = "Option::is_none" + )] + pub stripe_provider_portal_seat_plan_id: Option, #[serde(rename = "basePrice", skip_serializing_if = "Option::is_none")] pub base_price: Option, #[serde(rename = "seatPrice", skip_serializing_if = "Option::is_none")] pub seat_price: Option, + #[serde( + rename = "providerPortalSeatPrice", + skip_serializing_if = "Option::is_none" + )] + pub provider_portal_seat_price: Option, #[serde(rename = "allowSeatAutoscale", skip_serializing_if = "Option::is_none")] pub allow_seat_autoscale: Option, #[serde( @@ -81,8 +91,10 @@ impl PasswordManagerPlanFeaturesResponseModel { PasswordManagerPlanFeaturesResponseModel { stripe_plan_id: None, stripe_seat_plan_id: None, + stripe_provider_portal_seat_plan_id: None, base_price: None, seat_price: None, + provider_portal_seat_price: None, allow_seat_autoscale: None, has_additional_seats_option: None, max_additional_seats: None, diff --git a/crates/bitwarden-api-api/src/models/plan_response_model.rs b/crates/bitwarden-api-api/src/models/plan_response_model.rs index d475ea3c5..e6d3a34d4 100644 --- a/crates/bitwarden-api-api/src/models/plan_response_model.rs +++ b/crates/bitwarden-api-api/src/models/plan_response_model.rs @@ -18,8 +18,8 @@ pub struct PlanResponseModel { pub object: Option, #[serde(rename = "type", skip_serializing_if = "Option::is_none")] pub r#type: Option, - #[serde(rename = "product", skip_serializing_if = "Option::is_none")] - pub product: Option, + #[serde(rename = "productTier", skip_serializing_if = "Option::is_none")] + pub product_tier: Option, #[serde(rename = "name", skip_serializing_if = "Option::is_none")] pub name: Option, #[serde(rename = "isAnnual", skip_serializing_if = "Option::is_none")] @@ -82,7 +82,7 @@ impl PlanResponseModel { PlanResponseModel { object: None, r#type: None, - product: None, + product_tier: None, name: None, is_annual: None, name_localization_key: None, diff --git a/crates/bitwarden-api-api/src/models/policy_type.rs b/crates/bitwarden-api-api/src/models/policy_type.rs index b20dfeb42..75f35b0ca 100644 --- a/crates/bitwarden-api-api/src/models/policy_type.rs +++ b/crates/bitwarden-api-api/src/models/policy_type.rs @@ -39,6 +39,7 @@ pub enum PolicyType { MaximumVaultTimeout = 9, DisablePersonalVaultExport = 10, ActivateAutofill = 11, + AutomaticAppLogIn = 12, } impl std::fmt::Display for PolicyType { @@ -56,6 +57,7 @@ impl std::fmt::Display for PolicyType { Self::MaximumVaultTimeout => write!(f, "9"), Self::DisablePersonalVaultExport => write!(f, "10"), Self::ActivateAutofill => write!(f, "11"), + Self::AutomaticAppLogIn => write!(f, "12"), } } } diff --git a/crates/bitwarden-api-api/src/models/prelogin_request_model.rs b/crates/bitwarden-api-api/src/models/prelogin_request_model.rs deleted file mode 100644 index 3fc815e5c..000000000 --- a/crates/bitwarden-api-api/src/models/prelogin_request_model.rs +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 - */ - -use serde::{Deserialize, Serialize}; - -use crate::models; - -#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] -pub struct PreloginRequestModel { - #[serde(rename = "email")] - pub email: String, -} - -impl PreloginRequestModel { - pub fn new(email: String) -> PreloginRequestModel { - PreloginRequestModel { email } - } -} diff --git a/crates/bitwarden-api-api/src/models/prelogin_response_model.rs b/crates/bitwarden-api-api/src/models/prelogin_response_model.rs deleted file mode 100644 index 8bf4aaf76..000000000 --- a/crates/bitwarden-api-api/src/models/prelogin_response_model.rs +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 - */ - -use serde::{Deserialize, Serialize}; - -use crate::models; - -#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] -pub struct PreloginResponseModel { - #[serde(rename = "kdf", skip_serializing_if = "Option::is_none")] - pub kdf: Option, - #[serde(rename = "kdfIterations", skip_serializing_if = "Option::is_none")] - pub kdf_iterations: Option, - #[serde(rename = "kdfMemory", skip_serializing_if = "Option::is_none")] - pub kdf_memory: Option, - #[serde(rename = "kdfParallelism", skip_serializing_if = "Option::is_none")] - pub kdf_parallelism: Option, -} - -impl PreloginResponseModel { - pub fn new() -> PreloginResponseModel { - PreloginResponseModel { - kdf: None, - kdf_iterations: None, - kdf_memory: None, - kdf_parallelism: None, - } - } -} diff --git a/crates/bitwarden-api-api/src/models/product_type.rs b/crates/bitwarden-api-api/src/models/product_tier_type.rs similarity index 86% rename from crates/bitwarden-api-api/src/models/product_type.rs rename to crates/bitwarden-api-api/src/models/product_tier_type.rs index 38512819c..825de4856 100644 --- a/crates/bitwarden-api-api/src/models/product_type.rs +++ b/crates/bitwarden-api-api/src/models/product_tier_type.rs @@ -26,7 +26,7 @@ use crate::models; serde_repr::Serialize_repr, serde_repr::Deserialize_repr, )] -pub enum ProductType { +pub enum ProductTierType { Free = 0, Families = 1, Teams = 2, @@ -34,7 +34,7 @@ pub enum ProductType { TeamsStarter = 4, } -impl std::fmt::Display for ProductType { +impl std::fmt::Display for ProductTierType { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Self::Free => write!(f, "0"), @@ -46,8 +46,8 @@ impl std::fmt::Display for ProductType { } } -impl Default for ProductType { - fn default() -> ProductType { +impl Default for ProductTierType { + fn default() -> ProductTierType { Self::Free } } diff --git a/crates/bitwarden-api-api/src/models/profile_organization_response_model.rs b/crates/bitwarden-api-api/src/models/profile_organization_response_model.rs index fc11b8284..42c22f998 100644 --- a/crates/bitwarden-api-api/src/models/profile_organization_response_model.rs +++ b/crates/bitwarden-api-api/src/models/profile_organization_response_model.rs @@ -87,6 +87,8 @@ pub struct ProfileOrganizationResponseModel { pub reset_password_enrolled: Option, #[serde(rename = "userId", skip_serializing_if = "Option::is_none")] pub user_id: Option, + #[serde(rename = "organizationUserId", skip_serializing_if = "Option::is_none")] + pub organization_user_id: Option, #[serde( rename = "hasPublicAndPrivateKeys", skip_serializing_if = "Option::is_none" @@ -108,8 +110,8 @@ pub struct ProfileOrganizationResponseModel { skip_serializing_if = "Option::is_none" )] pub family_sponsorship_available: Option, - #[serde(rename = "planProductType", skip_serializing_if = "Option::is_none")] - pub plan_product_type: Option, + #[serde(rename = "productTierType", skip_serializing_if = "Option::is_none")] + pub product_tier_type: Option, #[serde( rename = "keyConnectorEnabled", skip_serializing_if = "Option::is_none" @@ -147,11 +149,6 @@ pub struct ProfileOrganizationResponseModel { skip_serializing_if = "Option::is_none" )] pub allow_admin_access_to_all_collection_items: Option, - #[serde( - rename = "flexibleCollections", - skip_serializing_if = "Option::is_none" - )] - pub flexible_collections: Option, } impl ProfileOrganizationResponseModel { @@ -189,13 +186,14 @@ impl ProfileOrganizationResponseModel { permissions: None, reset_password_enrolled: None, user_id: None, + organization_user_id: None, has_public_and_private_keys: None, provider_id: None, provider_name: None, provider_type: None, family_sponsorship_friendly_name: None, family_sponsorship_available: None, - plan_product_type: None, + product_tier_type: None, key_connector_enabled: None, key_connector_url: None, family_sponsorship_last_sync_date: None, @@ -204,7 +202,6 @@ impl ProfileOrganizationResponseModel { access_secrets_manager: None, limit_collection_creation_deletion: None, allow_admin_access_to_all_collection_items: None, - flexible_collections: None, } } } diff --git a/crates/bitwarden-api-api/src/models/profile_provider_organization_response_model.rs b/crates/bitwarden-api-api/src/models/profile_provider_organization_response_model.rs index dc102a396..92cfbd46b 100644 --- a/crates/bitwarden-api-api/src/models/profile_provider_organization_response_model.rs +++ b/crates/bitwarden-api-api/src/models/profile_provider_organization_response_model.rs @@ -87,6 +87,8 @@ pub struct ProfileProviderOrganizationResponseModel { pub reset_password_enrolled: Option, #[serde(rename = "userId", skip_serializing_if = "Option::is_none")] pub user_id: Option, + #[serde(rename = "organizationUserId", skip_serializing_if = "Option::is_none")] + pub organization_user_id: Option, #[serde( rename = "hasPublicAndPrivateKeys", skip_serializing_if = "Option::is_none" @@ -108,8 +110,8 @@ pub struct ProfileProviderOrganizationResponseModel { skip_serializing_if = "Option::is_none" )] pub family_sponsorship_available: Option, - #[serde(rename = "planProductType", skip_serializing_if = "Option::is_none")] - pub plan_product_type: Option, + #[serde(rename = "productTierType", skip_serializing_if = "Option::is_none")] + pub product_tier_type: Option, #[serde( rename = "keyConnectorEnabled", skip_serializing_if = "Option::is_none" @@ -147,11 +149,6 @@ pub struct ProfileProviderOrganizationResponseModel { skip_serializing_if = "Option::is_none" )] pub allow_admin_access_to_all_collection_items: Option, - #[serde( - rename = "flexibleCollections", - skip_serializing_if = "Option::is_none" - )] - pub flexible_collections: Option, } impl ProfileProviderOrganizationResponseModel { @@ -189,13 +186,14 @@ impl ProfileProviderOrganizationResponseModel { permissions: None, reset_password_enrolled: None, user_id: None, + organization_user_id: None, has_public_and_private_keys: None, provider_id: None, provider_name: None, provider_type: None, family_sponsorship_friendly_name: None, family_sponsorship_available: None, - plan_product_type: None, + product_tier_type: None, key_connector_enabled: None, key_connector_url: None, family_sponsorship_last_sync_date: None, @@ -204,7 +202,6 @@ impl ProfileProviderOrganizationResponseModel { access_secrets_manager: None, limit_collection_creation_deletion: None, allow_admin_access_to_all_collection_items: None, - flexible_collections: None, } } } diff --git a/crates/bitwarden-api-api/src/models/project_counts_response_model.rs b/crates/bitwarden-api-api/src/models/project_counts_response_model.rs new file mode 100644 index 000000000..adcc97c82 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/project_counts_response_model.rs @@ -0,0 +1,36 @@ +/* + * 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 + */ + +use serde::{Deserialize, Serialize}; + +use crate::models; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct ProjectCountsResponseModel { + #[serde(rename = "object", skip_serializing_if = "Option::is_none")] + pub object: Option, + #[serde(rename = "secrets", skip_serializing_if = "Option::is_none")] + pub secrets: Option, + #[serde(rename = "people", skip_serializing_if = "Option::is_none")] + pub people: Option, + #[serde(rename = "serviceAccounts", skip_serializing_if = "Option::is_none")] + pub service_accounts: Option, +} + +impl ProjectCountsResponseModel { + pub fn new() -> ProjectCountsResponseModel { + ProjectCountsResponseModel { + object: None, + secrets: None, + people: None, + service_accounts: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/project_people_access_policies_response_model.rs b/crates/bitwarden-api-api/src/models/project_people_access_policies_response_model.rs index 43a5e71ff..83ba39e22 100644 --- a/crates/bitwarden-api-api/src/models/project_people_access_policies_response_model.rs +++ b/crates/bitwarden-api-api/src/models/project_people_access_policies_response_model.rs @@ -17,12 +17,12 @@ pub struct ProjectPeopleAccessPoliciesResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, #[serde(rename = "userAccessPolicies", skip_serializing_if = "Option::is_none")] - pub user_access_policies: Option>, + pub user_access_policies: Option>, #[serde( rename = "groupAccessPolicies", skip_serializing_if = "Option::is_none" )] - pub group_access_policies: Option>, + pub group_access_policies: Option>, } impl ProjectPeopleAccessPoliciesResponseModel { diff --git a/crates/bitwarden-api-api/src/models/project_service_accounts_access_policies_request_model.rs b/crates/bitwarden-api-api/src/models/project_service_accounts_access_policies_request_model.rs index 5356de2a5..3c54b4062 100644 --- a/crates/bitwarden-api-api/src/models/project_service_accounts_access_policies_request_model.rs +++ b/crates/bitwarden-api-api/src/models/project_service_accounts_access_policies_request_model.rs @@ -14,17 +14,16 @@ use crate::models; #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] pub struct ProjectServiceAccountsAccessPoliciesRequestModel { - #[serde( - rename = "serviceAccountAccessPolicyRequests", - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "serviceAccountAccessPolicyRequests")] pub service_account_access_policy_requests: Option>, } impl ProjectServiceAccountsAccessPoliciesRequestModel { - pub fn new() -> ProjectServiceAccountsAccessPoliciesRequestModel { + pub fn new( + service_account_access_policy_requests: Option>, + ) -> ProjectServiceAccountsAccessPoliciesRequestModel { ProjectServiceAccountsAccessPoliciesRequestModel { - service_account_access_policy_requests: None, + service_account_access_policy_requests, } } } diff --git a/crates/bitwarden-api-api/src/models/project_service_accounts_access_policies_response_model.rs b/crates/bitwarden-api-api/src/models/project_service_accounts_access_policies_response_model.rs index 74c883c08..e5899817f 100644 --- a/crates/bitwarden-api-api/src/models/project_service_accounts_access_policies_response_model.rs +++ b/crates/bitwarden-api-api/src/models/project_service_accounts_access_policies_response_model.rs @@ -21,7 +21,7 @@ pub struct ProjectServiceAccountsAccessPoliciesResponseModel { skip_serializing_if = "Option::is_none" )] pub service_account_access_policies: - Option>, + Option>, } impl ProjectServiceAccountsAccessPoliciesResponseModel { diff --git a/crates/bitwarden-api-api/src/models/provider_organization_organization_details_response_model.rs b/crates/bitwarden-api-api/src/models/provider_organization_organization_details_response_model.rs index 9ea18d08a..c721d02de 100644 --- a/crates/bitwarden-api-api/src/models/provider_organization_organization_details_response_model.rs +++ b/crates/bitwarden-api-api/src/models/provider_organization_organization_details_response_model.rs @@ -34,6 +34,10 @@ pub struct ProviderOrganizationOrganizationDetailsResponseModel { pub user_count: Option, #[serde(rename = "seats", skip_serializing_if = "Option::is_none")] pub seats: Option, + #[serde(rename = "occupiedSeats", skip_serializing_if = "Option::is_none")] + pub occupied_seats: Option, + #[serde(rename = "remainingSeats", skip_serializing_if = "Option::is_none")] + pub remaining_seats: Option, #[serde(rename = "plan", skip_serializing_if = "Option::is_none")] pub plan: Option, #[serde(rename = "organizationName", skip_serializing_if = "Option::is_none")] @@ -53,6 +57,8 @@ impl ProviderOrganizationOrganizationDetailsResponseModel { revision_date: None, user_count: None, seats: None, + occupied_seats: None, + remaining_seats: None, plan: None, organization_name: None, } diff --git a/crates/bitwarden-api-api/src/models/provider_organization_response_model.rs b/crates/bitwarden-api-api/src/models/provider_organization_response_model.rs index 387189e4b..14342f584 100644 --- a/crates/bitwarden-api-api/src/models/provider_organization_response_model.rs +++ b/crates/bitwarden-api-api/src/models/provider_organization_response_model.rs @@ -34,6 +34,10 @@ pub struct ProviderOrganizationResponseModel { pub user_count: Option, #[serde(rename = "seats", skip_serializing_if = "Option::is_none")] pub seats: Option, + #[serde(rename = "occupiedSeats", skip_serializing_if = "Option::is_none")] + pub occupied_seats: Option, + #[serde(rename = "remainingSeats", skip_serializing_if = "Option::is_none")] + pub remaining_seats: Option, #[serde(rename = "plan", skip_serializing_if = "Option::is_none")] pub plan: Option, } @@ -51,6 +55,8 @@ impl ProviderOrganizationResponseModel { revision_date: None, user_count: None, seats: None, + occupied_seats: None, + remaining_seats: None, plan: None, } } diff --git a/crates/bitwarden-api-api/src/models/register_request_model.rs b/crates/bitwarden-api-api/src/models/register_request_model.rs deleted file mode 100644 index 714b5bb06..000000000 --- a/crates/bitwarden-api-api/src/models/register_request_model.rs +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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 - */ - -use serde::{Deserialize, Serialize}; - -use crate::models; - -#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] -pub struct RegisterRequestModel { - #[serde(rename = "name", skip_serializing_if = "Option::is_none")] - pub name: Option, - #[serde(rename = "email")] - pub email: String, - #[serde(rename = "masterPasswordHash")] - pub master_password_hash: String, - #[serde(rename = "masterPasswordHint", skip_serializing_if = "Option::is_none")] - pub master_password_hint: Option, - #[serde(rename = "captchaResponse", skip_serializing_if = "Option::is_none")] - pub captcha_response: Option, - #[serde(rename = "key", skip_serializing_if = "Option::is_none")] - pub key: Option, - #[serde(rename = "keys", skip_serializing_if = "Option::is_none")] - pub keys: Option>, - #[serde(rename = "token", skip_serializing_if = "Option::is_none")] - pub token: Option, - #[serde(rename = "organizationUserId", skip_serializing_if = "Option::is_none")] - pub organization_user_id: Option, - #[serde(rename = "kdf", skip_serializing_if = "Option::is_none")] - pub kdf: Option, - #[serde(rename = "kdfIterations", skip_serializing_if = "Option::is_none")] - pub kdf_iterations: Option, - #[serde(rename = "kdfMemory", skip_serializing_if = "Option::is_none")] - pub kdf_memory: Option, - #[serde(rename = "kdfParallelism", skip_serializing_if = "Option::is_none")] - pub kdf_parallelism: Option, - #[serde(rename = "referenceData", skip_serializing_if = "Option::is_none")] - pub reference_data: Option>, -} - -impl RegisterRequestModel { - pub fn new(email: String, master_password_hash: String) -> RegisterRequestModel { - RegisterRequestModel { - name: None, - email, - master_password_hash, - master_password_hint: None, - captcha_response: None, - key: None, - keys: None, - token: None, - organization_user_id: None, - kdf: None, - kdf_iterations: None, - kdf_memory: None, - kdf_parallelism: None, - reference_data: None, - } - } -} diff --git a/crates/bitwarden-api-api/src/models/register_response_model.rs b/crates/bitwarden-api-api/src/models/register_response_model.rs deleted file mode 100644 index 560f2409a..000000000 --- a/crates/bitwarden-api-api/src/models/register_response_model.rs +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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 - */ - -use serde::{Deserialize, Serialize}; - -use crate::models; - -#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] -pub struct RegisterResponseModel { - #[serde(rename = "object", skip_serializing_if = "Option::is_none")] - pub object: Option, - #[serde(rename = "captchaBypassToken", skip_serializing_if = "Option::is_none")] - pub captcha_bypass_token: Option, -} - -impl RegisterResponseModel { - pub fn new() -> RegisterResponseModel { - RegisterResponseModel { - object: None, - captcha_bypass_token: None, - } - } -} diff --git a/crates/bitwarden-api-api/src/models/request_sm_access_request_model.rs b/crates/bitwarden-api-api/src/models/request_sm_access_request_model.rs new file mode 100644 index 000000000..6ff8811db --- /dev/null +++ b/crates/bitwarden-api-api/src/models/request_sm_access_request_model.rs @@ -0,0 +1,30 @@ +/* + * 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 + */ + +use serde::{Deserialize, Serialize}; + +use crate::models; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct RequestSmAccessRequestModel { + #[serde(rename = "organizationId")] + pub organization_id: uuid::Uuid, + #[serde(rename = "emailContent")] + pub email_content: String, +} + +impl RequestSmAccessRequestModel { + pub fn new(organization_id: uuid::Uuid, email_content: String) -> RequestSmAccessRequestModel { + RequestSmAccessRequestModel { + organization_id, + email_content, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/reset_password_with_org_id_request_model.rs b/crates/bitwarden-api-api/src/models/reset_password_with_org_id_request_model.rs index 3bc06379f..a3ce5927f 100644 --- a/crates/bitwarden-api-api/src/models/reset_password_with_org_id_request_model.rs +++ b/crates/bitwarden-api-api/src/models/reset_password_with_org_id_request_model.rs @@ -16,6 +16,8 @@ use crate::models; pub struct ResetPasswordWithOrgIdRequestModel { #[serde(rename = "resetPasswordKey", skip_serializing_if = "Option::is_none")] pub reset_password_key: Option, + #[serde(rename = "masterPasswordHash", skip_serializing_if = "Option::is_none")] + pub master_password_hash: Option, #[serde(rename = "organizationId")] pub organization_id: uuid::Uuid, } @@ -24,6 +26,7 @@ impl ResetPasswordWithOrgIdRequestModel { pub fn new(organization_id: uuid::Uuid) -> ResetPasswordWithOrgIdRequestModel { ResetPasswordWithOrgIdRequestModel { reset_password_key: None, + master_password_hash: None, organization_id, } } diff --git a/crates/bitwarden-api-api/src/models/secret_access_policies_requests_model.rs b/crates/bitwarden-api-api/src/models/secret_access_policies_requests_model.rs new file mode 100644 index 000000000..6eaf09b36 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/secret_access_policies_requests_model.rs @@ -0,0 +1,37 @@ +/* + * 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 + */ + +use serde::{Deserialize, Serialize}; + +use crate::models; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct SecretAccessPoliciesRequestsModel { + #[serde(rename = "userAccessPolicyRequests")] + pub user_access_policy_requests: Option>, + #[serde(rename = "groupAccessPolicyRequests")] + pub group_access_policy_requests: Option>, + #[serde(rename = "serviceAccountAccessPolicyRequests")] + pub service_account_access_policy_requests: Option>, +} + +impl SecretAccessPoliciesRequestsModel { + pub fn new( + user_access_policy_requests: Option>, + group_access_policy_requests: Option>, + service_account_access_policy_requests: Option>, + ) -> SecretAccessPoliciesRequestsModel { + SecretAccessPoliciesRequestsModel { + user_access_policy_requests, + group_access_policy_requests, + service_account_access_policy_requests, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/secret_access_policies_response_model.rs b/crates/bitwarden-api-api/src/models/secret_access_policies_response_model.rs new file mode 100644 index 000000000..947926ad6 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/secret_access_policies_response_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 + */ + +use serde::{Deserialize, Serialize}; + +use crate::models; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct SecretAccessPoliciesResponseModel { + #[serde(rename = "object", skip_serializing_if = "Option::is_none")] + pub object: Option, + #[serde(rename = "userAccessPolicies", skip_serializing_if = "Option::is_none")] + pub user_access_policies: Option>, + #[serde( + rename = "groupAccessPolicies", + skip_serializing_if = "Option::is_none" + )] + pub group_access_policies: Option>, + #[serde( + rename = "serviceAccountAccessPolicies", + skip_serializing_if = "Option::is_none" + )] + pub service_account_access_policies: + Option>, +} + +impl SecretAccessPoliciesResponseModel { + pub fn new() -> SecretAccessPoliciesResponseModel { + SecretAccessPoliciesResponseModel { + object: None, + user_access_policies: None, + group_access_policies: None, + service_account_access_policies: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/secret_create_request_model.rs b/crates/bitwarden-api-api/src/models/secret_create_request_model.rs index 7325ba8e0..91b55cceb 100644 --- a/crates/bitwarden-api-api/src/models/secret_create_request_model.rs +++ b/crates/bitwarden-api-api/src/models/secret_create_request_model.rs @@ -22,6 +22,11 @@ pub struct SecretCreateRequestModel { pub note: String, #[serde(rename = "projectIds", skip_serializing_if = "Option::is_none")] pub project_ids: Option>, + #[serde( + rename = "accessPoliciesRequests", + skip_serializing_if = "Option::is_none" + )] + pub access_policies_requests: Option>, } impl SecretCreateRequestModel { @@ -31,6 +36,7 @@ impl SecretCreateRequestModel { value, note, project_ids: None, + access_policies_requests: None, } } } diff --git a/crates/bitwarden-api-api/src/models/secret_update_request_model.rs b/crates/bitwarden-api-api/src/models/secret_update_request_model.rs index c0f11a8a1..878fb7875 100644 --- a/crates/bitwarden-api-api/src/models/secret_update_request_model.rs +++ b/crates/bitwarden-api-api/src/models/secret_update_request_model.rs @@ -22,6 +22,11 @@ pub struct SecretUpdateRequestModel { pub note: String, #[serde(rename = "projectIds", skip_serializing_if = "Option::is_none")] pub project_ids: Option>, + #[serde( + rename = "accessPoliciesRequests", + skip_serializing_if = "Option::is_none" + )] + pub access_policies_requests: Option>, } impl SecretUpdateRequestModel { @@ -31,6 +36,7 @@ impl SecretUpdateRequestModel { value, note, project_ids: None, + access_policies_requests: None, } } } diff --git a/crates/bitwarden-api-api/src/models/service_account_access_policy_response_model.rs b/crates/bitwarden-api-api/src/models/service_account_access_policy_response_model.rs new file mode 100644 index 000000000..5418235f6 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/service_account_access_policy_response_model.rs @@ -0,0 +1,39 @@ +/* + * 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 + */ + +use serde::{Deserialize, Serialize}; + +use crate::models; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct ServiceAccountAccessPolicyResponseModel { + #[serde(rename = "object", skip_serializing_if = "Option::is_none")] + pub object: Option, + #[serde(rename = "read", skip_serializing_if = "Option::is_none")] + pub read: Option, + #[serde(rename = "write", skip_serializing_if = "Option::is_none")] + pub write: Option, + #[serde(rename = "serviceAccountId", skip_serializing_if = "Option::is_none")] + pub service_account_id: Option, + #[serde(rename = "serviceAccountName", skip_serializing_if = "Option::is_none")] + pub service_account_name: Option, +} + +impl ServiceAccountAccessPolicyResponseModel { + pub fn new() -> ServiceAccountAccessPolicyResponseModel { + ServiceAccountAccessPolicyResponseModel { + object: None, + read: None, + write: None, + service_account_id: None, + service_account_name: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/service_account_counts_response_model.rs b/crates/bitwarden-api-api/src/models/service_account_counts_response_model.rs new file mode 100644 index 000000000..fd382406e --- /dev/null +++ b/crates/bitwarden-api-api/src/models/service_account_counts_response_model.rs @@ -0,0 +1,36 @@ +/* + * 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 + */ + +use serde::{Deserialize, Serialize}; + +use crate::models; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct ServiceAccountCountsResponseModel { + #[serde(rename = "object", skip_serializing_if = "Option::is_none")] + pub object: Option, + #[serde(rename = "projects", skip_serializing_if = "Option::is_none")] + pub projects: Option, + #[serde(rename = "people", skip_serializing_if = "Option::is_none")] + pub people: Option, + #[serde(rename = "accessTokens", skip_serializing_if = "Option::is_none")] + pub access_tokens: Option, +} + +impl ServiceAccountCountsResponseModel { + pub fn new() -> ServiceAccountCountsResponseModel { + ServiceAccountCountsResponseModel { + object: None, + projects: None, + people: None, + access_tokens: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/service_account_granted_policies_permission_details_response_model.rs b/crates/bitwarden-api-api/src/models/service_account_granted_policies_permission_details_response_model.rs index d55f2bec7..8d7458867 100644 --- a/crates/bitwarden-api-api/src/models/service_account_granted_policies_permission_details_response_model.rs +++ b/crates/bitwarden-api-api/src/models/service_account_granted_policies_permission_details_response_model.rs @@ -21,7 +21,7 @@ pub struct ServiceAccountGrantedPoliciesPermissionDetailsResponseModel { skip_serializing_if = "Option::is_none" )] pub granted_project_policies: - Option>, + Option>, } impl ServiceAccountGrantedPoliciesPermissionDetailsResponseModel { diff --git a/crates/bitwarden-api-api/src/models/service_account_granted_policies_request_model.rs b/crates/bitwarden-api-api/src/models/service_account_granted_policies_request_model.rs index d9f87b85f..7fe6a0d26 100644 --- a/crates/bitwarden-api-api/src/models/service_account_granted_policies_request_model.rs +++ b/crates/bitwarden-api-api/src/models/service_account_granted_policies_request_model.rs @@ -14,17 +14,16 @@ use crate::models; #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] pub struct ServiceAccountGrantedPoliciesRequestModel { - #[serde( - rename = "projectGrantedPolicyRequests", - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "projectGrantedPolicyRequests")] pub project_granted_policy_requests: Option>, } impl ServiceAccountGrantedPoliciesRequestModel { - pub fn new() -> ServiceAccountGrantedPoliciesRequestModel { + pub fn new( + project_granted_policy_requests: Option>, + ) -> ServiceAccountGrantedPoliciesRequestModel { ServiceAccountGrantedPoliciesRequestModel { - project_granted_policy_requests: None, + project_granted_policy_requests, } } } diff --git a/crates/bitwarden-api-api/src/models/service_account_people_access_policies_response_model.rs b/crates/bitwarden-api-api/src/models/service_account_people_access_policies_response_model.rs index 84cf3ac18..affba169a 100644 --- a/crates/bitwarden-api-api/src/models/service_account_people_access_policies_response_model.rs +++ b/crates/bitwarden-api-api/src/models/service_account_people_access_policies_response_model.rs @@ -17,12 +17,12 @@ pub struct ServiceAccountPeopleAccessPoliciesResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, #[serde(rename = "userAccessPolicies", skip_serializing_if = "Option::is_none")] - pub user_access_policies: Option>, + pub user_access_policies: Option>, #[serde( rename = "groupAccessPolicies", skip_serializing_if = "Option::is_none" )] - pub group_access_policies: Option>, + pub group_access_policies: Option>, } impl ServiceAccountPeopleAccessPoliciesResponseModel { diff --git a/crates/bitwarden-api-api/src/models/tax_information_request_body.rs b/crates/bitwarden-api-api/src/models/tax_information_request_body.rs new file mode 100644 index 000000000..539608881 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/tax_information_request_body.rs @@ -0,0 +1,45 @@ +/* + * 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 + */ + +use serde::{Deserialize, Serialize}; + +use crate::models; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct TaxInformationRequestBody { + #[serde(rename = "country")] + pub country: String, + #[serde(rename = "postalCode")] + pub postal_code: String, + #[serde(rename = "taxId", skip_serializing_if = "Option::is_none")] + pub tax_id: Option, + #[serde(rename = "line1", skip_serializing_if = "Option::is_none")] + pub line1: Option, + #[serde(rename = "line2", skip_serializing_if = "Option::is_none")] + pub line2: Option, + #[serde(rename = "city", skip_serializing_if = "Option::is_none")] + pub city: Option, + #[serde(rename = "state", skip_serializing_if = "Option::is_none")] + pub state: Option, +} + +impl TaxInformationRequestBody { + pub fn new(country: String, postal_code: String) -> TaxInformationRequestBody { + TaxInformationRequestBody { + country, + postal_code, + tax_id: None, + line1: None, + line2: None, + city: None, + state: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/two_factor_authenticator_disable_request_model.rs b/crates/bitwarden-api-api/src/models/two_factor_authenticator_disable_request_model.rs new file mode 100644 index 000000000..6d05df555 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/two_factor_authenticator_disable_request_model.rs @@ -0,0 +1,52 @@ +/* + * 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 + */ + +use serde::{Deserialize, Serialize}; + +use crate::models; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct TwoFactorAuthenticatorDisableRequestModel { + #[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 = "type")] + pub r#type: models::TwoFactorProviderType, + #[serde(rename = "userVerificationToken")] + pub user_verification_token: String, + #[serde(rename = "key")] + pub key: String, +} + +impl TwoFactorAuthenticatorDisableRequestModel { + pub fn new( + r#type: models::TwoFactorProviderType, + user_verification_token: String, + key: String, + ) -> TwoFactorAuthenticatorDisableRequestModel { + TwoFactorAuthenticatorDisableRequestModel { + master_password_hash: None, + otp: None, + auth_request_access_code: None, + secret: None, + r#type, + user_verification_token, + key, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/two_factor_authenticator_response_model.rs b/crates/bitwarden-api-api/src/models/two_factor_authenticator_response_model.rs index bc4eaa339..dba1dc0fa 100644 --- a/crates/bitwarden-api-api/src/models/two_factor_authenticator_response_model.rs +++ b/crates/bitwarden-api-api/src/models/two_factor_authenticator_response_model.rs @@ -20,6 +20,11 @@ pub struct TwoFactorAuthenticatorResponseModel { pub enabled: Option, #[serde(rename = "key", skip_serializing_if = "Option::is_none")] pub key: Option, + #[serde( + rename = "userVerificationToken", + skip_serializing_if = "Option::is_none" + )] + pub user_verification_token: Option, } impl TwoFactorAuthenticatorResponseModel { @@ -28,6 +33,7 @@ impl TwoFactorAuthenticatorResponseModel { object: None, enabled: None, key: None, + user_verification_token: None, } } } diff --git a/crates/bitwarden-api-api/src/models/two_factor_duo_response_model.rs b/crates/bitwarden-api-api/src/models/two_factor_duo_response_model.rs index c1afa6b1f..35574f821 100644 --- a/crates/bitwarden-api-api/src/models/two_factor_duo_response_model.rs +++ b/crates/bitwarden-api-api/src/models/two_factor_duo_response_model.rs @@ -24,6 +24,10 @@ pub struct TwoFactorDuoResponseModel { pub secret_key: Option, #[serde(rename = "integrationKey", skip_serializing_if = "Option::is_none")] pub integration_key: Option, + #[serde(rename = "clientSecret", skip_serializing_if = "Option::is_none")] + pub client_secret: Option, + #[serde(rename = "clientId", skip_serializing_if = "Option::is_none")] + pub client_id: Option, } impl TwoFactorDuoResponseModel { @@ -34,6 +38,8 @@ impl TwoFactorDuoResponseModel { host: None, secret_key: None, integration_key: None, + client_secret: None, + client_id: None, } } } diff --git a/crates/bitwarden-api-api/src/models/update_key_request_model.rs b/crates/bitwarden-api-api/src/models/update_key_request_model.rs index e721fbecb..179c0fddd 100644 --- a/crates/bitwarden-api-api/src/models/update_key_request_model.rs +++ b/crates/bitwarden-api-api/src/models/update_key_request_model.rs @@ -33,6 +33,8 @@ pub struct UpdateKeyRequestModel { pub emergency_access_keys: Option>, #[serde(rename = "resetPasswordKeys", skip_serializing_if = "Option::is_none")] pub reset_password_keys: Option>, + #[serde(rename = "webAuthnKeys", skip_serializing_if = "Option::is_none")] + pub web_authn_keys: Option>, } impl UpdateKeyRequestModel { @@ -50,6 +52,7 @@ impl UpdateKeyRequestModel { sends: None, emergency_access_keys: None, reset_password_keys: None, + web_authn_keys: None, } } } diff --git a/crates/bitwarden-api-api/src/models/update_tde_offboarding_password_request_model.rs b/crates/bitwarden-api-api/src/models/update_tde_offboarding_password_request_model.rs new file mode 100644 index 000000000..aeb1f424b --- /dev/null +++ b/crates/bitwarden-api-api/src/models/update_tde_offboarding_password_request_model.rs @@ -0,0 +1,36 @@ +/* + * 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 + */ + +use serde::{Deserialize, Serialize}; + +use crate::models; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct UpdateTdeOffboardingPasswordRequestModel { + #[serde(rename = "newMasterPasswordHash")] + pub new_master_password_hash: String, + #[serde(rename = "key")] + pub key: String, + #[serde(rename = "masterPasswordHint", skip_serializing_if = "Option::is_none")] + pub master_password_hint: Option, +} + +impl UpdateTdeOffboardingPasswordRequestModel { + pub fn new( + new_master_password_hash: String, + key: String, + ) -> UpdateTdeOffboardingPasswordRequestModel { + UpdateTdeOffboardingPasswordRequestModel { + new_master_password_hash, + key, + master_password_hint: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/update_two_factor_authenticator_request_model.rs b/crates/bitwarden-api-api/src/models/update_two_factor_authenticator_request_model.rs index af4d177b5..314699a72 100644 --- a/crates/bitwarden-api-api/src/models/update_two_factor_authenticator_request_model.rs +++ b/crates/bitwarden-api-api/src/models/update_two_factor_authenticator_request_model.rs @@ -29,6 +29,11 @@ pub struct UpdateTwoFactorAuthenticatorRequestModel { pub token: String, #[serde(rename = "key")] pub key: String, + #[serde( + rename = "userVerificationToken", + skip_serializing_if = "Option::is_none" + )] + pub user_verification_token: Option, } impl UpdateTwoFactorAuthenticatorRequestModel { @@ -40,6 +45,7 @@ impl UpdateTwoFactorAuthenticatorRequestModel { secret: None, token, key, + user_verification_token: None, } } } diff --git a/crates/bitwarden-api-api/src/models/update_two_factor_duo_request_model.rs b/crates/bitwarden-api-api/src/models/update_two_factor_duo_request_model.rs index 61bf2c446..5a4989c52 100644 --- a/crates/bitwarden-api-api/src/models/update_two_factor_duo_request_model.rs +++ b/crates/bitwarden-api-api/src/models/update_two_factor_duo_request_model.rs @@ -25,27 +25,29 @@ pub struct UpdateTwoFactorDuoRequestModel { pub auth_request_access_code: Option, #[serde(rename = "secret", skip_serializing_if = "Option::is_none")] pub secret: Option, - #[serde(rename = "integrationKey")] - pub integration_key: String, - #[serde(rename = "secretKey")] - pub secret_key: String, + #[serde(rename = "clientId", skip_serializing_if = "Option::is_none")] + pub client_id: Option, + #[serde(rename = "clientSecret", skip_serializing_if = "Option::is_none")] + pub client_secret: Option, + #[serde(rename = "integrationKey", skip_serializing_if = "Option::is_none")] + pub integration_key: Option, + #[serde(rename = "secretKey", skip_serializing_if = "Option::is_none")] + pub secret_key: Option, #[serde(rename = "host")] pub host: String, } impl UpdateTwoFactorDuoRequestModel { - pub fn new( - integration_key: String, - secret_key: String, - host: String, - ) -> UpdateTwoFactorDuoRequestModel { + pub fn new(host: String) -> UpdateTwoFactorDuoRequestModel { UpdateTwoFactorDuoRequestModel { master_password_hash: None, otp: None, auth_request_access_code: None, secret: None, - integration_key, - secret_key, + client_id: None, + client_secret: None, + integration_key: None, + secret_key: None, host, } } diff --git a/crates/bitwarden-api-api/src/models/user_project_access_policy_response_model.rs b/crates/bitwarden-api-api/src/models/user_access_policy_response_model.rs similarity index 58% rename from crates/bitwarden-api-api/src/models/user_project_access_policy_response_model.rs rename to crates/bitwarden-api-api/src/models/user_access_policy_response_model.rs index c06deebde..5585a9c4f 100644 --- a/crates/bitwarden-api-api/src/models/user_project_access_policy_response_model.rs +++ b/crates/bitwarden-api-api/src/models/user_access_policy_response_model.rs @@ -13,19 +13,13 @@ use serde::{Deserialize, Serialize}; use crate::models; #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] -pub struct UserProjectAccessPolicyResponseModel { +pub struct UserAccessPolicyResponseModel { #[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 = "read", skip_serializing_if = "Option::is_none")] pub read: Option, #[serde(rename = "write", skip_serializing_if = "Option::is_none")] pub write: 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 = "organizationUserId", skip_serializing_if = "Option::is_none")] pub organization_user_id: Option, #[serde( @@ -33,27 +27,18 @@ pub struct UserProjectAccessPolicyResponseModel { skip_serializing_if = "Option::is_none" )] pub organization_user_name: Option, - #[serde(rename = "userId", skip_serializing_if = "Option::is_none")] - pub user_id: Option, - #[serde(rename = "grantedProjectId", skip_serializing_if = "Option::is_none")] - pub granted_project_id: Option, #[serde(rename = "currentUser", skip_serializing_if = "Option::is_none")] pub current_user: Option, } -impl UserProjectAccessPolicyResponseModel { - pub fn new() -> UserProjectAccessPolicyResponseModel { - UserProjectAccessPolicyResponseModel { +impl UserAccessPolicyResponseModel { + pub fn new() -> UserAccessPolicyResponseModel { + UserAccessPolicyResponseModel { object: None, - id: None, read: None, write: None, - creation_date: None, - revision_date: None, organization_user_id: None, organization_user_name: None, - user_id: None, - granted_project_id: None, current_user: None, } } diff --git a/crates/bitwarden-api-api/src/models/user_service_account_access_policy_response_model.rs b/crates/bitwarden-api-api/src/models/user_service_account_access_policy_response_model.rs deleted file mode 100644 index 1eaf94cda..000000000 --- a/crates/bitwarden-api-api/src/models/user_service_account_access_policy_response_model.rs +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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 - */ - -use serde::{Deserialize, Serialize}; - -use crate::models; - -#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] -pub struct UserServiceAccountAccessPolicyResponseModel { - #[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 = "read", skip_serializing_if = "Option::is_none")] - pub read: Option, - #[serde(rename = "write", skip_serializing_if = "Option::is_none")] - pub write: 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 = "organizationUserId", skip_serializing_if = "Option::is_none")] - pub organization_user_id: Option, - #[serde( - rename = "organizationUserName", - skip_serializing_if = "Option::is_none" - )] - pub organization_user_name: Option, - #[serde(rename = "userId", skip_serializing_if = "Option::is_none")] - pub user_id: Option, - #[serde( - rename = "grantedServiceAccountId", - skip_serializing_if = "Option::is_none" - )] - pub granted_service_account_id: Option, - #[serde(rename = "currentUser", skip_serializing_if = "Option::is_none")] - pub current_user: Option, -} - -impl UserServiceAccountAccessPolicyResponseModel { - pub fn new() -> UserServiceAccountAccessPolicyResponseModel { - UserServiceAccountAccessPolicyResponseModel { - object: None, - id: None, - read: None, - write: None, - creation_date: None, - revision_date: None, - organization_user_id: None, - organization_user_name: None, - user_id: None, - granted_service_account_id: None, - current_user: None, - } - } -} diff --git a/crates/bitwarden-api-api/src/models/web_authn_credential_response_model.rs b/crates/bitwarden-api-api/src/models/web_authn_credential_response_model.rs index 56534214f..fb1f9004f 100644 --- a/crates/bitwarden-api-api/src/models/web_authn_credential_response_model.rs +++ b/crates/bitwarden-api-api/src/models/web_authn_credential_response_model.rs @@ -22,6 +22,10 @@ pub struct WebAuthnCredentialResponseModel { pub name: Option, #[serde(rename = "prfStatus", skip_serializing_if = "Option::is_none")] pub prf_status: 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 WebAuthnCredentialResponseModel { @@ -31,6 +35,8 @@ impl WebAuthnCredentialResponseModel { id: None, name: None, prf_status: None, + encrypted_user_key: None, + encrypted_public_key: None, } } } diff --git a/crates/bitwarden-api-api/src/models/web_authn_login_rotate_key_request_model.rs b/crates/bitwarden-api-api/src/models/web_authn_login_rotate_key_request_model.rs new file mode 100644 index 000000000..b1beddeaa --- /dev/null +++ b/crates/bitwarden-api-api/src/models/web_authn_login_rotate_key_request_model.rs @@ -0,0 +1,37 @@ +/* + * 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 + */ + +use serde::{Deserialize, Serialize}; + +use crate::models; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct WebAuthnLoginRotateKeyRequestModel { + #[serde(rename = "id")] + pub id: uuid::Uuid, + #[serde(rename = "encryptedUserKey")] + pub encrypted_user_key: String, + #[serde(rename = "encryptedPublicKey")] + pub encrypted_public_key: String, +} + +impl WebAuthnLoginRotateKeyRequestModel { + pub fn new( + id: uuid::Uuid, + encrypted_user_key: String, + encrypted_public_key: String, + ) -> WebAuthnLoginRotateKeyRequestModel { + WebAuthnLoginRotateKeyRequestModel { + id, + encrypted_user_key, + encrypted_public_key, + } + } +} diff --git a/crates/bitwarden-api-identity/.openapi-generator/FILES b/crates/bitwarden-api-identity/.openapi-generator/FILES index 968fd8fe4..43c2edd75 100644 --- a/crates/bitwarden-api-identity/.openapi-generator/FILES +++ b/crates/bitwarden-api-identity/.openapi-generator/FILES @@ -15,9 +15,15 @@ src/models/keys_request_model.rs src/models/mod.rs src/models/prelogin_request_model.rs src/models/prelogin_response_model.rs +src/models/product_tier_type.rs +src/models/product_type.rs src/models/public_key_credential_descriptor.rs src/models/public_key_credential_type.rs +src/models/register_finish_request_model.rs src/models/register_request_model.rs src/models/register_response_model.rs +src/models/register_send_verification_email_request_model.rs +src/models/register_verification_email_clicked_request_model.rs +src/models/trial_send_verification_email_request_model.rs src/models/user_verification_requirement.rs src/models/web_authn_login_assertion_options_response_model.rs diff --git a/crates/bitwarden-api-identity/Cargo.toml b/crates/bitwarden-api-identity/Cargo.toml index 00ceada04..9d4d81a0d 100644 --- a/crates/bitwarden-api-identity/Cargo.toml +++ b/crates/bitwarden-api-identity/Cargo.toml @@ -13,10 +13,14 @@ license-file.workspace = true keywords.workspace = true [dependencies] -serde = { version = ">=1.0.163, <2", features = ["derive"] } -serde_with = { version = ">=3.8, <4", default-features = false, features = ["base64", "std", "macros"] } -serde_json = ">=1.0.96, <2" -serde_repr = ">=0.1.12, <0.2" +serde = { workspace = true } +serde_with = { version = ">=3.8, <4", default-features = false, features = [ + "base64", + "std", + "macros", +] } +serde_json = { workspace = true } +serde_repr = { workspace = true } url = ">=2.5, <3" -uuid = { version = ">=1.3.3, <2", features = ["serde", "v4"] } -reqwest = { version = ">=0.12, <0.13", features = ["json", "multipart", "http2"], default-features = false } +uuid = { workspace = true } +reqwest = { workspace = true } diff --git a/crates/bitwarden-api-identity/README.md b/crates/bitwarden-api-identity/README.md index 16339f7a9..ec789a3fb 100644 --- a/crates/bitwarden-api-identity/README.md +++ b/crates/bitwarden-api-identity/README.md @@ -27,18 +27,22 @@ bitwarden-api-identity = { path = "./bitwarden-api-identity" } All URIs are relative to _http://localhost_ -| Class | Method | HTTP request | Description | -| ------------- | ---------------------------------------------------------------------------------------------------------- | -------------------------------------------- | ----------- | -| _AccountsApi_ | [**accounts_prelogin_post**](docs/AccountsApi.md#accounts_prelogin_post) | **POST** /accounts/prelogin | -| _AccountsApi_ | [**accounts_register_post**](docs/AccountsApi.md#accounts_register_post) | **POST** /accounts/register | -| _AccountsApi_ | [**accounts_webauthn_assertion_options_get**](docs/AccountsApi.md#accounts_webauthn_assertion_options_get) | **GET** /accounts/webauthn/assertion-options | -| _InfoApi_ | [**alive_get**](docs/InfoApi.md#alive_get) | **GET** /alive | -| _InfoApi_ | [**now_get**](docs/InfoApi.md#now_get) | **GET** /now | -| _InfoApi_ | [**version_get**](docs/InfoApi.md#version_get) | **GET** /version | -| _SsoApi_ | [**sso_external_callback_get**](docs/SsoApi.md#sso_external_callback_get) | **GET** /sso/ExternalCallback | -| _SsoApi_ | [**sso_external_challenge_get**](docs/SsoApi.md#sso_external_challenge_get) | **GET** /sso/ExternalChallenge | -| _SsoApi_ | [**sso_login_get**](docs/SsoApi.md#sso_login_get) | **GET** /sso/Login | -| _SsoApi_ | [**sso_pre_validate_get**](docs/SsoApi.md#sso_pre_validate_get) | **GET** /sso/PreValidate | +| Class | Method | HTTP request | Description | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------ | ----------- | +| _AccountsApi_ | [**accounts_prelogin_post**](docs/AccountsApi.md#accounts_prelogin_post) | **POST** /accounts/prelogin | +| _AccountsApi_ | [**accounts_register_finish_post**](docs/AccountsApi.md#accounts_register_finish_post) | **POST** /accounts/register/finish | +| _AccountsApi_ | [**accounts_register_post**](docs/AccountsApi.md#accounts_register_post) | **POST** /accounts/register | +| _AccountsApi_ | [**accounts_register_send_verification_email_post**](docs/AccountsApi.md#accounts_register_send_verification_email_post) | **POST** /accounts/register/send-verification-email | +| _AccountsApi_ | [**accounts_register_verification_email_clicked_post**](docs/AccountsApi.md#accounts_register_verification_email_clicked_post) | **POST** /accounts/register/verification-email-clicked | +| _AccountsApi_ | [**accounts_trial_send_verification_email_post**](docs/AccountsApi.md#accounts_trial_send_verification_email_post) | **POST** /accounts/trial/send-verification-email | +| _AccountsApi_ | [**accounts_webauthn_assertion_options_get**](docs/AccountsApi.md#accounts_webauthn_assertion_options_get) | **GET** /accounts/webauthn/assertion-options | +| _InfoApi_ | [**alive_get**](docs/InfoApi.md#alive_get) | **GET** /alive | +| _InfoApi_ | [**now_get**](docs/InfoApi.md#now_get) | **GET** /now | +| _InfoApi_ | [**version_get**](docs/InfoApi.md#version_get) | **GET** /version | +| _SsoApi_ | [**sso_external_callback_get**](docs/SsoApi.md#sso_external_callback_get) | **GET** /sso/ExternalCallback | +| _SsoApi_ | [**sso_external_challenge_get**](docs/SsoApi.md#sso_external_challenge_get) | **GET** /sso/ExternalChallenge | +| _SsoApi_ | [**sso_login_get**](docs/SsoApi.md#sso_login_get) | **GET** /sso/Login | +| _SsoApi_ | [**sso_pre_validate_get**](docs/SsoApi.md#sso_pre_validate_get) | **GET** /sso/PreValidate | ## Documentation For Models @@ -49,10 +53,16 @@ All URIs are relative to _http://localhost_ - [KeysRequestModel](docs/KeysRequestModel.md) - [PreloginRequestModel](docs/PreloginRequestModel.md) - [PreloginResponseModel](docs/PreloginResponseModel.md) +- [ProductTierType](docs/ProductTierType.md) +- [ProductType](docs/ProductType.md) - [PublicKeyCredentialDescriptor](docs/PublicKeyCredentialDescriptor.md) - [PublicKeyCredentialType](docs/PublicKeyCredentialType.md) +- [RegisterFinishRequestModel](docs/RegisterFinishRequestModel.md) - [RegisterRequestModel](docs/RegisterRequestModel.md) - [RegisterResponseModel](docs/RegisterResponseModel.md) +- [RegisterSendVerificationEmailRequestModel](docs/RegisterSendVerificationEmailRequestModel.md) +- [RegisterVerificationEmailClickedRequestModel](docs/RegisterVerificationEmailClickedRequestModel.md) +- [TrialSendVerificationEmailRequestModel](docs/TrialSendVerificationEmailRequestModel.md) - [UserVerificationRequirement](docs/UserVerificationRequirement.md) - [WebAuthnLoginAssertionOptionsResponseModel](docs/WebAuthnLoginAssertionOptionsResponseModel.md) diff --git a/crates/bitwarden-api-identity/src/apis/accounts_api.rs b/crates/bitwarden-api-identity/src/apis/accounts_api.rs index 420326cfc..25474d97e 100644 --- a/crates/bitwarden-api-identity/src/apis/accounts_api.rs +++ b/crates/bitwarden-api-identity/src/apis/accounts_api.rs @@ -21,6 +21,13 @@ pub enum AccountsPreloginPostError { UnknownValue(serde_json::Value), } +/// struct for typed errors of method [`accounts_register_finish_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum AccountsRegisterFinishPostError { + UnknownValue(serde_json::Value), +} + /// struct for typed errors of method [`accounts_register_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -28,6 +35,27 @@ pub enum AccountsRegisterPostError { UnknownValue(serde_json::Value), } +/// struct for typed errors of method [`accounts_register_send_verification_email_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum AccountsRegisterSendVerificationEmailPostError { + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`accounts_register_verification_email_clicked_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum AccountsRegisterVerificationEmailClickedPostError { + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`accounts_trial_send_verification_email_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum AccountsTrialSendVerificationEmailPostError { + UnknownValue(serde_json::Value), +} + /// struct for typed errors of method [`accounts_webauthn_assertion_options_get`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -73,6 +101,47 @@ pub async fn accounts_prelogin_post( } } +pub async fn accounts_register_finish_post( + configuration: &configuration::Configuration, + register_finish_request_model: Option, +) -> Result> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/accounts/register/finish", + 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()); + } + local_var_req_builder = local_var_req_builder.json(®ister_finish_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 accounts_register_post( configuration: &configuration::Configuration, register_request_model: Option, @@ -111,6 +180,138 @@ pub async fn accounts_register_post( } } +pub async fn accounts_register_send_verification_email_post( + configuration: &configuration::Configuration, + register_send_verification_email_request_model: Option< + models::RegisterSendVerificationEmailRequestModel, + >, +) -> Result<(), Error> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/accounts/register/send-verification-email", + 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()); + } + local_var_req_builder = + local_var_req_builder.json(®ister_send_verification_email_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 accounts_register_verification_email_clicked_post( + configuration: &configuration::Configuration, + register_verification_email_clicked_request_model: Option< + models::RegisterVerificationEmailClickedRequestModel, + >, +) -> Result<(), Error> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/accounts/register/verification-email-clicked", + 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()); + } + local_var_req_builder = + local_var_req_builder.json(®ister_verification_email_clicked_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 accounts_trial_send_verification_email_post( + configuration: &configuration::Configuration, + trial_send_verification_email_request_model: Option< + models::TrialSendVerificationEmailRequestModel, + >, +) -> Result<(), Error> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/accounts/trial/send-verification-email", + 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()); + } + local_var_req_builder = + local_var_req_builder.json(&trial_send_verification_email_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 accounts_webauthn_assertion_options_get( configuration: &configuration::Configuration, ) -> Result< diff --git a/crates/bitwarden-api-identity/src/models/mod.rs b/crates/bitwarden-api-identity/src/models/mod.rs index 715e29686..00203b714 100644 --- a/crates/bitwarden-api-identity/src/models/mod.rs +++ b/crates/bitwarden-api-identity/src/models/mod.rs @@ -12,14 +12,26 @@ pub mod prelogin_request_model; pub use self::prelogin_request_model::PreloginRequestModel; pub mod prelogin_response_model; pub use self::prelogin_response_model::PreloginResponseModel; +pub mod product_tier_type; +pub use self::product_tier_type::ProductTierType; +pub mod product_type; +pub use self::product_type::ProductType; pub mod public_key_credential_descriptor; pub use self::public_key_credential_descriptor::PublicKeyCredentialDescriptor; pub mod public_key_credential_type; pub use self::public_key_credential_type::PublicKeyCredentialType; +pub mod register_finish_request_model; +pub use self::register_finish_request_model::RegisterFinishRequestModel; pub mod register_request_model; pub use self::register_request_model::RegisterRequestModel; pub mod register_response_model; pub use self::register_response_model::RegisterResponseModel; +pub mod register_send_verification_email_request_model; +pub use self::register_send_verification_email_request_model::RegisterSendVerificationEmailRequestModel; +pub mod register_verification_email_clicked_request_model; +pub use self::register_verification_email_clicked_request_model::RegisterVerificationEmailClickedRequestModel; +pub mod trial_send_verification_email_request_model; +pub use self::trial_send_verification_email_request_model::TrialSendVerificationEmailRequestModel; pub mod user_verification_requirement; pub use self::user_verification_requirement::UserVerificationRequirement; pub mod web_authn_login_assertion_options_response_model; diff --git a/crates/bitwarden-api-identity/src/models/product_tier_type.rs b/crates/bitwarden-api-identity/src/models/product_tier_type.rs new file mode 100644 index 000000000..1673f0f68 --- /dev/null +++ b/crates/bitwarden-api-identity/src/models/product_tier_type.rs @@ -0,0 +1,53 @@ +/* + * Bitwarden Identity + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: v1 + * + * Generated by: https://openapi-generator.tech + */ + +use serde::{Deserialize, Serialize}; + +use crate::models; + +/// +#[repr(i64)] +#[derive( + Clone, + Copy, + Debug, + Eq, + PartialEq, + Ord, + PartialOrd, + Hash, + serde_repr::Serialize_repr, + serde_repr::Deserialize_repr, +)] +pub enum ProductTierType { + Free = 0, + Families = 1, + Teams = 2, + Enterprise = 3, + TeamsStarter = 4, +} + +impl std::fmt::Display for ProductTierType { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::Free => write!(f, "0"), + Self::Families => write!(f, "1"), + Self::Teams => write!(f, "2"), + Self::Enterprise => write!(f, "3"), + Self::TeamsStarter => write!(f, "4"), + } + } +} + +impl Default for ProductTierType { + fn default() -> ProductTierType { + Self::Free + } +} diff --git a/crates/bitwarden-api-identity/src/models/product_type.rs b/crates/bitwarden-api-identity/src/models/product_type.rs new file mode 100644 index 000000000..ade8bd227 --- /dev/null +++ b/crates/bitwarden-api-identity/src/models/product_type.rs @@ -0,0 +1,47 @@ +/* + * Bitwarden Identity + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: v1 + * + * Generated by: https://openapi-generator.tech + */ + +use serde::{Deserialize, Serialize}; + +use crate::models; + +/// +#[repr(i64)] +#[derive( + Clone, + Copy, + Debug, + Eq, + PartialEq, + Ord, + PartialOrd, + Hash, + serde_repr::Serialize_repr, + serde_repr::Deserialize_repr, +)] +pub enum ProductType { + PasswordManager = 0, + SecretsManager = 1, +} + +impl std::fmt::Display for ProductType { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::PasswordManager => write!(f, "0"), + Self::SecretsManager => write!(f, "1"), + } + } +} + +impl Default for ProductType { + fn default() -> ProductType { + Self::PasswordManager + } +} diff --git a/crates/bitwarden-api-identity/src/models/register_finish_request_model.rs b/crates/bitwarden-api-identity/src/models/register_finish_request_model.rs new file mode 100644 index 000000000..d1afeea48 --- /dev/null +++ b/crates/bitwarden-api-identity/src/models/register_finish_request_model.rs @@ -0,0 +1,70 @@ +/* + * Bitwarden Identity + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: v1 + * + * Generated by: https://openapi-generator.tech + */ + +use serde::{Deserialize, Serialize}; + +use crate::models; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct RegisterFinishRequestModel { + #[serde(rename = "email")] + pub email: Option, + #[serde( + rename = "emailVerificationToken", + skip_serializing_if = "Option::is_none" + )] + pub email_verification_token: Option, + #[serde(rename = "masterPasswordHash")] + pub master_password_hash: Option, + #[serde(rename = "masterPasswordHint", skip_serializing_if = "Option::is_none")] + pub master_password_hint: Option, + #[serde(rename = "userSymmetricKey")] + pub user_symmetric_key: Option, + #[serde(rename = "userAsymmetricKeys")] + pub user_asymmetric_keys: Box, + #[serde(rename = "kdf")] + pub kdf: models::KdfType, + #[serde(rename = "kdfIterations")] + pub kdf_iterations: i32, + #[serde(rename = "kdfMemory", skip_serializing_if = "Option::is_none")] + pub kdf_memory: Option, + #[serde(rename = "kdfParallelism", skip_serializing_if = "Option::is_none")] + pub kdf_parallelism: Option, + #[serde(rename = "organizationUserId", skip_serializing_if = "Option::is_none")] + pub organization_user_id: Option, + #[serde(rename = "orgInviteToken", skip_serializing_if = "Option::is_none")] + pub org_invite_token: Option, +} + +impl RegisterFinishRequestModel { + pub fn new( + email: Option, + master_password_hash: Option, + user_symmetric_key: Option, + user_asymmetric_keys: models::KeysRequestModel, + kdf: models::KdfType, + kdf_iterations: i32, + ) -> RegisterFinishRequestModel { + RegisterFinishRequestModel { + email, + email_verification_token: None, + master_password_hash, + master_password_hint: None, + user_symmetric_key, + user_asymmetric_keys: Box::new(user_asymmetric_keys), + kdf, + kdf_iterations, + kdf_memory: None, + kdf_parallelism: None, + organization_user_id: None, + org_invite_token: None, + } + } +} diff --git a/crates/bitwarden-api-identity/src/models/register_send_verification_email_request_model.rs b/crates/bitwarden-api-identity/src/models/register_send_verification_email_request_model.rs new file mode 100644 index 000000000..eca497c72 --- /dev/null +++ b/crates/bitwarden-api-identity/src/models/register_send_verification_email_request_model.rs @@ -0,0 +1,36 @@ +/* + * Bitwarden Identity + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: v1 + * + * Generated by: https://openapi-generator.tech + */ + +use serde::{Deserialize, Serialize}; + +use crate::models; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct RegisterSendVerificationEmailRequestModel { + #[serde(rename = "name", skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(rename = "email")] + pub email: Option, + #[serde( + rename = "receiveMarketingEmails", + skip_serializing_if = "Option::is_none" + )] + pub receive_marketing_emails: Option, +} + +impl RegisterSendVerificationEmailRequestModel { + pub fn new(email: Option) -> RegisterSendVerificationEmailRequestModel { + RegisterSendVerificationEmailRequestModel { + name: None, + email, + receive_marketing_emails: None, + } + } +} diff --git a/crates/bitwarden-api-identity/src/models/register_verification_email_clicked_request_model.rs b/crates/bitwarden-api-identity/src/models/register_verification_email_clicked_request_model.rs new file mode 100644 index 000000000..1c5dff25f --- /dev/null +++ b/crates/bitwarden-api-identity/src/models/register_verification_email_clicked_request_model.rs @@ -0,0 +1,33 @@ +/* + * Bitwarden Identity + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: v1 + * + * Generated by: https://openapi-generator.tech + */ + +use serde::{Deserialize, Serialize}; + +use crate::models; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct RegisterVerificationEmailClickedRequestModel { + #[serde(rename = "email")] + pub email: Option, + #[serde(rename = "emailVerificationToken")] + pub email_verification_token: Option, +} + +impl RegisterVerificationEmailClickedRequestModel { + pub fn new( + email: Option, + email_verification_token: Option, + ) -> RegisterVerificationEmailClickedRequestModel { + RegisterVerificationEmailClickedRequestModel { + email, + email_verification_token, + } + } +} diff --git a/crates/bitwarden-api-identity/src/models/trial_send_verification_email_request_model.rs b/crates/bitwarden-api-identity/src/models/trial_send_verification_email_request_model.rs new file mode 100644 index 000000000..547bfc06b --- /dev/null +++ b/crates/bitwarden-api-identity/src/models/trial_send_verification_email_request_model.rs @@ -0,0 +1,42 @@ +/* + * Bitwarden Identity + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: v1 + * + * Generated by: https://openapi-generator.tech + */ + +use serde::{Deserialize, Serialize}; + +use crate::models; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct TrialSendVerificationEmailRequestModel { + #[serde(rename = "name", skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(rename = "email")] + pub email: Option, + #[serde( + rename = "receiveMarketingEmails", + skip_serializing_if = "Option::is_none" + )] + pub receive_marketing_emails: Option, + #[serde(rename = "productTier", skip_serializing_if = "Option::is_none")] + pub product_tier: Option, + #[serde(rename = "products", skip_serializing_if = "Option::is_none")] + pub products: Option>, +} + +impl TrialSendVerificationEmailRequestModel { + pub fn new(email: Option) -> TrialSendVerificationEmailRequestModel { + TrialSendVerificationEmailRequestModel { + name: None, + email, + receive_marketing_emails: None, + product_tier: None, + products: None, + } + } +} diff --git a/crates/bitwarden-c/src/c.rs b/crates/bitwarden-c/src/c.rs index 32abe3dc0..158c38025 100644 --- a/crates/bitwarden-c/src/c.rs +++ b/crates/bitwarden-c/src/c.rs @@ -4,17 +4,24 @@ use bitwarden_json::client::Client; use crate::{box_ptr, ffi_ref}; +#[repr(C)] +pub struct CClient { + /// Associates the tokio runtime to the `Client`, ensuring the runtime has the same lifecycle + /// as the `Client`. + runtime: tokio::runtime::Runtime, + client: Client, +} + #[no_mangle] -#[tokio::main] -pub async extern "C" fn run_command( - c_str_ptr: *const c_char, - client_ptr: *const Client, -) -> *mut c_char { +pub extern "C" fn run_command(c_str_ptr: *const c_char, client_ptr: *const CClient) -> *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()) .expect("Input should be a valid string"); - let result = client.run_command(input_str).await; + let result = client + .runtime + .block_on(client.client.run_command(input_str)); + match std::ffi::CString::new(result) { Ok(cstr) => cstr.into_raw(), Err(_) => panic!("failed to return command result: null encountered"), @@ -23,21 +30,29 @@ pub async extern "C" fn run_command( // Init client, potential leak! You need to call free_mem after this! #[no_mangle] -pub extern "C" fn init(c_str_ptr: *const c_char) -> *mut Client { +pub extern "C" fn init(c_str_ptr: *const c_char) -> *mut CClient { // This will only fail if another logger was already initialized, so we can ignore the result let _ = env_logger::try_init(); - if c_str_ptr.is_null() { - box_ptr!(Client::new(None)) + + let runtime = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .expect("Failed to build tokio runtime"); + + let client = if c_str_ptr.is_null() { + Client::new(None) } else { let input_string = str::from_utf8(unsafe { CStr::from_ptr(c_str_ptr) }.to_bytes()) .expect("Input should be a valid string") .to_owned(); - box_ptr!(Client::new(Some(input_string))) - } + Client::new(Some(input_string)) + }; + + box_ptr!(CClient { runtime, client }) } // Free mem #[no_mangle] -pub extern "C" fn free_mem(client_ptr: *mut Client) { +pub extern "C" fn free_mem(client_ptr: *mut CClient) { std::mem::drop(unsafe { Box::from_raw(client_ptr) }); } diff --git a/crates/bitwarden-cli/README.md b/crates/bitwarden-cli/README.md new file mode 100644 index 000000000..651de965b --- /dev/null +++ b/crates/bitwarden-cli/README.md @@ -0,0 +1,6 @@ +# Bitwarden Cli + +This is an internal crate for the Bitwarden SDK do not depend on this directly and use the +[`bitwarden`](https://crates.io/crates/bitwarden) crate instead. + +This crate does not follow semantic versioning and the public interface may change at any time. diff --git a/crates/bitwarden-core/Cargo.toml b/crates/bitwarden-core/Cargo.toml new file mode 100644 index 000000000..d212afb7a --- /dev/null +++ b/crates/bitwarden-core/Cargo.toml @@ -0,0 +1,67 @@ +[package] +name = "bitwarden-core" +description = """ +Internal crate for the bitwarden crate. Do not use. +""" +keywords = ["bitwarden"] + +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +homepage.workspace = true +repository.workspace = true +license-file.workspace = true + +[features] +internal = ["dep:zxcvbn"] +no-memory-hardening = [ + "bitwarden-crypto/no-memory-hardening", +] # Disable memory hardening features +uniffi = ["bitwarden-crypto/uniffi", "dep:uniffi"] # Uniffi bindings +secrets = [] # Secrets manager API +wasm = ["dep:wasm-bindgen", "dep:tsify-next"] # WASM support + +[dependencies] +base64 = ">=0.22.1, <0.23" +bitwarden-api-api = { workspace = true } +bitwarden-api-identity = { workspace = true } +bitwarden-crypto = { workspace = true } +chrono = { workspace = true, features = ["std"] } +# 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"] } +hmac = ">=0.12.1, <0.13" +log = ">=0.4.18, <0.5" +rand = ">=0.8.5, <0.9" +reqwest = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +serde_qs = { workspace = true } +serde_repr = { workspace = true } +sha1 = ">=0.10.5, <0.11" +sha2 = ">=0.10.6, <0.11" +thiserror = { workspace = true } +uniffi = { workspace = true, optional = true, features = ["tokio"] } +uuid = { workspace = true } +validator = { workspace = true } +wasm-bindgen = { workspace = true, optional = true } +zeroize = { version = ">=1.7.0, <2.0", features = ["derive", "aarch64"] } +zxcvbn = { version = ">=3.0.1, <4.0", optional = true } +tsify-next = { workspace = true, optional = true } + +[target.'cfg(not(target_arch="wasm32"))'.dependencies] +# By default, we use rustls as the TLS stack and rust-platform-verifier to support user-installed root certificates +# The only exception is WASM, as it just uses the browsers/node fetch +reqwest = { workspace = true, features = ["rustls-tls-manual-roots"] } +rustls-platform-verifier = "0.3.4" + +[dev-dependencies] +bitwarden-crypto = { workspace = true } +rand_chacha = "0.3.1" +tokio = { workspace = true, features = ["rt"] } +wiremock = "0.6.0" +zeroize = { version = ">=1.7.0, <2.0", features = ["derive", "aarch64"] } + +[lints] +workspace = true diff --git a/crates/bitwarden-core/README.md b/crates/bitwarden-core/README.md new file mode 100644 index 000000000..baddb938e --- /dev/null +++ b/crates/bitwarden-core/README.md @@ -0,0 +1,6 @@ +# Bitwarden Core + +This is an internal crate for the Bitwarden SDK do not depend on this directly and use the +[`bitwarden`](https://crates.io/crates/bitwarden) crate instead. + +This crate does not follow semantic versioning and the public interface may change at any time. diff --git a/crates/bitwarden/src/.gitignore b/crates/bitwarden-core/src/.gitignore similarity index 100% rename from crates/bitwarden/src/.gitignore rename to crates/bitwarden-core/src/.gitignore diff --git a/crates/bitwarden/src/admin_console/mod.rs b/crates/bitwarden-core/src/admin_console/mod.rs similarity index 100% rename from crates/bitwarden/src/admin_console/mod.rs rename to crates/bitwarden-core/src/admin_console/mod.rs diff --git a/crates/bitwarden/src/admin_console/policy.rs b/crates/bitwarden-core/src/admin_console/policy.rs similarity index 94% rename from crates/bitwarden/src/admin_console/policy.rs rename to crates/bitwarden-core/src/admin_console/policy.rs index d8ed0b761..2cf6a5530 100644 --- a/crates/bitwarden/src/admin_console/policy.rs +++ b/crates/bitwarden-core/src/admin_console/policy.rs @@ -6,7 +6,10 @@ use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use uuid::Uuid; -use crate::error::{require, Error, Result}; +use crate::{ + error::{Error, Result}, + require, +}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] pub struct Policy { @@ -34,6 +37,7 @@ pub enum PolicyType { 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 + AutomaticAppLogIn = 12, } impl TryFrom for Policy { @@ -75,6 +79,9 @@ impl From for PolicyType { PolicyType::DisablePersonalVaultExport } bitwarden_api_api::models::PolicyType::ActivateAutofill => PolicyType::ActivateAutofill, + bitwarden_api_api::models::PolicyType::AutomaticAppLogIn => { + PolicyType::AutomaticAppLogIn + } } } } diff --git a/crates/bitwarden/src/auth/access_token.rs b/crates/bitwarden-core/src/auth/access_token.rs similarity index 100% rename from crates/bitwarden/src/auth/access_token.rs rename to crates/bitwarden-core/src/auth/access_token.rs diff --git a/crates/bitwarden/src/auth/api/mod.rs b/crates/bitwarden-core/src/auth/api/mod.rs similarity index 100% rename from crates/bitwarden/src/auth/api/mod.rs rename to crates/bitwarden-core/src/auth/api/mod.rs diff --git a/crates/bitwarden/src/auth/api/request/access_token_request.rs b/crates/bitwarden-core/src/auth/api/request/access_token_request.rs similarity index 100% rename from crates/bitwarden/src/auth/api/request/access_token_request.rs rename to crates/bitwarden-core/src/auth/api/request/access_token_request.rs diff --git a/crates/bitwarden/src/auth/api/request/api_token_request.rs b/crates/bitwarden-core/src/auth/api/request/api_token_request.rs similarity index 100% rename from crates/bitwarden/src/auth/api/request/api_token_request.rs rename to crates/bitwarden-core/src/auth/api/request/api_token_request.rs diff --git a/crates/bitwarden/src/auth/api/request/auth_request_token_request.rs b/crates/bitwarden-core/src/auth/api/request/auth_request_token_request.rs similarity index 92% rename from crates/bitwarden/src/auth/api/request/auth_request_token_request.rs rename to crates/bitwarden-core/src/auth/api/request/auth_request_token_request.rs index cf5ae7ee4..2b2a47839 100644 --- a/crates/bitwarden/src/auth/api/request/auth_request_token_request.rs +++ b/crates/bitwarden-core/src/auth/api/request/auth_request_token_request.rs @@ -3,9 +3,8 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::{ - auth::api::response::IdentityTokenResponse, - client::{client_settings::DeviceType, ApiConfigurations}, - error::Result, + auth::api::response::IdentityTokenResponse, client::ApiConfigurations, error::Result, + DeviceType, }; #[derive(Serialize, Deserialize, Debug)] @@ -27,6 +26,7 @@ pub struct AuthRequestTokenRequest { access_code: String, } +#[allow(dead_code)] impl AuthRequestTokenRequest { pub fn new( email: &str, diff --git a/crates/bitwarden/src/auth/api/request/mod.rs b/crates/bitwarden-core/src/auth/api/request/mod.rs similarity index 92% rename from crates/bitwarden/src/auth/api/request/mod.rs rename to crates/bitwarden-core/src/auth/api/request/mod.rs index c0cb45251..0d9f39877 100644 --- a/crates/bitwarden/src/auth/api/request/mod.rs +++ b/crates/bitwarden-core/src/auth/api/request/mod.rs @@ -1,21 +1,20 @@ +#[cfg(feature = "secrets")] mod access_token_request; -#[cfg(feature = "internal")] +#[cfg(feature = "secrets")] +pub(crate) use access_token_request::*; + mod api_token_request; +pub(crate) use api_token_request::*; + #[cfg(feature = "internal")] mod password_token_request; #[cfg(feature = "internal")] -mod renew_token_request; +pub(crate) use password_token_request::*; -pub(crate) use access_token_request::*; -#[cfg(feature = "internal")] -pub(crate) use api_token_request::*; +mod renew_token_request; use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; -#[cfg(feature = "internal")] -pub(crate) use password_token_request::*; -#[cfg(feature = "internal")] pub(crate) use renew_token_request::*; -#[cfg(feature = "internal")] mod auth_request_token_request; #[cfg(feature = "internal")] pub(crate) use auth_request_token_request::*; diff --git a/crates/bitwarden/src/auth/api/request/password_token_request.rs b/crates/bitwarden-core/src/auth/api/request/password_token_request.rs similarity index 97% rename from crates/bitwarden/src/auth/api/request/password_token_request.rs rename to crates/bitwarden-core/src/auth/api/request/password_token_request.rs index 2f6414bcd..f1482fab4 100644 --- a/crates/bitwarden/src/auth/api/request/password_token_request.rs +++ b/crates/bitwarden-core/src/auth/api/request/password_token_request.rs @@ -6,8 +6,9 @@ use crate::{ api::response::IdentityTokenResponse, login::{TwoFactorProvider, TwoFactorRequest}, }, - client::{client_settings::DeviceType, ApiConfigurations}, + client::ApiConfigurations, error::Result, + DeviceType, }; #[derive(Serialize, Deserialize, Debug)] diff --git a/crates/bitwarden/src/auth/api/request/renew_token_request.rs b/crates/bitwarden-core/src/auth/api/request/renew_token_request.rs similarity index 100% rename from crates/bitwarden/src/auth/api/request/renew_token_request.rs rename to crates/bitwarden-core/src/auth/api/request/renew_token_request.rs diff --git a/crates/bitwarden/src/auth/api/response/identity_captcha_response.rs b/crates/bitwarden-core/src/auth/api/response/identity_captcha_response.rs similarity index 100% rename from crates/bitwarden/src/auth/api/response/identity_captcha_response.rs rename to crates/bitwarden-core/src/auth/api/response/identity_captcha_response.rs diff --git a/crates/bitwarden/src/auth/api/response/identity_payload_response.rs b/crates/bitwarden-core/src/auth/api/response/identity_payload_response.rs similarity index 100% rename from crates/bitwarden/src/auth/api/response/identity_payload_response.rs rename to crates/bitwarden-core/src/auth/api/response/identity_payload_response.rs diff --git a/crates/bitwarden/src/auth/api/response/identity_refresh_response.rs b/crates/bitwarden-core/src/auth/api/response/identity_refresh_response.rs similarity index 100% rename from crates/bitwarden/src/auth/api/response/identity_refresh_response.rs rename to crates/bitwarden-core/src/auth/api/response/identity_refresh_response.rs diff --git a/crates/bitwarden/src/auth/api/response/identity_success_response.rs b/crates/bitwarden-core/src/auth/api/response/identity_success_response.rs similarity index 100% rename from crates/bitwarden/src/auth/api/response/identity_success_response.rs rename to crates/bitwarden-core/src/auth/api/response/identity_success_response.rs diff --git a/crates/bitwarden/src/auth/api/response/identity_token_fail_response.rs b/crates/bitwarden-core/src/auth/api/response/identity_token_fail_response.rs similarity index 100% rename from crates/bitwarden/src/auth/api/response/identity_token_fail_response.rs rename to crates/bitwarden-core/src/auth/api/response/identity_token_fail_response.rs diff --git a/crates/bitwarden/src/auth/api/response/identity_token_response.rs b/crates/bitwarden-core/src/auth/api/response/identity_token_response.rs similarity index 100% rename from crates/bitwarden/src/auth/api/response/identity_token_response.rs rename to crates/bitwarden-core/src/auth/api/response/identity_token_response.rs diff --git a/crates/bitwarden/src/auth/api/response/identity_two_factor_response.rs b/crates/bitwarden-core/src/auth/api/response/identity_two_factor_response.rs similarity index 100% rename from crates/bitwarden/src/auth/api/response/identity_two_factor_response.rs rename to crates/bitwarden-core/src/auth/api/response/identity_two_factor_response.rs diff --git a/crates/bitwarden/src/auth/api/response/mod.rs b/crates/bitwarden-core/src/auth/api/response/mod.rs similarity index 100% rename from crates/bitwarden/src/auth/api/response/mod.rs rename to crates/bitwarden-core/src/auth/api/response/mod.rs diff --git a/crates/bitwarden/src/auth/api/response/two_factor_provider_data/authenticator.rs b/crates/bitwarden-core/src/auth/api/response/two_factor_provider_data/authenticator.rs similarity index 100% rename from crates/bitwarden/src/auth/api/response/two_factor_provider_data/authenticator.rs rename to crates/bitwarden-core/src/auth/api/response/two_factor_provider_data/authenticator.rs diff --git a/crates/bitwarden/src/auth/api/response/two_factor_provider_data/duo.rs b/crates/bitwarden-core/src/auth/api/response/two_factor_provider_data/duo.rs similarity index 100% rename from crates/bitwarden/src/auth/api/response/two_factor_provider_data/duo.rs rename to crates/bitwarden-core/src/auth/api/response/two_factor_provider_data/duo.rs diff --git a/crates/bitwarden/src/auth/api/response/two_factor_provider_data/email.rs b/crates/bitwarden-core/src/auth/api/response/two_factor_provider_data/email.rs similarity index 100% rename from crates/bitwarden/src/auth/api/response/two_factor_provider_data/email.rs rename to crates/bitwarden-core/src/auth/api/response/two_factor_provider_data/email.rs diff --git a/crates/bitwarden/src/auth/api/response/two_factor_provider_data/mod.rs b/crates/bitwarden-core/src/auth/api/response/two_factor_provider_data/mod.rs similarity index 100% rename from crates/bitwarden/src/auth/api/response/two_factor_provider_data/mod.rs rename to crates/bitwarden-core/src/auth/api/response/two_factor_provider_data/mod.rs diff --git a/crates/bitwarden/src/auth/api/response/two_factor_provider_data/organization_duo.rs b/crates/bitwarden-core/src/auth/api/response/two_factor_provider_data/organization_duo.rs similarity index 100% rename from crates/bitwarden/src/auth/api/response/two_factor_provider_data/organization_duo.rs rename to crates/bitwarden-core/src/auth/api/response/two_factor_provider_data/organization_duo.rs diff --git a/crates/bitwarden/src/auth/api/response/two_factor_provider_data/remember.rs b/crates/bitwarden-core/src/auth/api/response/two_factor_provider_data/remember.rs similarity index 100% rename from crates/bitwarden/src/auth/api/response/two_factor_provider_data/remember.rs rename to crates/bitwarden-core/src/auth/api/response/two_factor_provider_data/remember.rs diff --git a/crates/bitwarden/src/auth/api/response/two_factor_provider_data/web_authn.rs b/crates/bitwarden-core/src/auth/api/response/two_factor_provider_data/web_authn.rs similarity index 100% rename from crates/bitwarden/src/auth/api/response/two_factor_provider_data/web_authn.rs rename to crates/bitwarden-core/src/auth/api/response/two_factor_provider_data/web_authn.rs diff --git a/crates/bitwarden/src/auth/api/response/two_factor_provider_data/yubi_key.rs b/crates/bitwarden-core/src/auth/api/response/two_factor_provider_data/yubi_key.rs similarity index 100% rename from crates/bitwarden/src/auth/api/response/two_factor_provider_data/yubi_key.rs rename to crates/bitwarden-core/src/auth/api/response/two_factor_provider_data/yubi_key.rs diff --git a/crates/bitwarden/src/auth/api/response/two_factor_providers.rs b/crates/bitwarden-core/src/auth/api/response/two_factor_providers.rs similarity index 100% rename from crates/bitwarden/src/auth/api/response/two_factor_providers.rs rename to crates/bitwarden-core/src/auth/api/response/two_factor_providers.rs diff --git a/crates/bitwarden/src/auth/auth_request.rs b/crates/bitwarden-core/src/auth/auth_request.rs similarity index 92% rename from crates/bitwarden/src/auth/auth_request.rs rename to crates/bitwarden-core/src/auth/auth_request.rs index caf560260..1d6ce1802 100644 --- a/crates/bitwarden/src/auth/auth_request.rs +++ b/crates/bitwarden-core/src/auth/auth_request.rs @@ -1,11 +1,13 @@ use base64::{engine::general_purpose::STANDARD, Engine}; use bitwarden_crypto::{ - fingerprint, AsymmetricCryptoKey, AsymmetricEncString, AsymmetricPublicCryptoKey, + fingerprint, generate_random_alphanumeric, AsymmetricCryptoKey, AsymmetricEncString, + AsymmetricPublicCryptoKey, }; #[cfg(feature = "internal")] use bitwarden_crypto::{EncString, KeyDecryptable, SymmetricCryptoKey}; -use bitwarden_generators::{password, PasswordGeneratorRequest}; +#[cfg(feature = "internal")] +use crate::client::encryption_settings::EncryptionSettingsError; use crate::{error::Error, Client}; #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] @@ -40,14 +42,7 @@ pub(crate) fn new_auth_request(email: &str) -> Result Result Result { +) -> Result { let key = AsymmetricCryptoKey::from_der(&STANDARD.decode(private_key)?)?; let mut key: Vec = user_key.decrypt_with_key(&key)?; @@ -69,7 +64,7 @@ pub(crate) fn auth_request_decrypt_master_key( private_key: String, master_key: AsymmetricEncString, user_key: EncString, -) -> Result { +) -> Result { use bitwarden_crypto::MasterKey; let key = AsymmetricCryptoKey::from_der(&STANDARD.decode(private_key)?)?; @@ -83,13 +78,13 @@ pub(crate) fn auth_request_decrypt_master_key( /// /// Encrypts the user key with a public key. pub(crate) fn approve_auth_request( - client: &mut Client, + client: &Client, public_key: String, ) -> Result { let public_key = AsymmetricPublicCryptoKey::from_der(&STANDARD.decode(public_key)?)?; - let enc = client.get_encryption_settings()?; - let key = enc.get_key(&None).ok_or(Error::VaultLocked)?; + let enc = client.internal.get_encryption_settings()?; + let key = enc.get_key(&None)?; Ok(AsymmetricEncString::encrypt_rsa2048_oaep_sha1( &key.to_vec(), @@ -122,18 +117,18 @@ fn test_auth_request() { mod tests { use std::num::NonZeroU32; - use bitwarden_crypto::Kdf; + use bitwarden_crypto::{Kdf, MasterKey}; use super::*; use crate::mobile::crypto::{AuthRequestMethod, InitUserCryptoMethod, InitUserCryptoRequest}; #[test] fn test_approve() { - let mut client = Client::new(None); + let client = Client::new(None); - let master_key = bitwarden_crypto::MasterKey::derive( - "asdfasdfasdf".as_bytes(), - "test@bitwarden.com".as_bytes(), + let master_key = MasterKey::derive( + "asdfasdfasdf", + "test@bitwarden.com", &Kdf::PBKDF2 { iterations: NonZeroU32::new(600_000).unwrap(), }, @@ -143,6 +138,7 @@ mod tests { let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(); let private_key ="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(); client + .internal .initialize_user_crypto_master_key(master_key, user_key, private_key) .unwrap(); @@ -153,7 +149,7 @@ mod tests { let fingerprint = fingerprint("test@bitwarden.com", &pbkey).unwrap(); assert_eq!(fingerprint, "childless-unfair-prowler-dropbox-designate"); - approve_auth_request(&mut client, public_key.to_owned()).unwrap(); + approve_auth_request(&client, public_key.to_owned()).unwrap(); } #[tokio::test] @@ -204,22 +200,21 @@ mod tests { let private_key = "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="; // Initialize an existing client which is unlocked - let mut existing_device = Client::new(None); + let existing_device = Client::new(None); - let master_key = - bitwarden_crypto::MasterKey::derive("asdfasdfasdf".as_bytes(), email.as_bytes(), &kdf) - .unwrap(); + let master_key = MasterKey::derive("asdfasdfasdf", email, &kdf).unwrap(); existing_device + .internal .initialize_user_crypto_master_key(master_key, user_key, private_key.parse().unwrap()) .unwrap(); // Initialize a new device which will request to be logged in - let mut new_device = Client::new(None); + let new_device = Client::new(None); // Initialize an auth request, and approve it on the existing device let auth_req = new_auth_request(email).unwrap(); - let approved_req = approve_auth_request(&mut existing_device, auth_req.public_key).unwrap(); + let approved_req = approve_auth_request(&existing_device, auth_req.public_key).unwrap(); // Unlock the vault using the approved request new_device @@ -242,12 +237,14 @@ mod tests { // same assert_eq!( existing_device + .internal .get_encryption_settings() .unwrap() .get_key(&None) .unwrap() .to_base64(), new_device + .internal .get_encryption_settings() .unwrap() .get_key(&None) diff --git a/crates/bitwarden/src/auth/client_auth.rs b/crates/bitwarden-core/src/auth/client_auth.rs similarity index 80% rename from crates/bitwarden/src/auth/client_auth.rs rename to crates/bitwarden-core/src/auth/client_auth.rs index 88f8b0a09..829868556 100644 --- a/crates/bitwarden/src/auth/client_auth.rs +++ b/crates/bitwarden-core/src/auth/client_auth.rs @@ -1,44 +1,42 @@ #[cfg(feature = "internal")] -use bitwarden_crypto::{AsymmetricEncString, DeviceKey, TrustDeviceResponse}; +use bitwarden_crypto::{ + AsymmetricEncString, CryptoError, DeviceKey, EncString, Kdf, TrustDeviceResponse, +}; -#[cfg(feature = "internal")] -use crate::auth::login::NewAuthRequestResponse; #[cfg(feature = "secrets")] use crate::auth::login::{login_access_token, AccessTokenLoginRequest, AccessTokenLoginResponse}; -use crate::{auth::renew::renew_token, error::Result, Client}; #[cfg(feature = "internal")] -use crate::{ - auth::{ - auth_request::{approve_auth_request, new_auth_request}, - login::{ - login_api_key, login_password, send_two_factor_email, ApiKeyLoginRequest, - ApiKeyLoginResponse, PasswordLoginRequest, PasswordLoginResponse, - TwoFactorEmailRequest, - }, - password::{ - password_strength, satisfies_policy, validate_password, validate_password_user_key, - MasterPasswordPolicyOptions, - }, - register::{make_register_keys, register}, - tde::{make_register_tde_keys, RegisterTdeKeyResponse}, - AuthRequestResponse, RegisterKeyResponse, RegisterRequest, +use crate::auth::{ + auth_request::{approve_auth_request, new_auth_request}, + key_connector::{make_key_connector_keys, KeyConnectorResponse}, + login::{ + login_api_key, login_password, send_two_factor_email, ApiKeyLoginRequest, + ApiKeyLoginResponse, NewAuthRequestResponse, PasswordLoginRequest, PasswordLoginResponse, + TwoFactorEmailRequest, + }, + password::{ + password_strength, satisfies_policy, validate_password, validate_password_user_key, + MasterPasswordPolicyOptions, }, - client::Kdf, - error::Error, + pin::validate_pin, + register::{make_register_keys, register}, + tde::{make_register_tde_keys, RegisterTdeKeyResponse}, + AuthRequestResponse, RegisterKeyResponse, RegisterRequest, }; +use crate::{auth::renew::renew_token, error::Result, Client}; pub struct ClientAuth<'a> { - pub(crate) client: &'a mut crate::Client, + pub(crate) client: &'a crate::Client, } impl<'a> ClientAuth<'a> { - pub async fn renew_token(&mut self) -> Result<()> { - renew_token(self.client).await + pub async fn renew_token(&self) -> Result<()> { + renew_token(&self.client.internal).await } #[cfg(feature = "secrets")] pub async fn login_access_token( - &mut self, + &self, input: &AccessTokenLoginRequest, ) -> Result { login_access_token(self.client, input).await @@ -47,7 +45,7 @@ impl<'a> ClientAuth<'a> { #[cfg(feature = "internal")] impl<'a> ClientAuth<'a> { - pub async fn password_strength( + pub fn password_strength( &self, password: String, email: String, @@ -56,7 +54,7 @@ impl<'a> ClientAuth<'a> { password_strength(password, email, additional_inputs) } - pub async fn satisfies_policy( + pub fn satisfies_policy( &self, password: String, strength: u8, @@ -75,7 +73,7 @@ impl<'a> ClientAuth<'a> { } pub fn make_register_tde_keys( - &mut self, + &self, email: String, org_public_key: String, remember_device: bool, @@ -83,11 +81,16 @@ impl<'a> ClientAuth<'a> { make_register_tde_keys(self.client, email, org_public_key, remember_device) } - pub async fn register(&mut self, input: &RegisterRequest) -> Result<()> { + pub fn make_key_connector_keys(&self) -> Result { + let mut rng = rand::thread_rng(); + make_key_connector_keys(&mut rng) + } + + pub async fn register(&self, input: &RegisterRequest) -> Result<()> { register(self.client, input).await } - pub async fn prelogin(&mut self, email: String) -> Result { + pub async fn prelogin(&self, email: String) -> Result { use crate::auth::login::{parse_prelogin, request_prelogin}; let response = request_prelogin(self.client, email).await?; @@ -95,20 +98,17 @@ impl<'a> ClientAuth<'a> { } pub async fn login_password( - &mut self, + &self, input: &PasswordLoginRequest, ) -> Result { login_password(self.client, input).await } - pub async fn login_api_key( - &mut self, - input: &ApiKeyLoginRequest, - ) -> Result { + pub async fn login_api_key(&self, input: &ApiKeyLoginRequest) -> Result { login_api_key(self.client, input).await } - pub async fn send_two_factor_email(&mut self, tf: &TwoFactorEmailRequest) -> Result<()> { + pub async fn send_two_factor_email(&self, tf: &TwoFactorEmailRequest) -> Result<()> { send_two_factor_email(self.client, tf).await } @@ -124,11 +124,15 @@ impl<'a> ClientAuth<'a> { validate_password_user_key(self.client, password, encrypted_user_key) } + pub fn validate_pin(&self, pin: String, pin_protected_user_key: EncString) -> Result { + validate_pin(self.client, pin, pin_protected_user_key) + } + pub fn new_auth_request(&self, email: &str) -> Result { new_auth_request(email) } - pub fn approve_auth_request(&mut self, public_key: String) -> Result { + pub fn approve_auth_request(&self, public_key: String) -> Result { approve_auth_request(self.client, public_key) } @@ -140,7 +144,7 @@ impl<'a> ClientAuth<'a> { #[cfg(feature = "internal")] impl<'a> ClientAuth<'a> { pub async fn login_device( - &mut self, + &self, email: String, device_identifier: String, ) -> Result { @@ -149,7 +153,7 @@ impl<'a> ClientAuth<'a> { send_new_auth_request(self.client, email, device_identifier).await } - pub async fn login_device_complete(&mut self, auth_req: NewAuthRequestResponse) -> Result<()> { + pub async fn login_device_complete(&self, auth_req: NewAuthRequestResponse) -> Result<()> { use crate::auth::login::complete_auth_request; complete_auth_request(self.client, auth_req).await @@ -158,19 +162,20 @@ impl<'a> ClientAuth<'a> { #[cfg(feature = "internal")] fn trust_device(client: &Client) -> Result { - let enc = client.get_encryption_settings()?; + let enc = client.internal.get_encryption_settings()?; - let user_key = enc.get_key(&None).ok_or(Error::VaultLocked)?; + let user_key = enc.get_key(&None)?; Ok(DeviceKey::trust_device(user_key)?) } impl<'a> Client { - pub fn auth(&'a mut self) -> ClientAuth<'a> { + pub fn auth(&'a self) -> ClientAuth<'a> { ClientAuth { client: self } } } +/* #[cfg(test)] mod tests { @@ -182,7 +187,7 @@ mod tests { use crate::{auth::login::AccessTokenLoginRequest, secrets_manager::secrets::*}; // Create the mock server with the necessary routes for this test - let (_server, mut client) = crate::util::start_mock(vec![ + let (_server, client) = crate::util::start_mock(vec![ Mock::given(matchers::path("/identity/connect/token")) .respond_with(ResponseTemplate::new(200).set_body_json( serde_json::json!({ @@ -269,3 +274,4 @@ mod tests { assert_eq!(res.value, "TEST"); } } + */ diff --git a/crates/bitwarden/src/auth/jwt_token.rs b/crates/bitwarden-core/src/auth/jwt_token.rs similarity index 100% rename from crates/bitwarden/src/auth/jwt_token.rs rename to crates/bitwarden-core/src/auth/jwt_token.rs diff --git a/crates/bitwarden-core/src/auth/key_connector.rs b/crates/bitwarden-core/src/auth/key_connector.rs new file mode 100644 index 000000000..b4006b472 --- /dev/null +++ b/crates/bitwarden-core/src/auth/key_connector.rs @@ -0,0 +1,42 @@ +use bitwarden_crypto::{CryptoError, MasterKey, RsaKeyPair}; + +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +pub struct KeyConnectorResponse { + pub master_key: String, + pub encrypted_user_key: String, + pub keys: RsaKeyPair, +} + +pub(super) fn make_key_connector_keys( + mut rng: impl rand::RngCore, +) -> Result { + let master_key = MasterKey::generate(&mut rng); + let (user_key, encrypted_user_key) = master_key.make_user_key()?; + let keys = user_key.make_key_pair()?; + + Ok(KeyConnectorResponse { + master_key: master_key.to_base64(), + encrypted_user_key: encrypted_user_key.to_string(), + keys, + }) +} + +#[cfg(test)] +mod tests { + use rand::SeedableRng; + use rand_chacha::ChaCha8Rng; + + use super::*; + + #[test] + fn test_make_key_connector_keys() { + let mut rng = ChaCha8Rng::from_seed([0u8; 32]); + + let result = make_key_connector_keys(&mut rng).unwrap(); + + assert_eq!( + result.master_key, + "PgDvL4lfQNZ/W7joHwmloSyEDsPOmn87GBvhiO9xGh4=" + ); + } +} diff --git a/crates/bitwarden/src/auth/login/access_token.rs b/crates/bitwarden-core/src/auth/login/access_token.rs similarity index 83% rename from crates/bitwarden/src/auth/login/access_token.rs rename to crates/bitwarden-core/src/auth/login/access_token.rs index 9366e631f..c445e43c7 100644 --- a/crates/bitwarden/src/auth/login/access_token.rs +++ b/crates/bitwarden-core/src/auth/login/access_token.rs @@ -14,13 +14,14 @@ use crate::{ AccessToken, JWTToken, }, client::{LoginMethod, ServiceAccountLoginMethod}, - error::{require, Error, Result}, + error::{Error, Result}, + require, secrets_manager::state::{self, ClientState}, Client, }; pub(crate) async fn login_access_token( - client: &mut Client, + client: &Client, input: &AccessTokenLoginRequest, ) -> Result { //info!("api key logging in"); @@ -30,13 +31,15 @@ pub(crate) async fn login_access_token( 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()), - }, - )); + client + .internal + .set_login_method(LoginMethod::ServiceAccount( + ServiceAccountLoginMethod::AccessToken { + access_token, + organization_id, + state_file: Some(state_file.to_path_buf()), + }, + )); return Ok(AccessTokenLoginResponse { authenticated: true, @@ -78,37 +81,39 @@ pub(crate) async fn login_access_token( _ = state::set(state_file, &access_token, state); } - client.set_tokens( + client.internal.set_tokens( r.access_token.clone(), r.refresh_token.clone(), r.expires_in, ); - client.set_login_method(LoginMethod::ServiceAccount( - ServiceAccountLoginMethod::AccessToken { - access_token, - organization_id, - state_file: input.state_file.clone(), - }, - )); - - client.initialize_crypto_single_key(encryption_key); + client + .internal + .set_login_method(LoginMethod::ServiceAccount( + ServiceAccountLoginMethod::AccessToken { + access_token, + organization_id, + state_file: input.state_file.clone(), + }, + )); + + client.internal.initialize_crypto_single_key(encryption_key); } AccessTokenLoginResponse::process_response(response) } async fn request_access_token( - client: &mut Client, + client: &Client, input: &AccessToken, ) -> Result { - let config = client.get_api_configurations().await; + let config = client.internal.get_api_configurations().await; AccessTokenRequest::new(input.access_token_id, &input.client_secret) - .send(config) + .send(&config) .await } fn load_tokens_from_state( - client: &mut Client, + client: &Client, state_file: &Path, access_token: &AccessToken, ) -> Result { @@ -125,8 +130,10 @@ fn load_tokens_from_state( .map_err(|_| "Bad organization id.")?; let encryption_key = SymmetricCryptoKey::try_from(client_state.encryption_key)?; - client.set_tokens(client_state.token, None, time_till_expiration as u64); - client.initialize_crypto_single_key(encryption_key); + client + .internal + .set_tokens(client_state.token, None, time_till_expiration as u64); + client.internal.initialize_crypto_single_key(encryption_key); return Ok(organization_id); } diff --git a/crates/bitwarden/src/auth/login/api_key.rs b/crates/bitwarden-core/src/auth/login/api_key.rs similarity index 80% rename from crates/bitwarden/src/auth/login/api_key.rs rename to crates/bitwarden-core/src/auth/login/api_key.rs index 8b83e3a38..cce246528 100644 --- a/crates/bitwarden/src/auth/login/api_key.rs +++ b/crates/bitwarden-core/src/auth/login/api_key.rs @@ -9,12 +9,12 @@ use crate::{ JWTToken, }, client::{LoginMethod, UserLoginMethod}, - error::{require, Result}, - Client, + error::Result, + require, Client, }; pub(crate) async fn login_api_key( - client: &mut Client, + client: &Client, input: &ApiKeyLoginRequest, ) -> Result { //info!("api key logging in"); @@ -32,37 +32,41 @@ pub(crate) async fn login_api_key( let kdf = client.auth().prelogin(email.clone()).await?; - client.set_tokens( + client.internal.set_tokens( r.access_token.clone(), r.refresh_token.clone(), r.expires_in, ); - let master_key = MasterKey::derive(input.password.as_bytes(), email.as_bytes(), &kdf)?; + let master_key = MasterKey::derive(&input.password, &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, - })); + client + .internal + .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 = require!(r.key.as_deref()).parse()?; let private_key: EncString = require!(r.private_key.as_deref()).parse()?; - client.initialize_user_crypto_master_key(master_key, user_key, private_key)?; + client + .internal + .initialize_user_crypto_master_key(master_key, user_key, private_key)?; } ApiKeyLoginResponse::process_response(response) } async fn request_api_identity_tokens( - client: &mut Client, + client: &Client, input: &ApiKeyLoginRequest, ) -> Result { - let config = client.get_api_configurations().await; + let config = client.internal.get_api_configurations().await; ApiTokenRequest::new(&input.client_id, &input.client_secret) - .send(config) + .send(&config) .await } diff --git a/crates/bitwarden/src/auth/login/auth_request.rs b/crates/bitwarden-core/src/auth/login/auth_request.rs similarity index 85% rename from crates/bitwarden/src/auth/login/auth_request.rs rename to crates/bitwarden-core/src/auth/login/auth_request.rs index fb449e5d4..912105db0 100644 --- a/crates/bitwarden/src/auth/login/auth_request.rs +++ b/crates/bitwarden-core/src/auth/login/auth_request.rs @@ -11,9 +11,9 @@ use crate::{ auth_request::new_auth_request, }, client::{LoginMethod, UserLoginMethod}, - error::{require, Result}, + error::Result, mobile::crypto::{AuthRequestMethod, InitUserCryptoMethod, InitUserCryptoRequest}, - Client, + require, Client, }; pub struct NewAuthRequestResponse { @@ -26,11 +26,11 @@ pub struct NewAuthRequestResponse { } pub(crate) async fn send_new_auth_request( - client: &mut Client, + client: &Client, email: String, device_identifier: String, ) -> Result { - let config = client.get_api_configurations().await; + let config = client.internal.get_api_configurations().await; let auth = new_auth_request(&email)?; @@ -55,10 +55,10 @@ pub(crate) async fn send_new_auth_request( } pub(crate) async fn complete_auth_request( - client: &mut Client, + client: &Client, auth_req: NewAuthRequestResponse, ) -> Result<()> { - let config = client.get_api_configurations().await; + let config = client.internal.get_api_configurations().await; let res = auth_requests_id_response_get( &config.api, @@ -80,22 +80,24 @@ pub(crate) async fn complete_auth_request( config.device_type, &auth_req.device_identifier, ) - .send(config) + .send(&config) .await?; if let IdentityTokenResponse::Authenticated(r) = response { let kdf = Kdf::default(); - client.set_tokens( + client.internal.set_tokens( r.access_token.clone(), r.refresh_token.clone(), r.expires_in, ); - client.set_login_method(LoginMethod::User(UserLoginMethod::Username { - client_id: "web".to_owned(), - email: auth_req.email.to_owned(), - kdf: kdf.clone(), - })); + client + .internal + .set_login_method(LoginMethod::User(UserLoginMethod::Username { + client_id: "web".to_owned(), + email: auth_req.email.to_owned(), + kdf: kdf.clone(), + })); let method = match res.master_password_hash { Some(_) => AuthRequestMethod::MasterKey { diff --git a/crates/bitwarden/src/auth/login/mod.rs b/crates/bitwarden-core/src/auth/login/mod.rs similarity index 90% rename from crates/bitwarden/src/auth/login/mod.rs rename to crates/bitwarden-core/src/auth/login/mod.rs index 7fac112f5..0c90dc973 100644 --- a/crates/bitwarden/src/auth/login/mod.rs +++ b/crates/bitwarden-core/src/auth/login/mod.rs @@ -1,6 +1,8 @@ #[cfg(feature = "internal")] +use bitwarden_crypto::Kdf; +#[cfg(feature = "internal")] use { - crate::{client::Kdf, error::Result, Client}, + crate::{error::Result, Client}, bitwarden_api_identity::{ apis::accounts_api::accounts_prelogin_post, models::{PreloginRequestModel, PreloginResponseModel}, @@ -9,11 +11,13 @@ use { pub mod response; +#[cfg(any(feature = "internal", feature = "secrets"))] mod password; #[cfg(feature = "internal")] pub(crate) use password::login_password; #[cfg(feature = "internal")] pub use password::PasswordLoginRequest; +#[cfg(any(feature = "internal", feature = "secrets"))] pub use password::PasswordLoginResponse; #[cfg(feature = "internal")] mod two_factor; @@ -45,11 +49,11 @@ pub use access_token::{AccessTokenLoginRequest, AccessTokenLoginResponse}; #[cfg(feature = "internal")] pub(crate) async fn request_prelogin( - client: &mut Client, + client: &Client, email: String, ) -> Result { let request_model = PreloginRequestModel::new(email); - let config = client.get_api_configurations().await; + let config = client.internal.get_api_configurations().await; Ok(accounts_prelogin_post(&config.identity, Some(request_model)).await?) } diff --git a/crates/bitwarden/src/auth/login/password.rs b/crates/bitwarden-core/src/auth/login/password.rs similarity index 85% rename from crates/bitwarden/src/auth/login/password.rs rename to crates/bitwarden-core/src/auth/login/password.rs index e0cb67dbe..0521c9d91 100644 --- a/crates/bitwarden/src/auth/login/password.rs +++ b/crates/bitwarden-core/src/auth/login/password.rs @@ -1,4 +1,6 @@ #[cfg(feature = "internal")] +use bitwarden_crypto::Kdf; +#[cfg(feature = "internal")] use log::info; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -6,7 +8,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "internal")] use crate::{ auth::{api::request::PasswordTokenRequest, login::TwoFactorRequest}, - client::{Kdf, LoginMethod}, + client::LoginMethod, Client, }; use crate::{ @@ -19,41 +21,41 @@ use crate::{ #[cfg(feature = "internal")] pub(crate) async fn login_password( - client: &mut Client, + client: &Client, input: &PasswordLoginRequest, ) -> Result { use bitwarden_crypto::{EncString, HashPurpose, MasterKey}; - use crate::{client::UserLoginMethod, error::require}; + use crate::{client::UserLoginMethod, require}; info!("password logging in"); - let master_key = MasterKey::derive( - input.password.as_bytes(), - input.email.as_bytes(), - &input.kdf, - )?; + let master_key = MasterKey::derive(&input.password, &input.email, &input.kdf)?; let password_hash = master_key .derive_master_key_hash(input.password.as_bytes(), HashPurpose::ServerAuthorization)?; let response = request_identity_tokens(client, input, &password_hash).await?; if let IdentityTokenResponse::Authenticated(r) = &response { - client.set_tokens( + client.internal.set_tokens( r.access_token.clone(), r.refresh_token.clone(), r.expires_in, ); - client.set_login_method(LoginMethod::User(UserLoginMethod::Username { - client_id: "web".to_owned(), - email: input.email.to_owned(), - kdf: input.kdf.to_owned(), - })); + client + .internal + .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 = require!(r.key.as_deref()).parse()?; let private_key: EncString = require!(r.private_key.as_deref()).parse()?; - client.initialize_user_crypto_master_key(master_key, user_key, private_key)?; + client + .internal + .initialize_user_crypto_master_key(master_key, user_key, private_key)?; } PasswordLoginResponse::process_response(response) @@ -61,13 +63,13 @@ pub(crate) async fn login_password( #[cfg(feature = "internal")] async fn request_identity_tokens( - client: &mut Client, + client: &Client, input: &PasswordLoginRequest, password_hash: &str, ) -> Result { - use crate::client::client_settings::DeviceType; + use crate::DeviceType; - let config = client.get_api_configurations().await; + let config = client.internal.get_api_configurations().await; PasswordTokenRequest::new( &input.email, password_hash, @@ -75,7 +77,7 @@ async fn request_identity_tokens( "b86dd6ab-4265-4ddf-a7f1-eb28d5677f33", &input.two_factor, ) - .send(config) + .send(&config) .await } diff --git a/crates/bitwarden/src/auth/login/response/captcha_response.rs b/crates/bitwarden-core/src/auth/login/response/captcha_response.rs similarity index 100% rename from crates/bitwarden/src/auth/login/response/captcha_response.rs rename to crates/bitwarden-core/src/auth/login/response/captcha_response.rs diff --git a/crates/bitwarden/src/auth/login/response/mod.rs b/crates/bitwarden-core/src/auth/login/response/mod.rs similarity index 100% rename from crates/bitwarden/src/auth/login/response/mod.rs rename to crates/bitwarden-core/src/auth/login/response/mod.rs diff --git a/crates/bitwarden/src/auth/login/response/two_factor/authenticator.rs b/crates/bitwarden-core/src/auth/login/response/two_factor/authenticator.rs similarity index 100% rename from crates/bitwarden/src/auth/login/response/two_factor/authenticator.rs rename to crates/bitwarden-core/src/auth/login/response/two_factor/authenticator.rs diff --git a/crates/bitwarden/src/auth/login/response/two_factor/duo.rs b/crates/bitwarden-core/src/auth/login/response/two_factor/duo.rs similarity index 100% rename from crates/bitwarden/src/auth/login/response/two_factor/duo.rs rename to crates/bitwarden-core/src/auth/login/response/two_factor/duo.rs diff --git a/crates/bitwarden/src/auth/login/response/two_factor/email.rs b/crates/bitwarden-core/src/auth/login/response/two_factor/email.rs similarity index 100% rename from crates/bitwarden/src/auth/login/response/two_factor/email.rs rename to crates/bitwarden-core/src/auth/login/response/two_factor/email.rs diff --git a/crates/bitwarden/src/auth/login/response/two_factor/mod.rs b/crates/bitwarden-core/src/auth/login/response/two_factor/mod.rs similarity index 100% rename from crates/bitwarden/src/auth/login/response/two_factor/mod.rs rename to crates/bitwarden-core/src/auth/login/response/two_factor/mod.rs diff --git a/crates/bitwarden/src/auth/login/response/two_factor/remember.rs b/crates/bitwarden-core/src/auth/login/response/two_factor/remember.rs similarity index 100% rename from crates/bitwarden/src/auth/login/response/two_factor/remember.rs rename to crates/bitwarden-core/src/auth/login/response/two_factor/remember.rs diff --git a/crates/bitwarden/src/auth/login/response/two_factor/two_factor_providers.rs b/crates/bitwarden-core/src/auth/login/response/two_factor/two_factor_providers.rs similarity index 100% rename from crates/bitwarden/src/auth/login/response/two_factor/two_factor_providers.rs rename to crates/bitwarden-core/src/auth/login/response/two_factor/two_factor_providers.rs diff --git a/crates/bitwarden/src/auth/login/response/two_factor/web_authn.rs b/crates/bitwarden-core/src/auth/login/response/two_factor/web_authn.rs similarity index 100% rename from crates/bitwarden/src/auth/login/response/two_factor/web_authn.rs rename to crates/bitwarden-core/src/auth/login/response/two_factor/web_authn.rs diff --git a/crates/bitwarden/src/auth/login/response/two_factor/yubi_key.rs b/crates/bitwarden-core/src/auth/login/response/two_factor/yubi_key.rs similarity index 100% rename from crates/bitwarden/src/auth/login/response/two_factor/yubi_key.rs rename to crates/bitwarden-core/src/auth/login/response/two_factor/yubi_key.rs diff --git a/crates/bitwarden/src/auth/login/two_factor.rs b/crates/bitwarden-core/src/auth/login/two_factor.rs similarity index 95% rename from crates/bitwarden/src/auth/login/two_factor.rs rename to crates/bitwarden-core/src/auth/login/two_factor.rs index c8f0cc55b..c0a4c10ab 100644 --- a/crates/bitwarden/src/auth/login/two_factor.rs +++ b/crates/bitwarden-core/src/auth/login/two_factor.rs @@ -16,7 +16,7 @@ pub struct TwoFactorEmailRequest { } pub(crate) async fn send_two_factor_email( - client: &mut Client, + client: &Client, input: &TwoFactorEmailRequest, ) -> Result<()> { // TODO: This should be resolved from the client @@ -29,7 +29,7 @@ pub(crate) async fn send_two_factor_email( HashPurpose::ServerAuthorization, )?; - let config = client.get_api_configurations().await; + let config = client.internal.get_api_configurations().await; bitwarden_api_api::apis::two_factor_api::two_factor_send_email_login_post( &config.api, Some(TwoFactorEmailRequestModel { diff --git a/crates/bitwarden/src/auth/mod.rs b/crates/bitwarden-core/src/auth/mod.rs similarity index 84% rename from crates/bitwarden/src/auth/mod.rs rename to crates/bitwarden-core/src/auth/mod.rs index ee409ddd6..30babdc7d 100644 --- a/crates/bitwarden/src/auth/mod.rs +++ b/crates/bitwarden-core/src/auth/mod.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "internal")] +use bitwarden_crypto::{HashPurpose, Kdf, MasterKey}; + mod access_token; pub(super) mod api; pub mod client_auth; @@ -5,28 +8,35 @@ mod jwt_token; pub mod login; #[cfg(feature = "internal")] pub mod password; +#[cfg(feature = "internal")] +pub mod pin; pub mod renew; pub use access_token::AccessToken; pub use jwt_token::JWTToken; -#[cfg(feature = "internal")] -mod register; -#[cfg(feature = "internal")] -use bitwarden_crypto::{HashPurpose, MasterKey}; -#[cfg(feature = "internal")] -pub use register::{RegisterKeyResponse, RegisterRequest}; + #[cfg(feature = "internal")] mod auth_request; #[cfg(feature = "internal")] pub use auth_request::AuthRequestResponse; #[cfg(feature = "internal")] pub(crate) use auth_request::{auth_request_decrypt_master_key, auth_request_decrypt_user_key}; + +#[cfg(feature = "internal")] +mod register; +#[cfg(feature = "internal")] +pub use register::{RegisterKeyResponse, RegisterRequest}; + #[cfg(feature = "internal")] mod tde; #[cfg(feature = "internal")] pub use tde::RegisterTdeKeyResponse; +#[cfg(feature = "internal")] +mod key_connector; +#[cfg(feature = "internal")] +pub use key_connector::KeyConnectorResponse; #[cfg(feature = "internal")] -use crate::{client::Kdf, error::Result}; +use crate::error::Result; #[cfg(feature = "internal")] fn determine_password_hash( @@ -35,7 +45,7 @@ fn determine_password_hash( password: &str, purpose: HashPurpose, ) -> Result { - let master_key = MasterKey::derive(password.as_bytes(), email.as_bytes(), kdf)?; + let master_key = MasterKey::derive(password, email, kdf)?; Ok(master_key.derive_master_key_hash(password.as_bytes(), purpose)?) } diff --git a/crates/bitwarden/src/auth/password/mod.rs b/crates/bitwarden-core/src/auth/password/mod.rs similarity index 100% rename from crates/bitwarden/src/auth/password/mod.rs rename to crates/bitwarden-core/src/auth/password/mod.rs diff --git a/crates/bitwarden/src/auth/password/policy.rs b/crates/bitwarden-core/src/auth/password/policy.rs similarity index 100% rename from crates/bitwarden/src/auth/password/policy.rs rename to crates/bitwarden-core/src/auth/password/policy.rs diff --git a/crates/bitwarden/src/auth/password/strength.rs b/crates/bitwarden-core/src/auth/password/strength.rs similarity index 97% rename from crates/bitwarden/src/auth/password/strength.rs rename to crates/bitwarden-core/src/auth/password/strength.rs index 66b5b5823..6f1757fac 100644 --- a/crates/bitwarden/src/auth/password/strength.rs +++ b/crates/bitwarden-core/src/auth/password/strength.rs @@ -13,7 +13,7 @@ pub(crate) fn password_strength( let mut arr: Vec<_> = inputs.iter().map(String::as_str).collect(); arr.extend(GLOBAL_INPUTS); - zxcvbn(&password, &arr).map_or(0, |e| e.score()) + zxcvbn(&password, &arr).score().into() } fn email_to_user_inputs(email: &str) -> Vec { diff --git a/crates/bitwarden/src/auth/password/validate.rs b/crates/bitwarden-core/src/auth/password/validate.rs similarity index 79% rename from crates/bitwarden/src/auth/password/validate.rs rename to crates/bitwarden-core/src/auth/password/validate.rs index 537b819ea..b5f9ccfd2 100644 --- a/crates/bitwarden/src/auth/password/validate.rs +++ b/crates/bitwarden-core/src/auth/password/validate.rs @@ -14,11 +14,12 @@ pub(crate) fn validate_password( password_hash: String, ) -> Result { let login_method = client - .login_method - .as_ref() + .internal + .get_login_method() .ok_or(Error::NotAuthenticated)?; - if let LoginMethod::User(login_method) = login_method { + #[allow(irrefutable_let_patterns)] + if let LoginMethod::User(login_method) = login_method.as_ref() { match login_method { UserLoginMethod::Username { email, kdf, .. } | UserLoginMethod::ApiKey { email, kdf, .. } => { @@ -44,24 +45,23 @@ pub(crate) fn validate_password_user_key( encrypted_user_key: String, ) -> Result { let login_method = client - .login_method - .as_ref() + .internal + .get_login_method() .ok_or(Error::NotAuthenticated)?; - if let LoginMethod::User(login_method) = login_method { + #[allow(irrefutable_let_patterns)] + if let LoginMethod::User(login_method) = login_method.as_ref() { match login_method { UserLoginMethod::Username { email, kdf, .. } | UserLoginMethod::ApiKey { email, kdf, .. } => { - let master_key = MasterKey::derive(password.as_bytes(), email.as_bytes(), kdf)?; + let master_key = MasterKey::derive(&password, email, kdf)?; let user_key = master_key .decrypt_user_key(encrypted_user_key.parse()?) .map_err(|_| "wrong password")?; - let enc = client - .get_encryption_settings() - .map_err(|_| Error::VaultLocked)?; + let enc = client.internal.get_encryption_settings()?; - let existing_key = enc.get_key(&None).ok_or(Error::VaultLocked)?; + let existing_key = enc.get_key(&None)?; if user_key.to_vec() != existing_key.to_vec() { return Err("wrong user key".into()); @@ -78,22 +78,26 @@ pub(crate) fn validate_password_user_key( #[cfg(test)] mod tests { + use bitwarden_crypto::Kdf; + use crate::auth::password::{validate::validate_password_user_key, validate_password}; #[test] fn test_validate_password() { use std::num::NonZeroU32; - use crate::client::{Client, Kdf, LoginMethod, UserLoginMethod}; + use crate::client::{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 client = Client::new(None); + client + .internal + .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(); @@ -108,9 +112,11 @@ mod tests { fn test_validate_password_user_key() { use std::num::NonZeroU32; - use crate::client::{Client, Kdf, LoginMethod, UserLoginMethod}; + use bitwarden_crypto::{Kdf, MasterKey}; - let mut client = Client::new(None); + use crate::client::{Client, LoginMethod, UserLoginMethod}; + + let client = Client::new(None); let password = "asdfasdfasdf"; let email = "test@bitwarden.com"; @@ -118,20 +124,21 @@ mod tests { iterations: NonZeroU32::new(600_000).unwrap(), }; - client.set_login_method(LoginMethod::User(UserLoginMethod::Username { - email: email.to_string(), - kdf: kdf.clone(), - client_id: "1".to_string(), - })); + client + .internal + .set_login_method(LoginMethod::User(UserLoginMethod::Username { + email: email.to_string(), + kdf: kdf.clone(), + client_id: "1".to_string(), + })); - let master_key = - bitwarden_crypto::MasterKey::derive(password.as_bytes(), email.as_bytes(), &kdf) - .unwrap(); + let master_key = MasterKey::derive(password, email, &kdf).unwrap(); let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE="; let private_key = "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(); client + .internal .initialize_user_crypto_master_key(master_key, user_key.parse().unwrap(), private_key) .unwrap(); @@ -148,29 +155,33 @@ mod tests { fn test_validate_password_user_key_wrong_password() { use std::num::NonZeroU32; - use crate::client::{Client, Kdf, LoginMethod, UserLoginMethod}; + use bitwarden_crypto::{Kdf, MasterKey}; + + use crate::client::{Client, LoginMethod, UserLoginMethod}; - let mut client = Client::new(None); + let client = Client::new(None); - let password = b"asdfasdfasdf"; + let password = "asdfasdfasdf"; let email = "test@bitwarden.com"; let kdf = Kdf::PBKDF2 { iterations: NonZeroU32::new(600_000).unwrap(), }; - client.set_login_method(LoginMethod::User(UserLoginMethod::Username { - email: email.to_string(), - kdf: kdf.clone(), - client_id: "1".to_string(), - })); + client + .internal + .set_login_method(LoginMethod::User(UserLoginMethod::Username { + email: email.to_string(), + kdf: kdf.clone(), + client_id: "1".to_string(), + })); - let master_key = - bitwarden_crypto::MasterKey::derive(password, email.as_bytes(), &kdf).unwrap(); + let master_key = MasterKey::derive(password, email, &kdf).unwrap(); let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE="; let private_key = "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(); client + .internal .initialize_user_crypto_master_key(master_key, user_key.parse().unwrap(), private_key) .unwrap(); diff --git a/crates/bitwarden-core/src/auth/pin.rs b/crates/bitwarden-core/src/auth/pin.rs new file mode 100644 index 000000000..ee093bfa6 --- /dev/null +++ b/crates/bitwarden-core/src/auth/pin.rs @@ -0,0 +1,101 @@ +use bitwarden_crypto::{EncString, PinKey}; + +use crate::{ + client::{LoginMethod, UserLoginMethod}, + error::{Error, Result}, + Client, +}; + +pub(crate) fn validate_pin( + client: &Client, + pin: String, + pin_protected_user_key: EncString, +) -> Result { + let login_method = client + .internal + .get_login_method() + .ok_or(Error::NotAuthenticated)?; + + #[allow(irrefutable_let_patterns)] + let LoginMethod::User(login_method) = login_method.as_ref() else { + return Err(Error::NotAuthenticated); + }; + + match login_method { + UserLoginMethod::Username { email, kdf, .. } + | UserLoginMethod::ApiKey { email, kdf, .. } => { + let enc = client.internal.get_encryption_settings()?; + let user_key = enc.get_key(&None)?; + + let pin_key = PinKey::derive(pin.as_bytes(), email.as_bytes(), kdf)?; + + let Ok(decrypted_key) = pin_key.decrypt_user_key(pin_protected_user_key) else { + return Ok(false); + }; + + Ok(user_key.to_vec() == decrypted_key.to_vec()) + } + } +} + +#[cfg(test)] +mod tests { + use std::num::NonZeroU32; + + use bitwarden_crypto::{Kdf, MasterKey}; + + use super::*; + use crate::client::{Client, LoginMethod, UserLoginMethod}; + + fn init_client() -> Client { + let client = Client::new(None); + + let password = "asdfasdfasdf"; + let email = "test@bitwarden.com"; + let kdf = Kdf::PBKDF2 { + iterations: NonZeroU32::new(600_000).unwrap(), + }; + + client + .internal + .set_login_method(LoginMethod::User(UserLoginMethod::Username { + email: email.to_string(), + kdf: kdf.clone(), + client_id: "1".to_string(), + })); + + let master_key = MasterKey::derive(password, email, &kdf).unwrap(); + + let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE="; + let private_key = "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(); + + client + .internal + .initialize_user_crypto_master_key(master_key, user_key.parse().unwrap(), private_key) + .unwrap(); + + client + } + + #[test] + fn test_validate_valid_pin() { + let pin = "1234".to_string(); + let pin_protected_user_key = "2.BXgvdBUeEMyvumqAJkAzPA==|JScDPoqOkVdrC1X755Ubt8tS9pC/thvrvNf5CyNcRg8HZtZ466EcRo7aCqwUzLyTVNRkbCYtFYT+09acGGHur8tGuS7Kmg/pYeaUo4K0UKI=|NpIFg5P9z0SN1MffbixD9OQE0l+NiNmnRQJs/kTsyoQ=" + .parse() + .unwrap(); + + let client = init_client(); + assert!(validate_pin(&client, pin.clone(), pin_protected_user_key).unwrap()); + } + + #[test] + fn test_validate_invalid_pin() { + let pin = "1234".to_string(); + let pin_protected_user_key = "2.BXgvdBUeEMyvumqAJkAyPA==|JScDPoqOkVdrC1X755Ubt8tS9pC/thvrvNf5CyNcRg8HZtZ466EcRo7aCqwUzLyTVNRkbCYtFYT+09acGGHur8tGuS7Kmg/pYeaUo4K0UKI=|NpIFg5P9z0SN1MffbixD9OQE0l+NiNmnRQJs/kTsyoQ=" + .parse() + .unwrap(); + + let client = init_client(); + assert!(!validate_pin(&client, pin.clone(), pin_protected_user_key).unwrap()); + } +} diff --git a/crates/bitwarden/src/auth/register.rs b/crates/bitwarden-core/src/auth/register.rs similarity index 86% rename from crates/bitwarden/src/auth/register.rs rename to crates/bitwarden-core/src/auth/register.rs index 06313d42b..6a1c875fb 100644 --- a/crates/bitwarden/src/auth/register.rs +++ b/crates/bitwarden-core/src/auth/register.rs @@ -2,11 +2,11 @@ use bitwarden_api_identity::{ apis::accounts_api::accounts_register_post, models::{KeysRequestModel, RegisterRequestModel}, }; -use bitwarden_crypto::{default_pbkdf2_iterations, HashPurpose, MasterKey, RsaKeyPair}; +use bitwarden_crypto::{default_pbkdf2_iterations, HashPurpose, Kdf, MasterKey, RsaKeyPair}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::{client::Kdf, error::Result, Client}; +use crate::{error::Result, Client}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -18,8 +18,8 @@ pub struct RegisterRequest { } /// Half baked implementation of user registration -pub(super) async fn register(client: &mut Client, req: &RegisterRequest) -> Result<()> { - let config = client.get_api_configurations().await; +pub(super) async fn register(client: &Client, req: &RegisterRequest) -> Result<()> { + let config = client.internal.get_api_configurations().await; let kdf = Kdf::default(); @@ -57,7 +57,7 @@ pub(super) fn make_register_keys( password: String, kdf: Kdf, ) -> Result { - let master_key = MasterKey::derive(password.as_bytes(), email.as_bytes(), &kdf)?; + let master_key = MasterKey::derive(&password, &email, &kdf)?; let master_password_hash = master_key.derive_master_key_hash(password.as_bytes(), HashPurpose::ServerAuthorization)?; let (user_key, encrypted_user_key) = master_key.make_user_key()?; diff --git a/crates/bitwarden/src/auth/renew.rs b/crates/bitwarden-core/src/auth/renew.rs similarity index 66% rename from crates/bitwarden/src/auth/renew.rs rename to crates/bitwarden-core/src/auth/renew.rs index 783494ae2..b35d71d6e 100644 --- a/crates/bitwarden/src/auth/renew.rs +++ b/crates/bitwarden-core/src/auth/renew.rs @@ -1,37 +1,50 @@ use chrono::Utc; -#[cfg(feature = "internal")] -use crate::{auth::api::request::ApiTokenRequest, client::UserLoginMethod}; +#[cfg(feature = "secrets")] use crate::{ - auth::api::{request::AccessTokenRequest, response::IdentityTokenResponse}, - client::{Client, LoginMethod, ServiceAccountLoginMethod}, - error::{Error, Result}, + auth::api::request::AccessTokenRequest, + client::ServiceAccountLoginMethod, secrets_manager::state::{self, ClientState}, }; +use crate::{ + auth::api::{request::ApiTokenRequest, response::IdentityTokenResponse}, + client::{internal::InternalClient, LoginMethod, UserLoginMethod}, + error::{Error, Result}, +}; -pub(crate) async fn renew_token(client: &mut Client) -> Result<()> { +pub(crate) async fn renew_token(client: &InternalClient) -> Result<()> { const TOKEN_RENEW_MARGIN_SECONDS: i64 = 5 * 60; - if let (Some(expires), Some(login_method)) = (&client.token_expires_on, &client.login_method) { + let tokens = client + .tokens + .read() + .expect("RwLock is not poisoned") + .clone(); + let login_method = client + .login_method + .read() + .expect("RwLock is not poisoned") + .clone(); + + if let (Some(expires), Some(login_method)) = (tokens.expires_on, login_method) { if Utc::now().timestamp() < expires - TOKEN_RENEW_MARGIN_SECONDS { return Ok(()); } - let res = match login_method { - #[cfg(feature = "internal")] + let config = client + .__api_configurations + .read() + .expect("RwLock is not poisoned") + .clone(); + + let res = match login_method.as_ref() { LoginMethod::User(u) => match u { UserLoginMethod::Username { client_id, .. } => { - let refresh = client - .refresh_token - .as_deref() - .ok_or(Error::NotAuthenticated)?; + let refresh = tokens.refresh_token.ok_or(Error::NotAuthenticated)?; - crate::auth::api::request::RenewTokenRequest::new( - refresh.to_owned(), - client_id.to_owned(), - ) - .send(&client.__api_configurations) - .await? + crate::auth::api::request::RenewTokenRequest::new(refresh, client_id.to_owned()) + .send(&config) + .await? } UserLoginMethod::ApiKey { client_id, @@ -39,10 +52,11 @@ pub(crate) async fn renew_token(client: &mut Client) -> Result<()> { .. } => { ApiTokenRequest::new(client_id, client_secret) - .send(&client.__api_configurations) + .send(&config) .await? } }, + #[cfg(feature = "secrets")] LoginMethod::ServiceAccount(s) => match s { ServiceAccountLoginMethod::AccessToken { access_token, @@ -53,13 +67,13 @@ pub(crate) async fn renew_token(client: &mut Client) -> Result<()> { access_token.access_token_id, &access_token.client_secret, ) - .send(&client.__api_configurations) + .send(&config) .await?; if let (IdentityTokenResponse::Payload(r), Some(state_file), Ok(enc_settings)) = (&result, state_file, client.get_encryption_settings()) { - if let Some(enc_key) = enc_settings.get_key(&None) { + if let Ok(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); diff --git a/crates/bitwarden/src/auth/tde.rs b/crates/bitwarden-core/src/auth/tde.rs similarity index 79% rename from crates/bitwarden/src/auth/tde.rs rename to crates/bitwarden-core/src/auth/tde.rs index 02f36583c..8d99b50fe 100644 --- a/crates/bitwarden/src/auth/tde.rs +++ b/crates/bitwarden-core/src/auth/tde.rs @@ -10,7 +10,7 @@ use crate::{error::Result, Client}; /// generated user key, and encrypts the user key with the organization public key for admin /// password reset. If remember_device is true, it also generates a device key. pub(super) fn make_register_tde_keys( - client: &mut Client, + client: &Client, email: String, org_public_key: String, remember_device: bool, @@ -31,14 +31,18 @@ pub(super) fn make_register_tde_keys( None }; - client.set_login_method(crate::client::LoginMethod::User( - crate::client::UserLoginMethod::Username { - client_id: "".to_owned(), - email, - kdf: Kdf::default(), - }, - )); - client.initialize_user_crypto_decrypted_key(user_key.0, key_pair.private.clone())?; + client + .internal + .set_login_method(crate::client::LoginMethod::User( + crate::client::UserLoginMethod::Username { + client_id: "".to_owned(), + email, + kdf: Kdf::default(), + }, + )); + client + .internal + .initialize_user_crypto_decrypted_key(user_key.0, key_pair.private.clone())?; Ok(RegisterTdeKeyResponse { private_key: key_pair.private, diff --git a/crates/bitwarden-core/src/client/client.rs b/crates/bitwarden-core/src/client/client.rs new file mode 100644 index 000000000..b9bf4c516 --- /dev/null +++ b/crates/bitwarden-core/src/client/client.rs @@ -0,0 +1,106 @@ +use std::sync::{Arc, RwLock}; + +use reqwest::header::{self, HeaderValue}; + +use super::internal::InternalClient; +#[cfg(feature = "internal")] +use crate::client::flags::Flags; +use crate::client::{ + client_settings::ClientSettings, + internal::{ApiConfigurations, Tokens}, +}; + +/// The main struct to interact with the Bitwarden SDK. +#[derive(Debug)] +pub struct Client { + #[doc(hidden)] + pub internal: InternalClient, +} + +impl Client { + pub fn new(settings_input: Option) -> Self { + let settings = settings_input.unwrap_or_default(); + + fn new_client_builder() -> reqwest::ClientBuilder { + #[allow(unused_mut)] + let mut client_builder = reqwest::Client::builder(); + + #[cfg(not(target_arch = "wasm32"))] + { + client_builder = + client_builder.use_preconfigured_tls(rustls_platform_verifier::tls_config()); + } + + client_builder + } + + let external_client = new_client_builder().build().expect("Build should not fail"); + + let mut headers = header::HeaderMap::new(); + headers.append( + "Device-Type", + HeaderValue::from_str(&(settings.device_type as u8).to_string()) + .expect("All numbers are valid ASCII"), + ); + let client_builder = new_client_builder().default_headers(headers); + + let client = client_builder.build().expect("Build should not fail"); + + let identity = bitwarden_api_identity::apis::configuration::Configuration { + base_path: settings.identity_url, + user_agent: Some(settings.user_agent.clone()), + client: client.clone(), + basic_auth: None, + oauth_access_token: None, + bearer_access_token: None, + api_key: None, + }; + + let api = bitwarden_api_api::apis::configuration::Configuration { + base_path: settings.api_url, + user_agent: Some(settings.user_agent), + client, + basic_auth: None, + oauth_access_token: None, + bearer_access_token: None, + api_key: None, + }; + + Self { + internal: InternalClient { + tokens: RwLock::new(Tokens::default()), + login_method: RwLock::new(None), + #[cfg(feature = "internal")] + flags: RwLock::new(Flags::default()), + __api_configurations: RwLock::new(Arc::new(ApiConfigurations { + identity, + api, + device_type: settings.device_type, + })), + external_client, + encryption_settings: RwLock::new(None), + }, + } + } +} + +#[cfg(test)] +mod tests { + #[cfg(not(target_arch = "wasm32"))] + #[test] + fn test_reqwest_rustls_platform_verifier_are_compatible() { + // rustls-platform-verifier is generating a rustls::ClientConfig, + // which reqwest accepts as a &dyn Any and then downcasts it to a + // rustls::ClientConfig. + + // This means that if the rustls version of the two crates don't match, + // the downcast will fail and we will get a runtime error. + + // This tests is added to ensure that it doesn't happen. + + let _ = reqwest::ClientBuilder::new() + .use_preconfigured_tls(rustls_platform_verifier::tls_config()) + .build() + .unwrap(); + } +} diff --git a/crates/bitwarden/src/client/client_settings.rs b/crates/bitwarden-core/src/client/client_settings.rs similarity index 86% rename from crates/bitwarden/src/client/client_settings.rs rename to crates/bitwarden-core/src/client/client_settings.rs index 6e1907787..70e2e6839 100644 --- a/crates/bitwarden/src/client/client_settings.rs +++ b/crates/bitwarden-core/src/client/client_settings.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; /// Defaults to /// /// ``` -/// # use bitwarden::client::client_settings::{ClientSettings, DeviceType}; +/// # use bitwarden_core::{ClientSettings, DeviceType}; /// let settings = ClientSettings { /// identity_url: "https://identity.bitwarden.com".to_string(), /// api_url: "https://api.bitwarden.com".to_string(), @@ -19,6 +19,11 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(default, rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +#[cfg_attr( + feature = "wasm", + derive(tsify_next::Tsify), + tsify(into_wasm_abi, from_wasm_abi) +)] pub struct ClientSettings { /// The identity url of the targeted Bitwarden instance. Defaults to `https://identity.bitwarden.com` pub identity_url: String, @@ -44,6 +49,11 @@ impl Default for ClientSettings { #[allow(non_camel_case_types)] #[derive(Serialize, Deserialize, Copy, Clone, Debug, JsonSchema)] #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] +#[cfg_attr( + feature = "wasm", + derive(tsify_next::Tsify), + tsify(into_wasm_abi, from_wasm_abi) +)] pub enum DeviceType { Android = 0, iOS = 1, @@ -66,6 +76,9 @@ pub enum DeviceType { VivaldiBrowser = 18, VivaldiExtension = 19, SafariExtension = 20, - SDK = 21, + Server = 22, + WindowsCLI = 23, + MacOsCLI = 24, + LinuxCLI = 25, } diff --git a/crates/bitwarden/src/client/encryption_settings.rs b/crates/bitwarden-core/src/client/encryption_settings.rs similarity index 61% rename from crates/bitwarden/src/client/encryption_settings.rs rename to crates/bitwarden-core/src/client/encryption_settings.rs index 025b3cec7..9d9549021 100644 --- a/crates/bitwarden/src/client/encryption_settings.rs +++ b/crates/bitwarden-core/src/client/encryption_settings.rs @@ -1,13 +1,34 @@ use std::collections::HashMap; -use bitwarden_crypto::{AsymmetricCryptoKey, KeyContainer, SymmetricCryptoKey}; +use bitwarden_crypto::{AsymmetricCryptoKey, CryptoError, KeyContainer, SymmetricCryptoKey}; #[cfg(feature = "internal")] use bitwarden_crypto::{AsymmetricEncString, EncString, MasterKey}; +use thiserror::Error; use uuid::Uuid; #[cfg(feature = "internal")] use crate::error::Result; +use crate::VaultLocked; +#[derive(Debug, Error)] +pub enum EncryptionSettingsError { + #[error("Cryptography error, {0}")] + Crypto(#[from] bitwarden_crypto::CryptoError), + + #[error(transparent)] + InvalidBase64(#[from] base64::DecodeError), + + #[error(transparent)] + VaultLocked(#[from] VaultLocked), + + #[error("Invalid private key")] + InvalidPrivateKey, + + #[error("Missing private key")] + MissingPrivateKey, +} + +#[derive(Clone)] pub struct EncryptionSettings { user_key: SymmetricCryptoKey, pub(crate) private_key: Option, @@ -27,7 +48,7 @@ impl EncryptionSettings { master_key: MasterKey, user_key: EncString, private_key: EncString, - ) -> Result { + ) -> Result { // Decrypt the user key let user_key = master_key.decrypt_user_key(user_key)?; Self::new_decrypted_key(user_key, private_key) @@ -41,12 +62,25 @@ impl EncryptionSettings { pub(crate) fn new_decrypted_key( user_key: SymmetricCryptoKey, private_key: EncString, - ) -> Result { + ) -> Result { use bitwarden_crypto::KeyDecryptable; + use log::warn; let private_key = { let dec: Vec = private_key.decrypt_with_key(&user_key)?; - Some(AsymmetricCryptoKey::from_der(&dec)?) + + // FIXME: [PM-11690] - Temporarily ignore invalid private keys until we have a recovery + // process in place. + AsymmetricCryptoKey::from_der(&dec) + .map_err(|_| { + warn!("Invalid private key"); + }) + .ok() + + // Some( + // AsymmetricCryptoKey::from_der(&dec) + // .map_err(|_| EncryptionSettingsError::InvalidPrivateKey)?, + // ) }; Ok(EncryptionSettings { @@ -58,6 +92,7 @@ impl EncryptionSettings { /// Initialize the encryption settings with only a single decrypted key. /// This is used only for logging in Secrets Manager with an access token + #[cfg(feature = "secrets")] pub(crate) fn new_single_key(key: SymmetricCryptoKey) -> Self { EncryptionSettings { user_key: key, @@ -70,17 +105,23 @@ impl EncryptionSettings { pub(crate) fn set_org_keys( &mut self, org_enc_keys: Vec<(Uuid, AsymmetricEncString)>, - ) -> Result<&mut Self> { + ) -> Result<&Self, EncryptionSettingsError> { use bitwarden_crypto::KeyDecryptable; - use crate::error::Error; - - 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(); + // FIXME: [PM-11690] - Early abort to handle private key being corrupt + if org_enc_keys.is_empty() { + return Ok(self); + } + + let private_key = self + .private_key + .as_ref() + .ok_or(EncryptionSettingsError::MissingPrivateKey)?; + // Decrypt the org keys with the private key for (org_id, org_enc_key) in org_enc_keys { let mut dec: Vec = org_enc_key.decrypt_with_key(private_key)?; @@ -93,22 +134,25 @@ impl EncryptionSettings { Ok(self) } - pub(crate) fn get_key(&self, org_id: &Option) -> Option<&SymmetricCryptoKey> { + pub fn get_key(&self, org_id: &Option) -> Result<&SymmetricCryptoKey, CryptoError> { // If we don't have a private key set (to decode multiple org keys), we just use the main // user key if self.private_key.is_none() { - return Some(&self.user_key); + return Ok(&self.user_key); } match org_id { - Some(org_id) => self.org_keys.get(org_id), - None => Some(&self.user_key), + Some(org_id) => self + .org_keys + .get(org_id) + .ok_or(CryptoError::MissingKey(*org_id)), + None => Ok(&self.user_key), } } } impl KeyContainer for EncryptionSettings { - fn get_key(&self, org_id: &Option) -> Option<&SymmetricCryptoKey> { + fn get_key(&self, org_id: &Option) -> Result<&SymmetricCryptoKey, CryptoError> { EncryptionSettings::get_key(self, org_id) } } diff --git a/crates/bitwarden/src/client/flags.rs b/crates/bitwarden-core/src/client/flags.rs similarity index 100% rename from crates/bitwarden/src/client/flags.rs rename to crates/bitwarden-core/src/client/flags.rs diff --git a/crates/bitwarden-core/src/client/internal.rs b/crates/bitwarden-core/src/client/internal.rs new file mode 100644 index 000000000..d64ac75be --- /dev/null +++ b/crates/bitwarden-core/src/client/internal.rs @@ -0,0 +1,252 @@ +use std::sync::{Arc, RwLock}; + +#[cfg(any(feature = "internal", feature = "secrets"))] +use bitwarden_crypto::SymmetricCryptoKey; +#[cfg(feature = "internal")] +use bitwarden_crypto::{AsymmetricEncString, EncString, Kdf, MasterKey, PinKey}; +use chrono::Utc; +use uuid::Uuid; + +#[cfg(feature = "secrets")] +use super::login_method::ServiceAccountLoginMethod; +use crate::{ + auth::renew::renew_token, + client::{encryption_settings::EncryptionSettings, login_method::LoginMethod}, + error::{Result, VaultLocked}, + DeviceType, +}; +#[cfg(feature = "internal")] +use crate::{ + client::encryption_settings::EncryptionSettingsError, + client::{flags::Flags, login_method::UserLoginMethod}, + error::Error, +}; + +#[derive(Debug, Clone)] +pub struct ApiConfigurations { + pub identity: bitwarden_api_identity::apis::configuration::Configuration, + pub api: bitwarden_api_api::apis::configuration::Configuration, + pub device_type: DeviceType, +} + +#[derive(Debug, Default, Clone)] +pub(crate) struct Tokens { + // These two fields are always written to, but they are not read + // from the secrets manager SDK. + #[cfg_attr(not(feature = "internal"), allow(dead_code))] + access_token: Option, + pub(crate) expires_on: Option, + + #[cfg_attr(not(feature = "internal"), allow(dead_code))] + pub(crate) refresh_token: Option, +} + +#[derive(Debug)] +pub struct InternalClient { + pub(crate) tokens: RwLock, + pub(crate) login_method: RwLock>>, + + #[cfg(feature = "internal")] + pub(super) flags: RwLock, + + /// Use Client::get_api_configurations().await to access this. + /// It should only be used directly in renew_token + #[doc(hidden)] + pub(crate) __api_configurations: RwLock>, + + /// Reqwest client useable for external integrations like email forwarders, HIBP. + #[allow(unused)] + pub(crate) external_client: reqwest::Client, + + pub(super) encryption_settings: RwLock>>, +} + +impl InternalClient { + #[cfg(feature = "internal")] + pub fn load_flags(&self, flags: std::collections::HashMap) { + *self.flags.write().expect("RwLock is not poisoned") = Flags::load_from_map(flags); + } + + #[cfg(feature = "internal")] + pub fn get_flags(&self) -> Flags { + self.flags.read().expect("RwLock is not poisoned").clone() + } + + #[cfg(feature = "internal")] + pub(crate) fn get_login_method(&self) -> Option> { + self.login_method + .read() + .expect("RwLock is not poisoned") + .clone() + } + + pub fn get_access_token_organization(&self) -> Option { + match self + .login_method + .read() + .expect("RwLock is not poisoned") + .as_deref() + { + #[cfg(feature = "secrets")] + Some(LoginMethod::ServiceAccount(ServiceAccountLoginMethod::AccessToken { + organization_id, + .. + })) => Some(*organization_id), + _ => None, + } + } + + #[cfg(any(feature = "internal", feature = "secrets"))] + pub(crate) fn set_login_method(&self, login_method: LoginMethod) { + use log::debug; + + debug! {"setting login method: {:#?}", login_method} + *self.login_method.write().expect("RwLock is not poisoned") = Some(Arc::new(login_method)); + } + + pub(crate) fn set_tokens(&self, token: String, refresh_token: Option, expires_in: u64) { + *self.tokens.write().expect("RwLock is not poisoned") = Tokens { + access_token: Some(token.clone()), + expires_on: Some(Utc::now().timestamp() + expires_in as i64), + refresh_token, + }; + let mut guard = self + .__api_configurations + .write() + .expect("RwLock is not poisoned"); + + let inner = Arc::make_mut(&mut guard); + inner.identity.oauth_access_token = Some(token.clone()); + inner.api.oauth_access_token = Some(token); + } + + #[cfg(feature = "internal")] + pub fn is_authed(&self) -> bool { + let is_token_set = self + .tokens + .read() + .expect("RwLock is not poisoned") + .access_token + .is_some(); + let is_login_method_set = self + .login_method + .read() + .expect("RwLock is not poisoned") + .is_some(); + + is_token_set || is_login_method_set + } + + #[cfg(feature = "internal")] + pub fn get_kdf(&self) -> Result { + match self + .login_method + .read() + .expect("RwLock is not poisoned") + .as_deref() + { + Some(LoginMethod::User( + UserLoginMethod::Username { kdf, .. } | UserLoginMethod::ApiKey { kdf, .. }, + )) => Ok(kdf.clone()), + _ => Err(Error::NotAuthenticated), + } + } + + pub async fn get_api_configurations(&self) -> Arc { + // At the moment we ignore the error result from the token renewal, if it fails, + // the token will end up expiring and the next operation is going to fail anyway. + renew_token(self).await.ok(); + self.__api_configurations + .read() + .expect("RwLock is not poisoned") + .clone() + } + + #[cfg(feature = "internal")] + pub fn get_http_client(&self) -> &reqwest::Client { + &self.external_client + } + + pub fn get_encryption_settings(&self) -> Result, VaultLocked> { + self.encryption_settings + .read() + .expect("RwLock is not poisoned") + .clone() + .ok_or(VaultLocked) + } + + #[cfg(feature = "internal")] + pub(crate) fn initialize_user_crypto_master_key( + &self, + master_key: MasterKey, + user_key: EncString, + private_key: EncString, + ) -> Result<(), EncryptionSettingsError> { + *self + .encryption_settings + .write() + .expect("RwLock is not poisoned") = Some(Arc::new(EncryptionSettings::new( + master_key, + user_key, + private_key, + )?)); + + Ok(()) + } + + #[cfg(feature = "internal")] + pub(crate) fn initialize_user_crypto_decrypted_key( + &self, + user_key: SymmetricCryptoKey, + private_key: EncString, + ) -> Result<(), EncryptionSettingsError> { + *self + .encryption_settings + .write() + .expect("RwLock is not poisoned") = Some(Arc::new( + EncryptionSettings::new_decrypted_key(user_key, private_key)?, + )); + + Ok(()) + } + + #[cfg(feature = "internal")] + pub(crate) fn initialize_user_crypto_pin( + &self, + pin_key: PinKey, + pin_protected_user_key: EncString, + private_key: EncString, + ) -> Result<(), EncryptionSettingsError> { + let decrypted_user_key = pin_key.decrypt_user_key(pin_protected_user_key)?; + self.initialize_user_crypto_decrypted_key(decrypted_user_key, private_key) + } + + #[cfg(feature = "secrets")] + pub(crate) fn initialize_crypto_single_key(&self, key: SymmetricCryptoKey) { + *self + .encryption_settings + .write() + .expect("RwLock is not poisoned") = + Some(Arc::new(EncryptionSettings::new_single_key(key))); + } + + #[cfg(feature = "internal")] + pub fn initialize_org_crypto( + &self, + org_keys: Vec<(Uuid, AsymmetricEncString)>, + ) -> Result, EncryptionSettingsError> { + let mut guard = self + .encryption_settings + .write() + .expect("RwLock is not poisoned"); + + let Some(enc) = guard.as_mut() else { + return Err(VaultLocked.into()); + }; + + let inner = Arc::make_mut(enc); + inner.set_org_keys(org_keys)?; + + Ok(enc.clone()) + } +} diff --git a/crates/bitwarden-core/src/client/login_method.rs b/crates/bitwarden-core/src/client/login_method.rs new file mode 100644 index 000000000..67db15a71 --- /dev/null +++ b/crates/bitwarden-core/src/client/login_method.rs @@ -0,0 +1,46 @@ +#[cfg(feature = "secrets")] +use std::path::PathBuf; + +use bitwarden_crypto::Kdf; +#[cfg(feature = "secrets")] +use uuid::Uuid; + +#[cfg(feature = "secrets")] +use crate::auth::AccessToken; + +#[derive(Debug)] +pub(crate) enum LoginMethod { + #[allow(dead_code)] + User(UserLoginMethod), + // TODO: Organizations supports api key + // Organization(OrganizationLoginMethod), + #[cfg(feature = "secrets")] + ServiceAccount(ServiceAccountLoginMethod), +} + +#[allow(dead_code)] +#[derive(Debug)] +pub(crate) enum UserLoginMethod { + Username { + client_id: String, + email: String, + kdf: Kdf, + }, + ApiKey { + client_id: String, + client_secret: String, + + email: String, + kdf: Kdf, + }, +} + +#[cfg(feature = "secrets")] +#[derive(Debug)] +pub(crate) enum ServiceAccountLoginMethod { + AccessToken { + access_token: AccessToken, + organization_id: Uuid, + state_file: Option, + }, +} diff --git a/crates/bitwarden-core/src/client/mod.rs b/crates/bitwarden-core/src/client/mod.rs new file mode 100644 index 000000000..1ef7d9357 --- /dev/null +++ b/crates/bitwarden-core/src/client/mod.rs @@ -0,0 +1,20 @@ +//! Bitwarden SDK Client + +#[allow(clippy::module_inception)] +mod client; +pub mod client_settings; +pub mod encryption_settings; +pub mod internal; +pub use internal::ApiConfigurations; +pub mod login_method; +#[cfg(feature = "secrets")] +pub(crate) use login_method::ServiceAccountLoginMethod; +pub(crate) use login_method::{LoginMethod, UserLoginMethod}; +#[cfg(feature = "internal")] +mod flags; + +pub use client::Client; +pub use client_settings::{ClientSettings, DeviceType}; + +#[cfg(feature = "internal")] +pub mod test_accounts; diff --git a/crates/bitwarden/src/client/test_accounts.rs b/crates/bitwarden-core/src/client/test_accounts.rs similarity index 61% rename from crates/bitwarden/src/client/test_accounts.rs rename to crates/bitwarden-core/src/client/test_accounts.rs index 22927d7d8..858e1d2fa 100644 --- a/crates/bitwarden/src/client/test_accounts.rs +++ b/crates/bitwarden-core/src/client/test_accounts.rs @@ -1,3 +1,4 @@ +#![allow(clippy::unwrap_used)] use std::collections::HashMap; use bitwarden_crypto::Kdf; @@ -12,19 +13,17 @@ use crate::{ impl Client { pub async fn init_test_account(account: TestAccount) -> Self { - let mut client = Client::new(None); + let client = Client::new(None); - client.load_flags(HashMap::from([( + client.internal.load_flags(HashMap::from([( "enableCipherKeyEncryption".to_owned(), true, )])); - initialize_user_crypto(&mut client, account.user) - .await - .unwrap(); + initialize_user_crypto(&client, account.user).await.unwrap(); if let Some(org) = account.org { - initialize_org_crypto(&mut client, org).await.unwrap(); + initialize_org_crypto(&client, org).await.unwrap(); } client @@ -53,21 +52,14 @@ pub struct TestAccount { /// /// ```sql /// INSERT INTO vault_dev.dbo.[User] ( -/// Id, Name, Email, EmailVerified, MasterPassword, -/// MasterPasswordHint, Culture, SecurityStamp, -/// TwoFactorProviders, TwoFactorRecoveryCode, -/// EquivalentDomains, ExcludedGlobalEquivalentDomains, -/// AccountRevisionDate, [Key], PublicKey, -/// PrivateKey, Premium, PremiumExpirationDate, -/// Storage, MaxStorageGb, Gateway, GatewayCustomerId, -/// GatewaySubscriptionId, LicenseKey, -/// CreationDate, RevisionDate, RenewalReminderDate, -/// Kdf, KdfIterations, ReferenceData, -/// ApiKey, ForcePasswordReset, UsesKeyConnector, -/// FailedLoginCount, LastFailedLoginDate, -/// AvatarColor, KdfMemory, KdfParallelism, -/// LastPasswordChangeDate, LastKdfChangeDate, -/// LastKeyRotationDate, LastEmailChangeDate +/// Id, Name, Email, EmailVerified, MasterPassword, MasterPasswordHint, Culture, SecurityStamp, +/// TwoFactorProviders, TwoFactorRecoveryCode, EquivalentDomains, +/// ExcludedGlobalEquivalentDomains, AccountRevisionDate, [Key], PublicKey, PrivateKey, Premium, +/// PremiumExpirationDate, Storage, MaxStorageGb, Gateway, GatewayCustomerId, +/// GatewaySubscriptionId, LicenseKey, CreationDate, RevisionDate, RenewalReminderDate, Kdf, +/// KdfIterations, ReferenceData, ApiKey, ForcePasswordReset, UsesKeyConnector, FailedLoginCount, +/// LastFailedLoginDate, AvatarColor, KdfMemory, KdfParallelism, LastPasswordChangeDate, +/// LastKdfChangeDate, LastKeyRotationDate, LastEmailChangeDate /// ) /// VALUES /// ( @@ -144,3 +136,54 @@ pub fn test_bitwarden_com_account() -> TestAccount { }), } } + +/// ### `legacy@bitwarden.com` +/// +/// Account which has a user_key of type `AesCbc256_B64` which is deprecated. +/// +/// - Email: `legacy@bitwarden.com` +/// - Password: `asdfasdfasdf` +/// - PBKDF2: `600_000` iterations +/// +/// ```sql +/// /// INSERT INTO vault_dev.dbo.[User] ( +/// Id, Name, Email, EmailVerified, MasterPassword, MasterPasswordHint, Culture, SecurityStamp, +/// TwoFactorProviders, TwoFactorRecoveryCode, EquivalentDomains, +/// ExcludedGlobalEquivalentDomains, AccountRevisionDate, [Key], PublicKey, PrivateKey, Premium, +/// PremiumExpirationDate, Storage, MaxStorageGb, Gateway, GatewayCustomerId, +/// GatewaySubscriptionId, LicenseKey, CreationDate, RevisionDate, RenewalReminderDate, Kdf, +/// KdfIterations, ReferenceData, ApiKey, ForcePasswordReset, UsesKeyConnector, FailedLoginCount, +/// LastFailedLoginDate, AvatarColor, KdfMemory, KdfParallelism, LastPasswordChangeDate, +/// LastKdfChangeDate, LastKeyRotationDate, LastEmailChangeDate +/// ) +/// VALUES +/// ( +/// N'764335ba-bb3b-4646-9c19-b1a301229eb2', N'Legacy', N'legacy@bitwarden.com', 1, +/// N'AQAAAAIAAYagAAAAEBPKHRNzY+8XTZGODQaa4hQUW/7qsW73v5O8WmqsS+xplghgtX/qXS1JTHT6PNWUFw==', +/// null, N'en-US', N'a1611dff-b383-4d7f-bb37-18c37507c11e', null, null, null, null, +/// N'2024-07-05 13:27:01.4033333', +/// N'0.8UClLa8IPE1iZT7chy5wzQ==|6PVfHnVk5S3XqEtQemnM5yb4JodxmPkkWzmDRdfyHtjORmvxqlLX40tBJZ+CKxQWmS8tpEB5w39rbgHg/gqs0haGdZG4cPbywsgGzxZ7uNI=', +/// N'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvbVTK0cY7j/9GD4UTIZVywT3vWNIiy5LuI1l9MIKbWh+PyVUB5ySR++Z1tDwx5R87AtXwjQS09j6a4X+uGHW4lRQR3Dngb2CpMutLqFjKk7NYWSj3MU5bKlvsCf2Z59ECJEFqo3eAsHYyrL3CixRsZg3LkQcYT+VT/qOOcI6UkMX50lpma2Cvdf3IAgPf6Enkg/WJ3d7D6aU5YtexTY03uKVbOb2UoO2ZJ29VyYEVEJ+0S6pqe9laeHz/oJ9SI3sRqdEYRmyDsbUWJHRJr7eAVesmiUBU/Ls7t4k/oXjfiJ7K5wqL5yCQ0HkqK8zQhi9wB4u7kHwEa8sDJE+cFQ5bQIDAQAB', +/// N'2.leBIE5u0aQUeXi++JzAnrA==|P8x+hs00RJx7epw+49qVtBhLJxE/JTL5dEHg6kq5pbZLdUY8ZvWK49v0EqgHbv1r298N9+msoO9hmdSIVIAZyycemYDSoc1rX4S1KpS/ZMA/Vd3VLFb+o13Ts62GFQ5ygHKgQZfzjU6jO5P/B/0igzFoxyJDomhW5NBC1P9+e/5qNRZN8loKvAaWc/7XtpRayPQqWx+AgYc2ntb1GF5hRVrW4M47bG5ZKllbJWtQKg2sXIy2lDBbKLRFWF4RFzNVcXQGMoPdWLY0f3uTwUH01dyGmFFMbOvfBEuYqmZyPdd93ve8zuFOEqkj46Ulpq2CVG8NvZARTwsdKl6XB0wGuHFoTsDJT2SJGl67pBBKsVRGxy059QW+9hAIB+emIV0T/7+0rvdeSXZ4AbG+oXGEXFTkHefwJKfeT0MBTAjYKr7ZRLgqvf7n39+nCEJU4l22kp8FmjcWIU7AgNipdGHC+UT2yfOcYlvgBgWDcMXcbVDMyus9105RgcW6PHozUj7yjbohI/A3XWmAFufP6BSnmEFCKoik78X/ry09xwiH2rN4KVXe/k9LpRNB2QBGIVsfgCrkxjeE8r0nA59Rvwrhny1z5BkvMW/N1KrGuafg/IYgegx72gJNuZPZlFu1Vs7HxySHmzYvm3DPV7bzCaAxxNtvZmQquNIEnsDQfjJO76iL1JCtDqNJVzGLHTMTr7S5hkOcydcH3kfKwZdA1ULVd2qu0SwOUEP/ECjU/cS5INy6WPYzNMAe/g2DISpQjNwBb5K17PIiGOR7/Q/A6E8pVnkHiAXuUFr9aLOYN9BWSu5Z+BPHH65na2FDmssix5WV09I2sUBfvdNCjkrUGdYgo8E+vOTn35x9GJHF45uhmgC1yAn/+/RSpORlrSVJ7NNP11dn3htUpSsIy/b7ituAu8Ry5mhicFU8CXJL4NeMlXThUt8P++wxs4wMkBvJ8J9NJAVKbAOA2o+GOdjbh6Ww3IRegkurWh4oL/dFSx0LpaXJuw6HFT/LzticPlSwHtUP11hZ81seMsXmkSZd8IugRFfwpPl7N6PVRWDOKxLf4gPqcnJ11TvfasXy1uolV2vZCPbrbbVzQMPdVwL/OzwfhqsIgQZI8rsDMK5D2EX8MaT8MDfGcsYcVTL9PmuZYLpOUnnHX0A1opAAa9iPw3d+eWB/GAyLvKPnMTUqVNos8HcCktXckCshihA8QuBJOwg3m0j2LPSZ5Jvf8gbXauBmt9I4IlJq0xfpgquYY1WNnO8IcWE4N9W+ASvOr9gnduA6CkDeAlyMUFmdpkeCjGMcsV741bTCPApSQlL3/TOT1cjK3iejWpz0OaVHXyg02hW2fNkOfYfr81GvnLvlHxIg4Prw89gKuWU+kQk82lFQo6QQpqbCbJC2FleurD8tYoSY0srhuioVInffvTxw2NMF7FQEqUcsK9AMKSEiDqzBi35Um/fiE3JL4XZBFw8Xzl7X3ab5nlg8X+xD5uSZY+oxD3sDVXjLaQ5JUoys+MCm0FkUj85l0zT6rvM4QLhU1RDK1U51T9HJhh8hsFJsqL4abRzwEWG7PSi859zN4UsgyuQfmBJv/n7QAFCbrJhVBlGB1TKLZRzvgmKoxTYTG3cJFkjetLcUTwrwC9naxAQRfF4=|ufHf73IzJ707dx44w4fjkuD7tDa50OwmmkxcypAT9uQ=', +/// 0, null, null, null, null, null, null, null, N'2024-07-04 17:38:06.7866667', +/// N'2024-07-05 13:27:01.4033333', null, 0, 600000, +/// N'{"id":null,"initiationPath":"Registration form"}', N'Yi9yvlPzvbOuilU0f0pFYuEuxcDy11', +/// 0, 0, 0, null, null, null, null, null, null, null, N'2024-07-05 13:27:01.4033333' +/// ); +/// ``` +pub fn test_legacy_user_key_account() -> TestAccount { + TestAccount { + user: InitUserCryptoRequest { + kdf_params: Kdf::PBKDF2 { + iterations: 600_000.try_into().unwrap(), + }, + email: "legacy@bitwarden.com".to_owned(), + private_key: "2.leBIE5u0aQUeXi++JzAnrA==|P8x+hs00RJx7epw+49qVtBhLJxE/JTL5dEHg6kq5pbZLdUY8ZvWK49v0EqgHbv1r298N9+msoO9hmdSIVIAZyycemYDSoc1rX4S1KpS/ZMA/Vd3VLFb+o13Ts62GFQ5ygHKgQZfzjU6jO5P/B/0igzFoxyJDomhW5NBC1P9+e/5qNRZN8loKvAaWc/7XtpRayPQqWx+AgYc2ntb1GF5hRVrW4M47bG5ZKllbJWtQKg2sXIy2lDBbKLRFWF4RFzNVcXQGMoPdWLY0f3uTwUH01dyGmFFMbOvfBEuYqmZyPdd93ve8zuFOEqkj46Ulpq2CVG8NvZARTwsdKl6XB0wGuHFoTsDJT2SJGl67pBBKsVRGxy059QW+9hAIB+emIV0T/7+0rvdeSXZ4AbG+oXGEXFTkHefwJKfeT0MBTAjYKr7ZRLgqvf7n39+nCEJU4l22kp8FmjcWIU7AgNipdGHC+UT2yfOcYlvgBgWDcMXcbVDMyus9105RgcW6PHozUj7yjbohI/A3XWmAFufP6BSnmEFCKoik78X/ry09xwiH2rN4KVXe/k9LpRNB2QBGIVsfgCrkxjeE8r0nA59Rvwrhny1z5BkvMW/N1KrGuafg/IYgegx72gJNuZPZlFu1Vs7HxySHmzYvm3DPV7bzCaAxxNtvZmQquNIEnsDQfjJO76iL1JCtDqNJVzGLHTMTr7S5hkOcydcH3kfKwZdA1ULVd2qu0SwOUEP/ECjU/cS5INy6WPYzNMAe/g2DISpQjNwBb5K17PIiGOR7/Q/A6E8pVnkHiAXuUFr9aLOYN9BWSu5Z+BPHH65na2FDmssix5WV09I2sUBfvdNCjkrUGdYgo8E+vOTn35x9GJHF45uhmgC1yAn/+/RSpORlrSVJ7NNP11dn3htUpSsIy/b7ituAu8Ry5mhicFU8CXJL4NeMlXThUt8P++wxs4wMkBvJ8J9NJAVKbAOA2o+GOdjbh6Ww3IRegkurWh4oL/dFSx0LpaXJuw6HFT/LzticPlSwHtUP11hZ81seMsXmkSZd8IugRFfwpPl7N6PVRWDOKxLf4gPqcnJ11TvfasXy1uolV2vZCPbrbbVzQMPdVwL/OzwfhqsIgQZI8rsDMK5D2EX8MaT8MDfGcsYcVTL9PmuZYLpOUnnHX0A1opAAa9iPw3d+eWB/GAyLvKPnMTUqVNos8HcCktXckCshihA8QuBJOwg3m0j2LPSZ5Jvf8gbXauBmt9I4IlJq0xfpgquYY1WNnO8IcWE4N9W+ASvOr9gnduA6CkDeAlyMUFmdpkeCjGMcsV741bTCPApSQlL3/TOT1cjK3iejWpz0OaVHXyg02hW2fNkOfYfr81GvnLvlHxIg4Prw89gKuWU+kQk82lFQo6QQpqbCbJC2FleurD8tYoSY0srhuioVInffvTxw2NMF7FQEqUcsK9AMKSEiDqzBi35Um/fiE3JL4XZBFw8Xzl7X3ab5nlg8X+xD5uSZY+oxD3sDVXjLaQ5JUoys+MCm0FkUj85l0zT6rvM4QLhU1RDK1U51T9HJhh8hsFJsqL4abRzwEWG7PSi859zN4UsgyuQfmBJv/n7QAFCbrJhVBlGB1TKLZRzvgmKoxTYTG3cJFkjetLcUTwrwC9naxAQRfF4=|ufHf73IzJ707dx44w4fjkuD7tDa50OwmmkxcypAT9uQ=".to_owned(), + method: InitUserCryptoMethod::Password { + password: "asdfasdfasdf".to_owned(), + user_key: "0.8UClLa8IPE1iZT7chy5wzQ==|6PVfHnVk5S3XqEtQemnM5yb4JodxmPkkWzmDRdfyHtjORmvxqlLX40tBJZ+CKxQWmS8tpEB5w39rbgHg/gqs0haGdZG4cPbywsgGzxZ7uNI=".to_owned(), + } + }, +org: None, + } +} diff --git a/crates/bitwarden-core/src/error.rs b/crates/bitwarden-core/src/error.rs new file mode 100644 index 000000000..697ca6c99 --- /dev/null +++ b/crates/bitwarden-core/src/error.rs @@ -0,0 +1,213 @@ +//! Errors that can occur when using this SDK + +use std::{borrow::Cow, fmt::Debug}; + +use bitwarden_api_api::apis::Error as ApiError; +use bitwarden_api_identity::apis::Error as IdentityError; +use log::debug; +use reqwest::StatusCode; +use thiserror::Error; +use validator::ValidationErrors; + +#[cfg(feature = "internal")] +use crate::client::encryption_settings::EncryptionSettingsError; + +#[derive(Debug, Error)] +pub enum Error { + #[error(transparent)] + MissingFieldError(#[from] MissingFieldError), + #[error(transparent)] + VaultLocked(#[from] VaultLocked), + + #[error("The client is not authenticated or the session has expired")] + NotAuthenticated, + + #[error("Access token is not in a valid format: {0}")] + AccessTokenInvalid(#[from] AccessTokenInvalidError), + + #[error("The response received was invalid and could not be processed")] + InvalidResponse, + + #[error("Cryptography error, {0}")] + Crypto(#[from] bitwarden_crypto::CryptoError), + + #[error("Error parsing Identity response: {0}")] + IdentityFail(crate::auth::api::response::IdentityTokenFailResponse), + + #[error(transparent)] + Reqwest(#[from] reqwest::Error), + #[error(transparent)] + Serde(#[from] serde_json::Error), + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + InvalidBase64(#[from] base64::DecodeError), + #[error(transparent)] + Chrono(#[from] chrono::ParseError), + + #[error("Received error message from server: [{}] {}", .status, .message)] + ResponseContent { status: StatusCode, message: String }, + + #[error(transparent)] + ValidationError(#[from] ValidationError), + + #[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>), + + #[cfg(feature = "internal")] + #[error(transparent)] + EncryptionSettings(#[from] EncryptionSettingsError), +} + +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)] +pub enum AccessTokenInvalidError { + #[error("Doesn't contain a decryption key")] + NoKey, + #[error("Has the wrong number of parts")] + WrongParts, + #[error("Is the wrong version")] + WrongVersion, + #[error("Has an invalid identifier")] + InvalidUuid, + + #[error("Error decoding base64: {0}")] + InvalidBase64(#[from] base64::DecodeError), + + #[error("Invalid base64 length: expected {expected}, got {got}")] + InvalidBase64Length { expected: usize, got: usize }, +} + +// Ensure that the error messages implement Send and Sync +#[cfg(test)] +const _: () = { + fn assert_send() {} + fn assert_sync() {} + fn assert_all() { + assert_send::(); + assert_sync::(); + } +}; + +macro_rules! impl_bitwarden_error { + ($name:ident) => { + impl From<$name> for Error { + fn from(e: $name) -> Self { + match e { + $name::Reqwest(e) => Self::Reqwest(e), + $name::ResponseError(e) => Self::ResponseContent { + status: e.status, + message: e.content, + }, + $name::Serde(e) => Self::Serde(e), + $name::Io(e) => Self::Io(e), + } + } + } + }; +} +impl_bitwarden_error!(ApiError); +impl_bitwarden_error!(IdentityError); + +pub(crate) type Result = std::result::Result; + +#[derive(Debug, Error)] +#[error("The response received was missing a required field: {0}")] +pub struct MissingFieldError(pub &'static str); + +#[derive(Debug, Error)] +#[error("The client vault is locked and needs to be unlocked before use")] +pub struct VaultLocked; + +/// This macro is used to require that a value is present or return an error otherwise. +/// It is equivalent to using `val.ok_or(Error::MissingFields)?`, but easier to use and +/// with a more descriptive error message. +/// Note that this macro will return early from the function if the value is not present. +#[macro_export] +macro_rules! require { + ($val:expr) => { + match $val { + Some(val) => val, + None => return Err($crate::MissingFieldError(stringify!($val)).into()), + } + }; +} + +// Validation +#[derive(Debug, Error)] +pub enum ValidationError { + #[error("{0} must not be empty")] + Required(String), + #[error("{0} must not exceed {1} characters in length")] + ExceedsCharacterLength(String, u64), + #[error("{0} must not contain only whitespaces")] + OnlyWhitespaces(String), +} + +const VALIDATION_LENGTH_CODE: &str = "length"; +const VALIDATION_ONLY_WHITESPACES_CODE: &str = "only_whitespaces"; + +pub fn validate_only_whitespaces(value: &str) -> Result<(), validator::ValidationError> { + if !value.is_empty() && value.trim().is_empty() { + return Err(validator::ValidationError::new( + VALIDATION_ONLY_WHITESPACES_CODE, + )); + } + Ok(()) +} + +impl From for Error { + fn from(e: ValidationErrors) -> Self { + debug!("Validation errors: {:#?}", e); + for (field_name, errors) in e.field_errors() { + for error in errors { + match error.code.as_ref() { + VALIDATION_LENGTH_CODE => { + if error.params.contains_key("min") + && error.params["min"].as_u64().expect("Min provided") == 1 + && error.params["value"] + .as_str() + .expect("Value provided") + .is_empty() + { + return Error::ValidationError(ValidationError::Required( + field_name.to_string(), + )); + } else if error.params.contains_key("max") { + return Error::ValidationError( + ValidationError::ExceedsCharacterLength( + field_name.to_string(), + error.params["max"].as_u64().expect("Max provided"), + ), + ); + } + } + VALIDATION_ONLY_WHITESPACES_CODE => { + return Error::ValidationError(ValidationError::OnlyWhitespaces( + field_name.to_string(), + )); + } + _ => {} + } + } + } + format!("Unknown validation error: {:#?}", e).into() + } +} diff --git a/crates/bitwarden-core/src/lib.rs b/crates/bitwarden-core/src/lib.rs new file mode 100644 index 000000000..12b0df3c3 --- /dev/null +++ b/crates/bitwarden-core/src/lib.rs @@ -0,0 +1,21 @@ +#[cfg(feature = "uniffi")] +uniffi::setup_scaffolding!(); +#[cfg(feature = "uniffi")] +mod uniffi_support; + +#[cfg(feature = "internal")] +pub mod admin_console; +pub mod auth; +pub mod client; +mod error; +pub use error::{validate_only_whitespaces, Error, MissingFieldError, VaultLocked}; +#[cfg(feature = "internal")] +pub mod mobile; +#[cfg(feature = "internal")] +pub mod platform; +#[cfg(feature = "secrets")] +pub mod secrets_manager; +mod util; + +pub use bitwarden_crypto::ZeroizingAllocator; +pub use client::{Client, ClientSettings, DeviceType}; diff --git a/crates/bitwarden-core/src/mobile/client_crypto.rs b/crates/bitwarden-core/src/mobile/client_crypto.rs new file mode 100644 index 000000000..0175f2712 --- /dev/null +++ b/crates/bitwarden-core/src/mobile/client_crypto.rs @@ -0,0 +1,65 @@ +#[cfg(feature = "internal")] +use bitwarden_crypto::{AsymmetricEncString, EncString}; + +use super::crypto::{derive_key_connector, DeriveKeyConnectorRequest}; +use crate::{client::encryption_settings::EncryptionSettingsError, Client}; +#[cfg(feature = "internal")] +use crate::{ + error::Result, + mobile::crypto::{ + derive_pin_key, derive_pin_user_key, enroll_admin_password_reset, get_user_encryption_key, + initialize_org_crypto, initialize_user_crypto, update_password, DerivePinKeyResponse, + InitOrgCryptoRequest, InitUserCryptoRequest, UpdatePasswordResponse, + }, +}; + +pub struct ClientCrypto<'a> { + pub(crate) client: &'a crate::Client, +} + +impl<'a> ClientCrypto<'a> { + pub async fn initialize_user_crypto( + &self, + req: InitUserCryptoRequest, + ) -> Result<(), EncryptionSettingsError> { + initialize_user_crypto(self.client, req).await + } + + pub async fn initialize_org_crypto( + &self, + req: InitOrgCryptoRequest, + ) -> Result<(), EncryptionSettingsError> { + initialize_org_crypto(self.client, req).await + } + + pub async fn get_user_encryption_key(&self) -> Result { + get_user_encryption_key(self.client).await + } + + pub fn update_password(&self, new_password: String) -> Result { + update_password(self.client, new_password) + } + + pub fn derive_pin_key(&self, pin: String) -> Result { + derive_pin_key(self.client, pin) + } + + pub fn derive_pin_user_key(&self, encrypted_pin: EncString) -> Result { + derive_pin_user_key(self.client, encrypted_pin) + } + + pub fn enroll_admin_password_reset(&self, public_key: String) -> Result { + enroll_admin_password_reset(self.client, public_key) + } + + /// Derive the master key for migrating to the key connector + pub fn derive_key_connector(&self, request: DeriveKeyConnectorRequest) -> Result { + derive_key_connector(request) + } +} + +impl<'a> Client { + pub fn crypto(&'a self) -> ClientCrypto<'a> { + ClientCrypto { client: self } + } +} diff --git a/crates/bitwarden/src/mobile/client_kdf.rs b/crates/bitwarden-core/src/mobile/client_kdf.rs similarity index 53% rename from crates/bitwarden/src/mobile/client_kdf.rs rename to crates/bitwarden-core/src/mobile/client_kdf.rs index 4e62e5d59..8cbe38eab 100644 --- a/crates/bitwarden/src/mobile/client_kdf.rs +++ b/crates/bitwarden-core/src/mobile/client_kdf.rs @@ -1,9 +1,9 @@ -use bitwarden_crypto::HashPurpose; +use bitwarden_crypto::{HashPurpose, Kdf}; -use crate::{client::Kdf, error::Result, mobile::kdf::hash_password, Client}; +use crate::{error::Result, mobile::kdf::hash_password, Client}; pub struct ClientKdf<'a> { - pub(crate) client: &'a crate::Client, + pub(crate) _client: &'a crate::Client, } impl<'a> ClientKdf<'a> { @@ -14,12 +14,12 @@ impl<'a> ClientKdf<'a> { kdf_params: Kdf, purpose: HashPurpose, ) -> Result { - hash_password(self.client, email, password, kdf_params, purpose).await + hash_password(email, password, kdf_params, purpose).await } } impl<'a> Client { pub fn kdf(&'a self) -> ClientKdf<'a> { - ClientKdf { client: self } + ClientKdf { _client: self } } } diff --git a/crates/bitwarden/src/mobile/crypto.rs b/crates/bitwarden-core/src/mobile/crypto.rs similarity index 74% rename from crates/bitwarden/src/mobile/crypto.rs rename to crates/bitwarden-core/src/mobile/crypto.rs index ad48656a6..1b027df43 100644 --- a/crates/bitwarden/src/mobile/crypto.rs +++ b/crates/bitwarden-core/src/mobile/crypto.rs @@ -1,23 +1,24 @@ use std::collections::HashMap; -use bitwarden_crypto::{AsymmetricEncString, EncString}; -#[cfg(feature = "internal")] -use bitwarden_crypto::{KeyDecryptable, KeyEncryptable, MasterKey, SymmetricCryptoKey}; +use bitwarden_crypto::{ + AsymmetricEncString, EncString, Kdf, KeyDecryptable, KeyEncryptable, MasterKey, + SymmetricCryptoKey, +}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +#[cfg(feature = "wasm")] +use {tsify_next::Tsify, wasm_bindgen::prelude::*}; -#[cfg(feature = "internal")] -use crate::client::{LoginMethod, UserLoginMethod}; use crate::{ - client::Kdf, + client::{encryption_settings::EncryptionSettingsError, LoginMethod, UserLoginMethod}, error::{Error, Result}, Client, }; -#[cfg(feature = "internal")] #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] pub struct InitUserCryptoRequest { /// The user's KDF parameters, as received from the prelogin request pub kdf_params: Kdf, @@ -29,10 +30,10 @@ pub struct InitUserCryptoRequest { pub method: InitUserCryptoMethod, } -#[cfg(feature = "internal")] #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] +#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] pub enum InitUserCryptoMethod { Password { /// The user's master password @@ -65,12 +66,18 @@ pub enum InitUserCryptoMethod { /// The user's symmetric crypto key, encrypted with the Device Key. device_protected_user_key: AsymmetricEncString, }, + KeyConnector { + /// Base64 encoded master key, retrieved from the key connector. + master_key: String, + /// The user's encrypted symmetric crypto key + user_key: String, + }, } -#[cfg(feature = "internal")] #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] +#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] pub enum AuthRequestMethod { UserKey { /// User Key protected by the private key provided in `AuthRequestResponse`. @@ -84,9 +91,11 @@ pub enum AuthRequestMethod { }, } -#[cfg(feature = "internal")] -pub async fn initialize_user_crypto(client: &mut Client, req: InitUserCryptoRequest) -> Result<()> { - use bitwarden_crypto::DeviceKey; +pub async fn initialize_user_crypto( + client: &Client, + req: InitUserCryptoRequest, +) -> Result<(), EncryptionSettingsError> { + use bitwarden_crypto::{DeviceKey, PinKey}; use crate::auth::{auth_request_decrypt_master_key, auth_request_decrypt_user_key}; @@ -96,20 +105,27 @@ pub async fn initialize_user_crypto(client: &mut Client, req: InitUserCryptoRequ InitUserCryptoMethod::Password { password, user_key } => { let user_key: EncString = user_key.parse()?; - let master_key = - MasterKey::derive(password.as_bytes(), req.email.as_bytes(), &req.kdf_params)?; - client.initialize_user_crypto_master_key(master_key, user_key, private_key)?; + let master_key = MasterKey::derive(&password, &req.email, &req.kdf_params)?; + client + .internal + .initialize_user_crypto_master_key(master_key, user_key, private_key)?; } InitUserCryptoMethod::DecryptedKey { decrypted_user_key } => { let user_key = SymmetricCryptoKey::try_from(decrypted_user_key)?; - client.initialize_user_crypto_decrypted_key(user_key, private_key)?; + client + .internal + .initialize_user_crypto_decrypted_key(user_key, private_key)?; } InitUserCryptoMethod::Pin { pin, pin_protected_user_key, } => { - let pin_key = MasterKey::derive(pin.as_bytes(), req.email.as_bytes(), &req.kdf_params)?; - client.initialize_user_crypto_pin(pin_key, pin_protected_user_key, private_key)?; + let pin_key = PinKey::derive(pin.as_bytes(), req.email.as_bytes(), &req.kdf_params)?; + client.internal.initialize_user_crypto_pin( + pin_key, + pin_protected_user_key, + private_key, + )?; } InitUserCryptoMethod::AuthRequest { request_private_key, @@ -128,7 +144,9 @@ pub async fn initialize_user_crypto(client: &mut Client, req: InitUserCryptoRequ auth_request_key, )?, }; - client.initialize_user_crypto_decrypted_key(user_key, private_key)?; + client + .internal + .initialize_user_crypto_decrypted_key(user_key, private_key)?; } InitUserCryptoMethod::DeviceKey { device_key, @@ -139,48 +157,61 @@ pub async fn initialize_user_crypto(client: &mut Client, req: InitUserCryptoRequ let user_key = device_key .decrypt_user_key(protected_device_private_key, device_protected_user_key)?; - client.initialize_user_crypto_decrypted_key(user_key, private_key)?; + client + .internal + .initialize_user_crypto_decrypted_key(user_key, private_key)?; + } + InitUserCryptoMethod::KeyConnector { + master_key, + user_key, + } => { + let master_key = MasterKey::new(SymmetricCryptoKey::try_from(master_key)?); + let user_key: EncString = user_key.parse()?; + + client + .internal + .initialize_user_crypto_master_key(master_key, user_key, private_key)?; } } - client.set_login_method(crate::client::LoginMethod::User( - crate::client::UserLoginMethod::Username { - client_id: "".to_string(), - email: req.email, - kdf: req.kdf_params, - }, - )); + client + .internal + .set_login_method(crate::client::LoginMethod::User( + crate::client::UserLoginMethod::Username { + client_id: "".to_string(), + email: req.email, + kdf: req.kdf_params, + }, + )); Ok(()) } -#[cfg(feature = "internal")] #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] 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<()> { +pub async fn initialize_org_crypto( + client: &Client, + req: InitOrgCryptoRequest, +) -> Result<(), EncryptionSettingsError> { let organization_keys = req.organization_keys.into_iter().collect(); - client.initialize_org_crypto(organization_keys)?; + client.internal.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)?; +pub async fn get_user_encryption_key(client: &Client) -> Result { + let enc = client.internal.get_encryption_settings()?; + let user_key = enc.get_key(&None)?; Ok(user_key.to_base64()) } -#[cfg(feature = "internal")] #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] @@ -191,27 +222,23 @@ pub struct UpdatePasswordResponse { new_key: EncString, } -pub fn update_password( - client: &mut Client, - new_password: String, -) -> Result { - let user_key = client - .get_encryption_settings()? - .get_key(&None) - .ok_or(Error::VaultLocked)?; +pub fn update_password(client: &Client, new_password: String) -> Result { + let enc = client.internal.get_encryption_settings()?; + let user_key = enc.get_key(&None)?; let login_method = client - .login_method - .as_ref() + .internal + .get_login_method() .ok_or(Error::NotAuthenticated)?; // Derive a new master key from password - let new_master_key = match login_method { + let new_master_key = match login_method.as_ref() { LoginMethod::User( UserLoginMethod::Username { email, kdf, .. } | UserLoginMethod::ApiKey { email, kdf, .. }, - ) => MasterKey::derive(new_password.as_bytes(), email.as_bytes(), kdf)?, - _ => return Err(Error::NotAuthenticated), + ) => MasterKey::derive(&new_password, email, kdf)?, + #[cfg(feature = "secrets")] + LoginMethod::ServiceAccount(_) => return Err(Error::NotAuthenticated), }; let new_key = new_master_key.encrypt_user_key(user_key)?; @@ -227,7 +254,6 @@ pub fn update_password( }) } -#[cfg(feature = "internal")] #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] @@ -238,19 +264,16 @@ pub struct DerivePinKeyResponse { encrypted_pin: EncString, } -#[cfg(feature = "internal")] -pub fn derive_pin_key(client: &mut Client, pin: String) -> Result { - let user_key = client - .get_encryption_settings()? - .get_key(&None) - .ok_or(Error::VaultLocked)?; +pub fn derive_pin_key(client: &Client, pin: String) -> Result { + let enc = client.internal.get_encryption_settings()?; + let user_key = enc.get_key(&None)?; let login_method = client - .login_method - .as_ref() + .internal + .get_login_method() .ok_or(Error::NotAuthenticated)?; - let pin_protected_user_key = derive_pin_protected_user_key(&pin, login_method, user_key)?; + let pin_protected_user_key = derive_pin_protected_user_key(&pin, &login_method, user_key)?; Ok(DerivePinKeyResponse { pin_protected_user_key, @@ -258,50 +281,48 @@ pub fn derive_pin_key(client: &mut Client, pin: String) -> Result Result { - let user_key = client - .get_encryption_settings()? - .get_key(&None) - .ok_or(Error::VaultLocked)?; +pub fn derive_pin_user_key(client: &Client, encrypted_pin: EncString) -> Result { + let enc = client.internal.get_encryption_settings()?; + let user_key = enc.get_key(&None)?; let pin: String = encrypted_pin.decrypt_with_key(user_key)?; let login_method = client - .login_method - .as_ref() + .internal + .get_login_method() .ok_or(Error::NotAuthenticated)?; - derive_pin_protected_user_key(&pin, login_method, user_key) + derive_pin_protected_user_key(&pin, &login_method, user_key) } -#[cfg(feature = "internal")] fn derive_pin_protected_user_key( pin: &str, login_method: &LoginMethod, user_key: &SymmetricCryptoKey, ) -> Result { + use bitwarden_crypto::PinKey; + let derived_key = match login_method { LoginMethod::User( UserLoginMethod::Username { email, kdf, .. } | UserLoginMethod::ApiKey { email, kdf, .. }, - ) => MasterKey::derive(pin.as_bytes(), email.as_bytes(), kdf)?, - _ => return Err(Error::NotAuthenticated), + ) => PinKey::derive(pin.as_bytes(), email.as_bytes(), kdf)?, + #[cfg(feature = "secrets")] + LoginMethod::ServiceAccount(_) => return Err(Error::NotAuthenticated), }; Ok(derived_key.encrypt_user_key(user_key)?) } -#[cfg(feature = "internal")] pub(super) fn enroll_admin_password_reset( - client: &mut Client, + client: &Client, public_key: String, ) -> Result { use base64::{engine::general_purpose::STANDARD, Engine}; use bitwarden_crypto::AsymmetricPublicCryptoKey; let public_key = AsymmetricPublicCryptoKey::from_der(&STANDARD.decode(public_key)?)?; - let enc = client.get_encryption_settings()?; - let key = enc.get_key(&None).ok_or(Error::VaultLocked)?; + let enc = client.internal.get_encryption_settings()?; + let key = enc.get_key(&None)?; Ok(AsymmetricEncString::encrypt_rsa2048_oaep_sha1( &key.to_vec(), @@ -309,14 +330,36 @@ pub(super) fn enroll_admin_password_reset( )?) } +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +pub struct DeriveKeyConnectorRequest { + /// Encrypted user key, used to validate the master key + pub user_key_encrypted: EncString, + + pub password: String, + pub kdf: Kdf, + pub email: String, +} + +/// Derive the master key for migrating to the key connector +pub(super) fn derive_key_connector(request: DeriveKeyConnectorRequest) -> Result { + let master_key = MasterKey::derive(&request.password, &request.email, &request.kdf)?; + master_key + .decrypt_user_key(request.user_key_encrypted) + .map_err(|_| "wrong password")?; + + Ok(master_key.to_base64()) +} + #[cfg(test)] mod tests { + use std::num::NonZeroU32; + use super::*; - use crate::{client::Kdf, Client}; + use crate::Client; #[tokio::test] async fn test_update_password() { - let mut client = Client::new(None); + let 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="; @@ -325,7 +368,7 @@ mod tests { }; initialize_user_crypto( - &mut client, + & client, InitUserCryptoRequest { kdf_params: kdf.clone(), email: "test@bitwarden.com".into(), @@ -339,12 +382,12 @@ mod tests { .await .unwrap(); - let new_password_response = update_password(&mut client, "123412341234".into()).unwrap(); + let new_password_response = update_password(&client, "123412341234".into()).unwrap(); - let mut client2 = Client::new(None); + let client2 = Client::new(None); initialize_user_crypto( - &mut client2, + &client2, InitUserCryptoRequest { kdf_params: kdf.clone(), email: "test@bitwarden.com".into(), @@ -373,12 +416,14 @@ mod tests { assert_eq!( client + .internal .get_encryption_settings() .unwrap() .get_key(&None) .unwrap() .to_base64(), client2 + .internal .get_encryption_settings() .unwrap() .get_key(&None) @@ -389,12 +434,12 @@ mod tests { #[tokio::test] async fn test_initialize_user_crypto_pin() { - let mut client = Client::new(None); + let 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, + & client, InitUserCryptoRequest { kdf_params: Kdf::PBKDF2 { iterations: 100_000.try_into().unwrap(), @@ -410,12 +455,12 @@ mod tests { .await .unwrap(); - let pin_key = derive_pin_key(&mut client, "1234".into()).unwrap(); + let pin_key = derive_pin_key(&client, "1234".into()).unwrap(); // Verify we can unlock with the pin - let mut client2 = Client::new(None); + let client2 = Client::new(None); initialize_user_crypto( - &mut client2, + &client2, InitUserCryptoRequest { kdf_params: Kdf::PBKDF2 { iterations: 100_000.try_into().unwrap(), @@ -433,12 +478,14 @@ mod tests { assert_eq!( client + .internal .get_encryption_settings() .unwrap() .get_key(&None) .unwrap() .to_base64(), client2 + .internal .get_encryption_settings() .unwrap() .get_key(&None) @@ -447,13 +494,12 @@ mod tests { ); // Verify we can derive the pin protected user key from the encrypted pin - let pin_protected_user_key = - derive_pin_user_key(&mut client, pin_key.encrypted_pin).unwrap(); + let pin_protected_user_key = derive_pin_user_key(&client, pin_key.encrypted_pin).unwrap(); - let mut client3 = Client::new(None); + let client3 = Client::new(None); initialize_user_crypto( - &mut client3, + &client3, InitUserCryptoRequest { kdf_params: Kdf::PBKDF2 { iterations: 100_000.try_into().unwrap(), @@ -471,12 +517,14 @@ mod tests { assert_eq!( client + .internal .get_encryption_settings() .unwrap() .get_key(&None) .unwrap() .to_base64(), client3 + .internal .get_encryption_settings() .unwrap() .get_key(&None) @@ -485,19 +533,16 @@ mod tests { ); } - #[cfg(feature = "internal")] #[test] fn test_enroll_admin_password_reset() { - use std::num::NonZeroU32; - use base64::{engine::general_purpose::STANDARD, Engine}; use bitwarden_crypto::AsymmetricCryptoKey; - let mut client = Client::new(None); + let client = Client::new(None); - let master_key = bitwarden_crypto::MasterKey::derive( - "asdfasdfasdf".as_bytes(), - "test@bitwarden.com".as_bytes(), + let master_key = MasterKey::derive( + "asdfasdfasdf", + "test@bitwarden.com", &Kdf::PBKDF2 { iterations: NonZeroU32::new(600_000).unwrap(), }, @@ -507,23 +552,37 @@ mod tests { let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(); let private_key ="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(); client + .internal .initialize_user_crypto_master_key(master_key, user_key, private_key) .unwrap(); let public_key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsy7RFHcX3C8Q4/OMmhhbFReYWfB45W9PDTEA8tUZwZmtOiN2RErIS2M1c+K/4HoDJ/TjpbX1f2MZcr4nWvKFuqnZXyewFc+jmvKVewYi+NAu2++vqKq2kKcmMNhwoQDQdQIVy/Uqlp4Cpi2cIwO6ogq5nHNJGR3jm+CpyrafYlbz1bPvL3hbyoGDuG2tgADhyhXUdFuef2oF3wMvn1lAJAvJnPYpMiXUFmj1ejmbwtlxZDrHgUJvUcp7nYdwUKaFoi+sOttHn3u7eZPtNvxMjhSS/X/1xBIzP/mKNLdywH5LoRxniokUk+fV3PYUxJsiU3lV0Trc/tH46jqd8ZGjmwIDAQAB"; - let encrypted = enroll_admin_password_reset(&mut client, public_key.to_owned()).unwrap(); + let encrypted = enroll_admin_password_reset(&client, public_key.to_owned()).unwrap(); let private_key = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCzLtEUdxfcLxDj84yaGFsVF5hZ8Hjlb08NMQDy1RnBma06I3ZESshLYzVz4r/gegMn9OOltfV/Yxlyvida8oW6qdlfJ7AVz6Oa8pV7BiL40C7b76+oqraQpyYw2HChANB1AhXL9SqWngKmLZwjA7qiCrmcc0kZHeOb4KnKtp9iVvPVs+8veFvKgYO4ba2AAOHKFdR0W55/agXfAy+fWUAkC8mc9ikyJdQWaPV6OZvC2XFkOseBQm9Rynudh3BQpoWiL6w620efe7t5k+02/EyOFJL9f/XEEjM/+Yo0t3LAfkuhHGeKiRST59Xc9hTEmyJTeVXROtz+0fjqOp3xkaObAgMBAAECggEACs4xhnO0HaZhh1/iH7zORMIRXKeyxP2LQiTR8xwN5JJ9wRWmGAR9VasS7EZFTDidIGVME2u/h4s5EqXnhxfO+0gGksVvgNXJ/qw87E8K2216g6ZNo6vSGA7H1GH2voWwejJ4/k/cJug6dz2S402rRAKh2Wong1arYHSkVlQp3diiMa5FHAOSE+Cy09O2ZsaF9IXQYUtlW6AVXFrBEPYH2kvkaPXchh8VETMijo6tbvoKLnUHe+wTaDMls7hy8exjtVyI59r3DNzjy1lNGaGb5QSnFMXR+eHhPZc844Wv02MxC15zKABADrl58gpJyjTl6XpDdHCYGsmGpVGH3X9TQQKBgQDz/9beFjzq59ve6rGwn+EtnQfSsyYT+jr7GN8lNEXb3YOFXBgPhfFIcHRh2R00Vm9w2ApfAx2cd8xm2I6HuvQ1Os7g26LWazvuWY0Qzb+KaCLQTEGH1RnTq6CCG+BTRq/a3J8M4t38GV5TWlzv8wr9U4dl6FR4efjb65HXs1GQ4QKBgQC7/uHfrOTEHrLeIeqEuSl0vWNqEotFKdKLV6xpOvNuxDGbgW4/r/zaxDqt0YBOXmRbQYSEhmO3oy9J6XfE1SUln0gbavZeW0HESCAmUIC88bDnspUwS9RxauqT5aF8ODKN/bNCWCnBM1xyonPOs1oT1nyparJVdQoG//Y7vkB3+wKBgBqLqPq8fKAp3XfhHLfUjREDVoiLyQa/YI9U42IOz9LdxKNLo6p8rgVthpvmnRDGnpUuS+KOWjhdqDVANjF6G3t3DG7WNl8Rh5Gk2H4NhFswfSkgQrjebFLlBy9gjQVCWXt8KSmjvPbiY6q52Aaa8IUjA0YJAregvXxfopxO+/7BAoGARicvEtDp7WWnSc1OPoj6N14VIxgYcI7SyrzE0d/1x3ffKzB5e7qomNpxKzvqrVP8DzG7ydh8jaKPmv1MfF8tpYRy3AhmN3/GYwCnPqT75YYrhcrWcVdax5gmQVqHkFtIQkRSCIftzPLlpMGKha/YBV8c1fvC4LD0NPh/Ynv0gtECgYEAyOZg95/kte0jpgUEgwuMrzkhY/AaUJULFuR5MkyvReEbtSBQwV5tx60+T95PHNiFooWWVXiLMsAgyI2IbkxVR1Pzdri3gWK5CTfqb7kLuaj/B7SGvBa2Sxo478KS5K8tBBBWkITqo+wLC0mn3uZi1dyMWO1zopTA+KtEGF2dtGQ="; let private_key = AsymmetricCryptoKey::from_der(&STANDARD.decode(private_key).unwrap()).unwrap(); let decrypted: Vec = encrypted.decrypt_with_key(&private_key).unwrap(); - let expected = client - .get_encryption_settings() - .unwrap() - .get_key(&None) - .unwrap(); + let enc = client.internal.get_encryption_settings().unwrap(); + let expected = enc.get_key(&None).unwrap(); assert_eq!(&decrypted, &expected.to_vec()); } + + #[test] + fn test_derive_key_connector() { + let request = DeriveKeyConnectorRequest { + password: "asdfasdfasdf".to_string(), + email: "test@bitwarden.com".to_string(), + kdf: Kdf::PBKDF2 { + iterations: NonZeroU32::new(600_000).unwrap(), + }, + user_key_encrypted: "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(), + }; + + let result = derive_key_connector(request).unwrap(); + + assert_eq!(result, "ySXq1RVLKEaV1eoQE/ui9aFKIvXTl9PAXwp1MljfF50="); + } } diff --git a/crates/bitwarden/src/mobile/kdf.rs b/crates/bitwarden-core/src/mobile/kdf.rs similarity index 64% rename from crates/bitwarden/src/mobile/kdf.rs rename to crates/bitwarden-core/src/mobile/kdf.rs index 1c1972086..e52328ed8 100644 --- a/crates/bitwarden/src/mobile/kdf.rs +++ b/crates/bitwarden-core/src/mobile/kdf.rs @@ -1,15 +1,14 @@ use bitwarden_crypto::{HashPurpose, Kdf, MasterKey}; -use crate::{error::Result, Client}; +use crate::error::Result; pub async fn hash_password( - _client: &Client, email: String, password: String, kdf_params: Kdf, purpose: HashPurpose, ) -> Result { - let master_key = MasterKey::derive(password.as_bytes(), email.as_bytes(), &kdf_params)?; + let master_key = MasterKey::derive(&password, &email, &kdf_params)?; Ok(master_key.derive_master_key_hash(password.as_bytes(), purpose)?) } diff --git a/crates/bitwarden/src/mobile/mod.rs b/crates/bitwarden-core/src/mobile/mod.rs similarity index 82% rename from crates/bitwarden/src/mobile/mod.rs rename to crates/bitwarden-core/src/mobile/mod.rs index 9425116e5..7c6a916a8 100644 --- a/crates/bitwarden/src/mobile/mod.rs +++ b/crates/bitwarden-core/src/mobile/mod.rs @@ -1,7 +1,5 @@ pub mod crypto; pub mod kdf; -pub mod tool; -pub mod vault; mod client_crypto; mod client_kdf; diff --git a/crates/bitwarden/src/platform/client_platform.rs b/crates/bitwarden-core/src/platform/client_platform.rs similarity index 74% rename from crates/bitwarden/src/platform/client_platform.rs rename to crates/bitwarden-core/src/platform/client_platform.rs index 733a86e72..1f117d5fe 100644 --- a/crates/bitwarden/src/platform/client_platform.rs +++ b/crates/bitwarden-core/src/platform/client_platform.rs @@ -1,5 +1,3 @@ -#[cfg(feature = "uniffi")] -use super::ClientFido2; use super::{ generate_fingerprint::{generate_fingerprint, generate_user_fingerprint}, get_user_api_key, FingerprintRequest, FingerprintResponse, SecretVerificationRequest, @@ -8,7 +6,7 @@ use super::{ use crate::{error::Result, Client}; pub struct ClientPlatform<'a> { - pub(crate) client: &'a mut Client, + pub(crate) client: &'a Client, } impl<'a> ClientPlatform<'a> { @@ -26,17 +24,10 @@ impl<'a> ClientPlatform<'a> { ) -> Result { get_user_api_key(self.client, &input).await } - - #[cfg(feature = "uniffi")] - pub fn fido2(&'a mut self) -> ClientFido2<'a> { - ClientFido2 { - client: self.client, - } - } } impl<'a> Client { - pub fn platform(&'a mut self) -> ClientPlatform<'a> { + pub fn platform(&'a self) -> ClientPlatform<'a> { ClientPlatform { client: self } } } diff --git a/crates/bitwarden/src/platform/generate_fingerprint.rs b/crates/bitwarden-core/src/platform/generate_fingerprint.rs similarity index 90% rename from crates/bitwarden/src/platform/generate_fingerprint.rs rename to crates/bitwarden-core/src/platform/generate_fingerprint.rs index d62eb5436..5d31a1af1 100644 --- a/crates/bitwarden/src/platform/generate_fingerprint.rs +++ b/crates/bitwarden-core/src/platform/generate_fingerprint.rs @@ -33,12 +33,12 @@ pub(crate) fn generate_fingerprint(input: &FingerprintRequest) -> Result Result { info!("Generating fingerprint"); - let enc_settings = client.get_encryption_settings()?; + let enc_settings = client.internal.get_encryption_settings()?; let private_key = enc_settings .private_key .as_ref() @@ -54,8 +54,10 @@ pub(crate) fn generate_user_fingerprint( mod tests { use std::num::NonZeroU32; + use bitwarden_crypto::{Kdf, MasterKey}; + use super::*; - use crate::{client::Kdf, Client}; + use crate::Client; #[test] fn test_generate_user_fingerprint() { @@ -63,11 +65,11 @@ mod tests { 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); + let client = Client::new(None); - let master_key = bitwarden_crypto::MasterKey::derive( - "asdfasdfasdf".as_bytes(), - "robb@stark.com".as_bytes(), + let master_key = MasterKey::derive( + "asdfasdfasdf", + "robb@stark.com", &Kdf::PBKDF2 { iterations: NonZeroU32::new(600_000).unwrap(), }, @@ -75,6 +77,7 @@ mod tests { .unwrap(); client + .internal .initialize_user_crypto_master_key( master_key, user_key.parse().unwrap(), @@ -83,7 +86,7 @@ mod tests { .unwrap(); let fingerprint = - generate_user_fingerprint(&mut client, fingerprint_material.to_string()).unwrap(); + generate_user_fingerprint(&client, fingerprint_material.to_string()).unwrap(); assert_eq!(fingerprint, "turban-deftly-anime-chatroom-unselfish"); } diff --git a/crates/bitwarden/src/platform/get_user_api_key.rs b/crates/bitwarden-core/src/platform/get_user_api_key.rs similarity index 83% rename from crates/bitwarden/src/platform/get_user_api_key.rs rename to crates/bitwarden-core/src/platform/get_user_api_key.rs index 3e408d926..379707edd 100644 --- a/crates/bitwarden/src/platform/get_user_api_key.rs +++ b/crates/bitwarden-core/src/platform/get_user_api_key.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use bitwarden_api_api::{ apis::accounts_api::accounts_api_key_post, models::{ApiKeyResponseModel, SecretVerificationRequestModel}, @@ -10,31 +12,31 @@ use serde::{Deserialize, Serialize}; use super::SecretVerificationRequest; use crate::{ client::{LoginMethod, UserLoginMethod}, - error::{require, Error, Result}, - Client, + error::{Error, Result}, + require, Client, }; pub(crate) async fn get_user_api_key( - client: &mut Client, + client: &Client, input: &SecretVerificationRequest, ) -> Result { info!("Getting Api Key"); debug!("{:?}", input); let auth_settings = get_login_method(client)?; - let request = get_secret_verification_request(auth_settings, input)?; + let request = get_secret_verification_request(&auth_settings, input)?; - let config = client.get_api_configurations().await; + let config = client.internal.get_api_configurations().await; let response = accounts_api_key_post(&config.api, Some(request)).await?; UserApiKeyResponse::process_response(response) } -fn get_login_method(client: &Client) -> Result<&LoginMethod> { - if client.is_authed() { +fn get_login_method(client: &Client) -> Result> { + if client.internal.is_authed() { client + .internal .get_login_method() - .as_ref() .ok_or(Error::NotAuthenticated) } else { Err(Error::NotAuthenticated) @@ -50,7 +52,7 @@ fn get_secret_verification_request( .master_password .as_ref() .map(|p| { - let master_key = MasterKey::derive(p.as_bytes(), email.as_bytes(), kdf)?; + let master_key = MasterKey::derive(p, email, kdf)?; master_key.derive_master_key_hash(p.as_bytes(), HashPurpose::ServerAuthorization) }) diff --git a/crates/bitwarden/src/platform/mod.rs b/crates/bitwarden-core/src/platform/mod.rs similarity index 72% rename from crates/bitwarden/src/platform/mod.rs rename to crates/bitwarden-core/src/platform/mod.rs index b905d2356..031554be0 100644 --- a/crates/bitwarden/src/platform/mod.rs +++ b/crates/bitwarden-core/src/platform/mod.rs @@ -1,12 +1,8 @@ pub mod client_platform; -#[cfg(feature = "uniffi")] -pub mod fido2; mod generate_fingerprint; mod get_user_api_key; mod secret_verification_request; -#[cfg(feature = "uniffi")] -pub use fido2::{ClientFido2, Fido2Authenticator, Fido2Client}; 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/crates/bitwarden/src/platform/secret_verification_request.rs b/crates/bitwarden-core/src/platform/secret_verification_request.rs similarity index 100% rename from crates/bitwarden/src/platform/secret_verification_request.rs rename to crates/bitwarden-core/src/platform/secret_verification_request.rs diff --git a/crates/bitwarden-core/src/secrets_manager/mod.rs b/crates/bitwarden-core/src/secrets_manager/mod.rs new file mode 100644 index 000000000..266c62acc --- /dev/null +++ b/crates/bitwarden-core/src/secrets_manager/mod.rs @@ -0,0 +1 @@ +pub mod state; diff --git a/crates/bitwarden/src/secrets_manager/state.rs b/crates/bitwarden-core/src/secrets_manager/state.rs similarity index 100% rename from crates/bitwarden/src/secrets_manager/state.rs rename to crates/bitwarden-core/src/secrets_manager/state.rs diff --git a/crates/bitwarden/src/uniffi_support.rs b/crates/bitwarden-core/src/uniffi_support.rs similarity index 82% rename from crates/bitwarden/src/uniffi_support.rs rename to crates/bitwarden-core/src/uniffi_support.rs index b23a9cbef..fcdd2aa78 100644 --- a/crates/bitwarden/src/uniffi_support.rs +++ b/crates/bitwarden-core/src/uniffi_support.rs @@ -41,3 +41,12 @@ impl UniffiCustomTypeConverter for Uuid { obj.to_string() } } + +// Uniffi doesn't emit unused types, this is a dummy record to ensure that the custom type +// converters are emitted +#[allow(dead_code)] +#[derive(uniffi::Record)] +struct UniffiConverterDummyRecord { + uuid: Uuid, + date: DateTime, +} diff --git a/crates/bitwarden/src/util.rs b/crates/bitwarden-core/src/util.rs similarity index 85% rename from crates/bitwarden/src/util.rs rename to crates/bitwarden-core/src/util.rs index aaf47a1a6..59ef930d5 100644 --- a/crates/bitwarden/src/util.rs +++ b/crates/bitwarden-core/src/util.rs @@ -17,11 +17,11 @@ pub async fn start_mock(mocks: Vec) -> (wiremock::MockServer, cr server.register(mock).await; } - let settings = crate::client::client_settings::ClientSettings { + let settings = crate::ClientSettings { identity_url: format!("http://{}/identity", server.address()), api_url: format!("http://{}/api", server.address()), user_agent: "Bitwarden Rust-SDK [TEST]".into(), - device_type: crate::client::client_settings::DeviceType::SDK, + device_type: crate::DeviceType::SDK, }; (server, crate::Client::new(Some(settings))) diff --git a/crates/bitwarden/tests/register.rs b/crates/bitwarden-core/tests/register.rs similarity index 94% rename from crates/bitwarden/tests/register.rs rename to crates/bitwarden-core/tests/register.rs index 7a0c964fd..34f61ca3c 100644 --- a/crates/bitwarden/tests/register.rs +++ b/crates/bitwarden-core/tests/register.rs @@ -4,13 +4,13 @@ async fn test_register_initialize_crypto() { use std::num::NonZeroU32; - use bitwarden::{ + use bitwarden_core::{ mobile::crypto::{InitUserCryptoMethod, InitUserCryptoRequest}, Client, }; use bitwarden_crypto::Kdf; - let mut client = Client::new(None); + let client = Client::new(None); let email = "test@bitwarden.com"; let password = "test123"; diff --git a/crates/bitwarden/uniffi.toml b/crates/bitwarden-core/uniffi.toml similarity index 100% rename from crates/bitwarden/uniffi.toml rename to crates/bitwarden-core/uniffi.toml diff --git a/crates/bitwarden-crypto/Cargo.toml b/crates/bitwarden-crypto/Cargo.toml index b15770ced..7671a7762 100644 --- a/crates/bitwarden-crypto/Cargo.toml +++ b/crates/bitwarden-crypto/Cargo.toml @@ -15,10 +15,10 @@ keywords.workspace = true [features] default = [] +wasm = ["dep:tsify-next", "dep:wasm-bindgen"] # WASM support uniffi = ["dep:uniffi"] # Uniffi bindings no-memory-hardening = [] # Disable memory hardening features -test = [] # Test methods [dependencies] aes = { version = ">=0.8.2, <0.9", features = ["zeroize"] } @@ -26,7 +26,7 @@ argon2 = { version = ">=0.5.0, <0.6", features = [ "std", "zeroize", ], default-features = false } -base64 = ">=0.21.2, <0.23" +base64 = ">=0.22.1, <0.23" cbc = { version = ">=0.1.2, <0.2", features = ["alloc", "zeroize"] } generic-array = { version = ">=0.14.7, <1.0", features = ["zeroize"] } hkdf = ">=0.12.3, <0.13" @@ -37,20 +37,22 @@ pbkdf2 = { version = ">=0.12.1, <0.13", default-features = false } rand = ">=0.8.5, <0.9" rayon = ">=1.8.1, <2.0" rsa = ">=0.9.2, <0.10" -schemars = { version = ">=0.8, <0.9", features = ["uuid1"] } -serde = { version = ">=1.0, <2.0", features = ["derive"] } +schemars = { workspace = true } +serde = { workspace = true } 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.27.2", optional = true } -uuid = { version = ">=1.3.3, <2.0", features = ["serde"] } +thiserror = { workspace = true } +tsify-next = { workspace = true, optional = true } +uniffi = { workspace = true, optional = true } +uuid = { workspace = true } +wasm-bindgen = { workspace = true, optional = true } zeroize = { version = ">=1.7.0, <2.0", features = ["derive", "aarch64"] } [dev-dependencies] criterion = "0.5.1" rand_chacha = "0.3.1" -serde_json = ">=1.0.96, <2.0" +serde_json = { workspace = true } [[bench]] name = "default_allocator" diff --git a/crates/bitwarden-crypto/src/aes.rs b/crates/bitwarden-crypto/src/aes.rs index dde588563..14393dfae 100644 --- a/crates/bitwarden-crypto/src/aes.rs +++ b/crates/bitwarden-crypto/src/aes.rs @@ -133,7 +133,7 @@ fn decrypt_aes128(iv: &[u8; 16], data: Vec, key: &GenericArray) -> /// Decrypt using AES-128 in CBC mode with MAC. /// /// Behaves similar to [decrypt_aes128], but also validates the MAC. -pub fn decrypt_aes128_hmac( +pub(crate) fn decrypt_aes128_hmac( iv: &[u8; 16], mac: &[u8; 32], data: Vec, diff --git a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs index f9bda838a..9d4f0d635 100644 --- a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs @@ -11,11 +11,16 @@ use crate::{ rsa::encrypt_rsa2048_oaep_sha1, AsymmetricCryptoKey, AsymmetricEncryptable, KeyDecryptable, }; - // This module is a workaround to avoid deprecated warnings that come from the ZeroizeOnDrop // macro expansion #[allow(deprecated)] mod internal { + #[cfg(feature = "wasm")] + #[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)] + const TS_CUSTOM_TYPES: &'static str = r#" + export type AsymmetricEncString = string; + "#; + /// # Encrypted string primitive /// /// [AsymmetricEncString] is a Bitwarden specific primitive that represents an asymmetrically diff --git a/crates/bitwarden-crypto/src/enc_string/mod.rs b/crates/bitwarden-crypto/src/enc_string/mod.rs index 3250c1a58..3278b8064 100644 --- a/crates/bitwarden-crypto/src/enc_string/mod.rs +++ b/crates/bitwarden-crypto/src/enc_string/mod.rs @@ -1,4 +1,11 @@ -/// Encrypted string types +//! Encrypted string types +//! +//! [EncString] and [AsymmetricEncString] are Bitwarden specific primitive that represents a +//! encrypted string. They are are used together with the [KeyDecryptable][crate::KeyDecryptable] +//! and [KeyEncryptable][crate::KeyEncryptable] traits to encrypt and decrypt data using +//! [SymmetricCryptoKey][crate::SymmetricCryptoKey] and +//! [AsymmetricCryptoKey][crate::AsymmetricCryptoKey]s. + mod asymmetric; mod symmetric; diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index 44086da49..69711f74a 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -11,6 +11,12 @@ use crate::{ KeyDecryptable, KeyEncryptable, LocateKey, SymmetricCryptoKey, }; +#[cfg(feature = "wasm")] +#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)] +const TS_CUSTOM_TYPES: &'static str = r#" +export type EncString = string; +"#; + /// # Encrypted string primitive /// /// [EncString] is a Bitwarden specific primitive that represents a symmetrically encrypted string. @@ -44,7 +50,7 @@ use crate::{ /// - `[iv]`: (optional) is the initialization vector used for encryption. /// - `[data]`: is the encrypted data. /// - `[mac]`: (optional) is the MAC used to validate the integrity of the data. -#[derive(Clone, zeroize::ZeroizeOnDrop)] +#[derive(Clone, zeroize::ZeroizeOnDrop, PartialEq)] #[allow(unused, non_camel_case_types)] pub enum EncString { /// 0 @@ -203,7 +209,7 @@ impl serde::Serialize for EncString { } impl EncString { - pub fn encrypt_aes256_hmac( + pub(crate) fn encrypt_aes256_hmac( data_dec: &[u8], mac_key: &GenericArray, key: &GenericArray, @@ -237,6 +243,10 @@ impl KeyDecryptable> for EncString { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result> { match self { EncString::AesCbc256_B64 { iv, data } => { + if key.mac_key.is_some() { + return Err(CryptoError::MacNotProvided); + } + let dec = crate::aes::decrypt_aes256(iv, data.clone(), &key.key)?; Ok(dec) } @@ -266,6 +276,12 @@ impl KeyEncryptable for String { } } +impl KeyEncryptable for &str { + fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { + self.as_bytes().encrypt_with_key(key) + } +} + impl KeyDecryptable for EncString { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { let dec: Vec = self.decrypt_with_key(key)?; @@ -290,7 +306,9 @@ mod tests { use schemars::schema_for; use super::EncString; - use crate::{derive_symmetric_key, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}; + use crate::{ + derive_symmetric_key, CryptoError, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, + }; #[test] fn test_enc_string_roundtrip() { @@ -303,6 +321,17 @@ mod tests { assert_eq!(decrypted_str, test_string); } + #[test] + fn test_enc_string_ref_roundtrip() { + let key = derive_symmetric_key("test"); + + let test_string = "encrypted_test_string"; + let cipher = test_string.encrypt_with_key(&key).unwrap(); + + let decrypted_str: String = cipher.decrypt_with_key(&key).unwrap(); + assert_eq!(decrypted_str, test_string); + } + #[test] fn test_enc_string_serialization() { #[derive(serde::Serialize, serde::Deserialize)] @@ -401,6 +430,24 @@ mod tests { assert_eq!(dec_str, "EncryptMe!"); } + #[test] + fn test_decrypt_downgrade_encstring_prevention() { + // Simulate a potential downgrade attack by removing the mac portion of the `EncString` and + // attempt to decrypt it using a `SymmetricCryptoKey` with a mac key. + let key = "hvBMMb1t79YssFZkpetYsM3deyVuQv4r88Uj9gvYe0+G8EwxvW3v1iywVmSl61iwzd17JW5C/ivzxSP2C9h7Tw==".to_string(); + let key = SymmetricCryptoKey::try_from(key).unwrap(); + + // A "downgraded" `EncString` from `EncString::AesCbc256_HmacSha256_B64` (2) to + // `EncString::AesCbc256_B64` (0), with the mac portion removed. + // + let enc_str = "0.NQfjHLr6za7VQVAbrpL81w==|wfrjmyJ0bfwkQlySrhw8dA=="; + let enc_string: EncString = enc_str.parse().unwrap(); + assert_eq!(enc_string.enc_type(), 0); + + let result: Result = enc_string.decrypt_with_key(&key); + assert!(matches!(result, Err(CryptoError::MacNotProvided))); + } + #[test] fn test_decrypt_cbc128_hmac() { let key = "Gt1aZ8kTTgkF80bLtb7LiMZBcxEA2FA5mbvV4x7K208=".to_string(); diff --git a/crates/bitwarden-crypto/src/error.rs b/crates/bitwarden-crypto/src/error.rs index 7cfb354d7..2f9a58b8b 100644 --- a/crates/bitwarden-crypto/src/error.rs +++ b/crates/bitwarden-crypto/src/error.rs @@ -1,6 +1,7 @@ use std::fmt::Debug; use thiserror::Error; +use uuid::Uuid; use crate::fingerprint::FingerprintError; @@ -10,14 +11,21 @@ pub enum CryptoError { InvalidKey, #[error("The cipher's MAC doesn't match the expected value")] InvalidMac, + #[error("The key provided expects mac protected encstrings, but the mac is missing")] + MacNotProvided, #[error("Error while decrypting EncString")] KeyDecrypt, #[error("The cipher key has an invalid length")] InvalidKeyLen, #[error("The value is not a valid UTF8 String")] InvalidUtf8String, - #[error("Missing Key")] - MissingKey, + #[error("Missing Key for organization with ID {0}")] + MissingKey(Uuid), + #[error("The item was missing a required field: {0}")] + MissingField(&'static str), + + #[error("Insufficient KDF parameters")] + InsufficientKdfParameters, #[error("EncString error, {0}")] EncString(#[from] EncStringParseError), diff --git a/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs index be523bbc6..a00a1f842 100644 --- a/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs @@ -15,7 +15,7 @@ pub trait AsymmetricEncryptable { /// [AsymmetricEncString](crate::AsymmetricEncString), usually accompanied by a /// [AsymmetricCryptoKey] pub struct AsymmetricPublicCryptoKey { - pub(crate) key: RsaPublicKey, + key: RsaPublicKey, } impl AsymmetricPublicCryptoKey { @@ -36,6 +36,7 @@ impl AsymmetricEncryptable for AsymmetricPublicCryptoKey { /// An asymmetric encryption key. Contains both the public and private key. Can be used to both /// encrypt and decrypt [`AsymmetricEncString`](crate::AsymmetricEncString). +#[derive(Clone)] pub struct AsymmetricCryptoKey { // RsaPrivateKey is not a Copy type so this isn't completely necessary, but // to keep the compiler from making stack copies when moving this struct around, diff --git a/crates/bitwarden-crypto/src/keys/key_encryptable.rs b/crates/bitwarden-crypto/src/keys/key_encryptable.rs index d8f11a011..044be9fcb 100644 --- a/crates/bitwarden-crypto/src/keys/key_encryptable.rs +++ b/crates/bitwarden-crypto/src/keys/key_encryptable.rs @@ -1,12 +1,18 @@ -use std::{collections::HashMap, hash::Hash}; +use std::{collections::HashMap, hash::Hash, sync::Arc}; use rayon::prelude::*; use uuid::Uuid; -use crate::{error::Result, SymmetricCryptoKey}; +use crate::{error::Result, CryptoError, SymmetricCryptoKey}; pub trait KeyContainer: Send + Sync { - fn get_key(&self, org_id: &Option) -> Option<&SymmetricCryptoKey>; + fn get_key(&self, org_id: &Option) -> Result<&SymmetricCryptoKey, CryptoError>; +} + +impl KeyContainer for Arc { + fn get_key(&self, org_id: &Option) -> Result<&SymmetricCryptoKey, CryptoError> { + self.as_ref().get_key(org_id) + } } pub trait LocateKey { @@ -14,7 +20,7 @@ pub trait LocateKey { &self, enc: &'a dyn KeyContainer, org_id: &Option, - ) -> Option<&'a SymmetricCryptoKey> { + ) -> Result<&'a SymmetricCryptoKey, CryptoError> { enc.get_key(org_id) } } diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index dd81b9a3a..7a1e49596 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -1,8 +1,12 @@ use std::num::NonZeroU32; use base64::{engine::general_purpose::STANDARD, Engine}; +use generic_array::{typenum::U32, GenericArray}; +use rand::Rng; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +#[cfg(feature = "wasm")] +use {tsify_next::Tsify, wasm_bindgen::prelude::*}; use super::utils::{derive_kdf_key, stretch_kdf_key}; use crate::{util, CryptoError, EncString, KeyDecryptable, Result, SymmetricCryptoKey, UserKey}; @@ -14,6 +18,7 @@ use crate::{util, CryptoError, EncString, KeyDecryptable, Result, SymmetricCrypt #[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] +#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] pub enum Kdf { PBKDF2 { iterations: NonZeroU32, @@ -64,13 +69,30 @@ pub enum HashPurpose { pub struct MasterKey(SymmetricCryptoKey); impl MasterKey { - pub fn new(key: SymmetricCryptoKey) -> MasterKey { + pub fn new(key: SymmetricCryptoKey) -> Self { Self(key) } + /// Generate a new random master key. Primarily used for KeyConnector. + pub fn generate(mut rng: impl rand::RngCore) -> Self { + let mut key = Box::pin(GenericArray::::default()); + + rng.fill(key.as_mut_slice()); + + // Master Keys never contains a mac_key. + Self::new(SymmetricCryptoKey { key, mac_key: None }) + } + /// Derives a users master key from their password, email and KDF. - pub fn derive(password: &[u8], email: &[u8], kdf: &Kdf) -> Result { - derive_kdf_key(password, email, kdf).map(Self) + /// + /// Note: the email is trimmed and converted to lowercase before being used. + pub fn derive(password: &str, email: &str, kdf: &Kdf) -> Result { + derive_kdf_key( + password.as_bytes(), + email.trim().to_lowercase().as_bytes(), + kdf, + ) + .map(Self) } /// Derive the master key hash, used for local and remote password validation. @@ -85,28 +107,57 @@ impl MasterKey { make_user_key(rand::thread_rng(), self) } + /// Encrypt the users user key + pub fn encrypt_user_key(&self, user_key: &SymmetricCryptoKey) -> Result { + encrypt_user_key(&self.0, user_key) + } + /// Decrypt the users user key pub fn decrypt_user_key(&self, user_key: EncString) -> Result { - let stretched_key = stretch_kdf_key(&self.0)?; - - let mut dec: Vec = user_key.decrypt_with_key(&stretched_key)?; - SymmetricCryptoKey::try_from(dec.as_mut_slice()) + decrypt_user_key(&self.0, user_key) } - pub fn encrypt_user_key(&self, user_key: &SymmetricCryptoKey) -> Result { - let stretched_key = stretch_kdf_key(&self.0)?; - - EncString::encrypt_aes256_hmac( - &user_key.to_vec(), - stretched_key - .mac_key - .as_ref() - .ok_or(CryptoError::InvalidMac)?, - &stretched_key.key, - ) + pub fn to_base64(&self) -> String { + self.0.to_base64() } } +/// Helper function to encrypt a user key with a master or pin key. +pub(super) fn encrypt_user_key( + key: &SymmetricCryptoKey, + user_key: &SymmetricCryptoKey, +) -> Result { + let stretched_key = stretch_kdf_key(key)?; + + EncString::encrypt_aes256_hmac( + &user_key.to_vec(), + stretched_key + .mac_key + .as_ref() + .ok_or(CryptoError::InvalidMac)?, + &stretched_key.key, + ) +} + +/// Helper function to decrypt a user key with a master or pin key. +pub(super) fn decrypt_user_key( + key: &SymmetricCryptoKey, + user_key: EncString, +) -> Result { + let mut dec: Vec = match user_key { + // Legacy. user_keys were encrypted using `AesCbc256_B64` a long time ago. We've since + // moved to using `AesCbc256_HmacSha256_B64`. However, we still need to support + // decrypting these old keys. + EncString::AesCbc256_B64 { .. } => user_key.decrypt_with_key(key)?, + _ => { + let stretched_key = stretch_kdf_key(key)?; + user_key.decrypt_with_key(&stretched_key)? + } + }; + + SymmetricCryptoKey::try_from(dec.as_mut_slice()) +} + /// Generate a new random user key and encrypt it with the master key. fn make_user_key( mut rng: impl rand::RngCore, @@ -124,13 +175,13 @@ mod tests { use rand::SeedableRng; use super::{make_user_key, HashPurpose, Kdf, MasterKey}; - use crate::{keys::symmetric_crypto_key::derive_symmetric_key, SymmetricCryptoKey}; + use crate::{keys::symmetric_crypto_key::derive_symmetric_key, EncString, SymmetricCryptoKey}; #[test] fn test_master_key_derive_pbkdf2() { let master_key = MasterKey::derive( - b"67t9b5g67$%Dh89n", - b"test_key", + "67t9b5g67$%Dh89n", + "test_key", &Kdf::PBKDF2 { iterations: NonZeroU32::new(10000).unwrap(), }, @@ -150,8 +201,8 @@ mod tests { #[test] fn test_master_key_derive_argon2() { let master_key = MasterKey::derive( - b"67t9b5g67$%Dh89n", - b"test_key", + "67t9b5g67$%Dh89n", + "test_key", &Kdf::Argon2id { iterations: NonZeroU32::new(4).unwrap(), memory: NonZeroU32::new(32).unwrap(), @@ -172,26 +223,32 @@ mod tests { #[test] fn test_password_hash_pbkdf2() { - let password = b"asdfasdf"; - let salt = b"test_salt"; + let password = "asdfasdf"; + let salts = [ + "test@bitwarden.com", + "TEST@bitwarden.com", + " test@bitwarden.com", + ]; let kdf = Kdf::PBKDF2 { iterations: NonZeroU32::new(100_000).unwrap(), }; - let master_key = MasterKey::derive(password, salt, &kdf).unwrap(); + for salt in salts.iter() { + let master_key = MasterKey::derive(password, salt, &kdf).unwrap(); - assert_eq!( - "ZF6HjxUTSyBHsC+HXSOhZoXN+UuMnygV5YkWXCY4VmM=", - master_key - .derive_master_key_hash(password, HashPurpose::ServerAuthorization) - .unwrap(), - ); + assert_eq!( + "wmyadRMyBZOH7P/a/ucTCbSghKgdzDpPqUnu/DAVtSw=", + master_key + .derive_master_key_hash(password.as_bytes(), HashPurpose::ServerAuthorization) + .unwrap(), + ); + } } #[test] fn test_password_hash_argon2id() { - let password = b"asdfasdf"; - let salt = b"test_salt"; + let password = "asdfasdf"; + let salt = "test_salt"; let kdf = Kdf::Argon2id { iterations: NonZeroU32::new(4).unwrap(), memory: NonZeroU32::new(32).unwrap(), @@ -203,7 +260,7 @@ mod tests { assert_eq!( "PR6UjYmjmppTYcdyTiNbAhPJuQQOmynKbdEl1oyi/iQ=", master_key - .derive_master_key_hash(password, HashPurpose::ServerAuthorization) + .derive_master_key_hash(password.as_bytes(), HashPurpose::ServerAuthorization) .unwrap(), ); } @@ -271,4 +328,34 @@ mod tests { "Decrypted key doesn't match user key" ); } + + #[test] + fn test_decrypt_user_key_aes_cbc256_b64() { + let password = "asdfasdfasdf"; + let salt = "legacy@bitwarden.com"; + let kdf = Kdf::PBKDF2 { + iterations: NonZeroU32::new(600_000).unwrap(), + }; + + let master_key = MasterKey::derive(password, salt, &kdf).unwrap(); + + let user_key: EncString = "0.8UClLa8IPE1iZT7chy5wzQ==|6PVfHnVk5S3XqEtQemnM5yb4JodxmPkkWzmDRdfyHtjORmvxqlLX40tBJZ+CKxQWmS8tpEB5w39rbgHg/gqs0haGdZG4cPbywsgGzxZ7uNI=".parse().unwrap(); + + let decrypted = master_key.decrypt_user_key(user_key).unwrap(); + + assert_eq!( + decrypted.key.as_slice(), + [ + 12, 95, 151, 203, 37, 4, 236, 67, 137, 97, 90, 58, 6, 127, 242, 28, 209, 168, 125, + 29, 118, 24, 213, 44, 117, 202, 2, 115, 132, 165, 125, 148 + ] + ); + assert_eq!( + decrypted.mac_key.as_ref().unwrap().as_slice(), + [ + 186, 215, 234, 137, 24, 169, 227, 29, 218, 57, 180, 237, 73, 91, 189, 51, 253, 26, + 17, 52, 226, 4, 134, 75, 194, 208, 178, 133, 128, 224, 140, 167 + ] + ); + } } diff --git a/crates/bitwarden-crypto/src/keys/pin_key.rs b/crates/bitwarden-crypto/src/keys/pin_key.rs index 475b7ffd9..9e6c02db5 100644 --- a/crates/bitwarden-crypto/src/keys/pin_key.rs +++ b/crates/bitwarden-crypto/src/keys/pin_key.rs @@ -1,3 +1,4 @@ +use super::master_key::{decrypt_user_key, encrypt_user_key}; use crate::{ keys::{ key_encryptable::CryptoKey, @@ -17,8 +18,18 @@ impl PinKey { } /// Derives a users pin key from their password, email and KDF. - pub fn derive(password: &[u8], salt: &[u8], kdf: &Kdf) -> Result { - derive_kdf_key(password, salt, kdf).map(Self) + pub fn derive(password: &[u8], email: &[u8], kdf: &Kdf) -> Result { + derive_kdf_key(password, email, kdf).map(Self) + } + + /// Encrypt the users user key + pub fn encrypt_user_key(&self, user_key: &SymmetricCryptoKey) -> Result { + encrypt_user_key(&self.0, user_key) + } + + /// Decrypt the users user key + pub fn decrypt_user_key(&self, user_key: EncString) -> Result { + decrypt_user_key(&self.0, user_key) } } diff --git a/crates/bitwarden-crypto/src/keys/shareable_key.rs b/crates/bitwarden-crypto/src/keys/shareable_key.rs index 556432aea..c6a44405c 100644 --- a/crates/bitwarden-crypto/src/keys/shareable_key.rs +++ b/crates/bitwarden-crypto/src/keys/shareable_key.rs @@ -3,7 +3,7 @@ use std::pin::Pin; use aes::cipher::typenum::U64; use generic_array::GenericArray; use hmac::Mac; -use zeroize::{Zeroize, Zeroizing}; +use zeroize::Zeroizing; use crate::{ keys::SymmetricCryptoKey, @@ -20,18 +20,17 @@ pub fn derive_shareable_key( info: Option<&str>, ) -> SymmetricCryptoKey { // Because all inputs are fixed size, we can unwrap all errors here without issue - let mut res = PbkdfSha256Hmac::new_from_slice(format!("bitwarden-{}", name).as_bytes()) - .expect("hmac new_from_slice should not fail") - .chain_update(secret) - .finalize() - .into_bytes(); + let res = Zeroizing::new( + PbkdfSha256Hmac::new_from_slice(format!("bitwarden-{}", name).as_bytes()) + .expect("hmac new_from_slice should not fail") + .chain_update(secret) + .finalize() + .into_bytes(), + ); let mut key: Pin>> = hkdf_expand(&res, info).expect("Input is a valid size"); - // Zeroize the temporary buffer - res.zeroize(); - SymmetricCryptoKey::try_from(key.as_mut_slice()).expect("Key is a valid size") } diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index 4857aa2d4..971564b07 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -10,6 +10,7 @@ use super::key_encryptable::CryptoKey; use crate::CryptoError; /// A symmetric encryption key. Used to encrypt and decrypt [`EncString`](crate::EncString) +#[derive(Clone)] pub struct SymmetricCryptoKey { // GenericArray is equivalent to [u8; N], which is a Copy type placed on the stack. // To keep the compiler from making stack copies when moving this struct around, diff --git a/crates/bitwarden-crypto/src/keys/utils.rs b/crates/bitwarden-crypto/src/keys/utils.rs index a2df336e1..4b2c4c8cd 100644 --- a/crates/bitwarden-crypto/src/keys/utils.rs +++ b/crates/bitwarden-crypto/src/keys/utils.rs @@ -3,41 +3,66 @@ use std::pin::Pin; use generic_array::{typenum::U32, GenericArray}; use sha2::Digest; -use crate::{util::hkdf_expand, Kdf, Result, SymmetricCryptoKey}; +use crate::{util::hkdf_expand, CryptoError, Kdf, Result, SymmetricCryptoKey}; + +const PBKDF2_MIN_ITERATIONS: u32 = 5000; + +const ARGON2ID_MIN_MEMORY: u32 = 16 * 1024; +const ARGON2ID_MIN_ITERATIONS: u32 = 2; +const ARGON2ID_MIN_PARALLELISM: u32 = 1; /// Derive a generic key from a secret and salt using the provided KDF. pub(super) fn derive_kdf_key(secret: &[u8], salt: &[u8], kdf: &Kdf) -> Result { let mut hash = match kdf { - Kdf::PBKDF2 { iterations } => crate::util::pbkdf2(secret, salt, iterations.get()), + Kdf::PBKDF2 { iterations } => { + let iterations = iterations.get(); + if iterations < PBKDF2_MIN_ITERATIONS { + return Err(CryptoError::InsufficientKdfParameters); + } + crate::util::pbkdf2(secret, salt, iterations) + } Kdf::Argon2id { iterations, memory, parallelism, } => { + let memory = memory.get() * 1024; // Convert MiB to KiB; + let iterations = iterations.get(); + let parallelism = parallelism.get(); + + if memory < ARGON2ID_MIN_MEMORY + || iterations < ARGON2ID_MIN_ITERATIONS + || parallelism < ARGON2ID_MIN_PARALLELISM + { + return Err(CryptoError::InsufficientKdfParameters); + } + use argon2::*; - let argon = Argon2::new( - Algorithm::Argon2id, - Version::V0x13, - Params::new( - memory.get() * 1024, // Convert MiB to KiB - iterations.get(), - parallelism.get(), - Some(32), - )?, - ); + let params = Params::new(memory, iterations, parallelism, Some(32))?; + let argon = Argon2::new(Algorithm::Argon2id, Version::V0x13, params); let salt_sha = sha2::Sha256::new().chain_update(salt).finalize(); let mut hash = [0u8; 32]; argon.hash_password_into(secret, &salt_sha, &mut hash)?; + + // Argon2 is using some stack memory that is not zeroed. Eventually some function will + // overwrite the stack, but we use this trick to force the used stack to be zeroed. + #[inline(never)] + fn clear_stack() { + std::hint::black_box([0u8; 4096]); + } + clear_stack(); + hash } }; SymmetricCryptoKey::try_from(hash.as_mut_slice()) } +/// Stretch the given key using HKDF. pub(super) fn stretch_kdf_key(k: &SymmetricCryptoKey) -> Result { let key: Pin>> = hkdf_expand(&k.key, Some("enc"))?; let mac_key: Pin>> = hkdf_expand(&k.key, Some("mac"))?; @@ -47,6 +72,8 @@ pub(super) fn stretch_kdf_key(k: &SymmetricCryptoKey) -> Result NonZero { + NonZero::new(n).unwrap() + } + + let secret = [0u8; 32]; + let salt = [0u8; 32]; + + for kdf in [ + Kdf::PBKDF2 { + iterations: nz(4999), + }, + Kdf::Argon2id { + iterations: nz(1), + memory: nz(16), + parallelism: nz(1), + }, + Kdf::Argon2id { + iterations: nz(2), + memory: nz(15), + parallelism: nz(1), + }, + Kdf::Argon2id { + iterations: nz(1), + memory: nz(15), + parallelism: nz(1), + }, + ] { + assert_eq!( + derive_kdf_key(&secret, &salt, &kdf) + .unwrap_err() + .to_string(), + "Insufficient KDF parameters" + ); + } + } } diff --git a/crates/bitwarden-crypto/src/lib.rs b/crates/bitwarden-crypto/src/lib.rs index 3f54dec87..44efaac30 100644 --- a/crates/bitwarden-crypto/src/lib.rs +++ b/crates/bitwarden-crypto/src/lib.rs @@ -56,8 +56,9 @@ //! ## Crate features //! //! - `no-memory-hardening` - Disables memory hardening which ensures that allocated memory is -//! zeroed on drop. This feature primarily exists in case you do not want to use the standard -//! allocator, and we advise to still define a `global_allocator` using the [`ZeroizingAllocator`]. +//! zeroed on drop. This feature primarily exists in case you do not want to use the standard +//! allocator, and we advise to still define a `global_allocator` using the +//! [`ZeroizingAllocator`]. #[cfg(not(feature = "no-memory-hardening"))] #[global_allocator] @@ -76,9 +77,8 @@ pub use keys::*; mod rsa; pub use crate::rsa::RsaKeyPair; mod util; -pub use util::generate_random_bytes; +pub use util::{generate_random_alphanumeric, generate_random_bytes, pbkdf2}; mod wordlist; -pub use util::pbkdf2; pub use wordlist::EFF_LONG_WORD_LIST; mod allocator; pub use allocator::ZeroizingAllocator; diff --git a/crates/bitwarden-crypto/src/util.rs b/crates/bitwarden-crypto/src/util.rs index b78a4f5ec..d1ba5cdad 100644 --- a/crates/bitwarden-crypto/src/util.rs +++ b/crates/bitwarden-crypto/src/util.rs @@ -4,7 +4,7 @@ use ::aes::cipher::{ArrayLength, Unsigned}; use generic_array::GenericArray; use hmac::digest::OutputSizeUser; use rand::{ - distributions::{Distribution, Standard}, + distributions::{Alphanumeric, DistString, Distribution, Standard}, Rng, }; use zeroize::{Zeroize, Zeroizing}; @@ -39,6 +39,15 @@ where Zeroizing::new(rand::thread_rng().gen::()) } +/// Generate a random alphanumeric string of a given length +/// +/// Note: Do not use this generating user facing passwords. Use the `bitwarden-generator` crate for +/// that. +pub fn generate_random_alphanumeric(len: usize) -> String { + Alphanumeric.sample_string(&mut rand::thread_rng(), len) +} + +/// Derive pbkdf2 of a given password and salt 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-exporters/Cargo.toml b/crates/bitwarden-exporters/Cargo.toml index 96097c628..38cf49953 100644 --- a/crates/bitwarden-exporters/Cargo.toml +++ b/crates/bitwarden-exporters/Cargo.toml @@ -14,22 +14,22 @@ repository.workspace = true license-file.workspace = true keywords.workspace = true +[features] +uniffi = ["dep:uniffi"] # Uniffi bindings + [dependencies] -base64 = ">=0.21.2, <0.23" +base64 = ">=0.22.1, <0.23" +bitwarden-core = { workspace = true } bitwarden-crypto = { workspace = true } -chrono = { version = ">=0.4.26, <0.5", features = [ - "clock", - "serde", - "std", -], default-features = false } +bitwarden-vault = { workspace = true } +chrono = { workspace = true, features = ["std"] } csv = "1.3.0" -serde = { version = ">=1.0, <2.0", features = ["derive"] } -serde_json = ">=1.0.96, <2.0" -thiserror = ">=1.0.40, <2.0" -uuid = { version = ">=1.3.3, <2.0", features = ["serde", "v4"] } - -[dev-dependencies] -bitwarden-crypto = { workspace = true, features = ["test"] } +schemars = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +thiserror = { workspace = true } +uniffi = { workspace = true, optional = true } +uuid = { workspace = true } [lints] workspace = true diff --git a/crates/bitwarden-exporters/src/client_exporter.rs b/crates/bitwarden-exporters/src/client_exporter.rs new file mode 100644 index 000000000..ffc9963c1 --- /dev/null +++ b/crates/bitwarden-exporters/src/client_exporter.rs @@ -0,0 +1,45 @@ +use bitwarden_core::Client; +use bitwarden_vault::{Cipher, Collection, Folder}; + +use crate::{ + export::{export_organization_vault, export_vault}, + ExportError, ExportFormat, +}; + +pub struct ClientExporters<'a> { + client: &'a Client, +} + +impl<'a> ClientExporters<'a> { + fn new(client: &'a Client) -> Self { + Self { client } + } + + pub fn export_vault( + &self, + folders: Vec, + ciphers: Vec, + format: ExportFormat, + ) -> Result { + export_vault(self.client, folders, ciphers, format) + } + + pub fn export_organization_vault( + &self, + collections: Vec, + ciphers: Vec, + format: ExportFormat, + ) -> Result { + export_organization_vault(collections, ciphers, format) + } +} + +pub trait ClientExportersExt<'a> { + fn exporters(&'a self) -> ClientExporters<'a>; +} + +impl<'a> ClientExportersExt<'a> for Client { + fn exporters(&'a self) -> ClientExporters<'a> { + ClientExporters::new(self) + } +} diff --git a/crates/bitwarden-exporters/src/error.rs b/crates/bitwarden-exporters/src/error.rs new file mode 100644 index 000000000..d23819696 --- /dev/null +++ b/crates/bitwarden-exporters/src/error.rs @@ -0,0 +1,21 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ExportError { + #[error(transparent)] + MissingField(#[from] bitwarden_core::MissingFieldError), + #[error(transparent)] + VaultLocked(#[from] bitwarden_core::VaultLocked), + + #[error("CSV error: {0}")] + Csv(#[from] crate::csv::CsvError), + #[error("JSON error: {0}")] + Json(#[from] crate::json::JsonError), + #[error("Encrypted JSON error: {0}")] + EncryptedJsonError(#[from] crate::encrypted_json::EncryptedJsonError), + + #[error(transparent)] + BitwardenError(#[from] bitwarden_core::Error), + #[error(transparent)] + BitwardenCryptoError(#[from] bitwarden_crypto::CryptoError), +} diff --git a/crates/bitwarden-exporters/src/export.rs b/crates/bitwarden-exporters/src/export.rs new file mode 100644 index 000000000..7fa05413d --- /dev/null +++ b/crates/bitwarden-exporters/src/export.rs @@ -0,0 +1,43 @@ +use bitwarden_core::Client; +use bitwarden_crypto::KeyDecryptable; +use bitwarden_vault::{Cipher, CipherView, Collection, Folder, FolderView}; + +use crate::{ + csv::export_csv, encrypted_json::export_encrypted_json, json::export_json, ExportError, + ExportFormat, +}; + +pub(crate) fn export_vault( + client: &Client, + folders: Vec, + ciphers: Vec, + format: ExportFormat, +) -> Result { + let enc = client.internal.get_encryption_settings()?; + let key = enc.get_key(&None)?; + + let folders: Vec = folders.decrypt_with_key(key)?; + let folders: Vec = folders.into_iter().flat_map(|f| f.try_into()).collect(); + + let ciphers: Vec = ciphers.decrypt_with_key(key)?; + let ciphers: Vec = ciphers.into_iter().flat_map(|c| c.try_into()).collect(); + + match format { + ExportFormat::Csv => Ok(export_csv(folders, ciphers)?), + ExportFormat::Json => Ok(export_json(folders, ciphers)?), + ExportFormat::EncryptedJson { password } => Ok(export_encrypted_json( + folders, + ciphers, + password, + client.internal.get_kdf()?, + )?), + } +} + +pub(crate) fn export_organization_vault( + _collections: Vec, + _ciphers: Vec, + _format: ExportFormat, +) -> Result { + todo!(); +} diff --git a/crates/bitwarden-exporters/src/lib.rs b/crates/bitwarden-exporters/src/lib.rs index 762556388..75b0503e2 100644 --- a/crates/bitwarden-exporters/src/lib.rs +++ b/crates/bitwarden-exporters/src/lib.rs @@ -1,22 +1,28 @@ use std::fmt; -use bitwarden_crypto::Kdf; use chrono::{DateTime, Utc}; -use thiserror::Error; +use schemars::JsonSchema; use uuid::Uuid; +#[cfg(feature = "uniffi")] +uniffi::setup_scaffolding!(); + +mod client_exporter; mod csv; -use crate::csv::export_csv; -mod json; -use json::export_json; mod encrypted_json; - -use encrypted_json::export_encrypted_json; - -pub enum Format { +mod json; +mod models; +pub use client_exporter::{ClientExporters, ClientExportersExt}; +mod error; +mod export; +pub use error::ExportError; + +#[derive(JsonSchema)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] +pub enum ExportFormat { Csv, Json, - EncryptedJson { password: String, kdf: Kdf }, + EncryptedJson { password: String }, } /// Export representation of a Bitwarden folder. @@ -126,27 +132,3 @@ pub struct Identity { pub passport_number: Option, pub license_number: Option, } - -#[derive(Error, Debug)] -pub enum ExportError { - #[error("CSV error: {0}")] - Csv(#[from] csv::CsvError), - #[error("JSON error: {0}")] - Json(#[from] json::JsonError), - #[error("Encrypted JSON error: {0}")] - EncryptedJsonError(#[from] encrypted_json::EncryptedJsonError), -} - -pub fn export( - folders: Vec, - ciphers: Vec, - format: Format, -) -> Result { - match format { - Format::Csv => Ok(export_csv(folders, ciphers)?), - Format::Json => Ok(export_json(folders, ciphers)?), - Format::EncryptedJson { password, kdf } => { - Ok(export_encrypted_json(folders, ciphers, password, kdf)?) - } - } -} diff --git a/crates/bitwarden/src/tool/exporters/mod.rs b/crates/bitwarden-exporters/src/models.rs similarity index 55% rename from crates/bitwarden/src/tool/exporters/mod.rs rename to crates/bitwarden-exporters/src/models.rs index 359cab8c5..1d56e7233 100644 --- a/crates/bitwarden/src/tool/exporters/mod.rs +++ b/crates/bitwarden-exporters/src/models.rs @@ -1,86 +1,10 @@ -use bitwarden_crypto::KeyDecryptable; -use bitwarden_exporters::export; -use schemars::JsonSchema; - -use crate::{ - client::{LoginMethod, UserLoginMethod}, - error::{require, Error, Result}, - vault::{ - login::LoginUriView, Cipher, CipherType, CipherView, Collection, FieldView, Folder, - FolderView, SecureNoteType, - }, - Client, +use bitwarden_core::{require, MissingFieldError}; +use bitwarden_vault::{ + CipherType, CipherView, FieldView, FolderView, LoginUriView, SecureNoteType, }; -mod client_exporter; -pub use client_exporter::ClientExporters; - -#[derive(JsonSchema)] -#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] -pub enum ExportFormat { - Csv, - Json, - EncryptedJson { password: String }, -} - -pub(super) fn export_vault( - client: &Client, - folders: Vec, - ciphers: Vec, - format: ExportFormat, -) -> Result { - let enc = client.get_encryption_settings()?; - let key = enc.get_key(&None).ok_or(Error::VaultLocked)?; - - let folders: Vec = folders.decrypt_with_key(key)?; - let folders: Vec = - folders.into_iter().flat_map(|f| f.try_into()).collect(); - - let ciphers: Vec = ciphers.decrypt_with_key(key)?; - let ciphers: Vec = - ciphers.into_iter().flat_map(|c| c.try_into()).collect(); - - let format = convert_format(client, format)?; - - Ok(export(folders, ciphers, format)?) -} - -fn convert_format( - client: &Client, - format: ExportFormat, -) -> Result { - let login_method = client - .login_method - .as_ref() - .ok_or(Error::NotAuthenticated)?; - - let kdf = match login_method { - LoginMethod::User( - UserLoginMethod::Username { kdf, .. } | UserLoginMethod::ApiKey { kdf, .. }, - ) => kdf, - _ => return Err(Error::NotAuthenticated), - }; - - Ok(match format { - ExportFormat::Csv => bitwarden_exporters::Format::Csv, - ExportFormat::Json => bitwarden_exporters::Format::Json, - ExportFormat::EncryptedJson { password } => bitwarden_exporters::Format::EncryptedJson { - password, - kdf: kdf.clone(), - }, - }) -} - -pub(super) fn export_organization_vault( - _collections: Vec, - _ciphers: Vec, - _format: ExportFormat, -) -> Result { - todo!(); -} - -impl TryFrom for bitwarden_exporters::Folder { - type Error = Error; +impl TryFrom for crate::Folder { + type Error = MissingFieldError; fn try_from(value: FolderView) -> Result { Ok(Self { @@ -90,14 +14,14 @@ impl TryFrom for bitwarden_exporters::Folder { } } -impl TryFrom for bitwarden_exporters::Cipher { - type Error = Error; +impl TryFrom for crate::Cipher { + type Error = MissingFieldError; fn try_from(value: CipherView) -> Result { let r = match value.r#type { CipherType::Login => { let l = require!(value.login); - bitwarden_exporters::CipherType::Login(Box::new(bitwarden_exporters::Login { + crate::CipherType::Login(Box::new(crate::Login { username: l.username, password: l.password, login_uris: l @@ -109,18 +33,16 @@ impl TryFrom for bitwarden_exporters::Cipher { totp: l.totp, })) } - CipherType::SecureNote => bitwarden_exporters::CipherType::SecureNote(Box::new( - bitwarden_exporters::SecureNote { - r#type: value - .secure_note - .map(|t| t.r#type) - .unwrap_or(SecureNoteType::Generic) - .into(), - }, - )), + CipherType::SecureNote => crate::CipherType::SecureNote(Box::new(crate::SecureNote { + r#type: value + .secure_note + .map(|t| t.r#type) + .unwrap_or(SecureNoteType::Generic) + .into(), + })), CipherType::Card => { let c = require!(value.card); - bitwarden_exporters::CipherType::Card(Box::new(bitwarden_exporters::Card { + crate::CipherType::Card(Box::new(crate::Card { cardholder_name: c.cardholder_name, exp_month: c.exp_month, exp_year: c.exp_year, @@ -131,7 +53,7 @@ impl TryFrom for bitwarden_exporters::Cipher { } CipherType::Identity => { let i = require!(value.identity); - bitwarden_exporters::CipherType::Identity(Box::new(bitwarden_exporters::Identity { + crate::CipherType::Identity(Box::new(crate::Identity { title: i.title, first_name: i.first_name, middle_name: i.middle_name, @@ -175,7 +97,7 @@ impl TryFrom for bitwarden_exporters::Cipher { } } -impl From for bitwarden_exporters::Field { +impl From for crate::Field { fn from(value: FieldView) -> Self { Self { name: value.name, @@ -186,7 +108,7 @@ impl From for bitwarden_exporters::Field { } } -impl From for bitwarden_exporters::LoginUri { +impl From for crate::LoginUri { fn from(value: LoginUriView) -> Self { Self { r#match: value.r#match.map(|v| v as u8), @@ -195,23 +117,20 @@ impl From for bitwarden_exporters::LoginUri { } } -impl From for bitwarden_exporters::SecureNoteType { +impl From for crate::SecureNoteType { fn from(value: SecureNoteType) -> Self { match value { - SecureNoteType::Generic => bitwarden_exporters::SecureNoteType::Generic, + SecureNoteType::Generic => crate::SecureNoteType::Generic, } } } #[cfg(test)] mod tests { - use std::num::NonZeroU32; - - use bitwarden_crypto::Kdf; + use bitwarden_vault::{CipherRepromptType, LoginView}; use chrono::{DateTime, Utc}; use super::*; - use crate::vault::{login::LoginView, CipherRepromptType}; #[test] fn test_try_from_folder_view() { @@ -221,7 +140,7 @@ mod tests { revision_date: "2024-01-30T17:55:36.150Z".parse().unwrap(), }; - let f: bitwarden_exporters::Folder = view.try_into().unwrap(); + let f: crate::Folder = view.try_into().unwrap(); assert_eq!( f.id, @@ -267,7 +186,7 @@ mod tests { revision_date: "2024-01-30T17:55:36.150Z".parse().unwrap(), }; - let cipher: bitwarden_exporters::Cipher = cipher_view.try_into().unwrap(); + let cipher: crate::Cipher = cipher_view.try_into().unwrap(); assert_eq!( cipher.id, @@ -289,7 +208,7 @@ mod tests { ); assert_eq!(cipher.deleted_date, None); - if let bitwarden_exporters::CipherType::Login(l) = cipher.r#type { + if let crate::CipherType::Login(l) = cipher.r#type { assert_eq!(l.username, Some("test_username".to_string())); assert_eq!(l.password, Some("test_password".to_string())); assert!(l.login_uris.is_empty()); @@ -298,35 +217,4 @@ mod tests { panic!("Expected login type"); } } - - #[test] - fn test_convert_format() { - let mut client = Client::new(None); - client.set_login_method(LoginMethod::User(UserLoginMethod::Username { - client_id: "7b821276-e27c-400b-9853-606393c87f18".to_owned(), - email: "test@bitwarden.com".to_owned(), - kdf: Kdf::PBKDF2 { - iterations: NonZeroU32::new(600_000).unwrap(), - }, - })); - - assert!(matches!( - convert_format(&client, ExportFormat::Csv).unwrap(), - bitwarden_exporters::Format::Csv - )); - assert!(matches!( - convert_format(&client, ExportFormat::Json).unwrap(), - bitwarden_exporters::Format::Json - )); - assert!(matches!( - convert_format( - &client, - ExportFormat::EncryptedJson { - password: "password".to_string() - } - ) - .unwrap(), - bitwarden_exporters::Format::EncryptedJson { .. } - )); - } } diff --git a/crates/bitwarden-exporters/uniffi.toml b/crates/bitwarden-exporters/uniffi.toml new file mode 100644 index 000000000..7a57dfabd --- /dev/null +++ b/crates/bitwarden-exporters/uniffi.toml @@ -0,0 +1,9 @@ +[bindings.kotlin] +package_name = "com.bitwarden.exporters" +generate_immutable_records = true +android = true + +[bindings.swift] +ffi_module_name = "BitwardenExportersFFI" +module_name = "BitwardenExporters" +generate_immutable_records = true diff --git a/crates/bitwarden-fido/Cargo.toml b/crates/bitwarden-fido/Cargo.toml new file mode 100644 index 000000000..20f5cdaf6 --- /dev/null +++ b/crates/bitwarden-fido/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "bitwarden-fido" +description = """ +Internal crate for the bitwarden crate. Do not use. +""" + +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +homepage.workspace = true +repository.workspace = true +license-file.workspace = true +keywords.workspace = true + +[features] +uniffi = ["dep:uniffi", "bitwarden-core/uniffi", "bitwarden-vault/uniffi"] + +[dependencies] +async-trait = ">=0.1.80, <0.2" +base64 = ">=0.22.1, <0.23" +bitwarden-core = { workspace = true } +bitwarden-crypto = { workspace = true } +bitwarden-vault = { workspace = true } +chrono = { workspace = true } +coset = { version = "0.3.7" } +itertools = "0.13.0" +log = ">=0.4.18, <0.5" +p256 = { version = ">=0.13.2, <0.14" } +passkey = { git = "https://github.com/bitwarden/passkey-rs", rev = "ff757604cd7b4e8f321ed1616fef7e40e21ac5df" } +passkey-client = { git = "https://github.com/bitwarden/passkey-rs", rev = "ff757604cd7b4e8f321ed1616fef7e40e21ac5df", features = [ + "android-asset-validation", +] } +reqwest = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +thiserror = { workspace = true } +uniffi = { workspace = true, optional = true } +uuid = { workspace = true } + +[lints] +workspace = true diff --git a/crates/bitwarden-fido/README.md b/crates/bitwarden-fido/README.md new file mode 100644 index 000000000..b94a1cb83 --- /dev/null +++ b/crates/bitwarden-fido/README.md @@ -0,0 +1,6 @@ +# Bitwarden Fido + +This is an internal crate for the Bitwarden SDK do not depend on this directly and use the +[`bitwarden`](https://crates.io/crates/bitwarden) crate instead. + +This crate does not follow semantic versioning and the public interface may change at any time. diff --git a/crates/bitwarden/src/platform/fido2/authenticator.rs b/crates/bitwarden-fido/src/authenticator.rs similarity index 58% rename from crates/bitwarden/src/platform/fido2/authenticator.rs rename to crates/bitwarden-fido/src/authenticator.rs index 69aae6f0c..cbddaf94b 100644 --- a/crates/bitwarden/src/platform/fido2/authenticator.rs +++ b/crates/bitwarden-fido/src/authenticator.rs @@ -1,6 +1,9 @@ use std::sync::Mutex; -use bitwarden_crypto::KeyEncryptable; +use bitwarden_core::{Client, VaultLocked}; +use bitwarden_crypto::{CryptoError, KeyContainer, KeyEncryptable}; +use bitwarden_vault::{CipherError, CipherView}; +use itertools::Itertools; use log::error; use passkey::{ authenticator::{Authenticator, DiscoverabilitySupport, StoreInfo, UIHint, UserCheck}, @@ -9,34 +12,116 @@ use passkey::{ Passkey, }, }; +use thiserror::Error; use super::{ - types::*, CheckUserOptions, CheckUserResult, CipherViewContainer, Fido2CredentialStore, - Fido2UserInterface, SelectedCredential, AAGUID, + try_from_credential_new_view, types::*, CheckUserOptions, CipherViewContainer, + Fido2CredentialStore, Fido2UserInterface, SelectedCredential, UnknownEnum, AAGUID, }; use crate::{ - error::{require, Error, Result}, - platform::fido2::string_to_guid_bytes, - vault::{ - login::Fido2CredentialView, CipherView, Fido2CredentialFullView, Fido2CredentialNewView, - }, - Client, + fill_with_credential, string_to_guid_bytes, try_from_credential_full, Fido2CallbackError, + FillCredentialError, InvalidGuid, }; +#[derive(Debug, Error)] +pub enum GetSelectedCredentialError { + #[error("No selected credential available")] + NoSelectedCredential, + #[error("No fido2 credentials found")] + NoCredentialFound, + + #[error(transparent)] + VaultLocked(#[from] VaultLocked), + #[error(transparent)] + CipherError(#[from] CipherError), +} + +#[derive(Debug, Error)] +pub enum MakeCredentialError { + #[error(transparent)] + PublicKeyCredentialParametersError(#[from] PublicKeyCredentialParametersError), + #[error(transparent)] + UnknownEnum(#[from] UnknownEnum), + #[error(transparent)] + Serde(#[from] serde_json::Error), + #[error("Missing attested_credential_data")] + MissingAttestedCredentialData, + #[error("make_credential error: {0}")] + Other(String), +} + +#[derive(Debug, Error)] +pub enum GetAssertionError { + #[error(transparent)] + UnknownEnum(#[from] UnknownEnum), + #[error(transparent)] + Serde(#[from] serde_json::Error), + #[error(transparent)] + GetSelectedCredentialError(#[from] GetSelectedCredentialError), + #[error(transparent)] + InvalidGuid(#[from] InvalidGuid), + #[error("missing user")] + MissingUser, + #[error("get_assertion error: {0}")] + Other(String), +} + +#[derive(Debug, Error)] +pub enum SilentlyDiscoverCredentialsError { + #[error(transparent)] + CipherError(#[from] CipherError), + #[error(transparent)] + VaultLocked(#[from] VaultLocked), + #[error(transparent)] + InvalidGuid(#[from] InvalidGuid), + #[error(transparent)] + Fido2CallbackError(#[from] Fido2CallbackError), + #[error(transparent)] + FromCipherViewError(#[from] Fido2CredentialAutofillViewError), +} + +#[derive(Debug, Error)] +pub enum CredentialsForAutofillError { + #[error(transparent)] + CipherError(#[from] CipherError), + #[error(transparent)] + VaultLocked(#[from] VaultLocked), + #[error(transparent)] + InvalidGuid(#[from] InvalidGuid), + #[error(transparent)] + Fido2CallbackError(#[from] Fido2CallbackError), + #[error(transparent)] + FromCipherViewError(#[from] Fido2CredentialAutofillViewError), +} + pub struct Fido2Authenticator<'a> { - pub(crate) client: &'a mut Client, - pub(crate) user_interface: &'a dyn Fido2UserInterface, - pub(crate) credential_store: &'a dyn Fido2CredentialStore, + pub client: &'a Client, + pub user_interface: &'a dyn Fido2UserInterface, + pub credential_store: &'a dyn Fido2CredentialStore, - pub(crate) selected_credential: Mutex>, + pub(crate) selected_cipher: Mutex>, pub(crate) requested_uv: Mutex>, } impl<'a> Fido2Authenticator<'a> { + pub fn new( + client: &'a Client, + user_interface: &'a dyn Fido2UserInterface, + credential_store: &'a dyn Fido2CredentialStore, + ) -> Fido2Authenticator<'a> { + Fido2Authenticator { + client, + user_interface, + credential_store, + selected_cipher: Mutex::new(None), + requested_uv: Mutex::new(None), + } + } + pub async fn make_credential( &mut self, request: MakeCredentialRequest, - ) -> Result { + ) -> Result { // Insert the received UV to be able to return it later in check_user self.requested_uv .get_mut() @@ -82,19 +167,20 @@ impl<'a> Fido2Authenticator<'a> { let response = match response { Ok(x) => x, - Err(e) => return Err(format!("make_credential error: {e:?}").into()), + Err(e) => return Err(MakeCredentialError::Other(format!("{e:?}"))), }; + let attestation_object = response.as_bytes().to_vec(); let authenticator_data = response.auth_data.to_vec(); let attested_credential_data = response .auth_data .attested_credential_data - .ok_or("Missing attested_credential_data")?; + .ok_or(MakeCredentialError::MissingAttestedCredentialData)?; let credential_id = attested_credential_data.credential_id().to_vec(); Ok(MakeCredentialResult { authenticator_data, - attested_credential_data: attested_credential_data.into_iter().collect(), + attestation_object, credential_id, }) } @@ -102,7 +188,7 @@ impl<'a> Fido2Authenticator<'a> { pub async fn get_assertion( &mut self, request: GetAssertionRequest, - ) -> Result { + ) -> Result { // Insert the received UV to be able to return it later in check_user self.requested_uv .get_mut() @@ -139,36 +225,63 @@ impl<'a> Fido2Authenticator<'a> { let response = match response { Ok(x) => x, - Err(e) => return Err(format!("get_assertion error: {e:?}").into()), + Err(e) => return Err(GetAssertionError::Other(format!("{e:?}"))), }; + let selected_credential = self.get_selected_credential()?; let authenticator_data = response.auth_data.to_vec(); - let credential_id = response - .auth_data - .attested_credential_data - .ok_or("Missing attested_credential_data")? - .credential_id() - .to_vec(); + let credential_id = string_to_guid_bytes(&selected_credential.credential.credential_id)?; Ok(GetAssertionResult { credential_id, authenticator_data, signature: response.signature.into(), - user_handle: response.user.ok_or("Missing user")?.id.into(), - selected_credential: self.get_selected_credential()?, + user_handle: response + .user + .ok_or(GetAssertionError::MissingUser)? + .id + .into(), + selected_credential, }) } pub async fn silently_discover_credentials( &mut self, rp_id: String, - ) -> Result> { + ) -> Result, SilentlyDiscoverCredentialsError> { + let enc = self.client.internal.get_encryption_settings()?; let result = self.credential_store.find_credentials(None, rp_id).await?; - Ok(result + + result .into_iter() - .filter_map(|c| c.login?.fido2_credentials) - .flatten() - .collect()) + .map( + |cipher| -> Result, SilentlyDiscoverCredentialsError> { + Ok(Fido2CredentialAutofillView::from_cipher_view(&cipher, &*enc)?) + }, + ) + .flatten_ok() + .collect() + } + + /// Returns all Fido2 credentials that can be used for autofill, in a view + /// tailored for integration with OS autofill systems. + pub async fn credentials_for_autofill( + &mut self, + ) -> Result, CredentialsForAutofillError> { + let enc = self.client.internal.get_encryption_settings()?; + let all_credentials = self.credential_store.all_credentials().await?; + + all_credentials + .into_iter() + .map( + |cipher| -> Result, CredentialsForAutofillError> { + Ok(Fido2CredentialAutofillView::from_cipher_view( + &cipher, &*enc, + )?) + }, + ) + .flatten_ok() + .collect() } pub(super) fn get_authenticator( @@ -197,18 +310,24 @@ impl<'a> Fido2Authenticator<'a> { } } - pub(super) fn get_selected_credential(&self) -> Result { + pub(super) fn get_selected_credential( + &self, + ) -> Result { + let enc = self.client.internal.get_encryption_settings()?; + let cipher = self - .selected_credential + .selected_cipher .lock() .expect("Mutex is not poisoned") .clone() - .ok_or("No selected credential available")?; + .ok_or(GetSelectedCredentialError::NoSelectedCredential)?; - let login = require!(cipher.login.as_ref()); - let creds = require!(login.fido2_credentials.as_ref()); + let creds = cipher.decrypt_fido2_credentials(&*enc)?; - let credential = creds.first().ok_or("No Fido2 credentials found")?.clone(); + let credential = creds + .first() + .ok_or(GetSelectedCredentialError::NoCredentialFound)? + .clone(); Ok(SelectedCredential { cipher, credential }) } @@ -230,12 +349,24 @@ impl passkey::authenticator::CredentialStore for CredentialStoreImpl<'_> { ids: Option<&[passkey::types::webauthn::PublicKeyCredentialDescriptor]>, rp_id: &str, ) -> Result, StatusCode> { + #[derive(Debug, Error)] + enum InnerError { + #[error(transparent)] + VaultLocked(#[from] VaultLocked), + #[error(transparent)] + CipherError(#[from] CipherError), + #[error(transparent)] + CryptoError(#[from] CryptoError), + #[error(transparent)] + Fido2CallbackError(#[from] Fido2CallbackError), + } + // This is just a wrapper around the actual implementation to allow for ? error handling async fn inner( this: &CredentialStoreImpl<'_>, ids: Option<&[passkey::types::webauthn::PublicKeyCredentialDescriptor]>, rp_id: &str, - ) -> Result> { + ) -> Result, InnerError> { let ids: Option>> = ids.map(|ids| ids.iter().map(|id| id.id.clone().into()).collect()); @@ -245,7 +376,11 @@ impl passkey::authenticator::CredentialStore for CredentialStoreImpl<'_> { .find_credentials(ids, rp_id.to_string()) .await?; - let enc = this.authenticator.client.get_encryption_settings()?; + let enc = this + .authenticator + .client + .internal + .get_encryption_settings()?; // Remove any that don't have Fido2 credentials let creds: Vec<_> = ciphers @@ -260,10 +395,10 @@ impl passkey::authenticator::CredentialStore for CredentialStoreImpl<'_> { // When using the credential for authentication we have to ask the user to pick one. if this.create_credential { - creds + Ok(creds .into_iter() - .map(|c| CipherViewContainer::new(c, enc)) - .collect() + .map(|c| CipherViewContainer::new(c, &*enc)) + .collect::>()?) } else { let picked = this .authenticator @@ -273,12 +408,12 @@ impl passkey::authenticator::CredentialStore for CredentialStoreImpl<'_> { // Store the selected credential for later use this.authenticator - .selected_credential + .selected_cipher .lock() .expect("Mutex is not poisoned") .replace(picked.clone()); - Ok(vec![CipherViewContainer::new(picked, enc)?]) + Ok(vec![CipherViewContainer::new(picked, &*enc)?]) } } @@ -295,34 +430,61 @@ impl passkey::authenticator::CredentialStore for CredentialStoreImpl<'_> { cred: Passkey, user: passkey::types::ctap2::make_credential::PublicKeyCredentialUserEntity, rp: passkey::types::ctap2::make_credential::PublicKeyCredentialRpEntity, - _options: passkey::types::ctap2::get_assertion::Options, + options: passkey::types::ctap2::get_assertion::Options, ) -> Result<(), StatusCode> { + #[derive(Debug, Error)] + enum InnerError { + #[error(transparent)] + VaultLocked(#[from] VaultLocked), + #[error(transparent)] + FillCredentialError(#[from] FillCredentialError), + #[error(transparent)] + CipherError(#[from] CipherError), + #[error(transparent)] + CryptoError(#[from] CryptoError), + #[error(transparent)] + Fido2CallbackError(#[from] Fido2CallbackError), + + #[error("No selected credential available")] + NoSelectedCredential, + } + // This is just a wrapper around the actual implementation to allow for ? error handling async fn inner( this: &mut CredentialStoreImpl<'_>, cred: Passkey, user: passkey::types::ctap2::make_credential::PublicKeyCredentialUserEntity, rp: passkey::types::ctap2::make_credential::PublicKeyCredentialRpEntity, - ) -> Result<()> { - let enc = this.authenticator.client.get_encryption_settings()?; + options: passkey::types::ctap2::get_assertion::Options, + ) -> Result<(), InnerError> { + let enc = this + .authenticator + .client + .internal + .get_encryption_settings()?; - let cred = Fido2CredentialFullView::try_from_credential(cred, user, rp)?; + let cred = try_from_credential_full(cred, user, rp, options)?; // Get the previously selected cipher and add the new credential to it - let mut selected: CipherView = this.authenticator.get_selected_credential()?.cipher; - selected.set_new_fido2_credentials(enc, vec![cred])?; + let mut selected: CipherView = this + .authenticator + .selected_cipher + .lock() + .expect("Mutex is not poisoned") + .clone() + .ok_or(InnerError::NoSelectedCredential)?; + + selected.set_new_fido2_credentials(&*enc, vec![cred])?; // Store the updated credential for later use this.authenticator - .selected_credential + .selected_cipher .lock() .expect("Mutex is not poisoned") .replace(selected.clone()); // Encrypt the updated cipher before sending it to the clients to be stored - let key = enc - .get_key(&selected.organization_id) - .ok_or(Error::VaultLocked)?; + let key = enc.get_key(&selected.organization_id)?; let encrypted = selected.encrypt_with_key(key)?; this.authenticator @@ -333,7 +495,7 @@ impl passkey::authenticator::CredentialStore for CredentialStoreImpl<'_> { Ok(()) } - inner(self, cred, user, rp).await.map_err(|e| { + inner(self, cred, user, rp, options).await.map_err(|e| { error!("Error saving credential: {e:?}"); VendorError::try_from(0xF1) .expect("Valid vendor error code") @@ -342,9 +504,36 @@ impl passkey::authenticator::CredentialStore for CredentialStoreImpl<'_> { } async fn update_credential(&mut self, cred: Passkey) -> Result<(), StatusCode> { + #[derive(Debug, Error)] + enum InnerError { + #[error(transparent)] + VaultLocked(#[from] VaultLocked), + #[error(transparent)] + InvalidGuid(#[from] InvalidGuid), + #[error("Credential ID does not match selected credential")] + CredentialIdMismatch, + #[error(transparent)] + FillCredentialError(#[from] FillCredentialError), + #[error(transparent)] + CipherError(#[from] CipherError), + #[error(transparent)] + CryptoError(#[from] CryptoError), + #[error(transparent)] + Fido2CallbackError(#[from] Fido2CallbackError), + #[error(transparent)] + GetSelectedCredentialError(#[from] GetSelectedCredentialError), + } + // This is just a wrapper around the actual implementation to allow for ? error handling - async fn inner(this: &mut CredentialStoreImpl<'_>, cred: Passkey) -> Result<()> { - let enc = this.authenticator.client.get_encryption_settings()?; + async fn inner( + this: &mut CredentialStoreImpl<'_>, + cred: Passkey, + ) -> Result<(), InnerError> { + let enc = this + .authenticator + .client + .internal + .get_encryption_settings()?; // Get the previously selected cipher and update the credential let selected = this.authenticator.get_selected_credential()?; @@ -353,25 +542,23 @@ impl passkey::authenticator::CredentialStore for CredentialStoreImpl<'_> { let new_id: &Vec = &cred.credential_id; let selected_id = string_to_guid_bytes(&selected.credential.credential_id)?; if new_id != &selected_id { - return Err("Credential ID does not match selected credential".into()); + return Err(InnerError::CredentialIdMismatch); } - let cred = selected.credential.fill_with_credential(cred)?; + let cred = fill_with_credential(&selected.credential, cred)?; let mut selected = selected.cipher; - selected.set_new_fido2_credentials(enc, vec![cred])?; + selected.set_new_fido2_credentials(&*enc, vec![cred])?; // Store the updated credential for later use this.authenticator - .selected_credential + .selected_cipher .lock() .expect("Mutex is not poisoned") .replace(selected.clone()); // Encrypt the updated cipher before sending it to the clients to be stored - let key = enc - .get_key(&selected.organization_id) - .ok_or(Error::VaultLocked)?; + let key = enc.get_key(&selected.organization_id)?; let encrypted = selected.encrypt_with_key(key)?; this.authenticator @@ -421,10 +608,10 @@ impl passkey::authenticator::UserValidationMethod for UserValidationMethodImpl<' let result = match hint { UIHint::RequestNewCredential(user, rp) => { - let new_credential = Fido2CredentialNewView::try_from_credential(user, rp) + let new_credential = try_from_credential_new_view(user, rp) .map_err(|_| Ctap2Error::InvalidCredential)?; - let cipher_view = self + let (cipher_view, user_check) = self .authenticator .user_interface .check_user_and_pick_credential_for_creation(options, new_credential) @@ -432,15 +619,12 @@ impl passkey::authenticator::UserValidationMethod for UserValidationMethodImpl<' .map_err(|_| Ctap2Error::OperationDenied)?; self.authenticator - .selected_credential + .selected_cipher .lock() .expect("Mutex is not poisoned") .replace(cipher_view); - Ok(CheckUserResult { - user_present: true, - user_verified: verification != UV::Discouraged, - }) + Ok(user_check) } _ => { self.authenticator diff --git a/crates/bitwarden/src/platform/fido2/client.rs b/crates/bitwarden-fido/src/client.rs similarity index 79% rename from crates/bitwarden/src/platform/fido2/client.rs rename to crates/bitwarden-fido/src/client.rs index f2f5703cf..ac4330be7 100644 --- a/crates/bitwarden/src/platform/fido2/client.rs +++ b/crates/bitwarden-fido/src/client.rs @@ -1,29 +1,49 @@ -use reqwest::Url; +use passkey::client::WebauthnError; +use thiserror::Error; use super::{ + authenticator::GetSelectedCredentialError, get_string_name_from_enum, types::{ AuthenticatorAssertionResponse, AuthenticatorAttestationResponse, ClientData, - ClientExtensionResults, CredPropsResult, + ClientExtensionResults, CredPropsResult, Origin, }, Fido2Authenticator, PublicKeyCredentialAuthenticatorAssertionResponse, PublicKeyCredentialAuthenticatorAttestationResponse, }; -use crate::error::Result; +use crate::types::InvalidOriginError; + +#[derive(Debug, Error)] +pub enum Fido2ClientError { + #[error(transparent)] + InvalidOrigin(#[from] InvalidOriginError), + #[error(transparent)] + Serde(#[from] serde_json::Error), + #[error(transparent)] + GetSelectedCredentialError(#[from] GetSelectedCredentialError), + + #[error("Webauthn error: {0:?}")] + Webauthn(WebauthnError), +} + +impl From for Fido2ClientError { + fn from(e: WebauthnError) -> Self { + Self::Webauthn(e) + } +} pub struct Fido2Client<'a> { - pub(crate) authenticator: Fido2Authenticator<'a>, + pub authenticator: Fido2Authenticator<'a>, } impl<'a> Fido2Client<'a> { pub async fn register( &mut self, - origin: String, + origin: Origin, request: String, client_data: ClientData, - ) -> Result { - let origin = Url::parse(&origin).map_err(|e| format!("Invalid origin: {}", e))?; - + ) -> Result { + let origin: passkey::client::Origin = origin.try_into()?; let request: passkey::types::webauthn::CredentialCreationOptions = serde_json::from_str(&request)?; @@ -42,7 +62,7 @@ impl<'a> Fido2Client<'a> { let rp_id = request.public_key.rp.id.clone(); let mut client = passkey::client::Client::new(self.authenticator.get_authenticator(true)); - let result = client.register(&origin, request, client_data).await?; + let result = client.register(origin, request, client_data).await?; Ok(PublicKeyCredentialAuthenticatorAttestationResponse { id: result.id, @@ -73,12 +93,11 @@ impl<'a> Fido2Client<'a> { pub async fn authenticate( &mut self, - origin: String, + origin: Origin, request: String, client_data: ClientData, - ) -> Result { - let origin = Url::parse(&origin).map_err(|e| format!("Invalid origin: {}", e))?; - + ) -> Result { + let origin: passkey::client::Origin = origin.try_into()?; let request: passkey::types::webauthn::CredentialRequestOptions = serde_json::from_str(&request)?; @@ -91,7 +110,7 @@ impl<'a> Fido2Client<'a> { .replace(uv); let mut client = passkey::client::Client::new(self.authenticator.get_authenticator(false)); - let result = client.authenticate(&origin, request, client_data).await?; + let result = client.authenticate(origin, request, client_data).await?; Ok(PublicKeyCredentialAuthenticatorAssertionResponse { id: result.id, diff --git a/crates/bitwarden-fido/src/client_fido.rs b/crates/bitwarden-fido/src/client_fido.rs new file mode 100644 index 000000000..84b3de6a1 --- /dev/null +++ b/crates/bitwarden-fido/src/client_fido.rs @@ -0,0 +1,67 @@ +use bitwarden_core::Client; +use bitwarden_vault::CipherView; +use thiserror::Error; + +use crate::{ + Fido2Authenticator, Fido2Client, Fido2CredentialAutofillView, Fido2CredentialAutofillViewError, + Fido2CredentialStore, Fido2UserInterface, +}; + +pub struct ClientFido2<'a> { + #[allow(dead_code)] + pub(crate) client: &'a Client, +} + +#[derive(Debug, Error)] +pub enum DecryptFido2AutofillCredentialsError { + #[error(transparent)] + VaultLocked(#[from] bitwarden_core::VaultLocked), + #[error(transparent)] + Fido2CredentialAutofillViewError(#[from] Fido2CredentialAutofillViewError), +} + +impl<'a> ClientFido2<'a> { + pub fn new(client: &'a Client) -> Self { + Self { client } + } + + pub fn create_authenticator( + &'a self, + user_interface: &'a dyn Fido2UserInterface, + credential_store: &'a dyn Fido2CredentialStore, + ) -> Fido2Authenticator<'a> { + Fido2Authenticator::new(self.client, user_interface, credential_store) + } + + pub fn create_client( + &'a self, + user_interface: &'a dyn Fido2UserInterface, + credential_store: &'a dyn Fido2CredentialStore, + ) -> Fido2Client<'a> { + Fido2Client { + authenticator: self.create_authenticator(user_interface, credential_store), + } + } + + pub fn decrypt_fido2_autofill_credentials( + &'a self, + cipher_view: CipherView, + ) -> Result, DecryptFido2AutofillCredentialsError> { + let enc = self.client.internal.get_encryption_settings()?; + + Ok(Fido2CredentialAutofillView::from_cipher_view( + &cipher_view, + &*enc, + )?) + } +} + +pub trait ClientFido2Ext<'a> { + fn fido2(&'a self) -> ClientFido2<'a>; +} + +impl<'a> ClientFido2Ext<'a> for Client { + fn fido2(&'a self) -> ClientFido2<'a> { + ClientFido2::new(self) + } +} diff --git a/crates/bitwarden-fido/src/crypto.rs b/crates/bitwarden-fido/src/crypto.rs new file mode 100644 index 000000000..7d2ccab98 --- /dev/null +++ b/crates/bitwarden-fido/src/crypto.rs @@ -0,0 +1,102 @@ +use coset::{iana, CoseKey}; +use p256::{ + pkcs8::{DecodePrivateKey, EncodePrivateKey}, + SecretKey, +}; +use passkey::authenticator::{private_key_from_cose_key, CoseKeyPair}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum CoseKeyToPkcs8Error { + #[error("Failed to extract private key from cose_key")] + FailedToExtractPrivateKeyFromCoseKey, + #[error("Failed to convert P256 private key to PKC8")] + FailedToConvertP256PrivateKeyToPkcs8, +} + +pub(crate) fn cose_key_to_pkcs8(cose_key: &CoseKey) -> Result, CoseKeyToPkcs8Error> { + // cose_key. + let secret_key = private_key_from_cose_key(cose_key).map_err(|error| { + log::error!("Failed to extract private key from cose_key: {:?}", error); + CoseKeyToPkcs8Error::FailedToExtractPrivateKeyFromCoseKey + })?; + + let vec = secret_key + .to_pkcs8_der() + .map_err(|error| { + log::error!("Failed to convert P256 private key to PKC8: {:?}", error); + CoseKeyToPkcs8Error::FailedToConvertP256PrivateKeyToPkcs8 + })? + .as_bytes() + .to_vec(); + + Ok(vec) +} + +#[derive(Debug, Error)] +#[error("Failed to extract private key from secret_key")] +pub struct PrivateKeyFromSecretKeyError; + +pub fn pkcs8_to_cose_key(secret_key: &[u8]) -> Result { + let secret_key = SecretKey::from_pkcs8_der(secret_key).map_err(|error| { + log::error!("Failed to extract private key from secret_key: {:?}", error); + PrivateKeyFromSecretKeyError + })?; + + let cose_key_pair = CoseKeyPair::from_secret_key(&secret_key, iana::Algorithm::ES256); + Ok(cose_key_pair.private) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn private_key_for_testing() -> CoseKey { + // Hardcoded CoseKey for testing purposes + let bytes = vec![ + 166, 1, 2, 3, 38, 32, 1, 33, 88, 32, 200, 30, 161, 146, 196, 121, 165, 149, 92, 232, + 49, 48, 245, 253, 73, 234, 204, 3, 209, 153, 166, 77, 59, 232, 70, 16, 206, 77, 84, + 156, 28, 77, 34, 88, 32, 82, 141, 165, 28, 241, 82, 31, 33, 183, 206, 29, 91, 93, 111, + 216, 216, 26, 62, 211, 49, 191, 86, 238, 118, 241, 124, 131, 106, 214, 95, 170, 160, + 35, 88, 32, 147, 171, 4, 49, 68, 170, 47, 51, 74, 211, 94, 40, 212, 244, 95, 55, 154, + 92, 171, 241, 0, 55, 84, 151, 79, 244, 151, 198, 135, 45, 97, 238, + ]; + + ::from_slice(bytes.as_slice()).unwrap() + } + + #[test] + fn test_cose_key_to_pkcs8_and_back() { + let cose_key = private_key_for_testing(); + + let pkcs8 = cose_key_to_pkcs8(&cose_key).expect("CoseKey to PKCS8 failed"); + let cose_key2 = pkcs8_to_cose_key(&pkcs8).expect("PKCS8 to CoseKey failed"); + + assert_eq!(cose_key, cose_key2); + } + + fn pkcs8_key_for_testing() -> Vec { + vec![ + 0x30, 0x81, 0x87, 0x02, 0x01, 0x00, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x04, + 0x6d, 0x30, 0x6b, 0x02, 0x01, 0x01, 0x04, 0x20, 0x06, 0x76, 0x5e, 0x85, 0xe0, 0x7f, + 0xef, 0x43, 0xaa, 0x17, 0xe0, 0x7a, 0xd7, 0x85, 0x63, 0x01, 0x80, 0x70, 0x8c, 0x6c, + 0x61, 0x43, 0x7d, 0xc3, 0xb1, 0xe6, 0xf9, 0x09, 0x24, 0xeb, 0x1f, 0xf5, 0xa1, 0x44, + 0x03, 0x42, 0x00, 0x04, 0x35, 0x9a, 0x52, 0xf3, 0x82, 0x44, 0x66, 0x5f, 0x3f, 0xe2, + 0xc4, 0x0b, 0x1c, 0x16, 0x34, 0xc5, 0x60, 0x07, 0x3a, 0x25, 0xfe, 0x7e, 0x7f, 0x7f, + 0xda, 0xd4, 0x1c, 0x36, 0x90, 0x00, 0xee, 0xb1, 0x8e, 0x92, 0xb3, 0xac, 0x91, 0x7f, + 0xb1, 0x8c, 0xa4, 0x85, 0xe7, 0x03, 0x07, 0xd1, 0xf5, 0x5b, 0xd3, 0x7b, 0xc3, 0x56, + 0x11, 0xdf, 0xbc, 0x7a, 0x97, 0x70, 0x32, 0x4b, 0x3c, 0x84, 0x05, 0x71, + ] + } + + #[test] + fn test_pkcs8_to_cose_key_and_back() { + let pkcs8 = pkcs8_key_for_testing(); + + let cose_key = pkcs8_to_cose_key(&pkcs8).expect("PKCS8 to CoseKey failed"); + let pkcs8_2 = cose_key_to_pkcs8(&cose_key).expect("CoseKey to PKCS8 failed"); + + assert_eq!(pkcs8, pkcs8_2); + } +} diff --git a/crates/bitwarden-fido/src/lib.rs b/crates/bitwarden-fido/src/lib.rs new file mode 100644 index 000000000..be1dfdb53 --- /dev/null +++ b/crates/bitwarden-fido/src/lib.rs @@ -0,0 +1,297 @@ +use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; +use bitwarden_crypto::KeyContainer; +use bitwarden_vault::{ + CipherError, CipherView, Fido2CredentialFullView, Fido2CredentialNewView, Fido2CredentialView, +}; +use crypto::{CoseKeyToPkcs8Error, PrivateKeyFromSecretKeyError}; +use passkey::types::{ctap2::Aaguid, Passkey}; + +#[cfg(feature = "uniffi")] +uniffi::setup_scaffolding!(); +#[cfg(feature = "uniffi")] +mod uniffi_support; + +mod authenticator; +mod client; +mod client_fido; +mod crypto; +mod traits; +mod types; +pub use authenticator::{ + CredentialsForAutofillError, Fido2Authenticator, GetAssertionError, MakeCredentialError, + SilentlyDiscoverCredentialsError, +}; +pub use client::{Fido2Client, Fido2ClientError}; +pub use client_fido::{ClientFido2, ClientFido2Ext, DecryptFido2AutofillCredentialsError}; +pub use passkey::authenticator::UIHint; +use thiserror::Error; +pub use traits::{ + CheckUserOptions, CheckUserResult, Fido2CallbackError, Fido2CredentialStore, + Fido2UserInterface, Verification, +}; +pub use types::{ + AuthenticatorAssertionResponse, AuthenticatorAttestationResponse, ClientData, + Fido2CredentialAutofillView, Fido2CredentialAutofillViewError, GetAssertionRequest, + GetAssertionResult, MakeCredentialRequest, MakeCredentialResult, Options, Origin, + PublicKeyCredentialAuthenticatorAssertionResponse, + PublicKeyCredentialAuthenticatorAttestationResponse, PublicKeyCredentialRpEntity, + PublicKeyCredentialUserEntity, UnverifiedAssetLink, +}; + +use self::crypto::{cose_key_to_pkcs8, pkcs8_to_cose_key}; + +// This is the AAGUID for the Bitwarden Passkey provider (d548826e-79b4-db40-a3d8-11116f7e8349) +// It is used for the Relaying Parties to identify the authenticator during registration +const AAGUID: Aaguid = Aaguid([ + 0xd5, 0x48, 0x82, 0x6e, 0x79, 0xb4, 0xdb, 0x40, 0xa3, 0xd8, 0x11, 0x11, 0x6f, 0x7e, 0x83, 0x49, +]); + +#[allow(dead_code)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +pub struct SelectedCredential { + cipher: CipherView, + credential: Fido2CredentialView, +} + +// This container is needed so we can properly implement the TryFrom trait for Passkey +// Otherwise we need to decrypt the Fido2 credentials every time we create a CipherView +#[derive(Clone)] +pub(crate) struct CipherViewContainer { + cipher: CipherView, + fido2_credentials: Vec, +} + +impl CipherViewContainer { + fn new(cipher: CipherView, enc: &dyn KeyContainer) -> Result { + let fido2_credentials = cipher.get_fido2_credentials(enc)?; + Ok(Self { + cipher, + fido2_credentials, + }) + } +} + +#[derive(Debug, Error)] +pub enum Fido2Error { + #[error(transparent)] + DecodeError(#[from] base64::DecodeError), + + #[error(transparent)] + UnknownEnum(#[from] UnknownEnum), + + #[error(transparent)] + InvalidGuid(#[from] InvalidGuid), + + #[error(transparent)] + PrivateKeyFromSecretKeyError(#[from] PrivateKeyFromSecretKeyError), + + #[error("No Fido2 credentials found")] + NoFido2CredentialsFound, +} + +impl TryFrom for Passkey { + type Error = Fido2Error; + + fn try_from(value: CipherViewContainer) -> Result { + let cred = value + .fido2_credentials + .first() + .ok_or(Fido2Error::NoFido2CredentialsFound)?; + + try_from_credential_full_view(cred.clone()) + } +} + +fn try_from_credential_full_view(value: Fido2CredentialFullView) -> Result { + let counter: u32 = value.counter.parse().expect("Invalid counter"); + let counter = (counter != 0).then_some(counter); + let key_value = URL_SAFE_NO_PAD.decode(value.key_value)?; + let user_handle = value + .user_handle + .map(|u| URL_SAFE_NO_PAD.decode(u)) + .transpose()?; + + let key = pkcs8_to_cose_key(&key_value)?; + + Ok(Passkey { + key, + credential_id: string_to_guid_bytes(&value.credential_id)?.into(), + rp_id: value.rp_id.clone(), + user_handle: user_handle.map(|u| u.into()), + counter, + }) +} + +#[derive(Debug, Error)] +pub enum FillCredentialError { + #[error(transparent)] + InvalidInputLength(#[from] InvalidInputLength), + #[error(transparent)] + CoseKeyToPkcs8Error(#[from] CoseKeyToPkcs8Error), +} + +pub fn fill_with_credential( + view: &Fido2CredentialView, + value: Passkey, +) -> Result { + let cred_id: Vec = value.credential_id.into(); + let user_handle = value + .user_handle + .map(|u| URL_SAFE_NO_PAD.encode(u.to_vec())); + let key_value = URL_SAFE_NO_PAD.encode(cose_key_to_pkcs8(&value.key)?); + + Ok(Fido2CredentialFullView { + credential_id: guid_bytes_to_string(&cred_id)?, + key_type: "public-key".to_owned(), + key_algorithm: "ECDSA".to_owned(), + key_curve: "P-256".to_owned(), + key_value, + rp_id: value.rp_id, + rp_name: view.rp_name.clone(), + user_handle, + + counter: value.counter.unwrap_or(0).to_string(), + user_name: view.user_name.clone(), + user_display_name: view.user_display_name.clone(), + discoverable: "true".to_owned(), + creation_date: chrono::offset::Utc::now(), + }) +} + +pub(crate) fn try_from_credential_new_view( + user: &passkey::types::ctap2::make_credential::PublicKeyCredentialUserEntity, + rp: &passkey::types::ctap2::make_credential::PublicKeyCredentialRpEntity, +) -> Result { + let cred_id: Vec = vec![0; 16]; + let user_handle = URL_SAFE_NO_PAD.encode(user.id.to_vec()); + + Ok(Fido2CredentialNewView { + // TODO: Why do we have a credential id here? + credential_id: guid_bytes_to_string(&cred_id)?, + key_type: "public-key".to_owned(), + key_algorithm: "ECDSA".to_owned(), + key_curve: "P-256".to_owned(), + rp_id: rp.id.clone(), + rp_name: rp.name.clone(), + user_handle: Some(user_handle), + + counter: 0.to_string(), + user_name: user.name.clone(), + user_display_name: user.display_name.clone(), + creation_date: chrono::offset::Utc::now(), + }) +} + +pub(crate) fn try_from_credential_full( + value: Passkey, + user: passkey::types::ctap2::make_credential::PublicKeyCredentialUserEntity, + rp: passkey::types::ctap2::make_credential::PublicKeyCredentialRpEntity, + options: passkey::types::ctap2::get_assertion::Options, +) -> Result { + let cred_id: Vec = value.credential_id.into(); + let key_value = URL_SAFE_NO_PAD.encode(cose_key_to_pkcs8(&value.key)?); + let user_handle = URL_SAFE_NO_PAD.encode(user.id.to_vec()); + + Ok(Fido2CredentialFullView { + credential_id: guid_bytes_to_string(&cred_id)?, + key_type: "public-key".to_owned(), + key_algorithm: "ECDSA".to_owned(), + key_curve: "P-256".to_owned(), + key_value, + rp_id: value.rp_id, + rp_name: rp.name, + user_handle: Some(user_handle), + + counter: value.counter.unwrap_or(0).to_string(), + user_name: user.name, + user_display_name: user.display_name, + discoverable: options.rk.to_string(), + creation_date: chrono::offset::Utc::now(), + }) +} + +#[derive(Debug, Error)] +#[error("Input should be a 16 byte array")] +pub struct InvalidInputLength; + +pub fn guid_bytes_to_string(source: &[u8]) -> Result { + if source.len() != 16 { + return Err(InvalidInputLength); + } + Ok(uuid::Uuid::from_bytes(source.try_into().expect("Invalid length")).to_string()) +} + +#[derive(Debug, Error)] +#[error("Invalid GUID")] +pub struct InvalidGuid; + +pub fn string_to_guid_bytes(source: &str) -> Result, InvalidGuid> { + if source.starts_with("b64.") { + let bytes = URL_SAFE_NO_PAD + .decode(source.trim_start_matches("b64.")) + .map_err(|_| InvalidGuid)?; + Ok(bytes) + } else { + let Ok(uuid) = uuid::Uuid::try_parse(source) else { + return Err(InvalidGuid); + }; + Ok(uuid.as_bytes().to_vec()) + } +} + +#[derive(Debug, Error)] +#[error("Unknown enum value")] +pub struct UnknownEnum; + +// Some utilities to convert back and forth between enums and strings +fn get_enum_from_string_name(s: &str) -> Result { + let serialized = format!(r#""{}""#, s); + let deserialized: T = serde_json::from_str(&serialized).map_err(|_| UnknownEnum)?; + Ok(deserialized) +} + +fn get_string_name_from_enum(s: impl serde::Serialize) -> Result { + let serialized = serde_json::to_string(&s)?; + let deserialized: String = serde_json::from_str(&serialized)?; + Ok(deserialized) +} + +#[cfg(test)] +mod tests { + use passkey::types::webauthn::AuthenticatorAttachment; + + use super::{get_enum_from_string_name, get_string_name_from_enum}; + + #[test] + fn test_enum_string_conversion_works_as_expected() { + assert_eq!( + get_string_name_from_enum(AuthenticatorAttachment::CrossPlatform).unwrap(), + "cross-platform" + ); + + assert_eq!( + get_enum_from_string_name::("cross-platform").unwrap(), + AuthenticatorAttachment::CrossPlatform + ); + } + + #[test] + fn string_to_guid_with_uuid_works() { + let uuid = "d548826e-79b4-db40-a3d8-11116f7e8349"; + let bytes = super::string_to_guid_bytes(uuid).unwrap(); + assert_eq!( + bytes, + vec![213, 72, 130, 110, 121, 180, 219, 64, 163, 216, 17, 17, 111, 126, 131, 73] + ); + } + + #[test] + fn string_to_guid_with_b64_works() { + let b64 = "b64.1UiCbnm020Cj2BERb36DSQ"; + let bytes = super::string_to_guid_bytes(b64).unwrap(); + assert_eq!( + bytes, + vec![213, 72, 130, 110, 121, 180, 219, 64, 163, 216, 17, 17, 111, 126, 131, 73] + ); + } +} diff --git a/crates/bitwarden/src/platform/fido2/traits.rs b/crates/bitwarden-fido/src/traits.rs similarity index 88% rename from crates/bitwarden/src/platform/fido2/traits.rs rename to crates/bitwarden-fido/src/traits.rs index cc27e4bd6..215e5934d 100644 --- a/crates/bitwarden/src/platform/fido2/traits.rs +++ b/crates/bitwarden-fido/src/traits.rs @@ -1,11 +1,7 @@ +use bitwarden_vault::{Cipher, CipherView, Fido2CredentialNewView}; use passkey::authenticator::UIHint; use thiserror::Error; -use crate::{ - error::Result, - vault::{Cipher, CipherView, Fido2CredentialNewView}, -}; - #[derive(Debug, Error)] pub enum Fido2CallbackError { #[error("The operation requires user interaction")] @@ -33,7 +29,7 @@ pub trait Fido2UserInterface: Send + Sync { &self, options: CheckUserOptions, new_credential: Fido2CredentialNewView, - ) -> Result; + ) -> Result<(CipherView, CheckUserResult), Fido2CallbackError>; async fn is_verification_enabled(&self) -> bool; } @@ -45,6 +41,8 @@ pub trait Fido2CredentialStore: Send + Sync { rip_id: String, ) -> Result, Fido2CallbackError>; + async fn all_credentials(&self) -> Result, Fido2CallbackError>; + async fn save_credential(&self, cred: Cipher) -> Result<(), Fido2CallbackError>; } diff --git a/crates/bitwarden/src/platform/fido2/types.rs b/crates/bitwarden-fido/src/types.rs similarity index 61% rename from crates/bitwarden/src/platform/fido2/types.rs rename to crates/bitwarden-fido/src/types.rs index bee66dbe8..409db7a98 100644 --- a/crates/bitwarden/src/platform/fido2/types.rs +++ b/crates/bitwarden-fido/src/types.rs @@ -1,7 +1,102 @@ +use std::borrow::Cow; + +use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; +use bitwarden_crypto::KeyContainer; +use bitwarden_vault::{CipherError, CipherView}; use passkey::types::webauthn::UserVerificationRequirement; -use serde::Serialize; +use reqwest::Url; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use super::{ + get_enum_from_string_name, string_to_guid_bytes, InvalidGuid, SelectedCredential, UnknownEnum, + Verification, +}; + +#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +pub struct Fido2CredentialAutofillView { + pub credential_id: Vec, + pub cipher_id: uuid::Uuid, + pub rp_id: String, + pub user_name_for_ui: Option, + pub user_handle: Vec, +} + +trait NoneWhitespace { + /// Convert only whitespace to None + fn none_whitespace(&self) -> Option; +} + +impl NoneWhitespace for String { + fn none_whitespace(&self) -> Option { + match self.trim() { + "" => None, + s => Some(s.to_owned()), + } + } +} + +impl NoneWhitespace for Option { + fn none_whitespace(&self) -> Option { + self.as_ref().and_then(|s| s.none_whitespace()) + } +} + +#[derive(Debug, Error)] +pub enum Fido2CredentialAutofillViewError { + #[error( + "Autofill credentials can only be created from existing ciphers that have a cipher id" + )] + MissingCipherId, + + #[error(transparent)] + InvalidGuid(#[from] InvalidGuid), -use super::{get_enum_from_string_name, SelectedCredential, Verification}; + #[error(transparent)] + CipherError(#[from] CipherError), + + #[error(transparent)] + Base64DecodeError(#[from] base64::DecodeError), +} + +impl Fido2CredentialAutofillView { + pub fn from_cipher_view( + cipher: &CipherView, + enc: &dyn KeyContainer, + ) -> Result, Fido2CredentialAutofillViewError> { + let credentials = cipher.decrypt_fido2_credentials(enc)?; + + credentials + .into_iter() + .filter_map(|c| -> Option> { + c.user_handle + .map(|u| URL_SAFE_NO_PAD.decode(u)) + .map(|user_handle| { + Ok(Fido2CredentialAutofillView { + credential_id: string_to_guid_bytes(&c.credential_id)?, + cipher_id: cipher + .id + .ok_or(Fido2CredentialAutofillViewError::MissingCipherId)?, + rp_id: c.rp_id.clone(), + user_handle: user_handle?, + user_name_for_ui: c + .user_name + .none_whitespace() + .or(c.user_display_name.none_whitespace()) + .or(cipher + .login + .as_ref() + .and_then(|l| l.username.none_whitespace())) + .or(cipher.name.none_whitespace()), + }) + }) + }) + .collect() + } +} #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] pub struct PublicKeyCredentialRpEntity { @@ -22,16 +117,26 @@ pub struct PublicKeyCredentialParameters { pub alg: i64, } +#[derive(Debug, Error)] +pub enum PublicKeyCredentialParametersError { + #[error("Invalid algorithm")] + InvalidAlgorithm, + + #[error("Unknown type")] + UnknownEnum(#[from] UnknownEnum), +} + impl TryFrom for passkey::types::webauthn::PublicKeyCredentialParameters { - type Error = crate::error::Error; + type Error = PublicKeyCredentialParametersError; fn try_from(value: PublicKeyCredentialParameters) -> Result { use coset::iana::EnumI64; Ok(Self { ty: get_enum_from_string_name(&value.ty)?, - alg: coset::iana::Algorithm::from_i64(value.alg).ok_or("Invalid algorithm")?, + alg: coset::iana::Algorithm::from_i64(value.alg) + .ok_or(PublicKeyCredentialParametersError::InvalidAlgorithm)?, }) } } @@ -46,7 +151,7 @@ pub struct PublicKeyCredentialDescriptor { impl TryFrom for passkey::types::webauthn::PublicKeyCredentialDescriptor { - type Error = crate::error::Error; + type Error = UnknownEnum; fn try_from(value: PublicKeyCredentialDescriptor) -> Result { Ok(Self { @@ -80,7 +185,7 @@ pub struct MakeCredentialRequest { #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] pub struct MakeCredentialResult { pub authenticator_data: Vec, - pub attested_credential_data: Vec, + pub attestation_object: Vec, pub credential_id: Vec, } @@ -257,11 +362,72 @@ pub struct AuthenticatorAssertionResponse { pub user_handle: Vec, } +#[derive(Debug, Error)] +#[error("Invalid origin: {0}")] +pub struct InvalidOriginError(String); + +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +/// An Unverified asset link. +pub struct UnverifiedAssetLink { + /// Application package name. + package_name: String, + /// Fingerprint to compare. + sha256_cert_fingerprint: String, + /// Host to lookup the well known asset link. + host: String, + /// When sourced from the application statement list or parsed from host for passkeys. + /// Will be generated from `host` if not provided. + asset_link_url: Option, +} + +#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] +/// The origin of a WebAuthn request. +pub enum Origin { + /// A Url, meant for a request in the web browser. + Web(String), + /// An android digital asset fingerprint. + /// Meant for a request coming from an android application. + Android(UnverifiedAssetLink), +} + +impl<'a> TryFrom for passkey::client::Origin<'a> { + type Error = InvalidOriginError; + + fn try_from(value: Origin) -> Result { + Ok(match value { + Origin::Web(url) => { + let url = Url::parse(&url).map_err(|e| InvalidOriginError(format!("{}", e)))?; + passkey::client::Origin::Web(Cow::Owned(url)) + } + Origin::Android(link) => passkey::client::Origin::Android(link.try_into()?), + }) + } +} + +impl<'a> TryFrom for passkey::client::UnverifiedAssetLink<'a> { + type Error = InvalidOriginError; + + fn try_from(value: UnverifiedAssetLink) -> Result { + let asset_link_url = match value.asset_link_url { + Some(url) => Some(Url::parse(&url).map_err(|e| InvalidOriginError(format!("{}", e)))?), + None => None, + }; + + passkey::client::UnverifiedAssetLink::new( + Cow::from(value.package_name), + value.sha256_cert_fingerprint.as_str(), + Cow::from(value.host), + asset_link_url, + ) + .map_err(|e| InvalidOriginError(format!("{:?}", e))) + } +} + #[cfg(test)] mod tests { use serde::{Deserialize, Serialize}; - use crate::platform::fido2::types::AndroidClientData; + use super::AndroidClientData; // This is a stripped down of the passkey-rs implementation, to test the // serialization of the `ClientData` enum, and to make sure that () and None diff --git a/crates/bitwarden-fido/src/uniffi_support.rs b/crates/bitwarden-fido/src/uniffi_support.rs new file mode 100644 index 000000000..5bf94d09f --- /dev/null +++ b/crates/bitwarden-fido/src/uniffi_support.rs @@ -0,0 +1,3 @@ +use uuid::Uuid; + +uniffi::ffi_converter_forward!(Uuid, bitwarden_core::UniFfiTag, crate::UniFfiTag); diff --git a/crates/bitwarden-fido/uniffi.toml b/crates/bitwarden-fido/uniffi.toml new file mode 100644 index 000000000..395a85b21 --- /dev/null +++ b/crates/bitwarden-fido/uniffi.toml @@ -0,0 +1,9 @@ +[bindings.kotlin] +package_name = "com.bitwarden.fido" +generate_immutable_records = true +android = true + +[bindings.swift] +ffi_module_name = "BitwardenFidoFFI" +module_name = "BitwardenFido" +generate_immutable_records = true diff --git a/crates/bitwarden-generators/Cargo.toml b/crates/bitwarden-generators/Cargo.toml index ac7c2a65a..24e5d5ebc 100644 --- a/crates/bitwarden-generators/Cargo.toml +++ b/crates/bitwarden-generators/Cargo.toml @@ -17,21 +17,19 @@ keywords.workspace = true uniffi = ["dep:uniffi"] # Uniffi bindings [dependencies] +bitwarden-core = { workspace = true, features = ["internal"] } bitwarden-crypto = { workspace = true } rand = ">=0.8.5, <0.9" -reqwest = { version = ">=0.12, <0.13", features = [ - "http2", - "json", -], default-features = false } -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" -thiserror = ">=1.0.40, <2.0" -uniffi = { version = "=0.27.2", optional = true } +reqwest = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +thiserror = { workspace = true } +uniffi = { workspace = true, optional = true } [dev-dependencies] rand_chacha = "0.3.1" -tokio = { version = "1.36.0", features = ["rt", "macros"] } +tokio = { workspace = true, features = ["rt"] } wiremock = "0.6.0" [lints] diff --git a/crates/bitwarden/src/tool/client_generator.rs b/crates/bitwarden-generators/src/client_generator.rs similarity index 57% rename from crates/bitwarden/src/tool/client_generator.rs rename to crates/bitwarden-generators/src/client_generator.rs index 16c786f9b..d2e2b7483 100644 --- a/crates/bitwarden/src/tool/client_generator.rs +++ b/crates/bitwarden-generators/src/client_generator.rs @@ -1,16 +1,20 @@ -use bitwarden_generators::{passphrase, password, username}; +use bitwarden_core::Client; use crate::{ - error::Result, - generators::{PassphraseGeneratorRequest, PasswordGeneratorRequest, UsernameGeneratorRequest}, - Client, + passphrase::passphrase, password::password, username::username, PassphraseError, + PassphraseGeneratorRequest, PasswordError, PasswordGeneratorRequest, UsernameError, + UsernameGeneratorRequest, }; pub struct ClientGenerator<'a> { - pub(crate) client: &'a crate::Client, + client: &'a Client, } impl<'a> ClientGenerator<'a> { + fn new(client: &'a Client) -> Self { + Self { client } + } + /// Generates a random password. /// /// The character sets and password length can be customized using the `input` parameter. @@ -18,8 +22,10 @@ impl<'a> ClientGenerator<'a> { /// # Examples /// /// ``` - /// use bitwarden::{Client, generators::PasswordGeneratorRequest, error::Result}; - /// async fn test() -> Result<()> { + /// use bitwarden_core::Client; + /// use bitwarden_generators::{ClientGeneratorExt, PassphraseError, PasswordGeneratorRequest}; + /// + /// async fn test() -> Result<(), PassphraseError> { /// let input = PasswordGeneratorRequest { /// lowercase: true, /// uppercase: true, @@ -27,13 +33,13 @@ impl<'a> ClientGenerator<'a> { /// length: 20, /// ..Default::default() /// }; - /// let password = Client::new(None).generator().password(input).await.unwrap(); + /// let password = Client::new(None).generator().password(input).unwrap(); /// println!("{}", password); /// Ok(()) /// } /// ``` - pub async fn password(&self, input: PasswordGeneratorRequest) -> Result { - Ok(password(input)?) + pub fn password(&self, input: PasswordGeneratorRequest) -> Result { + password(input) } /// Generates a random passphrase. @@ -46,19 +52,21 @@ impl<'a> ClientGenerator<'a> { /// # Examples /// /// ``` - /// use bitwarden::{Client, generators::PassphraseGeneratorRequest, error::Result}; - /// async fn test() -> Result<()> { + /// use bitwarden_core::Client; + /// use bitwarden_generators::{ClientGeneratorExt, PassphraseError, PassphraseGeneratorRequest}; + /// + /// async fn test() -> Result<(), PassphraseError> { /// let input = PassphraseGeneratorRequest { /// num_words: 4, /// ..Default::default() /// }; - /// let passphrase = Client::new(None).generator().passphrase(input).await.unwrap(); + /// let passphrase = Client::new(None).generator().passphrase(input).unwrap(); /// println!("{}", passphrase); /// Ok(()) /// } /// ``` - pub async fn passphrase(&self, input: PassphraseGeneratorRequest) -> Result { - Ok(passphrase(input)?) + pub fn passphrase(&self, input: PassphraseGeneratorRequest) -> Result { + passphrase(input) } /// Generates a random username. @@ -69,8 +77,10 @@ impl<'a> ClientGenerator<'a> { /// will use third-party services, which may require a specific setup or API key. /// /// ``` - /// use bitwarden::{Client, generators::{UsernameGeneratorRequest}, error::Result}; - /// async fn test() -> Result<()> { + /// use bitwarden_core::Client; + /// use bitwarden_generators::{ClientGeneratorExt, UsernameError, UsernameGeneratorRequest}; + /// + /// async fn test() -> Result<(), UsernameError> { /// let input = UsernameGeneratorRequest::Word { /// capitalize: true, /// include_number: true, @@ -80,13 +90,17 @@ impl<'a> ClientGenerator<'a> { /// Ok(()) /// } /// ``` - pub async fn username(&self, input: UsernameGeneratorRequest) -> Result { - Ok(username(input, self.client.get_http_client()).await?) + pub async fn username(&self, input: UsernameGeneratorRequest) -> Result { + username(input, self.client.internal.get_http_client()).await } } -impl<'a> Client { - pub fn generator(&'a self) -> ClientGenerator<'a> { - ClientGenerator { client: self } +pub trait ClientGeneratorExt<'a> { + fn generator(&'a self) -> ClientGenerator<'a>; +} + +impl<'a> ClientGeneratorExt<'a> for Client { + fn generator(&'a self) -> ClientGenerator<'a> { + ClientGenerator::new(self) } } diff --git a/crates/bitwarden-generators/src/lib.rs b/crates/bitwarden-generators/src/lib.rs index 2584382f1..6455a759a 100644 --- a/crates/bitwarden-generators/src/lib.rs +++ b/crates/bitwarden-generators/src/lib.rs @@ -1,11 +1,13 @@ -mod passphrase; -pub use passphrase::{passphrase, PassphraseError, PassphraseGeneratorRequest}; -mod password; -mod util; -pub use password::{password, PasswordError, PasswordGeneratorRequest}; -mod username; -pub use username::{username, ForwarderServiceType, UsernameError, UsernameGeneratorRequest}; +mod client_generator; mod username_forwarders; +pub use client_generator::{ClientGenerator, ClientGeneratorExt}; +pub(crate) mod passphrase; +pub use passphrase::{PassphraseError, PassphraseGeneratorRequest}; +pub(crate) mod password; +pub use password::{PasswordError, PasswordGeneratorRequest}; +pub(crate) mod username; +pub use username::{ForwarderServiceType, UsernameError, UsernameGeneratorRequest}; +mod util; #[cfg(feature = "uniffi")] uniffi::setup_scaffolding!(); diff --git a/crates/bitwarden-generators/src/passphrase.rs b/crates/bitwarden-generators/src/passphrase.rs index 94b2c0acd..1fb83422e 100644 --- a/crates/bitwarden-generators/src/passphrase.rs +++ b/crates/bitwarden-generators/src/passphrase.rs @@ -77,7 +77,7 @@ impl PassphraseGeneratorRequest { } /// Implementation of the random passphrase generator. -pub fn passphrase(request: PassphraseGeneratorRequest) -> Result { +pub(crate) fn passphrase(request: PassphraseGeneratorRequest) -> Result { let options = request.validate_options()?; Ok(passphrase_with_rng(rand::thread_rng(), options)) } diff --git a/crates/bitwarden-generators/src/password.rs b/crates/bitwarden-generators/src/password.rs index 9865ffd4e..9821f8bbe 100644 --- a/crates/bitwarden-generators/src/password.rs +++ b/crates/bitwarden-generators/src/password.rs @@ -36,16 +36,16 @@ pub struct PasswordGeneratorRequest { 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 + /// When set, the value must be between 1 and 9. This value is ignored if 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 + /// When set, the value must be between 1 and 9. This value is ignored if 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 + /// When set, the value must be between 1 and 9. This value is ignored if 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 + /// When set, the value must be between 1 and 9. This value is ignored if special is false. pub min_special: Option, } @@ -219,7 +219,7 @@ impl PasswordGeneratorRequest { } /// Implementation of the random password generator. -pub fn password(input: PasswordGeneratorRequest) -> Result { +pub(crate) fn password(input: PasswordGeneratorRequest) -> Result { let options = input.validate_options()?; Ok(password_with_rng(rand::thread_rng(), options)) } diff --git a/crates/bitwarden-generators/src/username.rs b/crates/bitwarden-generators/src/username.rs index 830cb5b04..0140f77fb 100644 --- a/crates/bitwarden-generators/src/username.rs +++ b/crates/bitwarden-generators/src/username.rs @@ -131,7 +131,7 @@ impl ForwarderServiceType { /// /// Note: The HTTP client is passed in as a required parameter for convenience, /// as some username generators require making API calls. -pub async fn username( +pub(crate) async fn username( input: UsernameGeneratorRequest, http: &reqwest::Client, ) -> Result { diff --git a/crates/bitwarden-json/Cargo.toml b/crates/bitwarden-json/Cargo.toml index 4981e8f4b..b5b8052b0 100644 --- a/crates/bitwarden-json/Cargo.toml +++ b/crates/bitwarden-json/Cargo.toml @@ -16,16 +16,14 @@ repository.workspace = true license-file.workspace = true [features] -internal = ["bitwarden/internal"] # Internal testing methods -secrets = ["bitwarden/secrets"] # Secrets manager API +secrets = ["bitwarden/secrets"] # Secrets manager API [dependencies] -async-lock = ">=3.3.0, <4.0" bitwarden = { workspace = true } 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" +schemars = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } [lints] workspace = true diff --git a/crates/bitwarden-json/src/client.rs b/crates/bitwarden-json/src/client.rs index 91a00bcca..8da635a29 100644 --- a/crates/bitwarden-json/src/client.rs +++ b/crates/bitwarden-json/src/client.rs @@ -1,19 +1,23 @@ -use async_lock::Mutex; -use bitwarden::client::client_settings::ClientSettings; +use bitwarden::ClientSettings; +#[cfg(feature = "secrets")] +use bitwarden::{ + generators::ClientGeneratorExt, + secrets_manager::{ClientProjectsExt, ClientSecretsExt}, +}; #[cfg(feature = "secrets")] -use crate::command::{ProjectsCommand, SecretsCommand}; +use crate::command::{GeneratorsCommand, ProjectsCommand, SecretsCommand}; use crate::{ command::Command, response::{Response, ResponseIntoString}, }; -pub struct Client(Mutex); +pub struct Client(bitwarden::Client); impl Client { pub fn new(settings_input: Option) -> Self { let settings = Self::parse_settings(settings_input); - Self(Mutex::new(bitwarden::Client::new(settings))) + Self(bitwarden::Client::new(settings)) } pub async fn run_command(&self, input_str: &str) -> String { @@ -45,25 +49,13 @@ impl Client { } }; - let mut client = self.0.lock().await; + let client = &self.0; match cmd { - #[cfg(feature = "internal")] - Command::PasswordLogin(req) => client.auth().login_password(&req).await.into_string(), #[cfg(feature = "secrets")] - Command::AccessTokenLogin(req) => { + Command::LoginAccessToken(req) => { client.auth().login_access_token(&req).await.into_string() } - #[cfg(feature = "internal")] - Command::GetUserApiKey(req) => { - client.platform().get_user_api_key(req).await.into_string() - } - #[cfg(feature = "internal")] - Command::ApiKeyLogin(req) => client.auth().login_api_key(&req).await.into_string(), - #[cfg(feature = "internal")] - Command::Sync(req) => client.vault().sync(&req).await.into_string(), - #[cfg(feature = "internal")] - Command::Fingerprint(req) => client.platform().fingerprint(&req).into_string(), #[cfg(feature = "secrets")] Command::Secrets(cmd) => match cmd { @@ -86,6 +78,13 @@ impl Client { ProjectsCommand::Update(req) => client.projects().update(&req).await.into_string(), ProjectsCommand::Delete(req) => client.projects().delete(req).await.into_string(), }, + + #[cfg(feature = "secrets")] + Command::Generators(cmd) => match cmd { + GeneratorsCommand::GeneratePassword(req) => { + client.generator().password(req).into_string() + } + }, } } diff --git a/crates/bitwarden-json/src/command.rs b/crates/bitwarden-json/src/command.rs index 62b4bc843..faaa81776 100644 --- a/crates/bitwarden-json/src/command.rs +++ b/crates/bitwarden-json/src/command.rs @@ -1,6 +1,7 @@ #[cfg(feature = "secrets")] use bitwarden::{ auth::login::AccessTokenLoginRequest, + generators::PasswordGeneratorRequest, secrets_manager::{ projects::{ ProjectCreateRequest, ProjectGetRequest, ProjectPutRequest, ProjectsDeleteRequest, @@ -12,70 +13,26 @@ use bitwarden::{ }, }, }; -#[cfg(feature = "internal")] -use bitwarden::{ - auth::login::{ApiKeyLoginRequest, PasswordLoginRequest}, - platform::{FingerprintRequest, SecretVerificationRequest}, - vault::SyncRequest, -}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, JsonSchema, Debug)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub enum Command { - #[cfg(feature = "internal")] - /// Login with username and password - /// - /// This command is for initiating an authentication handshake with Bitwarden. - /// Authorization may fail due to requiring 2fa or captcha challenge completion - /// despite accurate credentials. - /// - /// This command is not capable of handling authentication requiring 2fa or captcha. - /// - /// Returns: [PasswordLoginResponse](bitwarden::auth::login::PasswordLoginResponse) - PasswordLogin(PasswordLoginRequest), - - #[cfg(feature = "internal")] - /// Login with API Key - /// - /// This command is for initiating an authentication handshake with Bitwarden. - /// - /// Returns: [ApiKeyLoginResponse](bitwarden::auth::login::ApiKeyLoginResponse) - ApiKeyLogin(ApiKeyLoginRequest), - #[cfg(feature = "secrets")] /// Login with Secrets Manager Access Token /// /// This command is for initiating an authentication handshake with Bitwarden. /// /// Returns: [ApiKeyLoginResponse](bitwarden::auth::login::ApiKeyLoginResponse) - AccessTokenLogin(AccessTokenLoginRequest), - - #[cfg(feature = "internal")] - /// > Requires Authentication - /// Get the API key of the currently authenticated user - /// - /// Returns: [UserApiKeyResponse](bitwarden::platform::UserApiKeyResponse) - GetUserApiKey(SecretVerificationRequest), - - #[cfg(feature = "internal")] - /// Get the user's passphrase - /// - /// Returns: String - Fingerprint(FingerprintRequest), - - #[cfg(feature = "internal")] - /// > Requires Authentication - /// Retrieve all user data, ciphers and organizations the user is a part of - /// - /// Returns: [SyncResponse](bitwarden::vault::SyncResponse) - Sync(SyncRequest), + LoginAccessToken(AccessTokenLoginRequest), #[cfg(feature = "secrets")] Secrets(SecretsCommand), #[cfg(feature = "secrets")] Projects(ProjectsCommand), + #[cfg(feature = "secrets")] + Generators(GeneratorsCommand), } #[cfg(feature = "secrets")] @@ -175,3 +132,13 @@ pub enum ProjectsCommand { /// Returns: [ProjectsDeleteResponse](bitwarden::secrets_manager::projects::ProjectsDeleteResponse) Delete(ProjectsDeleteRequest), } + +#[cfg(feature = "secrets")] +#[derive(Serialize, Deserialize, JsonSchema, Debug)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub enum GeneratorsCommand { + /// Generate a password + /// + /// Returns: [String] + GeneratePassword(PasswordGeneratorRequest), +} diff --git a/crates/bitwarden-napi/Cargo.toml b/crates/bitwarden-napi/Cargo.toml index 7bcd54aad..7a9deeb8f 100644 --- a/crates/bitwarden-napi/Cargo.toml +++ b/crates/bitwarden-napi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bitwarden-napi" -version = "0.3.1" +version = "1.0.0" description = """ N-API bindings for the Bitwarden Secrets Manager SDK """ @@ -22,7 +22,7 @@ bitwarden-json = { path = "../bitwarden-json", version = "0.3.0", features = [ "secrets", ] } env_logger = "0.11.1" -log = "0.4.20" +log = { workspace = true } napi = { version = "2", features = ["async"] } napi-derive = "2" diff --git a/crates/bitwarden-napi/README.md b/crates/bitwarden-napi/README.md index d9e3e7f27..6fce8d069 100644 --- a/crates/bitwarden-napi/README.md +++ b/crates/bitwarden-napi/README.md @@ -17,14 +17,12 @@ const settings: ClientSettings = { }; const accessToken = "-- REDACTED --"; +const stateFile = "some/path/to/state/file"; const client = new BitwardenClient(settings, LogLevel.Info); // Authenticating using a machine account access token -const result = await client.loginWithAccessToken(accessToken); -if (!result.success) { - throw Error("Authentication failed"); -} +await client.auth().loginAccessToken(accessToken, stateFile); // List secrets const secrets = await client.secrets().list(); diff --git a/crates/bitwarden-napi/binding.d.ts b/crates/bitwarden-napi/binding.d.ts index 50ec34289..174d33d92 100644 --- a/crates/bitwarden-napi/binding.d.ts +++ b/crates/bitwarden-napi/binding.d.ts @@ -10,7 +10,7 @@ export const enum LogLevel { Warn = 3, Error = 4, } -export class BitwardenClient { +export declare class BitwardenClient { constructor(settingsInput?: string | undefined | null, logLevel?: LogLevel | undefined | null); runCommand(commandInput: string): Promise; } diff --git a/crates/bitwarden-napi/binding.js b/crates/bitwarden-napi/binding.js index c3cf32037..3da97e8f9 100644 --- a/crates/bitwarden-napi/binding.js +++ b/crates/bitwarden-napi/binding.js @@ -1,4 +1,10 @@ -const { existsSync, readFileSync } = require("fs"); +/* tslint:disable */ +/* eslint-disable */ +/* prettier-ignore */ + +/* auto-generated by NAPI-RS */ + +const { existsSync, readFileSync } = require('fs') const { join } = require("path"); const { platform, arch } = process; @@ -11,7 +17,8 @@ function isMusl() { // For Node 10 if (!process.report || typeof process.report.getReport !== "function") { try { - return readFileSync("/usr/bin/ldd", "utf8").includes("musl"); + const lddPath = require("child_process").execSync("which ldd").toString().trim(); + return readFileSync(lddPath, "utf8").includes("musl"); } catch (e) { return true; } @@ -95,6 +102,15 @@ switch (platform) { } break; case "darwin": + localFileExisted = existsSync(join(__dirname, "sdk-napi.darwin-universal.node")); + try { + if (localFileExisted) { + nativeBinding = require("./sdk-napi.darwin-universal.node"); + } else { + nativeBinding = require("@bitwarden/sdk-napi-darwin-universal"); + } + break; + } catch {} switch (arch) { case "x64": localFileExisted = existsSync(join(__dirname, "sdk-napi.darwin-x64.node")); @@ -192,12 +208,62 @@ switch (platform) { } break; case "arm": - localFileExisted = existsSync(join(__dirname, "sdk-napi.linux-arm-gnueabihf.node")); + if (isMusl()) { + localFileExisted = existsSync(join(__dirname, "sdk-napi.linux-arm-musleabihf.node")); + try { + if (localFileExisted) { + nativeBinding = require("./sdk-napi.linux-arm-musleabihf.node"); + } else { + nativeBinding = require("@bitwarden/sdk-napi-linux-arm-musleabihf"); + } + } catch (e) { + loadError = e; + } + } else { + localFileExisted = existsSync(join(__dirname, "sdk-napi.linux-arm-gnueabihf.node")); + try { + if (localFileExisted) { + nativeBinding = require("./sdk-napi.linux-arm-gnueabihf.node"); + } else { + nativeBinding = require("@bitwarden/sdk-napi-linux-arm-gnueabihf"); + } + } catch (e) { + loadError = e; + } + } + break; + case "riscv64": + if (isMusl()) { + localFileExisted = existsSync(join(__dirname, "sdk-napi.linux-riscv64-musl.node")); + try { + if (localFileExisted) { + nativeBinding = require("./sdk-napi.linux-riscv64-musl.node"); + } else { + nativeBinding = require("@bitwarden/sdk-napi-linux-riscv64-musl"); + } + } catch (e) { + loadError = e; + } + } else { + localFileExisted = existsSync(join(__dirname, "sdk-napi.linux-riscv64-gnu.node")); + try { + if (localFileExisted) { + nativeBinding = require("./sdk-napi.linux-riscv64-gnu.node"); + } else { + nativeBinding = require("@bitwarden/sdk-napi-linux-riscv64-gnu"); + } + } catch (e) { + loadError = e; + } + } + break; + case "s390x": + localFileExisted = existsSync(join(__dirname, "sdk-napi.linux-s390x-gnu.node")); try { if (localFileExisted) { - nativeBinding = require("./sdk-napi.linux-arm-gnueabihf.node"); + nativeBinding = require("./sdk-napi.linux-s390x-gnu.node"); } else { - nativeBinding = require("@bitwarden/sdk-napi-linux-arm-gnueabihf"); + nativeBinding = require("@bitwarden/sdk-napi-linux-s390x-gnu"); } } catch (e) { loadError = e; diff --git a/crates/bitwarden-napi/npm/darwin-arm64/package.json b/crates/bitwarden-napi/npm/darwin-arm64/package.json index 52d785d87..88e3122c5 100644 --- a/crates/bitwarden-napi/npm/darwin-arm64/package.json +++ b/crates/bitwarden-napi/npm/darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/sdk-napi-darwin-arm64", - "version": "0.3.1", + "version": "1.0.0", "homepage": "https://github.com/bitwarden/sdk#readme", "bugs": { "url": "https://github.com/bitwarden/sdk/issues" diff --git a/crates/bitwarden-napi/npm/darwin-x64/package.json b/crates/bitwarden-napi/npm/darwin-x64/package.json index 0a1b06423..4650654ce 100644 --- a/crates/bitwarden-napi/npm/darwin-x64/package.json +++ b/crates/bitwarden-napi/npm/darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/sdk-napi-darwin-x64", - "version": "0.3.1", + "version": "1.0.0", "homepage": "https://github.com/bitwarden/sdk#readme", "bugs": { "url": "https://github.com/bitwarden/sdk/issues" diff --git a/crates/bitwarden-napi/npm/linux-x64-gnu/package.json b/crates/bitwarden-napi/npm/linux-x64-gnu/package.json index 2284d8415..ed3b62f80 100644 --- a/crates/bitwarden-napi/npm/linux-x64-gnu/package.json +++ b/crates/bitwarden-napi/npm/linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/sdk-napi-linux-x64-gnu", - "version": "0.3.1", + "version": "1.0.0", "homepage": "https://github.com/bitwarden/sdk#readme", "bugs": { "url": "https://github.com/bitwarden/sdk/issues" diff --git a/crates/bitwarden-napi/npm/win32-x64-msvc/package.json b/crates/bitwarden-napi/npm/win32-x64-msvc/package.json index 261554e93..5b2a93121 100644 --- a/crates/bitwarden-napi/npm/win32-x64-msvc/package.json +++ b/crates/bitwarden-napi/npm/win32-x64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/sdk-napi-win32-x64-msvc", - "version": "0.3.1", + "version": "1.0.0", "homepage": "https://github.com/bitwarden/sdk#readme", "bugs": { "url": "https://github.com/bitwarden/sdk/issues" diff --git a/crates/bitwarden-napi/package-lock.json b/crates/bitwarden-napi/package-lock.json index 708d7369f..eaa8d3473 100644 --- a/crates/bitwarden-napi/package-lock.json +++ b/crates/bitwarden-napi/package-lock.json @@ -1,64 +1,27 @@ { "name": "@bitwarden/sdk-napi", - "version": "0.3.1", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/sdk-napi", - "version": "0.3.1", + "version": "1.0.0", "license": "SEE LICENSE IN LICENSE", "devDependencies": { - "@napi-rs/cli": "^2.13.2", - "ts-node": "10.9.2", - "typescript": "^5.0.0" + "@napi-rs/cli": "2.18.4", + "typescript": "5.5.4" }, "engines": { "node": ">= 10" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "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.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, "node_modules/@napi-rs/cli": { - "version": "2.18.3", - "resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.18.3.tgz", - "integrity": "sha512-L0f4kP0dyG8W5Qtc7MtP73VvLLrOLyRcUEBzknIfu8Jk4Jfhrsx1ItMHgyalYqMSslWdY3ojEfAaU5sx1VyeQQ==", + "version": "2.18.4", + "resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.18.4.tgz", + "integrity": "sha512-SgJeA4df9DE2iAEpr3M2H0OKl/yjtg1BnRI5/JyowS71tUWhrfSu2LT0V3vlHET+g1hBVlrO60PmEXwUEKp8Mg==", "dev": true, + "license": "MIT", "bin": { "napi": "scripts/index.js" }, @@ -70,136 +33,12 @@ "url": "https://github.com/sponsors/Brooooooklyn" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true - }, - "node_modules/@types/node": { - "version": "20.12.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.11.tgz", - "integrity": "sha512-vDg9PZ/zi+Nqp6boSOT7plNuthRugEKixDv5sFTIpkE89MmNtEArAShI4mxuX2+UrLEe9pxC1vm2cjm9YlWbJw==", - "dev": true, - "peer": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "node_modules/ts-node": { - "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", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -207,28 +46,6 @@ "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, - "peer": true - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "engines": { - "node": ">=6" - } } } } diff --git a/crates/bitwarden-napi/package.json b/crates/bitwarden-napi/package.json index 509cf1763..31cee632b 100644 --- a/crates/bitwarden-napi/package.json +++ b/crates/bitwarden-napi/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/sdk-napi", - "version": "0.3.1", + "version": "1.0.0", "homepage": "https://github.com/bitwarden/sdk#readme", "bugs": { "url": "https://github.com/bitwarden/sdk/issues" @@ -29,9 +29,8 @@ "version": "napi version" }, "devDependencies": { - "@napi-rs/cli": "^2.13.2", - "ts-node": "10.9.2", - "typescript": "^5.0.0" + "@napi-rs/cli": "2.18.4", + "typescript": "5.5.4" }, "engines": { "node": ">= 10" diff --git a/crates/bitwarden-napi/src-ts/bitwarden_client/index.ts b/crates/bitwarden-napi/src-ts/bitwarden_client/index.ts index eefbb1204..3a3765a12 100644 --- a/crates/bitwarden-napi/src-ts/bitwarden_client/index.ts +++ b/crates/bitwarden-napi/src-ts/bitwarden_client/index.ts @@ -1,15 +1,34 @@ import * as rust from "../../binding"; import { LogLevel } from "../../binding"; import { - ClientSettings, Convert, - ResponseForAPIKeyLoginResponse, - ResponseForSecretIdentifiersResponse, - ResponseForSecretResponse, - ResponseForSecretsDeleteResponse, - ResponseForSecretsResponse, + ClientSettings, + ProjectResponse, + ProjectsDeleteResponse, + ProjectsResponse, + SecretIdentifiersResponse, + SecretResponse, + SecretsDeleteResponse, + SecretsResponse, + SecretsSyncResponse, } from "./schemas"; +function handleResponse(response: { + success: boolean; + errorMessage?: string | null; + data?: T | null; +}): T { + if (!response.success) { + throw new Error(response.errorMessage || ""); + } + + if (response.data === null) { + throw new Error(response.errorMessage || "SDK response data is null"); + } + + return response.data as T; +} + export class BitwardenClient { client: rust.BitwardenClient; @@ -18,33 +37,16 @@ export class BitwardenClient { this.client = new rust.BitwardenClient(settingsJson, loggingLevel ?? LogLevel.Info); } - async loginWithAccessToken(accessToken: string): Promise { - const commandInput = Convert.commandToJson({ - accessTokenLogin: { - accessToken: accessToken, - }, - }); - const response = await this.client.runCommand(commandInput); - - return Convert.toResponseForAPIKeyLoginResponse(response); + secrets(): SecretsClient { + return new SecretsClient(this.client); } - /* - async sync(excludeSubdomains = false): Promise { - const response = await this.client.runCommand( - Convert.commandToJson({ - sync: { - excludeSubdomains, - }, - }) - ); - - return Convert.toResponseForSyncResponse(response); + projects(): ProjectsClient { + return new ProjectsClient(this.client); } - */ - secrets(): SecretsClient { - return new SecretsClient(this.client); + auth(): AuthClient { + return new AuthClient(this.client); } } @@ -55,7 +57,7 @@ export class SecretsClient { this.client = client; } - async get(id: string): Promise { + async get(id: string): Promise { const response = await this.client.runCommand( Convert.commandToJson({ secrets: { @@ -64,10 +66,10 @@ export class SecretsClient { }), ); - return Convert.toResponseForSecretResponse(response); + return handleResponse(Convert.toResponseForSecretResponse(response)); } - async getByIds(ids: string[]): Promise { + async getByIds(ids: string[]): Promise { const response = await this.client.runCommand( Convert.commandToJson({ secrets: { @@ -76,27 +78,28 @@ export class SecretsClient { }), ); - return Convert.toResponseForSecretsResponse(response); + return handleResponse(Convert.toResponseForSecretsResponse(response)); } async create( - key: string, - note: string, organizationId: string, + key: string, value: string, - ): Promise { + note: string, + projectIds: string[], + ): Promise { const response = await this.client.runCommand( Convert.commandToJson({ secrets: { - create: { key, note, organizationId, value }, + create: { key, value, note, projectIds, organizationId }, }, }), ); - return Convert.toResponseForSecretResponse(response); + return handleResponse(Convert.toResponseForSecretResponse(response)); } - async list(organizationId: string): Promise { + async list(organizationId: string): Promise { const response = await this.client.runCommand( Convert.commandToJson({ secrets: { @@ -105,28 +108,29 @@ export class SecretsClient { }), ); - return Convert.toResponseForSecretIdentifiersResponse(response); + return handleResponse(Convert.toResponseForSecretIdentifiersResponse(response)); } async update( + organizationId: string, id: string, key: string, - note: string, - organizationId: string, value: string, - ): Promise { + note: string, + projectIds: string[], + ): Promise { const response = await this.client.runCommand( Convert.commandToJson({ secrets: { - update: { id, key, note, organizationId, value }, + update: { id, key, value, note, projectIds, organizationId }, }, }), ); - return Convert.toResponseForSecretResponse(response); + return handleResponse(Convert.toResponseForSecretResponse(response)); } - async delete(ids: string[]): Promise { + async delete(ids: string[]): Promise { const response = await this.client.runCommand( Convert.commandToJson({ secrets: { @@ -135,6 +139,107 @@ export class SecretsClient { }), ); - return Convert.toResponseForSecretsDeleteResponse(response); + return handleResponse(Convert.toResponseForSecretsDeleteResponse(response)); + } + + async sync(organizationId: string, lastSyncedDate?: Date): Promise { + const response = await this.client.runCommand( + Convert.commandToJson({ + secrets: { + sync: { organizationId, lastSyncedDate }, + }, + }), + ); + + return handleResponse(Convert.toResponseForSecretsSyncResponse(response)); + } +} + +export class ProjectsClient { + client: rust.BitwardenClient; + + constructor(client: rust.BitwardenClient) { + this.client = client; + } + + async get(id: string): Promise { + const response = await this.client.runCommand( + Convert.commandToJson({ + projects: { + get: { id }, + }, + }), + ); + + return handleResponse(Convert.toResponseForProjectResponse(response)); + } + + async create(organizationId: string, name: string): Promise { + const response = await this.client.runCommand( + Convert.commandToJson({ + projects: { + create: { name, organizationId }, + }, + }), + ); + + return handleResponse(Convert.toResponseForProjectResponse(response)); + } + + async list(organizationId: string): Promise { + const response = await this.client.runCommand( + Convert.commandToJson({ + projects: { + list: { organizationId }, + }, + }), + ); + + return handleResponse(Convert.toResponseForProjectsResponse(response)); + } + + async update(organizationId: string, id: string, name: string): Promise { + const response = await this.client.runCommand( + Convert.commandToJson({ + projects: { + update: { id, name, organizationId }, + }, + }), + ); + + return handleResponse(Convert.toResponseForProjectResponse(response)); + } + + async delete(ids: string[]): Promise { + const response = await this.client.runCommand( + Convert.commandToJson({ + projects: { + delete: { ids }, + }, + }), + ); + + return handleResponse(Convert.toResponseForProjectsDeleteResponse(response)); + } +} + +export class AuthClient { + client: rust.BitwardenClient; + + constructor(client: rust.BitwardenClient) { + this.client = client; + } + + async loginAccessToken(accessToken: string, stateFile?: string): Promise { + const response = await this.client.runCommand( + Convert.commandToJson({ + loginAccessToken: { + accessToken, + stateFile, + }, + }), + ); + + handleResponse(Convert.toResponseForAccessTokenLoginResponse(response)); } } diff --git a/crates/bitwarden-py/Cargo.toml b/crates/bitwarden-py/Cargo.toml index e9073d802..b637d5faa 100644 --- a/crates/bitwarden-py/Cargo.toml +++ b/crates/bitwarden-py/Cargo.toml @@ -17,18 +17,14 @@ crate-type = ["cdylib"] [dependencies] bitwarden-json = { path = "../bitwarden-json", features = ["secrets"] } -pyo3 = { version = "0.20.2", features = ["extension-module"] } -pyo3-log = "0.9.0" +pyo3 = { version = "0.22.1", features = ["extension-module"] } +pyo3-log = "0.11.0" [build-dependencies] -pyo3-build-config = { version = "0.20.2" } +pyo3-build-config = { version = "0.22.1" } [target.'cfg(not(target_arch="wasm32"))'.dependencies] -tokio = { version = "1.36.0", features = ["rt-multi-thread", "macros"] } -pyo3-asyncio = { version = "0.20.0", features = [ - "attributes", - "tokio-runtime", -] } +tokio = { workspace = true, features = ["rt-multi-thread"] } [lints] workspace = true diff --git a/crates/bitwarden-py/src/client.rs b/crates/bitwarden-py/src/client.rs index c3ea62444..9c12a624d 100644 --- a/crates/bitwarden-py/src/client.rs +++ b/crates/bitwarden-py/src/client.rs @@ -2,26 +2,27 @@ use bitwarden_json::client::Client as JsonClient; use pyo3::prelude::*; #[pyclass] -pub struct BitwardenClient(JsonClient); +pub struct BitwardenClient(tokio::runtime::Runtime, JsonClient); #[pymethods] impl BitwardenClient { #[new] + #[pyo3(signature = (settings_string=None))] pub fn new(settings_string: Option) -> Self { // This will only fail if another logger was already initialized, so we can ignore the // result let _ = pyo3_log::try_init(); - Self(JsonClient::new(settings_string)) + let runtime = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .expect("Failed to build tokio runtime"); + + Self(runtime, JsonClient::new(settings_string)) } #[pyo3(text_signature = "($self, command_input)")] fn run_command(&self, command_input: String) -> String { - run_command(&self.0, &command_input) + self.0.block_on(self.1.run_command(&command_input)) } } - -#[tokio::main] -async fn run_command(client: &JsonClient, input_str: &str) -> String { - client.run_command(input_str).await -} diff --git a/crates/bitwarden-py/src/python_module.rs b/crates/bitwarden-py/src/python_module.rs index 0df87a563..3e21b852c 100644 --- a/crates/bitwarden-py/src/python_module.rs +++ b/crates/bitwarden-py/src/python_module.rs @@ -3,7 +3,7 @@ use pyo3::prelude::*; use crate::client::BitwardenClient; #[pymodule] -fn bitwarden_py(_py: Python<'_>, m: &PyModule) -> PyResult<()> { +fn bitwarden_py(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; Ok(()) } diff --git a/crates/bitwarden-send/Cargo.toml b/crates/bitwarden-send/Cargo.toml new file mode 100644 index 000000000..33c0b76c6 --- /dev/null +++ b/crates/bitwarden-send/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "bitwarden-send" +description = """ +Internal crate for the bitwarden crate. Do not use. +""" + +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +homepage.workspace = true +repository.workspace = true +license-file.workspace = true +keywords.workspace = true + +[features] +uniffi = [ + "bitwarden-core/uniffi", + "bitwarden-crypto/uniffi", + "dep:uniffi", +] # Uniffi bindings + +[dependencies] +base64 = ">=0.22.1, <0.23" +bitwarden-api-api = { workspace = true } +bitwarden-core = { workspace = true } +bitwarden-crypto = { workspace = true } +chrono = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +serde_repr = { workspace = true } +thiserror = { workspace = true } +uniffi = { workspace = true, optional = true } +uuid = { workspace = true } +zeroize = { version = ">=1.7.0, <2.0" } + +[lints] +workspace = true diff --git a/crates/bitwarden-send/README.md b/crates/bitwarden-send/README.md new file mode 100644 index 000000000..85da845b4 --- /dev/null +++ b/crates/bitwarden-send/README.md @@ -0,0 +1,6 @@ +# Bitwarden Send + +This is an internal crate for the Bitwarden SDK do not depend on this directly and use the +[`bitwarden`](https://crates.io/crates/bitwarden) crate instead. + +This crate does not follow semantic versioning and the public interface may change at any time. diff --git a/crates/bitwarden-send/src/client_sends.rs b/crates/bitwarden-send/src/client_sends.rs new file mode 100644 index 000000000..bf496e013 --- /dev/null +++ b/crates/bitwarden-send/src/client_sends.rs @@ -0,0 +1,95 @@ +use std::path::Path; + +use bitwarden_core::{Client, Error}; +use bitwarden_crypto::{EncString, KeyDecryptable, KeyEncryptable}; + +use crate::{Send, SendListView, SendView}; + +pub struct ClientSends<'a> { + client: &'a Client, +} + +impl<'a> ClientSends<'a> { + fn new(client: &'a Client) -> Self { + Self { client } + } + + pub fn decrypt(&self, send: Send) -> Result { + let enc = self.client.internal.get_encryption_settings()?; + let key = enc.get_key(&None)?; + + let send_view = send.decrypt_with_key(key)?; + + Ok(send_view) + } + + pub fn decrypt_list(&self, sends: Vec) -> Result, Error> { + let enc = self.client.internal.get_encryption_settings()?; + let key = enc.get_key(&None)?; + + let send_views = sends.decrypt_with_key(key)?; + + Ok(send_views) + } + + pub fn decrypt_file( + &self, + send: Send, + encrypted_file_path: &Path, + decrypted_file_path: &Path, + ) -> Result<(), Error> { + let data = std::fs::read(encrypted_file_path)?; + let decrypted = self.decrypt_buffer(send, &data)?; + std::fs::write(decrypted_file_path, decrypted)?; + Ok(()) + } + + pub fn decrypt_buffer(&self, send: Send, encrypted_buffer: &[u8]) -> Result, Error> { + let enc = self.client.internal.get_encryption_settings()?; + let key = enc.get_key(&None)?; + let key = Send::get_key(&send.key, key)?; + + let buf = EncString::from_buffer(encrypted_buffer)?; + Ok(buf.decrypt_with_key(&key)?) + } + + pub fn encrypt(&self, send_view: SendView) -> Result { + let enc = self.client.internal.get_encryption_settings()?; + let key = enc.get_key(&None)?; + + let send = send_view.encrypt_with_key(key)?; + + Ok(send) + } + + pub fn encrypt_file( + &self, + send: Send, + decrypted_file_path: &Path, + encrypted_file_path: &Path, + ) -> Result<(), Error> { + let data = std::fs::read(decrypted_file_path)?; + let encrypted = self.encrypt_buffer(send, &data)?; + std::fs::write(encrypted_file_path, encrypted)?; + Ok(()) + } + + pub fn encrypt_buffer(&self, send: Send, buffer: &[u8]) -> Result, Error> { + let enc = self.client.internal.get_encryption_settings()?; + let key = enc.get_key(&None)?; + let key = Send::get_key(&send.key, key)?; + + let encrypted = buffer.encrypt_with_key(&key)?; + Ok(encrypted.to_buffer()?) + } +} + +pub trait ClientSendsExt<'a> { + fn sends(&'a self) -> ClientSends<'a>; +} + +impl<'a> ClientSendsExt<'a> for Client { + fn sends(&'a self) -> ClientSends<'a> { + ClientSends::new(self) + } +} diff --git a/crates/bitwarden-send/src/error.rs b/crates/bitwarden-send/src/error.rs new file mode 100644 index 000000000..9d220d9cd --- /dev/null +++ b/crates/bitwarden-send/src/error.rs @@ -0,0 +1,11 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum SendParseError { + #[error(transparent)] + Chrono(#[from] chrono::ParseError), + #[error(transparent)] + Crypto(#[from] bitwarden_crypto::CryptoError), + #[error(transparent)] + MissingFieldError(#[from] bitwarden_core::MissingFieldError), +} diff --git a/crates/bitwarden-send/src/lib.rs b/crates/bitwarden-send/src/lib.rs new file mode 100644 index 000000000..e29d7305f --- /dev/null +++ b/crates/bitwarden-send/src/lib.rs @@ -0,0 +1,11 @@ +#[cfg(feature = "uniffi")] +uniffi::setup_scaffolding!(); +#[cfg(feature = "uniffi")] +mod uniffi_support; + +mod error; +pub use error::SendParseError; +mod client_sends; +pub use client_sends::{ClientSends, ClientSendsExt}; +mod send; +pub use send::{Send, SendListView, SendView}; diff --git a/crates/bitwarden/src/tool/send.rs b/crates/bitwarden-send/src/send.rs similarity index 81% rename from crates/bitwarden/src/tool/send.rs rename to crates/bitwarden-send/src/send.rs index 2ce83dd0a..3222ec1cf 100644 --- a/crates/bitwarden/src/tool/send.rs +++ b/crates/bitwarden-send/src/send.rs @@ -3,6 +3,7 @@ use base64::{ Engine, }; use bitwarden_api_api::models::{SendFileModel, SendResponseModel, SendTextModel}; +use bitwarden_core::require; use bitwarden_crypto::{ derive_shareable_key, generate_random_bytes, CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, @@ -14,7 +15,7 @@ use serde_repr::{Deserialize_repr, Serialize_repr}; use uuid::Uuid; use zeroize::Zeroizing; -use crate::error::{require, Error, Result}; +use crate::SendParseError; const SEND_ITERATIONS: u32 = 100_000; @@ -141,7 +142,7 @@ pub struct SendListView { } impl Send { - pub(crate) fn get_key( + pub fn get_key( send_key: &EncString, enc_key: &SymmetricCryptoKey, ) -> Result { @@ -301,9 +302,9 @@ impl KeyEncryptable for SendView { } impl TryFrom for Send { - type Error = Error; + type Error = SendParseError; - fn try_from(send: SendResponseModel) -> Result { + fn try_from(send: SendResponseModel) -> Result { Ok(Send { id: send.id, access_id: send.access_id, @@ -335,9 +336,9 @@ impl From for SendType { } impl TryFrom for SendFile { - type Error = Error; + type Error = SendParseError; - fn try_from(file: SendFileModel) -> Result { + fn try_from(file: SendFileModel) -> Result { Ok(SendFile { id: file.id, file_name: require!(file.file_name).parse()?, @@ -348,9 +349,9 @@ impl TryFrom for SendFile { } impl TryFrom for SendText { - type Error = Error; + type Error = SendParseError; - fn try_from(text: SendTextModel) -> Result { + fn try_from(text: SendTextModel) -> Result { Ok(SendText { text: EncString::try_from_optional(text.text)?, hidden: text.hidden.unwrap_or(false), @@ -360,29 +361,44 @@ impl TryFrom for SendText { #[cfg(test)] mod tests { - use bitwarden_crypto::{KeyDecryptable, KeyEncryptable, MasterKey}; + use std::collections::HashMap; - use super::{Send, SendText, SendTextView, SendType}; - use crate::{ - client::{encryption_settings::EncryptionSettings, Kdf}, - tool::SendView, - }; + use bitwarden_crypto::{Kdf, KeyContainer, KeyDecryptable, KeyEncryptable, MasterKey}; + + use super::*; + + struct MockKeyContainer(HashMap, SymmetricCryptoKey>); + impl MockKeyContainer { + fn new(master_key: MasterKey, user_key: EncString) -> Result { + let user_key = master_key.decrypt_user_key(user_key)?; + Ok(Self(HashMap::from([(None, user_key)]))) + } + } + impl KeyContainer for MockKeyContainer { + fn get_key<'a>( + &'a self, + org_id: &Option, + ) -> Result<&'a SymmetricCryptoKey, CryptoError> { + self.0 + .get(org_id) + .ok_or(CryptoError::MissingKey(org_id.unwrap_or_default())) + } + } #[test] fn test_get_send_key() { // Initialize user encryption with some test data let master_key = MasterKey::derive( - "asdfasdfasdf".as_bytes(), - "test@bitwarden.com".as_bytes(), + "asdfasdfasdf", + "test@bitwarden.com", &Kdf::PBKDF2 { iterations: 345123.try_into().unwrap(), }, ) .unwrap(); - let enc = EncryptionSettings::new( + let enc = MockKeyContainer::new( master_key, "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(); let k = enc.get_key(&None).unwrap(); @@ -397,20 +413,19 @@ mod tests { assert_eq!(send_key_b64, "IR9ImHGm6rRuIjiN7csj94bcZR5WYTJj5GtNfx33zm6tJCHUl+QZlpNPba8g2yn70KnOHsAODLcR0um6E3MAlg=="); } - fn build_encryption_settings() -> EncryptionSettings { + fn build_encryption_settings() -> MockKeyContainer { let master_key = MasterKey::derive( - "asdfasdfasdf".as_bytes(), - "test@bitwarden.com".as_bytes(), + "asdfasdfasdf", + "test@bitwarden.com", &Kdf::PBKDF2 { iterations: 600_000.try_into().unwrap(), }, ) .unwrap(); - EncryptionSettings::new( + MockKeyContainer::new( master_key, "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() } diff --git a/crates/bitwarden-send/src/uniffi_support.rs b/crates/bitwarden-send/src/uniffi_support.rs new file mode 100644 index 000000000..932f0de7a --- /dev/null +++ b/crates/bitwarden-send/src/uniffi_support.rs @@ -0,0 +1,8 @@ +use bitwarden_crypto::EncString; +use uuid::Uuid; + +uniffi::ffi_converter_forward!(EncString, bitwarden_crypto::UniFfiTag, crate::UniFfiTag); + +type DateTime = chrono::DateTime; +uniffi::ffi_converter_forward!(DateTime, bitwarden_core::UniFfiTag, crate::UniFfiTag); +uniffi::ffi_converter_forward!(Uuid, bitwarden_core::UniFfiTag, crate::UniFfiTag); diff --git a/crates/bitwarden-send/uniffi.toml b/crates/bitwarden-send/uniffi.toml new file mode 100644 index 000000000..674ea3684 --- /dev/null +++ b/crates/bitwarden-send/uniffi.toml @@ -0,0 +1,9 @@ +[bindings.kotlin] +package_name = "com.bitwarden.send" +generate_immutable_records = true +android = true + +[bindings.swift] +ffi_module_name = "BitwardenSendFFI" +module_name = "BitwardenSend" +generate_immutable_records = true diff --git a/crates/bitwarden-sm/Cargo.toml b/crates/bitwarden-sm/Cargo.toml new file mode 100644 index 000000000..77fa9d7c1 --- /dev/null +++ b/crates/bitwarden-sm/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "bitwarden-sm" +description = """ +Internal crate for the bitwarden crate. Do not use. +""" + +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +homepage.workspace = true +repository.workspace = true +license-file.workspace = true +keywords.workspace = true + +[dependencies] +bitwarden-api-api = { workspace = true } +bitwarden-core = { workspace = true } +bitwarden-crypto = { workspace = true } +chrono = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +thiserror = { workspace = true } +uuid = { workspace = true } +validator = { workspace = true } + +[dev-dependencies] +tokio = { workspace = true, features = ["rt"] } + +[lints] +workspace = true diff --git a/crates/bitwarden-sm/src/client_projects.rs b/crates/bitwarden-sm/src/client_projects.rs new file mode 100644 index 000000000..13b2c6967 --- /dev/null +++ b/crates/bitwarden-sm/src/client_projects.rs @@ -0,0 +1,50 @@ +use bitwarden_core::{Client, Error}; + +use crate::projects::{ + create_project, delete_projects, get_project, list_projects, update_project, + ProjectCreateRequest, ProjectGetRequest, ProjectPutRequest, ProjectResponse, + ProjectsDeleteRequest, ProjectsDeleteResponse, ProjectsListRequest, ProjectsResponse, +}; + +pub struct ClientProjects<'a> { + pub client: &'a Client, +} + +impl<'a> ClientProjects<'a> { + pub fn new(client: &'a Client) -> Self { + Self { client } + } + + pub async fn get(&self, input: &ProjectGetRequest) -> Result { + get_project(self.client, input).await + } + + pub async fn create(&self, input: &ProjectCreateRequest) -> Result { + create_project(self.client, input).await + } + + pub async fn list(&self, input: &ProjectsListRequest) -> Result { + list_projects(self.client, input).await + } + + pub async fn update(&self, input: &ProjectPutRequest) -> Result { + update_project(self.client, input).await + } + + pub async fn delete( + &self, + input: ProjectsDeleteRequest, + ) -> Result { + delete_projects(self.client, input).await + } +} + +pub trait ClientProjectsExt<'a> { + fn projects(&'a self) -> ClientProjects<'a>; +} + +impl<'a> ClientProjectsExt<'a> for Client { + fn projects(&'a self) -> ClientProjects<'a> { + ClientProjects::new(self) + } +} diff --git a/crates/bitwarden-sm/src/client_secrets.rs b/crates/bitwarden-sm/src/client_secrets.rs new file mode 100644 index 000000000..2b1085a0a --- /dev/null +++ b/crates/bitwarden-sm/src/client_secrets.rs @@ -0,0 +1,70 @@ +use bitwarden_core::{Client, Error}; + +use crate::secrets::{ + create_secret, delete_secrets, get_secret, get_secrets_by_ids, list_secrets, + list_secrets_by_project, sync_secrets, update_secret, SecretCreateRequest, SecretGetRequest, + SecretIdentifiersByProjectRequest, SecretIdentifiersRequest, SecretIdentifiersResponse, + SecretPutRequest, SecretResponse, SecretsDeleteRequest, SecretsDeleteResponse, + SecretsGetRequest, SecretsResponse, SecretsSyncRequest, SecretsSyncResponse, +}; + +pub struct ClientSecrets<'a> { + client: &'a Client, +} + +impl<'a> ClientSecrets<'a> { + pub fn new(client: &'a Client) -> Self { + Self { client } + } + + pub async fn get(&self, input: &SecretGetRequest) -> Result { + get_secret(self.client, input).await + } + + pub async fn get_by_ids(&self, input: SecretsGetRequest) -> Result { + get_secrets_by_ids(self.client, input).await + } + + pub async fn create(&self, input: &SecretCreateRequest) -> Result { + create_secret(self.client, input).await + } + + pub async fn list( + &self, + input: &SecretIdentifiersRequest, + ) -> Result { + list_secrets(self.client, input).await + } + + pub async fn list_by_project( + &self, + input: &SecretIdentifiersByProjectRequest, + ) -> Result { + list_secrets_by_project(self.client, input).await + } + + pub async fn update(&self, input: &SecretPutRequest) -> Result { + update_secret(self.client, input).await + } + + pub async fn delete( + &self, + input: SecretsDeleteRequest, + ) -> Result { + delete_secrets(self.client, input).await + } + + pub async fn sync(&self, input: &SecretsSyncRequest) -> Result { + sync_secrets(self.client, input).await + } +} + +pub trait ClientSecretsExt<'a> { + fn secrets(&'a self) -> ClientSecrets<'a>; +} + +impl<'a> ClientSecretsExt<'a> for Client { + fn secrets(&'a self) -> ClientSecrets<'a> { + ClientSecrets::new(self) + } +} diff --git a/crates/bitwarden-sm/src/lib.rs b/crates/bitwarden-sm/src/lib.rs new file mode 100644 index 000000000..6f44bd16d --- /dev/null +++ b/crates/bitwarden-sm/src/lib.rs @@ -0,0 +1,7 @@ +mod client_projects; +mod client_secrets; +pub mod projects; +pub mod secrets; + +pub use client_projects::{ClientProjects, ClientProjectsExt}; +pub use client_secrets::{ClientSecrets, ClientSecretsExt}; diff --git a/crates/bitwarden-sm/src/projects/create.rs b/crates/bitwarden-sm/src/projects/create.rs new file mode 100644 index 000000000..bb377e23c --- /dev/null +++ b/crates/bitwarden-sm/src/projects/create.rs @@ -0,0 +1,116 @@ +use bitwarden_api_api::models::ProjectCreateRequestModel; +use bitwarden_core::{validate_only_whitespaces, Client, Error}; +use bitwarden_crypto::KeyEncryptable; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; +use validator::Validate; + +use super::ProjectResponse; + +#[derive(Serialize, Deserialize, Debug, JsonSchema, Validate)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct ProjectCreateRequest { + /// Organization where the project will be created + pub organization_id: Uuid, + #[validate(length(min = 1, max = 500), custom(function = validate_only_whitespaces))] + pub name: String, +} + +pub(crate) async fn create_project( + client: &Client, + input: &ProjectCreateRequest, +) -> Result { + input.validate()?; + + let enc = client.internal.get_encryption_settings()?; + let key = enc.get_key(&Some(input.organization_id))?; + + let project = Some(ProjectCreateRequestModel { + name: input.name.clone().trim().encrypt_with_key(key)?.to_string(), + }); + + let config = client.internal.get_api_configurations().await; + let res = bitwarden_api_api::apis::projects_api::organizations_organization_id_projects_post( + &config.api, + input.organization_id, + project, + ) + .await?; + + ProjectResponse::process_response(res, &enc) +} + +#[cfg(test)] +mod tests { + use super::*; + + async fn create_project(name: String) -> Result { + let input = ProjectCreateRequest { + organization_id: Uuid::new_v4(), + name, + }; + + super::create_project(&Client::new(None), &input).await + } + + #[tokio::test] + async fn test_create_project_request_name_empty_string() { + let response = create_project("".into()).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "name must not be empty" + ); + } + + #[tokio::test] + async fn test_create_project_request_name_all_whitespaces_space() { + let response = create_project(" ".into()).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "name must not contain only whitespaces" + ); + } + + #[tokio::test] + async fn test_create_project_request_name_all_whitespaces_tab() { + let response = create_project("\t".into()).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "name must not contain only whitespaces" + ); + } + + #[tokio::test] + async fn test_create_project_request_name_all_whitespaces_newline() { + let response = create_project("\n".into()).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "name must not contain only whitespaces" + ); + } + + #[tokio::test] + async fn test_create_project_request_name_all_whitespaces_combined() { + let response = create_project(" \t\n".into()).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "name must not contain only whitespaces" + ); + } + + #[tokio::test] + async fn test_create_project_request_name_501_character_length() { + let response = create_project("a".repeat(501)).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "name must not exceed 500 characters in length" + ); + } +} diff --git a/crates/bitwarden/src/secrets_manager/projects/delete.rs b/crates/bitwarden-sm/src/projects/delete.rs similarity index 85% rename from crates/bitwarden/src/secrets_manager/projects/delete.rs rename to crates/bitwarden-sm/src/projects/delete.rs index 05c808c7e..27e7f83e7 100644 --- a/crates/bitwarden/src/secrets_manager/projects/delete.rs +++ b/crates/bitwarden-sm/src/projects/delete.rs @@ -1,15 +1,11 @@ use bitwarden_api_api::models::{ BulkDeleteResponseModel, BulkDeleteResponseModelListResponseModel, }; +use bitwarden_core::{client::Client, require, Error}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::{ - client::Client, - error::{require, Result}, -}; - #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct ProjectsDeleteRequest { @@ -18,10 +14,10 @@ pub struct ProjectsDeleteRequest { } pub(crate) async fn delete_projects( - client: &mut Client, + client: &Client, input: ProjectsDeleteRequest, -) -> Result { - let config = client.get_api_configurations().await; +) -> Result { + let config = client.internal.get_api_configurations().await; let res = bitwarden_api_api::apis::projects_api::projects_delete_post(&config.api, Some(input.ids)) .await?; @@ -38,7 +34,7 @@ pub struct ProjectsDeleteResponse { impl ProjectsDeleteResponse { pub(crate) fn process_response( response: BulkDeleteResponseModelListResponseModel, - ) -> Result { + ) -> Result { Ok(ProjectsDeleteResponse { data: response .data @@ -60,7 +56,7 @@ pub struct ProjectDeleteResponse { impl ProjectDeleteResponse { pub(crate) fn process_response( response: BulkDeleteResponseModel, - ) -> Result { + ) -> Result { Ok(ProjectDeleteResponse { id: require!(response.id), error: response.error, diff --git a/crates/bitwarden/src/secrets_manager/projects/get.rs b/crates/bitwarden-sm/src/projects/get.rs similarity index 63% rename from crates/bitwarden/src/secrets_manager/projects/get.rs rename to crates/bitwarden-sm/src/projects/get.rs index 00afaa194..81a669739 100644 --- a/crates/bitwarden/src/secrets_manager/projects/get.rs +++ b/crates/bitwarden-sm/src/projects/get.rs @@ -1,9 +1,9 @@ +use bitwarden_core::{client::Client, Error}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; use super::ProjectResponse; -use crate::{client::Client, error::Result}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -13,14 +13,14 @@ pub struct ProjectGetRequest { } pub(crate) async fn get_project( - client: &mut Client, + client: &Client, input: &ProjectGetRequest, -) -> Result { - let config = client.get_api_configurations().await; +) -> Result { + let config = client.internal.get_api_configurations().await; let res = bitwarden_api_api::apis::projects_api::projects_id_get(&config.api, input.id).await?; - let enc = client.get_encryption_settings()?; + let enc = client.internal.get_encryption_settings()?; - ProjectResponse::process_response(res, enc) + ProjectResponse::process_response(res, &enc) } diff --git a/crates/bitwarden/src/secrets_manager/projects/list.rs b/crates/bitwarden-sm/src/projects/list.rs similarity index 81% rename from crates/bitwarden/src/secrets_manager/projects/list.rs rename to crates/bitwarden-sm/src/projects/list.rs index 5c491e14b..334e06007 100644 --- a/crates/bitwarden/src/secrets_manager/projects/list.rs +++ b/crates/bitwarden-sm/src/projects/list.rs @@ -1,13 +1,13 @@ use bitwarden_api_api::models::ProjectResponseModelListResponseModel; +use bitwarden_core::{ + client::{encryption_settings::EncryptionSettings, Client}, + Error, +}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; use super::ProjectResponse; -use crate::{ - client::{encryption_settings::EncryptionSettings, Client}, - error::Result, -}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -17,19 +17,19 @@ pub struct ProjectsListRequest { } pub(crate) async fn list_projects( - client: &mut Client, + client: &Client, input: &ProjectsListRequest, -) -> Result { - let config = client.get_api_configurations().await; +) -> Result { + let config = client.internal.get_api_configurations().await; let res = bitwarden_api_api::apis::projects_api::organizations_organization_id_projects_get( &config.api, input.organization_id, ) .await?; - let enc = client.get_encryption_settings()?; + let enc = client.internal.get_encryption_settings()?; - ProjectsResponse::process_response(res, enc) + ProjectsResponse::process_response(res, &enc) } #[derive(Serialize, Deserialize, Debug, JsonSchema)] @@ -42,7 +42,7 @@ impl ProjectsResponse { pub(crate) fn process_response( response: ProjectResponseModelListResponseModel, enc: &EncryptionSettings, - ) -> Result { + ) -> Result { let data = response.data.unwrap_or_default(); Ok(ProjectsResponse { diff --git a/crates/bitwarden/src/secrets_manager/projects/mod.rs b/crates/bitwarden-sm/src/projects/mod.rs similarity index 100% rename from crates/bitwarden/src/secrets_manager/projects/mod.rs rename to crates/bitwarden-sm/src/projects/mod.rs diff --git a/crates/bitwarden/src/secrets_manager/projects/project_response.rs b/crates/bitwarden-sm/src/projects/project_response.rs similarity index 77% rename from crates/bitwarden/src/secrets_manager/projects/project_response.rs rename to crates/bitwarden-sm/src/projects/project_response.rs index 82e98dff0..a70a3fd78 100644 --- a/crates/bitwarden/src/secrets_manager/projects/project_response.rs +++ b/crates/bitwarden-sm/src/projects/project_response.rs @@ -1,15 +1,11 @@ use bitwarden_api_api::models::ProjectResponseModel; -use bitwarden_crypto::{CryptoError, EncString, KeyDecryptable}; +use bitwarden_core::{client::encryption_settings::EncryptionSettings, require, Error}; +use bitwarden_crypto::{EncString, KeyDecryptable}; use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::{ - client::encryption_settings::EncryptionSettings, - error::{require, Result}, -}; - #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct ProjectResponse { @@ -24,11 +20,9 @@ impl ProjectResponse { pub(crate) fn process_response( response: ProjectResponseModel, enc: &EncryptionSettings, - ) -> Result { + ) -> Result { let organization_id = require!(response.organization_id); - let enc_key = enc - .get_key(&Some(organization_id)) - .ok_or(CryptoError::MissingKey)?; + let enc_key = enc.get_key(&Some(organization_id))?; let name = require!(response.name) .parse::()? diff --git a/crates/bitwarden-sm/src/projects/update.rs b/crates/bitwarden-sm/src/projects/update.rs new file mode 100644 index 000000000..359b66945 --- /dev/null +++ b/crates/bitwarden-sm/src/projects/update.rs @@ -0,0 +1,116 @@ +use bitwarden_api_api::models::ProjectUpdateRequestModel; +use bitwarden_core::{validate_only_whitespaces, Client, Error}; +use bitwarden_crypto::KeyEncryptable; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; +use validator::Validate; + +use super::ProjectResponse; + +#[derive(Serialize, Deserialize, Debug, JsonSchema, Validate)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct ProjectPutRequest { + /// ID of the project to modify + pub id: Uuid, + /// Organization ID of the project to modify + pub organization_id: Uuid, + #[validate(length(min = 1, max = 500), custom(function = validate_only_whitespaces))] + pub name: String, +} + +pub(crate) async fn update_project( + client: &Client, + input: &ProjectPutRequest, +) -> Result { + input.validate()?; + + let enc = client.internal.get_encryption_settings()?; + let key = enc.get_key(&Some(input.organization_id))?; + + let project = Some(ProjectUpdateRequestModel { + name: input.name.clone().trim().encrypt_with_key(key)?.to_string(), + }); + + let config = client.internal.get_api_configurations().await; + let res = + bitwarden_api_api::apis::projects_api::projects_id_put(&config.api, input.id, project) + .await?; + + ProjectResponse::process_response(res, &enc) +} + +#[cfg(test)] +mod tests { + use super::*; + + async fn update_project(name: String) -> Result { + let input = ProjectPutRequest { + id: Uuid::new_v4(), + organization_id: Uuid::new_v4(), + name, + }; + + super::update_project(&Client::new(None), &input).await + } + + #[tokio::test] + async fn test_update_project_request_name_empty_string() { + let response = update_project("".into()).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "name must not be empty" + ); + } + + #[tokio::test] + async fn test_update_project_request_name_all_whitespaces_space() { + let response = update_project(" ".into()).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "name must not contain only whitespaces" + ); + } + + #[tokio::test] + async fn test_update_project_request_name_all_whitespaces_tab() { + let response = update_project("\t".into()).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "name must not contain only whitespaces" + ); + } + + #[tokio::test] + async fn test_update_project_request_name_all_whitespaces_newline() { + let response = update_project("\n".into()).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "name must not contain only whitespaces" + ); + } + + #[tokio::test] + async fn test_update_project_request_name_all_whitespaces_combined() { + let response = update_project(" \t\n".into()).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "name must not contain only whitespaces" + ); + } + + #[tokio::test] + async fn test_update_project_request_name_501_character_length() { + let response = update_project("a".repeat(501)).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "name must not exceed 500 characters in length" + ); + } +} diff --git a/crates/bitwarden-sm/src/secrets/create.rs b/crates/bitwarden-sm/src/secrets/create.rs new file mode 100644 index 000000000..efd813877 --- /dev/null +++ b/crates/bitwarden-sm/src/secrets/create.rs @@ -0,0 +1,202 @@ +use bitwarden_api_api::models::SecretCreateRequestModel; +use bitwarden_core::{validate_only_whitespaces, Client, Error}; +use bitwarden_crypto::KeyEncryptable; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; +use validator::Validate; + +use super::SecretResponse; + +#[derive(Serialize, Deserialize, Debug, JsonSchema, Validate)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct SecretCreateRequest { + /// Organization where the secret will be created + pub organization_id: Uuid, + + #[validate(length(min = 1, max = 500), custom(function = validate_only_whitespaces))] + pub key: String, + #[validate(length(min = 1, max = 25_000))] + pub value: String, + #[validate(length(max = 7_000), custom(function = validate_only_whitespaces))] + pub note: String, + + /// IDs of the projects that this secret will belong to + pub project_ids: Option>, +} + +pub(crate) async fn create_secret( + client: &Client, + input: &SecretCreateRequest, +) -> Result { + input.validate()?; + + let enc = client.internal.get_encryption_settings()?; + let key = enc.get_key(&Some(input.organization_id))?; + + let secret = Some(SecretCreateRequestModel { + key: input.key.clone().trim().encrypt_with_key(key)?.to_string(), + value: input.value.clone().encrypt_with_key(key)?.to_string(), + note: input.note.clone().trim().encrypt_with_key(key)?.to_string(), + project_ids: input.project_ids.clone(), + access_policies_requests: None, + }); + + let config = client.internal.get_api_configurations().await; + let res = bitwarden_api_api::apis::secrets_api::organizations_organization_id_secrets_post( + &config.api, + input.organization_id, + secret, + ) + .await?; + + SecretResponse::process_response(res, &enc) +} + +#[cfg(test)] +mod tests { + use super::*; + + async fn create_secret( + key: Option, + value: Option, + note: Option, + ) -> Result { + let input = SecretCreateRequest { + organization_id: Uuid::new_v4(), + key: key.unwrap_or_else(|| "test key".into()), + value: value.unwrap_or_else(|| "test value".into()), + note: note.unwrap_or_else(|| "test note".into()), + project_ids: Some(vec![Uuid::new_v4()]), + }; + + super::create_secret(&Client::new(None), &input).await + } + + #[tokio::test] + async fn test_create_secret_request_key_empty_string() { + let response = create_secret(Some("".into()), None, None).await; + assert!(response.is_err()); + assert_eq!(response.err().unwrap().to_string(), "key must not be empty"); + } + + #[tokio::test] + async fn test_create_secret_request_key_all_whitespaces_space() { + let response = create_secret(Some(" ".into()), None, None).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "key must not contain only whitespaces" + ); + } + + #[tokio::test] + async fn test_create_secret_request_key_all_whitespaces_tab() { + let response = create_secret(Some("\t".into()), None, None).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "key must not contain only whitespaces" + ); + } + + #[tokio::test] + async fn test_create_secret_request_key_all_whitespaces_newline() { + let response = create_secret(Some("\n".into()), None, None).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "key must not contain only whitespaces" + ); + } + + #[tokio::test] + async fn test_create_secret_request_key_all_whitespaces_combined() { + let response = create_secret(Some(" \t\n".into()), None, None).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "key must not contain only whitespaces" + ); + } + + #[tokio::test] + async fn test_create_secret_request_key_501_character_length() { + let response = create_secret(Some("a".repeat(501)), None, None).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "key must not exceed 500 characters in length" + ); + } + + #[tokio::test] + async fn test_create_secret_request_value_empty_string() { + let response = create_secret(None, Some("".into()), None).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "value must not be empty" + ); + } + + #[tokio::test] + async fn test_create_secret_request_value_25001_character_length() { + let response = create_secret(None, Some("a".repeat(25001)), None).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "value must not exceed 25000 characters in length" + ); + } + + #[tokio::test] + async fn test_create_secret_request_note_all_whitespaces_space() { + let response = create_secret(None, None, Some(" ".into())).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "note must not contain only whitespaces" + ); + } + + #[tokio::test] + async fn test_create_secret_request_note_all_whitespaces_tab() { + let response = create_secret(None, None, Some("\t".into())).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "note must not contain only whitespaces" + ); + } + + #[tokio::test] + async fn test_create_secret_request_note_all_whitespaces_newline() { + let response = create_secret(None, None, Some("\n".into())).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "note must not contain only whitespaces" + ); + } + + #[tokio::test] + async fn test_create_secret_request_note_all_whitespaces_combined() { + let response = create_secret(None, None, Some(" \t\n".into())).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "note must not contain only whitespaces" + ); + } + + #[tokio::test] + async fn test_create_secret_request_note_7001_character_length() { + let response = create_secret(None, None, Some("a".repeat(7001))).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "note must not exceed 7000 characters in length" + ); + } +} diff --git a/crates/bitwarden/src/secrets_manager/secrets/delete.rs b/crates/bitwarden-sm/src/secrets/delete.rs similarity index 85% rename from crates/bitwarden/src/secrets_manager/secrets/delete.rs rename to crates/bitwarden-sm/src/secrets/delete.rs index f3fe264e1..377d19f62 100644 --- a/crates/bitwarden/src/secrets_manager/secrets/delete.rs +++ b/crates/bitwarden-sm/src/secrets/delete.rs @@ -1,15 +1,11 @@ use bitwarden_api_api::models::{ BulkDeleteResponseModel, BulkDeleteResponseModelListResponseModel, }; +use bitwarden_core::{client::Client, require, Error}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::{ - client::Client, - error::{require, Result}, -}; - #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct SecretsDeleteRequest { @@ -18,10 +14,10 @@ pub struct SecretsDeleteRequest { } pub(crate) async fn delete_secrets( - client: &mut Client, + client: &Client, input: SecretsDeleteRequest, -) -> Result { - let config = client.get_api_configurations().await; +) -> Result { + let config = client.internal.get_api_configurations().await; let res = bitwarden_api_api::apis::secrets_api::secrets_delete_post(&config.api, Some(input.ids)) .await?; @@ -38,7 +34,7 @@ pub struct SecretsDeleteResponse { impl SecretsDeleteResponse { pub(crate) fn process_response( response: BulkDeleteResponseModelListResponseModel, - ) -> Result { + ) -> Result { Ok(SecretsDeleteResponse { data: response .data @@ -60,7 +56,7 @@ pub struct SecretDeleteResponse { impl SecretDeleteResponse { pub(crate) fn process_response( response: BulkDeleteResponseModel, - ) -> Result { + ) -> Result { Ok(SecretDeleteResponse { id: require!(response.id), error: response.error, diff --git a/crates/bitwarden/src/secrets_manager/secrets/get.rs b/crates/bitwarden-sm/src/secrets/get.rs similarity index 63% rename from crates/bitwarden/src/secrets_manager/secrets/get.rs rename to crates/bitwarden-sm/src/secrets/get.rs index 622253a55..d0964df2e 100644 --- a/crates/bitwarden/src/secrets_manager/secrets/get.rs +++ b/crates/bitwarden-sm/src/secrets/get.rs @@ -1,9 +1,9 @@ +use bitwarden_core::{Client, Error}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; use super::SecretResponse; -use crate::{error::Result, Client}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -13,13 +13,13 @@ pub struct SecretGetRequest { } pub(crate) async fn get_secret( - client: &mut Client, + client: &Client, input: &SecretGetRequest, -) -> Result { - let config = client.get_api_configurations().await; +) -> Result { + let config = client.internal.get_api_configurations().await; let res = bitwarden_api_api::apis::secrets_api::secrets_id_get(&config.api, input.id).await?; - let enc = client.get_encryption_settings()?; + let enc = client.internal.get_encryption_settings()?; - SecretResponse::process_response(res, enc) + SecretResponse::process_response(res, &enc) } diff --git a/crates/bitwarden/src/secrets_manager/secrets/get_by_ids.rs b/crates/bitwarden-sm/src/secrets/get_by_ids.rs similarity index 69% rename from crates/bitwarden/src/secrets_manager/secrets/get_by_ids.rs rename to crates/bitwarden-sm/src/secrets/get_by_ids.rs index 032962849..b5714c4b7 100644 --- a/crates/bitwarden/src/secrets_manager/secrets/get_by_ids.rs +++ b/crates/bitwarden-sm/src/secrets/get_by_ids.rs @@ -1,10 +1,10 @@ use bitwarden_api_api::models::GetSecretsRequestModel; +use bitwarden_core::{client::Client, Error}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; use super::SecretsResponse; -use crate::{client::Client, error::Result}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -14,17 +14,17 @@ pub struct SecretsGetRequest { } pub(crate) async fn get_secrets_by_ids( - client: &mut Client, + client: &Client, input: SecretsGetRequest, -) -> Result { +) -> Result { let request = Some(GetSecretsRequestModel { ids: input.ids }); - let config = client.get_api_configurations().await; + let config = client.internal.get_api_configurations().await; let res = bitwarden_api_api::apis::secrets_api::secrets_get_by_ids_post(&config.api, request).await?; - let enc = client.get_encryption_settings()?; + let enc = client.internal.get_encryption_settings()?; - SecretsResponse::process_response(res, enc) + SecretsResponse::process_response(res, &enc) } diff --git a/crates/bitwarden/src/secrets_manager/secrets/list.rs b/crates/bitwarden-sm/src/secrets/list.rs similarity index 76% rename from crates/bitwarden/src/secrets_manager/secrets/list.rs rename to crates/bitwarden-sm/src/secrets/list.rs index 6cfa85516..60a5c9727 100644 --- a/crates/bitwarden/src/secrets_manager/secrets/list.rs +++ b/crates/bitwarden-sm/src/secrets/list.rs @@ -1,16 +1,15 @@ use bitwarden_api_api::models::{ SecretWithProjectsListResponseModel, SecretsWithProjectsInnerSecret, }; -use bitwarden_crypto::{CryptoError, EncString, KeyDecryptable}; +use bitwarden_core::{ + client::{encryption_settings::EncryptionSettings, Client}, + require, Error, +}; +use bitwarden_crypto::{EncString, KeyDecryptable}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::{ - client::{encryption_settings::EncryptionSettings, Client}, - error::{require, Result}, -}; - #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct SecretIdentifiersRequest { @@ -19,19 +18,19 @@ pub struct SecretIdentifiersRequest { } pub(crate) async fn list_secrets( - client: &mut Client, + client: &Client, input: &SecretIdentifiersRequest, -) -> Result { - let config = client.get_api_configurations().await; +) -> Result { + let config = client.internal.get_api_configurations().await; let res = bitwarden_api_api::apis::secrets_api::organizations_organization_id_secrets_get( &config.api, input.organization_id, ) .await?; - let enc = client.get_encryption_settings()?; + let enc = client.internal.get_encryption_settings()?; - SecretIdentifiersResponse::process_response(res, enc) + SecretIdentifiersResponse::process_response(res, &enc) } #[derive(Serialize, Deserialize, Debug, JsonSchema)] @@ -42,19 +41,19 @@ pub struct SecretIdentifiersByProjectRequest { } pub(crate) async fn list_secrets_by_project( - client: &mut Client, + client: &Client, input: &SecretIdentifiersByProjectRequest, -) -> Result { - let config = client.get_api_configurations().await; +) -> Result { + let config = client.internal.get_api_configurations().await; let res = bitwarden_api_api::apis::secrets_api::projects_project_id_secrets_get( &config.api, input.project_id, ) .await?; - let enc = client.get_encryption_settings()?; + let enc = client.internal.get_encryption_settings()?; - SecretIdentifiersResponse::process_response(res, enc) + SecretIdentifiersResponse::process_response(res, &enc) } #[derive(Serialize, Deserialize, Debug, JsonSchema)] @@ -67,7 +66,7 @@ impl SecretIdentifiersResponse { pub(crate) fn process_response( response: SecretWithProjectsListResponseModel, enc: &EncryptionSettings, - ) -> Result { + ) -> Result { Ok(SecretIdentifiersResponse { data: response .secrets @@ -92,11 +91,9 @@ impl SecretIdentifierResponse { pub(crate) fn process_response( response: SecretsWithProjectsInnerSecret, enc: &EncryptionSettings, - ) -> Result { + ) -> Result { let organization_id = require!(response.organization_id); - let enc_key = enc - .get_key(&Some(organization_id)) - .ok_or(CryptoError::MissingKey)?; + let enc_key = enc.get_key(&Some(organization_id))?; let key = require!(response.key) .parse::()? diff --git a/crates/bitwarden/src/secrets_manager/secrets/mod.rs b/crates/bitwarden-sm/src/secrets/mod.rs similarity index 100% rename from crates/bitwarden/src/secrets_manager/secrets/mod.rs rename to crates/bitwarden-sm/src/secrets/mod.rs diff --git a/crates/bitwarden/src/secrets_manager/secrets/secret_response.rs b/crates/bitwarden-sm/src/secrets/secret_response.rs similarity index 89% rename from crates/bitwarden/src/secrets_manager/secrets/secret_response.rs rename to crates/bitwarden-sm/src/secrets/secret_response.rs index 3b89629a4..e480ac3fc 100644 --- a/crates/bitwarden/src/secrets_manager/secrets/secret_response.rs +++ b/crates/bitwarden-sm/src/secrets/secret_response.rs @@ -1,17 +1,13 @@ use bitwarden_api_api::models::{ BaseSecretResponseModel, BaseSecretResponseModelListResponseModel, SecretResponseModel, }; -use bitwarden_crypto::{CryptoError, EncString, KeyDecryptable}; +use bitwarden_core::{client::encryption_settings::EncryptionSettings, require, Error}; +use bitwarden_crypto::{EncString, KeyDecryptable}; use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::{ - client::encryption_settings::EncryptionSettings, - error::{require, Result}, -}; - #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct SecretResponse { @@ -31,7 +27,7 @@ impl SecretResponse { pub(crate) fn process_response( response: SecretResponseModel, enc: &EncryptionSettings, - ) -> Result { + ) -> Result { let base = BaseSecretResponseModel { object: response.object, id: response.id, @@ -48,9 +44,9 @@ impl SecretResponse { pub(crate) fn process_base_response( response: BaseSecretResponseModel, enc: &EncryptionSettings, - ) -> Result { + ) -> Result { let org_id = response.organization_id; - let enc_key = enc.get_key(&org_id).ok_or(CryptoError::MissingKey)?; + let enc_key = enc.get_key(&org_id)?; let key = require!(response.key) .parse::()? @@ -91,7 +87,7 @@ impl SecretsResponse { pub(crate) fn process_response( response: BaseSecretResponseModelListResponseModel, enc: &EncryptionSettings, - ) -> Result { + ) -> Result { Ok(SecretsResponse { data: response .data diff --git a/crates/bitwarden/src/secrets_manager/secrets/sync.rs b/crates/bitwarden-sm/src/secrets/sync.rs similarity index 82% rename from crates/bitwarden/src/secrets_manager/secrets/sync.rs rename to crates/bitwarden-sm/src/secrets/sync.rs index e2546022d..9c922a002 100644 --- a/crates/bitwarden/src/secrets_manager/secrets/sync.rs +++ b/crates/bitwarden-sm/src/secrets/sync.rs @@ -1,15 +1,11 @@ use bitwarden_api_api::models::SecretsSyncResponseModel; +use bitwarden_core::{client::encryption_settings::EncryptionSettings, require, Client, Error}; use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; use super::SecretResponse; -use crate::{ - client::encryption_settings::EncryptionSettings, - error::{require, Result}, - Client, -}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -21,10 +17,10 @@ pub struct SecretsSyncRequest { } pub(crate) async fn sync_secrets( - client: &mut Client, + client: &Client, input: &SecretsSyncRequest, -) -> Result { - let config = client.get_api_configurations().await; +) -> Result { + let config = client.internal.get_api_configurations().await; let last_synced_date = input.last_synced_date.map(|date| date.to_rfc3339()); let res = bitwarden_api_api::apis::secrets_api::organizations_organization_id_secrets_sync_get( @@ -34,9 +30,9 @@ pub(crate) async fn sync_secrets( ) .await?; - let enc = client.get_encryption_settings()?; + let enc = client.internal.get_encryption_settings()?; - SecretsSyncResponse::process_response(res, enc) + SecretsSyncResponse::process_response(res, &enc) } #[derive(Serialize, Deserialize, Debug, JsonSchema)] @@ -50,7 +46,7 @@ impl SecretsSyncResponse { pub(crate) fn process_response( response: SecretsSyncResponseModel, enc: &EncryptionSettings, - ) -> Result { + ) -> Result { let has_changes = require!(response.has_changes); if has_changes { diff --git a/crates/bitwarden-sm/src/secrets/update.rs b/crates/bitwarden-sm/src/secrets/update.rs new file mode 100644 index 000000000..b80c57234 --- /dev/null +++ b/crates/bitwarden-sm/src/secrets/update.rs @@ -0,0 +1,198 @@ +use bitwarden_api_api::models::SecretUpdateRequestModel; +use bitwarden_core::{validate_only_whitespaces, Client, Error}; +use bitwarden_crypto::KeyEncryptable; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; +use validator::Validate; + +use super::SecretResponse; + +#[derive(Serialize, Deserialize, Debug, JsonSchema, Validate)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct SecretPutRequest { + /// ID of the secret to modify + pub id: Uuid, + /// Organization ID of the secret to modify + pub organization_id: Uuid, + #[validate(length(min = 1, max = 500), custom(function = validate_only_whitespaces))] + pub key: String, + #[validate(length(min = 1, max = 25_000))] + pub value: String, + #[validate(length(max = 7_000), custom(function = validate_only_whitespaces))] + pub note: String, + pub project_ids: Option>, +} + +pub(crate) async fn update_secret( + client: &Client, + input: &SecretPutRequest, +) -> Result { + input.validate()?; + + let enc = client.internal.get_encryption_settings()?; + let key = enc.get_key(&Some(input.organization_id))?; + + let secret = Some(SecretUpdateRequestModel { + key: input.key.clone().trim().encrypt_with_key(key)?.to_string(), + value: input.value.clone().encrypt_with_key(key)?.to_string(), + note: input.note.clone().trim().encrypt_with_key(key)?.to_string(), + project_ids: input.project_ids.clone(), + access_policies_requests: None, + }); + + let config = client.internal.get_api_configurations().await; + let res = + bitwarden_api_api::apis::secrets_api::secrets_id_put(&config.api, input.id, secret).await?; + + SecretResponse::process_response(res, &enc) +} + +#[cfg(test)] +mod tests { + use super::*; + + async fn update_secret( + key: Option, + value: Option, + note: Option, + ) -> Result { + let input = SecretPutRequest { + id: Uuid::new_v4(), + organization_id: Uuid::new_v4(), + key: key.unwrap_or_else(|| "test key".into()), + value: value.unwrap_or_else(|| "test value".into()), + note: note.unwrap_or_else(|| "test note".into()), + project_ids: Some(vec![Uuid::new_v4()]), + }; + + super::update_secret(&Client::new(None), &input).await + } + + #[tokio::test] + async fn test_update_secret_request_key_empty_string() { + let response = update_secret(Some("".into()), None, None).await; + assert!(response.is_err()); + assert_eq!(response.err().unwrap().to_string(), "key must not be empty"); + } + + #[tokio::test] + async fn test_update_secret_request_key_all_whitespaces_space() { + let response = update_secret(Some(" ".into()), None, None).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "key must not contain only whitespaces" + ); + } + + #[tokio::test] + async fn test_update_secret_request_key_all_whitespaces_tab() { + let response = update_secret(Some("\t".into()), None, None).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "key must not contain only whitespaces" + ); + } + + #[tokio::test] + async fn test_update_secret_request_key_all_whitespaces_newline() { + let response = update_secret(Some("\n".into()), None, None).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "key must not contain only whitespaces" + ); + } + + #[tokio::test] + async fn test_update_secret_request_key_all_whitespaces_combined() { + let response = update_secret(Some(" \t\n".into()), None, None).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "key must not contain only whitespaces" + ); + } + + #[tokio::test] + async fn test_update_secret_request_key_501_character_length() { + let response = update_secret(Some("a".repeat(501)), None, None).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "key must not exceed 500 characters in length" + ); + } + + #[tokio::test] + async fn test_update_secret_request_value_empty_string() { + let response = update_secret(None, Some("".into()), None).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "value must not be empty" + ); + } + + #[tokio::test] + async fn test_update_secret_request_value_25001_character_length() { + let response = update_secret(None, Some("a".repeat(25001)), None).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "value must not exceed 25000 characters in length" + ); + } + + #[tokio::test] + async fn test_update_secret_request_note_all_whitespaces_space() { + let response = update_secret(None, None, Some(" ".into())).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "note must not contain only whitespaces" + ); + } + + #[tokio::test] + async fn test_update_secret_request_note_all_whitespaces_tab() { + let response = update_secret(None, None, Some("\t".into())).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "note must not contain only whitespaces" + ); + } + + #[tokio::test] + async fn test_update_secret_request_note_all_whitespaces_newline() { + let response = update_secret(None, None, Some("\n".into())).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "note must not contain only whitespaces" + ); + } + + #[tokio::test] + async fn test_update_secret_request_note_all_whitespaces_combined() { + let response = update_secret(None, None, Some(" \t\n".into())).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "note must not contain only whitespaces" + ); + } + + #[tokio::test] + async fn test_update_secret_request_note_7001_character_length() { + let response = update_secret(None, None, Some("a".repeat(7001))).await; + assert!(response.is_err()); + assert_eq!( + response.err().unwrap().to_string(), + "note must not exceed 7000 characters in length" + ); + } +} diff --git a/crates/bitwarden-uniffi/Cargo.toml b/crates/bitwarden-uniffi/Cargo.toml index 62f9e5806..2e57e23b5 100644 --- a/crates/bitwarden-uniffi/Cargo.toml +++ b/crates/bitwarden-uniffi/Cargo.toml @@ -11,34 +11,41 @@ repository.workspace = true license-file.workspace = true [features] -docs = ["dep:schemars"] # Docs [lib] crate-type = ["lib", "staticlib", "cdylib"] bench = false [dependencies] -async-lock = "3.3.0" async-trait = "0.1.80" -bitwarden = { workspace = true, features = ["internal", "uniffi"] } +bitwarden-core = { workspace = true, features = ["uniffi"] } bitwarden-crypto = { workspace = true, features = ["uniffi"] } +bitwarden-exporters = { workspace = true, features = ["uniffi"] } +bitwarden-fido = { workspace = true, features = ["uniffi"] } bitwarden-generators = { workspace = true, features = ["uniffi"] } -chrono = { version = ">=0.4.26, <0.5", features = [ - "serde", - "std", -], default-features = false } -log = "0.4.20" +bitwarden-send = { workspace = true, features = ["uniffi"] } +bitwarden-vault = { workspace = true, features = ["uniffi"] } +chrono = { workspace = true, features = ["std"] } env_logger = "0.11.1" -schemars = { version = ">=0.8, <0.9", optional = true } -thiserror = ">=1.0.40, <2.0" -uniffi = "=0.27.2" -uuid = ">=1.3.3, <2" +log = { workspace = true } +schemars = { workspace = true, optional = true } +thiserror = { workspace = true } +uniffi = { workspace = true } +uuid = { workspace = true } [target.'cfg(target_os = "android")'.dependencies] -android_logger = "0.13" +android_logger = "0.14" + +# The use of rustls-platform-verifier requires some extra support to communicate with the Android platform +jni = ">=0.19, <0.20" +libloading = ">=0.8.1, <0.9" +rustls-platform-verifier = "0.3.4" + +[target.'cfg(target_os = "ios")'.dependencies] +oslog = "0.2.0" [build-dependencies] -uniffi = { version = "=0.27.2", features = ["build"] } +uniffi = { workspace = true, features = ["build"] } [lints] workspace = true diff --git a/crates/bitwarden-uniffi/README.md b/crates/bitwarden-uniffi/README.md index 4b2e61714..c2e4d6c45 100644 --- a/crates/bitwarden-uniffi/README.md +++ b/crates/bitwarden-uniffi/README.md @@ -1,13 +1 @@ # Bitwarden-uniffi - -## Generating documentation - -If desired we have some scripts that generates markdown documentation from the rustdoc output. - -```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 > doc.md -``` diff --git a/crates/bitwarden-uniffi/src/android_support.rs b/crates/bitwarden-uniffi/src/android_support.rs new file mode 100644 index 000000000..c6d8b70a7 --- /dev/null +++ b/crates/bitwarden-uniffi/src/android_support.rs @@ -0,0 +1,77 @@ +use std::error::Error; + +use jni::sys::{jint, jsize, JavaVM}; + +pub fn init() { + static ANDROID_INIT: std::sync::Once = std::sync::Once::new(); + + fn init_inner() -> Result<(), Box> { + let jvm = java_vm()?; + let env = jvm.attach_current_thread_permanently()?; + init_verifier(&env)?; + Ok(()) + } + + ANDROID_INIT.call_once(|| { + if let Err(e) = init_inner() { + log::error!("Failed to initialize Android support: {}", e); + } + }); +} + +type JniGetCreatedJavaVms = + unsafe extern "system" fn(vmBuf: *mut *mut JavaVM, bufLen: jsize, nVMs: *mut jsize) -> jint; +const JNI_GET_JAVA_VMS_NAME: &[u8] = b"JNI_GetCreatedJavaVMs"; + +fn java_vm() -> Result> { + // Ideally we would use JNI to get a reference to the JavaVM, but that's not possible since + // UniFFI uses JNA + // + // If we could use JNI, we'd just need to export a function and call it from the Android app: + // #[export_name = "Java_com_orgname_android_rust_init"] + // extern "C" fn java_init(env: JNIEnv, _class: JClass, context: JObject, ) -> jboolean { + // + // Instead we have to use libloading to get a reference to the JavaVM: + // + // https://github.com/mozilla/uniffi-rs/issues/1778#issuecomment-1807979746 + let lib = libloading::os::unix::Library::this(); + let get_created_java_vms: JniGetCreatedJavaVms = unsafe { *lib.get(JNI_GET_JAVA_VMS_NAME)? }; + + let mut java_vms: [*mut JavaVM; 1] = [std::ptr::null_mut() as *mut JavaVM]; + let mut vm_count: i32 = 0; + + let ok = unsafe { get_created_java_vms(java_vms.as_mut_ptr(), 1, &mut vm_count) }; + if ok != jni::sys::JNI_OK { + return Err("Failed to get JavaVM".into()); + } + if vm_count != 1 { + return Err(format!("Invalid JavaVM count: {vm_count}").into()); + } + + let jvm = unsafe { jni::JavaVM::from_raw(java_vms[0]) }?; + Ok(jvm) +} + +fn init_verifier(env: &jni::JNIEnv<'_>) -> jni::errors::Result<()> { + let activity_thread = env + .call_static_method( + "android/app/ActivityThread", + "currentActivityThread", + "()Landroid/app/ActivityThread;", + &[], + )? + .l()?; + + let context = env + .call_method( + activity_thread, + "getApplication", + "()Landroid/app/Application;", + &[], + )? + .l()?; + + Ok(rustls_platform_verifier::android::init_hosted( + &env, context, + )?) +} diff --git a/crates/bitwarden-uniffi/src/auth/mod.rs b/crates/bitwarden-uniffi/src/auth/mod.rs index 6c18bfc61..43496080a 100644 --- a/crates/bitwarden-uniffi/src/auth/mod.rs +++ b/crates/bitwarden-uniffi/src/auth/mod.rs @@ -1,10 +1,13 @@ use std::sync::Arc; -use bitwarden::auth::{ - password::MasterPasswordPolicyOptions, AuthRequestResponse, RegisterKeyResponse, - RegisterTdeKeyResponse, +use bitwarden_core::{ + auth::{ + password::MasterPasswordPolicyOptions, AuthRequestResponse, KeyConnectorResponse, + RegisterKeyResponse, RegisterTdeKeyResponse, + }, + Error, }; -use bitwarden_crypto::{AsymmetricEncString, HashPurpose, Kdf, TrustDeviceResponse}; +use bitwarden_crypto::{AsymmetricEncString, EncString, HashPurpose, Kdf, TrustDeviceResponse}; use crate::{error::Result, Client}; @@ -14,7 +17,7 @@ pub struct ClientAuth(pub(crate) Arc); #[uniffi::export(async_runtime = "tokio")] impl ClientAuth { /// **API Draft:** Calculate Password Strength - pub async fn password_strength( + pub fn password_strength( &self, password: String, email: String, @@ -22,15 +25,12 @@ impl ClientAuth { ) -> u8 { self.0 .0 - .write() - .await .auth() .password_strength(password, email, additional_inputs) - .await } /// Evaluate if the provided password satisfies the provided policy - pub async fn satisfies_policy( + pub fn satisfies_policy( &self, password: String, strength: u8, @@ -38,11 +38,8 @@ impl ClientAuth { ) -> bool { self.0 .0 - .write() - .await .auth() .satisfies_policy(password, strength, &policy) - .await } /// Hash the user password @@ -56,41 +53,43 @@ impl ClientAuth { Ok(self .0 .0 - .read() - .await .kdf() .hash_password(email, password, kdf_params, purpose) .await?) } /// Generate keys needed for registration process - pub async fn make_register_keys( + pub fn make_register_keys( &self, email: String, password: String, kdf: Kdf, ) -> Result { - Ok(self - .0 - .0 - .write() - .await - .auth() - .make_register_keys(email, password, kdf)?) + Ok(self.0 .0.auth().make_register_keys(email, password, kdf)?) } /// Generate keys needed for TDE process - pub async fn make_register_tde_keys( + pub fn make_register_tde_keys( &self, email: String, org_public_key: String, remember_device: bool, ) -> Result { - Ok(self.0 .0.write().await.auth().make_register_tde_keys( - email, - org_public_key, - remember_device, - )?) + Ok(self + .0 + .0 + .auth() + .make_register_tde_keys(email, org_public_key, remember_device)?) + } + + /// Generate keys needed to onboard a new user without master key to key connector + pub fn make_key_connector_keys(&self) -> Result { + Ok(self + .0 + .0 + .auth() + .make_key_connector_keys() + .map_err(Error::Crypto)?) } /// Validate the user password @@ -98,12 +97,10 @@ impl ClientAuth { /// 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 { + pub fn validate_password(&self, password: String, password_hash: String) -> Result { Ok(self .0 .0 - .write() - .await .auth() .validate_password(password, password_hash)?) } @@ -114,7 +111,7 @@ impl ClientAuth { /// password. Some example are login with device or TDE. /// /// This works by comparing the provided password against the encrypted user key. - pub async fn validate_password_user_key( + pub fn validate_password_user_key( &self, password: String, encrypted_user_key: String, @@ -122,30 +119,33 @@ impl ClientAuth { Ok(self .0 .0 - .write() - .await .auth() .validate_password_user_key(password, encrypted_user_key)?) } + /// Validate the user PIN + /// + /// To validate the user PIN, you need to have the user's pin_protected_user_key. This key is + /// obtained when enabling PIN unlock on the account with the `derive_pin_key` method. + /// + /// This works by comparing the decrypted user key with the current user key, so the client must + /// be unlocked. + pub fn validate_pin(&self, pin: String, pin_protected_user_key: EncString) -> Result { + Ok(self.0 .0.auth().validate_pin(pin, pin_protected_user_key)?) + } + /// Initialize a new auth request - pub async fn new_auth_request(&self, email: String) -> Result { - Ok(self.0 .0.write().await.auth().new_auth_request(&email)?) + pub fn new_auth_request(&self, email: String) -> Result { + Ok(self.0 .0.auth().new_auth_request(&email)?) } /// Approve an auth request - pub async fn approve_auth_request(&self, public_key: String) -> Result { - Ok(self - .0 - .0 - .write() - .await - .auth() - .approve_auth_request(public_key)?) + pub fn approve_auth_request(&self, public_key: String) -> Result { + Ok(self.0 .0.auth().approve_auth_request(public_key)?) } /// Trust the current device - pub async fn trust_device(&self) -> Result { - Ok(self.0 .0.write().await.auth().trust_device()?) + pub fn trust_device(&self) -> Result { + Ok(self.0 .0.auth().trust_device()?) } } diff --git a/crates/bitwarden-uniffi/src/crypto.rs b/crates/bitwarden-uniffi/src/crypto.rs index 0f877d52e..6404d62f6 100644 --- a/crates/bitwarden-uniffi/src/crypto.rs +++ b/crates/bitwarden-uniffi/src/crypto.rs @@ -1,7 +1,11 @@ use std::sync::Arc; -use bitwarden::mobile::crypto::{ - DerivePinKeyResponse, InitOrgCryptoRequest, InitUserCryptoRequest, UpdatePasswordResponse, +use bitwarden_core::{ + mobile::crypto::{ + DeriveKeyConnectorRequest, DerivePinKeyResponse, InitOrgCryptoRequest, + InitUserCryptoRequest, UpdatePasswordResponse, + }, + Error, }; use bitwarden_crypto::{AsymmetricEncString, EncString}; @@ -18,11 +22,10 @@ impl ClientCrypto { Ok(self .0 .0 - .write() - .await .crypto() .initialize_user_crypto(req) - .await?) + .await + .map_err(Error::EncryptionSettings)?) } /// Initialization method for the organization crypto. Needs to be called after @@ -31,69 +34,43 @@ impl ClientCrypto { Ok(self .0 .0 - .write() - .await .crypto() .initialize_org_crypto(req) - .await?) + .await + .map_err(Error::EncryptionSettings)?) } /// 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?) + Ok(self.0 .0.crypto().get_user_encryption_key().await?) } /// Update the user's password, which will re-encrypt the user's encryption key with the new /// password. This returns the new encrypted user key and the new password hash. - pub async fn update_password(&self, new_password: String) -> Result { - Ok(self - .0 - .0 - .write() - .await - .crypto() - .update_password(new_password) - .await?) + pub fn update_password(&self, new_password: String) -> Result { + Ok(self.0 .0.crypto().update_password(new_password)?) } /// 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?) + pub fn derive_pin_key(&self, pin: String) -> Result { + Ok(self.0 .0.crypto().derive_pin_key(pin)?) } /// Derives the pin protected user key from encrypted pin. Used when pin requires master /// password on first unlock. - pub async fn derive_pin_user_key(&self, encrypted_pin: EncString) -> Result { - Ok(self - .0 - .0 - .write() - .await - .crypto() - .derive_pin_user_key(encrypted_pin) - .await?) + pub fn derive_pin_user_key(&self, encrypted_pin: EncString) -> Result { + Ok(self.0 .0.crypto().derive_pin_user_key(encrypted_pin)?) } - pub async fn enroll_admin_password_reset( - &self, - public_key: String, - ) -> Result { - Ok(self - .0 - .0 - .write() - .await - .crypto() - .enroll_admin_password_reset(public_key)?) + pub fn enroll_admin_password_reset(&self, public_key: String) -> Result { + Ok(self.0 .0.crypto().enroll_admin_password_reset(public_key)?) + } + + /// Derive the master key for migrating to the key connector + pub fn derive_key_connector(&self, request: DeriveKeyConnectorRequest) -> Result { + Ok(self.0 .0.crypto().derive_key_connector(request)?) } } diff --git a/crates/bitwarden-uniffi/src/docs.rs b/crates/bitwarden-uniffi/src/docs.rs deleted file mode 100644 index 0569bbcb2..000000000 --- a/crates/bitwarden-uniffi/src/docs.rs +++ /dev/null @@ -1,48 +0,0 @@ -use bitwarden::{ - auth::password::MasterPasswordPolicyOptions, - generators::{PassphraseGeneratorRequest, PasswordGeneratorRequest}, - mobile::crypto::{InitOrgCryptoRequest, InitUserCryptoRequest}, - platform::FingerprintRequest, - tool::{ExportFormat, Send, SendListView, SendView}, - vault::{Cipher, CipherView, Collection, Folder, FolderView, TotpResponse}, -}; -use bitwarden_crypto::{HashPurpose, Kdf}; -use schemars::JsonSchema; - -#[derive(JsonSchema)] -#[allow(clippy::large_enum_variant)] -pub enum DocRef { - // Vault - Cipher(Cipher), - CipherView(CipherView), - Collection(Collection), - Folder(Folder), - FolderView(FolderView), - Send(Send), - SendView(SendView), - SendListView(SendListView), - - // Crypto - InitUserCryptoRequest(InitUserCryptoRequest), - InitOrgCryptoRequest(InitOrgCryptoRequest), - HashPurpose(HashPurpose), - - // Generators - PasswordGeneratorRequest(PasswordGeneratorRequest), - PassphraseGeneratorRequest(PassphraseGeneratorRequest), - - // Exporters - ExportFormat(ExportFormat), - - // Platform - FingerprintRequest(FingerprintRequest), - - // Auth - MasterPasswordPolicyOptions(MasterPasswordPolicyOptions), - - // Kdf - Kdf(Kdf), - - /// TOTP - TotpResponse(TotpResponse), -} diff --git a/crates/bitwarden-uniffi/src/error.rs b/crates/bitwarden-uniffi/src/error.rs index 5eef9bbd5..889e3c7fb 100644 --- a/crates/bitwarden-uniffi/src/error.rs +++ b/crates/bitwarden-uniffi/src/error.rs @@ -1,15 +1,24 @@ use std::fmt::{Display, Formatter}; +use bitwarden_exporters::ExportError; +use bitwarden_generators::{PassphraseError, PasswordError, UsernameError}; + // Name is converted from *Error to *Exception, so we can't just name the enum Error because // Exception already exists #[derive(uniffi::Error, Debug)] #[uniffi(flat_error)] pub enum BitwardenError { - E(bitwarden::error::Error), + E(Error), } -impl From for BitwardenError { - fn from(e: bitwarden::error::Error) -> Self { +impl From for BitwardenError { + fn from(e: bitwarden_core::Error) -> Self { + Self::E(e.into()) + } +} + +impl From for BitwardenError { + fn from(e: Error) -> Self { Self::E(e) } } @@ -31,3 +40,42 @@ impl std::error::Error for BitwardenError { } pub type Result = std::result::Result; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Core(#[from] bitwarden_core::Error), + + // Generators + #[error(transparent)] + UsernameError(#[from] UsernameError), + #[error(transparent)] + PassphraseError(#[from] PassphraseError), + #[error(transparent)] + PasswordError(#[from] PasswordError), + + // Vault + #[error(transparent)] + Cipher(#[from] bitwarden_vault::CipherError), + #[error(transparent)] + Totp(#[from] bitwarden_vault::TotpError), + + #[error(transparent)] + ExportError(#[from] ExportError), + + // Fido + #[error(transparent)] + MakeCredential(#[from] bitwarden_fido::MakeCredentialError), + #[error(transparent)] + GetAssertion(#[from] bitwarden_fido::GetAssertionError), + #[error(transparent)] + SilentlyDiscoverCredentials(#[from] bitwarden_fido::SilentlyDiscoverCredentialsError), + #[error(transparent)] + CredentialsForAutofillError(#[from] bitwarden_fido::CredentialsForAutofillError), + #[error(transparent)] + DecryptFido2AutofillCredentialsError( + #[from] bitwarden_fido::DecryptFido2AutofillCredentialsError, + ), + #[error(transparent)] + Fido2Client(#[from] bitwarden_fido::Fido2ClientError), +} diff --git a/crates/bitwarden-uniffi/src/lib.rs b/crates/bitwarden-uniffi/src/lib.rs index 318d47417..aae35c37a 100644 --- a/crates/bitwarden-uniffi/src/lib.rs +++ b/crates/bitwarden-uniffi/src/lib.rs @@ -2,9 +2,8 @@ uniffi::setup_scaffolding!(); use std::sync::Arc; -use async_lock::RwLock; use auth::ClientAuth; -use bitwarden::client::client_settings::ClientSettings; +use bitwarden_core::ClientSettings; pub mod auth; pub mod crypto; @@ -14,8 +13,8 @@ pub mod tool; mod uniffi_support; pub mod vault; -#[cfg(feature = "docs")] -pub mod docs; +#[cfg(target_os = "android")] +mod android_support; use crypto::ClientCrypto; use error::Result; @@ -24,15 +23,19 @@ use tool::{ClientExporters, ClientGenerators, ClientSends}; use vault::ClientVault; #[derive(uniffi::Object)] -pub struct Client(RwLock); +pub struct Client(bitwarden_core::Client); -#[uniffi::export] +#[uniffi::export(async_runtime = "tokio")] impl Client { /// Initialize a new instance of the SDK client #[uniffi::constructor] pub fn new(settings: Option) -> Arc { init_logger(); - Arc::new(Self(RwLock::new(bitwarden::Client::new(settings)))) + + #[cfg(target_os = "android")] + android_support::init(); + + Arc::new(Self(bitwarden_core::Client::new(settings))) } /// Crypto operations @@ -73,13 +76,30 @@ impl Client { pub fn echo(&self, msg: String) -> String { msg } + + /// Test method, calls http endpoint + pub async fn http_get(&self, url: String) -> Result { + let client = self.0.internal.get_http_client(); + let res = client + .get(&url) + .send() + .await + .map_err(bitwarden_core::Error::Reqwest)?; + + Ok(res.text().await.map_err(bitwarden_core::Error::Reqwest)?) + } } fn init_logger() { - #[cfg(not(target_os = "android"))] + #[cfg(not(any(target_os = "android", target_os = "ios")))] let _ = env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) .try_init(); + #[cfg(target_os = "ios")] + let _ = oslog::OsLogger::new("com.8bit.bitwarden") + .level_filter(log::LevelFilter::Info) + .init(); + #[cfg(target_os = "android")] android_logger::init_once( android_logger::Config::default().with_max_level(uniffi::deps::log::LevelFilter::Info), diff --git a/crates/bitwarden-uniffi/src/platform/fido2.rs b/crates/bitwarden-uniffi/src/platform/fido2.rs index 8a1c10e64..99c998648 100644 --- a/crates/bitwarden-uniffi/src/platform/fido2.rs +++ b/crates/bitwarden-uniffi/src/platform/fido2.rs @@ -1,17 +1,18 @@ use std::sync::Arc; -use bitwarden::{ - platform::fido2::{ - CheckUserOptions, ClientData, Fido2CallbackError as BitFido2CallbackError, - GetAssertionRequest, GetAssertionResult, MakeCredentialRequest, MakeCredentialResult, - PublicKeyCredentialAuthenticatorAssertionResponse, - PublicKeyCredentialAuthenticatorAttestationResponse, PublicKeyCredentialRpEntity, - PublicKeyCredentialUserEntity, - }, - vault::{Cipher, CipherView, Fido2CredentialNewView, Fido2CredentialView}, +use bitwarden_fido::{ + CheckUserOptions, ClientData, ClientFido2Ext, Fido2CallbackError as BitFido2CallbackError, + Fido2CredentialAutofillView, GetAssertionRequest, GetAssertionResult, MakeCredentialRequest, + MakeCredentialResult, Origin, PublicKeyCredentialAuthenticatorAssertionResponse, + PublicKeyCredentialAuthenticatorAttestationResponse, PublicKeyCredentialRpEntity, + PublicKeyCredentialUserEntity, }; +use bitwarden_vault::{Cipher, CipherView, Fido2CredentialNewView}; -use crate::{error::Result, Client}; +use crate::{ + error::{Error, Result}, + Client, +}; #[derive(uniffi::Object)] pub struct ClientFido2(pub(crate) Arc); @@ -41,6 +42,20 @@ impl ClientFido2 { credential_store, ))) } + + pub fn decrypt_fido2_autofill_credentials( + self: Arc, + cipher_view: CipherView, + ) -> Result> { + let result = self + .0 + .0 + .fido2() + .decrypt_fido2_autofill_credentials(cipher_view) + .map_err(Error::DecryptFido2AutofillCredentialsError)?; + + Ok(result) + } } #[derive(uniffi::Object)] @@ -56,44 +71,58 @@ impl ClientFido2Authenticator { &self, request: MakeCredentialRequest, ) -> Result { - let mut client = self.0 .0.write().await; - - let mut platform = client.platform(); - let mut fido2 = platform.fido2(); + let fido2 = self.0 .0.fido2(); let ui = UniffiTraitBridge(self.1.as_ref()); let cs = UniffiTraitBridge(self.2.as_ref()); - let mut auth = fido2.create_authenticator(&ui, &cs)?; + let mut auth = fido2.create_authenticator(&ui, &cs); - let result = auth.make_credential(request).await?; + let result = auth + .make_credential(request) + .await + .map_err(Error::MakeCredential)?; Ok(result) } pub async fn get_assertion(&self, request: GetAssertionRequest) -> Result { - let mut client = self.0 .0.write().await; - - let mut platform = client.platform(); - let mut fido2 = platform.fido2(); + let fido2 = self.0 .0.fido2(); let ui = UniffiTraitBridge(self.1.as_ref()); let cs = UniffiTraitBridge(self.2.as_ref()); - let mut auth = fido2.create_authenticator(&ui, &cs)?; + let mut auth = fido2.create_authenticator(&ui, &cs); - let result = auth.get_assertion(request).await?; + let result = auth + .get_assertion(request) + .await + .map_err(Error::GetAssertion)?; Ok(result) } pub async fn silently_discover_credentials( &self, rp_id: String, - ) -> Result> { - let mut client = self.0 .0.write().await; + ) -> Result> { + let fido2 = self.0 .0.fido2(); - let mut platform = client.platform(); - let mut fido2 = platform.fido2(); let ui = UniffiTraitBridge(self.1.as_ref()); let cs = UniffiTraitBridge(self.2.as_ref()); - let mut auth = fido2.create_authenticator(&ui, &cs)?; + let mut auth = fido2.create_authenticator(&ui, &cs); + + let result = auth + .silently_discover_credentials(rp_id) + .await + .map_err(Error::SilentlyDiscoverCredentials)?; + Ok(result) + } - let result = auth.silently_discover_credentials(rp_id).await?; + pub async fn credentials_for_autofill(&self) -> Result> { + let fido2 = self.0 .0.fido2(); + let ui = UniffiTraitBridge(self.1.as_ref()); + let cs = UniffiTraitBridge(self.2.as_ref()); + let mut auth = fido2.create_authenticator(&ui, &cs); + + let result = auth + .credentials_for_autofill() + .await + .map_err(Error::CredentialsForAutofillError)?; Ok(result) } } @@ -105,37 +134,37 @@ pub struct ClientFido2Client(pub(crate) ClientFido2Authenticator); impl ClientFido2Client { pub async fn register( &self, - origin: String, + origin: Origin, request: String, client_data: ClientData, ) -> Result { - let mut client = self.0 .0 .0.write().await; - - let mut platform = client.platform(); - let mut fido2 = platform.fido2(); + let fido2 = self.0 .0 .0.fido2(); let ui = UniffiTraitBridge(self.0 .1.as_ref()); let cs = UniffiTraitBridge(self.0 .2.as_ref()); - let mut client = fido2.create_client(&ui, &cs)?; + let mut client = fido2.create_client(&ui, &cs); - let result = client.register(origin, request, client_data).await?; + let result = client + .register(origin, request, client_data) + .await + .map_err(Error::Fido2Client)?; Ok(result) } pub async fn authenticate( &self, - origin: String, + origin: Origin, request: String, client_data: ClientData, ) -> Result { - let mut client = self.0 .0 .0.write().await; - - let mut platform = client.platform(); - let mut fido2 = platform.fido2(); + let fido2 = self.0 .0 .0.fido2(); let ui = UniffiTraitBridge(self.0 .1.as_ref()); let cs = UniffiTraitBridge(self.0 .2.as_ref()); - let mut client = fido2.create_client(&ui, &cs)?; + let mut client = fido2.create_client(&ui, &cs); - let result = client.authenticate(origin, request, client_data).await?; + let result = client + .authenticate(origin, request, client_data) + .await + .map_err(Error::Fido2Client)?; Ok(result) } } @@ -150,6 +179,22 @@ pub struct CheckUserResult { user_verified: bool, } +impl From for bitwarden_fido::CheckUserResult { + fn from(val: CheckUserResult) -> Self { + Self { + user_present: val.user_present, + user_verified: val.user_verified, + } + } +} + +#[allow(dead_code)] +#[derive(uniffi::Record)] +pub struct CheckUserAndPickCredentialForCreationResult { + cipher: CipherViewWrapper, + check_user_result: CheckUserResult, +} + #[derive(Debug, thiserror::Error, uniffi::Error)] pub enum Fido2CallbackError { #[error("The operation requires user interaction")] @@ -197,7 +242,7 @@ pub trait Fido2UserInterface: Send + Sync { &self, options: CheckUserOptions, new_credential: Fido2CredentialNewView, - ) -> Result; + ) -> Result; async fn is_verification_enabled(&self) -> bool; } @@ -210,6 +255,8 @@ pub trait Fido2CredentialStore: Send + Sync { rip_id: String, ) -> Result, Fido2CallbackError>; + async fn all_credentials(&self) -> Result, Fido2CallbackError>; + async fn save_credential(&self, cred: Cipher) -> Result<(), Fido2CallbackError>; } @@ -220,9 +267,7 @@ pub trait Fido2CredentialStore: Send + Sync { struct UniffiTraitBridge(T); #[async_trait::async_trait] -impl bitwarden::platform::fido2::Fido2CredentialStore - for UniffiTraitBridge<&dyn Fido2CredentialStore> -{ +impl bitwarden_fido::Fido2CredentialStore for UniffiTraitBridge<&dyn Fido2CredentialStore> { async fn find_credentials( &self, ids: Option>>, @@ -234,6 +279,10 @@ impl bitwarden::platform::fido2::Fido2CredentialStore .map_err(Into::into) } + async fn all_credentials(&self) -> Result, BitFido2CallbackError> { + self.0.all_credentials().await.map_err(Into::into) + } + async fn save_credential(&self, cred: Cipher) -> Result<(), BitFido2CallbackError> { self.0.save_credential(cred).await.map_err(Into::into) } @@ -256,9 +305,9 @@ pub enum UIHint { RequestExistingCredential(CipherView), } -impl From> for UIHint { - fn from(hint: bitwarden::platform::fido2::UIHint<'_, CipherView>) -> Self { - use bitwarden::platform::fido2::UIHint as BWUIHint; +impl From> for UIHint { + fn from(hint: bitwarden_fido::UIHint<'_, CipherView>) -> Self { + use bitwarden_fido::UIHint as BWUIHint; match hint { BWUIHint::InformExcludedCredentialFound(cipher) => { UIHint::InformExcludedCredentialFound(cipher.clone()) @@ -283,19 +332,16 @@ impl From> for UIHint { } #[async_trait::async_trait] -impl bitwarden::platform::fido2::Fido2UserInterface for UniffiTraitBridge<&dyn Fido2UserInterface> { +impl bitwarden_fido::Fido2UserInterface for UniffiTraitBridge<&dyn Fido2UserInterface> { async fn check_user<'a>( &self, options: CheckUserOptions, - hint: bitwarden::platform::fido2::UIHint<'a, CipherView>, - ) -> Result { + hint: bitwarden_fido::UIHint<'a, CipherView>, + ) -> Result { self.0 .check_user(options.clone(), hint.into()) .await - .map(|r| bitwarden::platform::fido2::CheckUserResult { - user_present: r.user_present, - user_verified: r.user_verified, - }) + .map(Into::into) .map_err(Into::into) } async fn pick_credential_for_authentication( @@ -312,11 +358,11 @@ impl bitwarden::platform::fido2::Fido2UserInterface for UniffiTraitBridge<&dyn F &self, options: CheckUserOptions, new_credential: Fido2CredentialNewView, - ) -> Result { + ) -> Result<(CipherView, bitwarden_fido::CheckUserResult), BitFido2CallbackError> { self.0 .check_user_and_pick_credential_for_creation(options, new_credential) .await - .map(|v| v.cipher) + .map(|v| (v.cipher.cipher, v.check_user_result.into())) .map_err(Into::into) } async fn is_verification_enabled(&self) -> bool { diff --git a/crates/bitwarden-uniffi/src/platform/mod.rs b/crates/bitwarden-uniffi/src/platform/mod.rs index 458306676..b5d944152 100644 --- a/crates/bitwarden-uniffi/src/platform/mod.rs +++ b/crates/bitwarden-uniffi/src/platform/mod.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use bitwarden::platform::FingerprintRequest; +use bitwarden_core::platform::FingerprintRequest; use crate::{error::Result, Client}; @@ -9,34 +9,25 @@ mod fido2; #[derive(uniffi::Object)] pub struct ClientPlatform(pub(crate) Arc); -#[uniffi::export(async_runtime = "tokio")] +#[uniffi::export] impl ClientPlatform { /// Fingerprint (public key) - pub async fn fingerprint(&self, req: FingerprintRequest) -> Result { - Ok(self - .0 - .0 - .write() - .await - .platform() - .fingerprint(&req)? - .fingerprint) + pub fn fingerprint(&self, req: FingerprintRequest) -> Result { + Ok(self.0 .0.platform().fingerprint(&req)?.fingerprint) } /// Fingerprint using logged in user's public key - pub async fn user_fingerprint(&self, fingerprint_material: String) -> Result { + pub fn user_fingerprint(&self, fingerprint_material: String) -> Result { Ok(self .0 .0 - .write() - .await .platform() .user_fingerprint(fingerprint_material)?) } /// Load feature flags into the client - pub async fn load_flags(&self, flags: std::collections::HashMap) -> Result<()> { - self.0 .0.write().await.load_flags(flags); + pub fn load_flags(&self, flags: std::collections::HashMap) -> Result<()> { + self.0 .0.internal.load_flags(flags); Ok(()) } diff --git a/crates/bitwarden-uniffi/src/tool/mod.rs b/crates/bitwarden-uniffi/src/tool/mod.rs index 3ad4f7f93..498884218 100644 --- a/crates/bitwarden-uniffi/src/tool/mod.rs +++ b/crates/bitwarden-uniffi/src/tool/mod.rs @@ -1,12 +1,16 @@ use std::sync::Arc; -use bitwarden::{ - generators::{PassphraseGeneratorRequest, PasswordGeneratorRequest, UsernameGeneratorRequest}, - tool::ExportFormat, - vault::{Cipher, Collection, Folder}, +use bitwarden_exporters::{ClientExportersExt, ExportFormat}; +use bitwarden_generators::{ + ClientGeneratorExt, PassphraseGeneratorRequest, PasswordGeneratorRequest, + UsernameGeneratorRequest, }; +use bitwarden_vault::{Cipher, Collection, Folder}; -use crate::{error::Result, Client}; +use crate::{ + error::{Error, Result}, + Client, +}; mod sends; pub use sends::ClientSends; @@ -17,27 +21,23 @@ pub struct ClientGenerators(pub(crate) Arc); #[uniffi::export(async_runtime = "tokio")] impl ClientGenerators { /// **API Draft:** Generate Password - pub async fn password(&self, settings: PasswordGeneratorRequest) -> Result { + pub fn password(&self, settings: PasswordGeneratorRequest) -> Result { Ok(self .0 .0 - .read() - .await .generator() .password(settings) - .await?) + .map_err(Error::PasswordError)?) } /// **API Draft:** Generate Passphrase - pub async fn passphrase(&self, settings: PassphraseGeneratorRequest) -> Result { + pub fn passphrase(&self, settings: PassphraseGeneratorRequest) -> Result { Ok(self .0 .0 - .read() - .await .generator() .passphrase(settings) - .await?) + .map_err(Error::PassphraseError)?) } /// **API Draft:** Generate Username @@ -45,21 +45,20 @@ impl ClientGenerators { Ok(self .0 .0 - .read() - .await .generator() .username(settings) - .await?) + .await + .map_err(Error::UsernameError)?) } } #[derive(uniffi::Object)] pub struct ClientExporters(pub(crate) Arc); -#[uniffi::export(async_runtime = "tokio")] +#[uniffi::export] impl ClientExporters { /// **API Draft:** Export user vault - pub async fn export_vault( + pub fn export_vault( &self, folders: Vec, ciphers: Vec, @@ -68,15 +67,13 @@ impl ClientExporters { Ok(self .0 .0 - .read() - .await .exporters() .export_vault(folders, ciphers, format) - .await?) + .map_err(Error::ExportError)?) } /// **API Draft:** Export organization vault - pub async fn export_organization_vault( + pub fn export_organization_vault( &self, collections: Vec, ciphers: Vec, @@ -85,10 +82,8 @@ impl ClientExporters { Ok(self .0 .0 - .read() - .await .exporters() .export_organization_vault(collections, ciphers, format) - .await?) + .map_err(Error::ExportError)?) } } diff --git a/crates/bitwarden-uniffi/src/tool/sends.rs b/crates/bitwarden-uniffi/src/tool/sends.rs index e6aef3e51..18732f57c 100644 --- a/crates/bitwarden-uniffi/src/tool/sends.rs +++ b/crates/bitwarden-uniffi/src/tool/sends.rs @@ -1,92 +1,64 @@ use std::{path::Path, sync::Arc}; -use bitwarden::tool::{Send, SendListView, SendView}; +use bitwarden_send::{ClientSendsExt, Send, SendListView, SendView}; use crate::{Client, Result}; #[derive(uniffi::Object)] pub struct ClientSends(pub Arc); -#[uniffi::export(async_runtime = "tokio")] +#[uniffi::export] impl ClientSends { /// Encrypt send - pub async fn encrypt(&self, send: SendView) -> Result { - Ok(self.0 .0.write().await.sends().encrypt(send).await?) + pub fn encrypt(&self, send: SendView) -> Result { + Ok(self.0 .0.sends().encrypt(send)?) } /// Encrypt a send file in memory - pub async fn encrypt_buffer(&self, send: Send, buffer: Vec) -> Result> { - Ok(self - .0 - .0 - .write() - .await - .sends() - .encrypt_buffer(send, &buffer) - .await?) + pub fn encrypt_buffer(&self, send: Send, buffer: Vec) -> Result> { + Ok(self.0 .0.sends().encrypt_buffer(send, &buffer)?) } /// Encrypt a send file located in the file system - pub async fn encrypt_file( + pub fn encrypt_file( &self, send: Send, decrypted_file_path: String, encrypted_file_path: String, ) -> Result<()> { - Ok(self - .0 - .0 - .write() - .await - .sends() - .encrypt_file( - send, - Path::new(&decrypted_file_path), - Path::new(&encrypted_file_path), - ) - .await?) + Ok(self.0 .0.sends().encrypt_file( + send, + Path::new(&decrypted_file_path), + Path::new(&encrypted_file_path), + )?) } /// Decrypt send - pub async fn decrypt(&self, send: Send) -> Result { - Ok(self.0 .0.write().await.sends().decrypt(send).await?) + pub fn decrypt(&self, send: Send) -> Result { + Ok(self.0 .0.sends().decrypt(send)?) } /// Decrypt send list - pub async fn decrypt_list(&self, sends: Vec) -> Result> { - Ok(self.0 .0.write().await.sends().decrypt_list(sends).await?) + pub fn decrypt_list(&self, sends: Vec) -> Result> { + Ok(self.0 .0.sends().decrypt_list(sends)?) } /// Decrypt a send file in memory - pub async fn decrypt_buffer(&self, send: Send, buffer: Vec) -> Result> { - Ok(self - .0 - .0 - .write() - .await - .sends() - .decrypt_buffer(send, &buffer) - .await?) + pub fn decrypt_buffer(&self, send: Send, buffer: Vec) -> Result> { + Ok(self.0 .0.sends().decrypt_buffer(send, &buffer)?) } /// Decrypt a send file located in the file system - pub async fn decrypt_file( + pub fn decrypt_file( &self, send: Send, encrypted_file_path: String, decrypted_file_path: String, ) -> Result<()> { - Ok(self - .0 - .0 - .write() - .await - .sends() - .decrypt_file( - send, - Path::new(&encrypted_file_path), - Path::new(&decrypted_file_path), - ) - .await?) + Ok(self.0 .0.sends().decrypt_file( + send, + Path::new(&encrypted_file_path), + Path::new(&decrypted_file_path), + )?) } } diff --git a/crates/bitwarden-uniffi/src/uniffi_support.rs b/crates/bitwarden-uniffi/src/uniffi_support.rs index a3422d752..282b03460 100644 --- a/crates/bitwarden-uniffi/src/uniffi_support.rs +++ b/crates/bitwarden-uniffi/src/uniffi_support.rs @@ -3,7 +3,11 @@ use uuid::Uuid; // 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); -uniffi::ffi_converter_forward!(AsymmetricEncString, bitwarden::UniFfiTag, crate::UniFfiTag); -uniffi::ffi_converter_forward!(Uuid, bitwarden::UniFfiTag, crate::UniFfiTag); +uniffi::ffi_converter_forward!(DateTime, bitwarden_core::UniFfiTag, crate::UniFfiTag); +uniffi::ffi_converter_forward!(EncString, bitwarden_core::UniFfiTag, crate::UniFfiTag); +uniffi::ffi_converter_forward!( + AsymmetricEncString, + bitwarden_core::UniFfiTag, + crate::UniFfiTag +); +uniffi::ffi_converter_forward!(Uuid, bitwarden_core::UniFfiTag, crate::UniFfiTag); diff --git a/crates/bitwarden-uniffi/src/vault/attachments.rs b/crates/bitwarden-uniffi/src/vault/attachments.rs index b844c0318..cb2a011a0 100644 --- a/crates/bitwarden-uniffi/src/vault/attachments.rs +++ b/crates/bitwarden-uniffi/src/vault/attachments.rs @@ -1,16 +1,18 @@ use std::{path::Path, sync::Arc}; -use bitwarden::vault::{Attachment, AttachmentEncryptResult, AttachmentView, Cipher}; +use bitwarden_vault::{ + Attachment, AttachmentEncryptResult, AttachmentView, Cipher, ClientVaultExt, +}; use crate::{Client, Result}; #[derive(uniffi::Object)] pub struct ClientAttachments(pub Arc); -#[uniffi::export(async_runtime = "tokio")] +#[uniffi::export] impl ClientAttachments { /// Encrypt an attachment file in memory - pub async fn encrypt_buffer( + pub fn encrypt_buffer( &self, cipher: Cipher, attachment: AttachmentView, @@ -19,39 +21,28 @@ impl ClientAttachments { Ok(self .0 .0 - .write() - .await .vault() .attachments() - .encrypt_buffer(cipher, attachment, &buffer) - .await?) + .encrypt_buffer(cipher, attachment, &buffer)?) } /// Encrypt an attachment file located in the file system - pub async fn encrypt_file( + pub fn encrypt_file( &self, cipher: Cipher, attachment: AttachmentView, decrypted_file_path: String, encrypted_file_path: String, ) -> Result { - Ok(self - .0 - .0 - .write() - .await - .vault() - .attachments() - .encrypt_file( - cipher, - attachment, - Path::new(&decrypted_file_path), - Path::new(&encrypted_file_path), - ) - .await?) + Ok(self.0 .0.vault().attachments().encrypt_file( + cipher, + attachment, + Path::new(&decrypted_file_path), + Path::new(&encrypted_file_path), + )?) } /// Decrypt an attachment file in memory - pub async fn decrypt_buffer( + pub fn decrypt_buffer( &self, cipher: Cipher, attachment: Attachment, @@ -60,35 +51,24 @@ impl ClientAttachments { Ok(self .0 .0 - .write() - .await .vault() .attachments() - .decrypt_buffer(cipher, attachment, &buffer) - .await?) + .decrypt_buffer(cipher, attachment, &buffer)?) } /// Decrypt an attachment file located in the file system - pub async fn decrypt_file( + pub fn decrypt_file( &self, cipher: Cipher, attachment: Attachment, encrypted_file_path: String, decrypted_file_path: String, ) -> Result<()> { - Ok(self - .0 - .0 - .write() - .await - .vault() - .attachments() - .decrypt_file( - cipher, - attachment, - Path::new(&encrypted_file_path), - Path::new(&decrypted_file_path), - ) - .await?) + Ok(self.0 .0.vault().attachments().decrypt_file( + cipher, + attachment, + Path::new(&encrypted_file_path), + Path::new(&decrypted_file_path), + )?) } } diff --git a/crates/bitwarden-uniffi/src/vault/ciphers.rs b/crates/bitwarden-uniffi/src/vault/ciphers.rs index 329ca3c17..64751b37c 100644 --- a/crates/bitwarden-uniffi/src/vault/ciphers.rs +++ b/crates/bitwarden-uniffi/src/vault/ciphers.rs @@ -1,56 +1,44 @@ use std::sync::Arc; -use bitwarden::vault::{Cipher, CipherListView, CipherView}; +use bitwarden_vault::{Cipher, CipherListView, CipherView, ClientVaultExt, Fido2CredentialView}; use uuid::Uuid; -use crate::{Client, Result}; +use crate::{error::Error, Client, Result}; #[derive(uniffi::Object)] pub struct ClientCiphers(pub Arc); -#[uniffi::export(async_runtime = "tokio")] +#[uniffi::export] impl ClientCiphers { /// Encrypt cipher - pub async fn encrypt(&self, cipher_view: CipherView) -> Result { - Ok(self - .0 - .0 - .write() - .await - .vault() - .ciphers() - .encrypt(cipher_view) - .await?) + pub fn encrypt(&self, cipher_view: CipherView) -> Result { + Ok(self.0 .0.vault().ciphers().encrypt(cipher_view)?) } /// Decrypt cipher - pub async fn decrypt(&self, cipher: Cipher) -> Result { - Ok(self - .0 - .0 - .write() - .await - .vault() - .ciphers() - .decrypt(cipher) - .await?) + pub fn decrypt(&self, cipher: Cipher) -> Result { + Ok(self.0 .0.vault().ciphers().decrypt(cipher)?) } /// Decrypt cipher list - pub async fn decrypt_list(&self, ciphers: Vec) -> Result> { + pub fn decrypt_list(&self, ciphers: Vec) -> Result> { + Ok(self.0 .0.vault().ciphers().decrypt_list(ciphers)?) + } + + pub fn decrypt_fido2_credentials( + &self, + cipher_view: CipherView, + ) -> Result> { Ok(self .0 .0 - .write() - .await .vault() .ciphers() - .decrypt_list(ciphers) - .await?) + .decrypt_fido2_credentials(cipher_view)?) } /// Move a cipher to an organization, reencrypting the cipher key if necessary - pub async fn move_to_organization( + pub fn move_to_organization( &self, cipher: CipherView, organization_id: Uuid, @@ -58,11 +46,9 @@ impl ClientCiphers { Ok(self .0 .0 - .write() - .await .vault() .ciphers() .move_to_organization(cipher, organization_id) - .await?) + .map_err(Error::Cipher)?) } } diff --git a/crates/bitwarden-uniffi/src/vault/collections.rs b/crates/bitwarden-uniffi/src/vault/collections.rs index 36315aaea..250588e8d 100644 --- a/crates/bitwarden-uniffi/src/vault/collections.rs +++ b/crates/bitwarden-uniffi/src/vault/collections.rs @@ -1,37 +1,21 @@ use std::sync::Arc; -use bitwarden::vault::{Collection, CollectionView}; +use bitwarden_vault::{ClientVaultExt, Collection, CollectionView}; use crate::{Client, Result}; #[derive(uniffi::Object)] pub struct ClientCollections(pub Arc); -#[uniffi::export(async_runtime = "tokio")] +#[uniffi::export] impl ClientCollections { /// Decrypt collection - pub async fn decrypt(&self, collection: Collection) -> Result { - Ok(self - .0 - .0 - .write() - .await - .vault() - .collections() - .decrypt(collection) - .await?) + pub fn decrypt(&self, collection: Collection) -> Result { + Ok(self.0 .0.vault().collections().decrypt(collection)?) } /// Decrypt collection list - pub async fn decrypt_list(&self, collections: Vec) -> Result> { - Ok(self - .0 - .0 - .write() - .await - .vault() - .collections() - .decrypt_list(collections) - .await?) + pub fn decrypt_list(&self, collections: Vec) -> Result> { + Ok(self.0 .0.vault().collections().decrypt_list(collections)?) } } diff --git a/crates/bitwarden-uniffi/src/vault/folders.rs b/crates/bitwarden-uniffi/src/vault/folders.rs index 8847b9a45..758d09173 100644 --- a/crates/bitwarden-uniffi/src/vault/folders.rs +++ b/crates/bitwarden-uniffi/src/vault/folders.rs @@ -1,50 +1,26 @@ use std::sync::Arc; -use bitwarden::vault::{Folder, FolderView}; +use bitwarden_vault::{ClientVaultExt, Folder, FolderView}; use crate::{Client, Result}; #[derive(uniffi::Object)] pub struct ClientFolders(pub Arc); -#[uniffi::export(async_runtime = "tokio")] +#[uniffi::export] impl ClientFolders { /// Encrypt folder - pub async fn encrypt(&self, folder: FolderView) -> Result { - Ok(self - .0 - .0 - .write() - .await - .vault() - .folders() - .encrypt(folder) - .await?) + pub fn encrypt(&self, folder: FolderView) -> Result { + Ok(self.0 .0.vault().folders().encrypt(folder)?) } /// Decrypt folder - pub async fn decrypt(&self, folder: Folder) -> Result { - Ok(self - .0 - .0 - .write() - .await - .vault() - .folders() - .decrypt(folder) - .await?) + pub fn decrypt(&self, folder: Folder) -> Result { + Ok(self.0 .0.vault().folders().decrypt(folder)?) } /// Decrypt folder list - pub async fn decrypt_list(&self, folders: Vec) -> Result> { - Ok(self - .0 - .0 - .write() - .await - .vault() - .folders() - .decrypt_list(folders) - .await?) + pub fn decrypt_list(&self, folders: Vec) -> Result> { + Ok(self.0 .0.vault().folders().decrypt_list(folders)?) } } diff --git a/crates/bitwarden-uniffi/src/vault/mod.rs b/crates/bitwarden-uniffi/src/vault/mod.rs index 62372523d..cec625e33 100644 --- a/crates/bitwarden-uniffi/src/vault/mod.rs +++ b/crates/bitwarden-uniffi/src/vault/mod.rs @@ -1,9 +1,12 @@ use std::sync::Arc; -use bitwarden::vault::TotpResponse; +use bitwarden_vault::{CipherListView, ClientVaultExt, TotpResponse}; use chrono::{DateTime, Utc}; -use crate::{error::Result, Client}; +use crate::{ + error::{Error, Result}, + Client, +}; pub mod attachments; pub mod ciphers; @@ -14,7 +17,7 @@ pub mod password_history; #[derive(uniffi::Object)] pub struct ClientVault(pub(crate) Arc); -#[uniffi::export(async_runtime = "tokio")] +#[uniffi::export] impl ClientVault { /// Folder operations pub fn folders(self: Arc) -> Arc { @@ -47,11 +50,26 @@ impl ClientVault { /// - A base32 encoded string /// - OTP Auth URI /// - Steam URI - pub async fn generate_totp( + pub fn generate_totp(&self, key: String, time: Option>) -> Result { + Ok(self + .0 + .0 + .vault() + .generate_totp(key, time) + .map_err(Error::Totp)?) + } + + /// Generate a TOTP code from a provided cipher list view. + pub fn generate_totp_cipher_view( &self, - key: String, + view: CipherListView, time: Option>, ) -> Result { - Ok(self.0 .0.write().await.vault().generate_totp(key, time)?) + Ok(self + .0 + .0 + .vault() + .generate_totp_cipher_view(view, time) + .map_err(Error::Totp)?) } } diff --git a/crates/bitwarden-uniffi/src/vault/password_history.rs b/crates/bitwarden-uniffi/src/vault/password_history.rs index 62c468d4b..330e91214 100644 --- a/crates/bitwarden-uniffi/src/vault/password_history.rs +++ b/crates/bitwarden-uniffi/src/vault/password_history.rs @@ -1,40 +1,26 @@ use std::sync::Arc; -use bitwarden::vault::{PasswordHistory, PasswordHistoryView}; +use bitwarden_vault::{ClientVaultExt, PasswordHistory, PasswordHistoryView}; use crate::{Client, Result}; #[derive(uniffi::Object)] pub struct ClientPasswordHistory(pub Arc); -#[uniffi::export(async_runtime = "tokio")] +#[uniffi::export] impl ClientPasswordHistory { /// Encrypt password history - pub async fn encrypt(&self, password_history: PasswordHistoryView) -> Result { + pub fn encrypt(&self, password_history: PasswordHistoryView) -> Result { Ok(self .0 .0 - .write() - .await .vault() .password_history() - .encrypt(password_history) - .await?) + .encrypt(password_history)?) } /// Decrypt password history - pub async fn decrypt_list( - &self, - list: Vec, - ) -> Result> { - Ok(self - .0 - .0 - .write() - .await - .vault() - .password_history() - .decrypt_list(list) - .await?) + pub fn decrypt_list(&self, list: Vec) -> Result> { + Ok(self.0 .0.vault().password_history().decrypt_list(list)?) } } diff --git a/crates/bitwarden-vault/Cargo.toml b/crates/bitwarden-vault/Cargo.toml new file mode 100644 index 000000000..3c7757e2f --- /dev/null +++ b/crates/bitwarden-vault/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "bitwarden-vault" +description = """ +Internal crate for the bitwarden crate. Do not use. +""" + +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +homepage.workspace = true +repository.workspace = true +license-file.workspace = true +keywords.workspace = true + +[features] +uniffi = [ + "bitwarden-core/uniffi", + "bitwarden-crypto/uniffi", + "dep:uniffi", +] # Uniffi bindings +wasm = ["dep:tsify-next", "dep:wasm-bindgen"] # WASM support + +[dependencies] +base64 = ">=0.22.1, <0.23" +bitwarden-api-api = { workspace = true } +bitwarden-core = { workspace = true, features = ["internal"] } +bitwarden-crypto = { workspace = true } +chrono = { workspace = true } +rand = ">=0.8.5, <0.9" +hmac = ">=0.12.1, <0.13" +reqwest = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +serde_repr = { workspace = true } +sha1 = ">=0.10.5, <0.11" +sha2 = ">=0.10.6, <0.11" +thiserror = { workspace = true } +uniffi = { version = "=0.28.1", optional = true } +uuid = { workspace = true } +tsify-next = { workspace = true, optional = true } +wasm-bindgen = { workspace = true, optional = true } + +[dev-dependencies] +tokio = { workspace = true, features = ["rt"] } + +[lints] +workspace = true diff --git a/crates/bitwarden-vault/README.md b/crates/bitwarden-vault/README.md new file mode 100644 index 000000000..9f3687616 --- /dev/null +++ b/crates/bitwarden-vault/README.md @@ -0,0 +1,6 @@ +# Bitwarden Vault + +This is an internal crate for the Bitwarden SDK do not depend on this directly and use the +[`bitwarden`](https://crates.io/crates/bitwarden) crate instead. + +This crate does not follow semantic versioning and the public interface may change at any time. diff --git a/crates/bitwarden/src/vault/cipher/attachment.rs b/crates/bitwarden-vault/src/cipher/attachment.rs similarity index 72% rename from crates/bitwarden/src/vault/cipher/attachment.rs rename to crates/bitwarden-vault/src/cipher/attachment.rs index 99d4e270f..d1a058c01 100644 --- a/crates/bitwarden/src/vault/cipher/attachment.rs +++ b/crates/bitwarden-vault/src/cipher/attachment.rs @@ -5,7 +5,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use super::Cipher; -use crate::error::{Error, Result}; +use crate::VaultParseError; #[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -73,13 +73,29 @@ impl<'a> KeyEncryptable for Attachm let encrypted_contents = self.contents.encrypt_with_key(&attachment_key)?; attachment.key = Some(attachment_key.to_vec().encrypt_with_key(ciphers_key)?); + let contents = encrypted_contents.to_buffer()?; + + // Once we have the encrypted contents, we can set the size of the attachment + attachment.size = Some(contents.len().to_string()); + attachment.size_name = Some(size_name(contents.len())); + Ok(AttachmentEncryptResult { attachment: attachment.encrypt_with_key(ciphers_key)?, - contents: encrypted_contents.to_buffer()?, + contents, }) } } +fn size_name(size: usize) -> String { + let units = ["Bytes", "KB", "MB", "GB", "TB"]; + let size = size as f64; + let unit = (size.ln() / 1024_f64.ln()).floor() as usize; + let size = size / 1024_f64.powi(unit as i32); + + let size_round = (size * 10.0_f64).round() as usize as f64 / 10.0_f64; + format!("{} {}", size_round, units[unit]) +} + impl KeyDecryptable> for AttachmentFile { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result, CryptoError> { let ciphers_key = Cipher::get_cipher_key(key, &self.cipher.key)?; @@ -125,9 +141,11 @@ impl KeyDecryptable for Attachment { } impl TryFrom for Attachment { - type Error = Error; + type Error = VaultParseError; - fn try_from(attachment: bitwarden_api_api::models::AttachmentResponseModel) -> Result { + fn try_from( + attachment: bitwarden_api_api::models::AttachmentResponseModel, + ) -> Result { Ok(Self { id: attachment.id, url: attachment.url, @@ -142,13 +160,78 @@ impl TryFrom for Attachment #[cfg(test)] mod tests { use base64::{engine::general_purpose::STANDARD, Engine}; - use bitwarden_crypto::{EncString, KeyDecryptable, SymmetricCryptoKey}; + use bitwarden_crypto::{EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}; - use crate::vault::{ + use crate::{ cipher::cipher::{CipherRepromptType, CipherType}, - Attachment, AttachmentFile, Cipher, + Attachment, AttachmentFile, AttachmentFileView, AttachmentView, Cipher, }; + #[test] + fn test_size_name_conversions() { + assert_eq!(super::size_name(0), "0 Bytes"); + assert_eq!(super::size_name(19), "19 Bytes"); + assert_eq!(super::size_name(1024), "1 KB"); + assert_eq!(super::size_name(1570), "1.5 KB"); + assert_eq!(super::size_name(1024 * 1024), "1 MB"); + assert_eq!(super::size_name(1024 * 18999), "18.6 MB"); + assert_eq!(super::size_name(1024 * 1024 * 1024), "1 GB"); + assert_eq!(super::size_name(1024 * 1024 * 1024 * 1024), "1 TB"); + } + + #[test] + fn test_encrypt_attachment() { + let user_key: SymmetricCryptoKey = "w2LO+nwV4oxwswVYCxlOfRUseXfvU03VzvKQHrqeklPgiMZrspUe6sOBToCnDn9Ay0tuCBn8ykVVRb7PWhub2Q==".to_string().try_into().unwrap(); + + let attachment = AttachmentView { + id: None, + url: None, + size: Some("100".into()), + size_name: Some("100 Bytes".into()), + file_name: Some("Test.txt".into()), + key: None, + }; + + let contents = b"This is a test file that we will encrypt. It's 100 bytes long, the encrypted version will be longer!"; + + let attachment_file = AttachmentFileView { + cipher: Cipher { + id: None, + organization_id: None, + folder_id: None, + collection_ids: Vec::new(), + key: Some("2.Gg8yCM4IIgykCZyq0O4+cA==|GJLBtfvSJTDJh/F7X4cJPkzI6ccnzJm5DYl3yxOW2iUn7DgkkmzoOe61sUhC5dgVdV0kFqsZPcQ0yehlN1DDsFIFtrb4x7LwzJNIkMgxNyg=|1rGkGJ8zcM5o5D0aIIwAyLsjMLrPsP3EWm3CctBO3Fw=".parse().unwrap()), + name: "2.d24xECyEdMZ3MG9s6SrGNw==|XvJlTeu5KJ22M3jKosy6iw==|8xGiQty4X61cDMx6PVqkJfSQ0ZTdA/5L9TpG7QfovoM=".parse().unwrap(), + notes: None, + r#type: CipherType::Login, + login: None, + identity: None, + card: None, + secure_note: None, + favorite: false, + reprompt: CipherRepromptType::None, + organization_use_totp: false, + edit: true, + view_password: true, + local_data: None, + attachments: None, + fields: None, + password_history: None, + creation_date: "2023-07-24T12:05:09.466666700Z".parse().unwrap(), + deleted_date: None, + revision_date: "2023-07-27T19:28:05.240Z".parse().unwrap(), + }, + attachment, + contents: contents.as_slice(), + }; + + let result = attachment_file.encrypt_with_key(&user_key).unwrap(); + + assert_eq!(result.contents.len(), 161); + assert_eq!(result.attachment.size, Some("161".into())); + assert_eq!(result.attachment.size_name, Some("161 Bytes".into())); + } + #[test] fn test_attachment_key() { let user_key: SymmetricCryptoKey = "w2LO+nwV4oxwswVYCxlOfRUseXfvU03VzvKQHrqeklPgiMZrspUe6sOBToCnDn9Ay0tuCBn8ykVVRb7PWhub2Q==".to_string().try_into().unwrap(); diff --git a/crates/bitwarden/src/vault/cipher/card.rs b/crates/bitwarden-vault/src/cipher/card.rs similarity index 95% rename from crates/bitwarden/src/vault/cipher/card.rs rename to crates/bitwarden-vault/src/cipher/card.rs index e7201177a..5a2396d92 100644 --- a/crates/bitwarden/src/vault/cipher/card.rs +++ b/crates/bitwarden-vault/src/cipher/card.rs @@ -5,7 +5,7 @@ use bitwarden_crypto::{ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::error::{Error, Result}; +use crate::VaultParseError; #[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -58,9 +58,9 @@ impl KeyDecryptable for Card { } impl TryFrom for Card { - type Error = Error; + type Error = VaultParseError; - fn try_from(card: CipherCardModel) -> Result { + 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)?, diff --git a/crates/bitwarden/src/vault/cipher/cipher.rs b/crates/bitwarden-vault/src/cipher/cipher.rs similarity index 76% rename from crates/bitwarden/src/vault/cipher/cipher.rs rename to crates/bitwarden-vault/src/cipher/cipher.rs index 15a1c124c..8ac4bfe79 100644 --- a/crates/bitwarden/src/vault/cipher/cipher.rs +++ b/crates/bitwarden-vault/src/cipher/cipher.rs @@ -1,4 +1,5 @@ use bitwarden_api_api::models::CipherDetailsResponseModel; +use bitwarden_core::{require, MissingFieldError, VaultLocked}; use bitwarden_crypto::{ CryptoError, EncString, KeyContainer, KeyDecryptable, KeyEncryptable, LocateKey, SymmetricCryptoKey, @@ -7,20 +8,31 @@ use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; +use thiserror::Error; use uuid::Uuid; use super::{ attachment, card, field, identity, local_data::{LocalData, LocalDataView}, - login, secure_note, + secure_note, }; -#[cfg(feature = "uniffi")] -use crate::vault::Fido2CredentialFullView; use crate::{ - error::{require, Error, Result}, - vault::password_history, + password_history, Fido2CredentialFullView, Fido2CredentialView, Login, LoginView, + VaultParseError, }; +#[derive(Debug, Error)] +pub enum CipherError { + #[error(transparent)] + MissingFieldError(#[from] MissingFieldError), + #[error(transparent)] + VaultLocked(#[from] VaultLocked), + #[error(transparent)] + CryptoError(#[from] CryptoError), + #[error("This cipher contains attachments without keys. Those attachments will need to be reuploaded to complete the operation")] + AttachmentsWithoutKeys, +} + #[derive(Clone, Copy, Serialize_repr, Deserialize_repr, Debug, JsonSchema)] #[repr(u8)] #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] @@ -31,7 +43,7 @@ pub enum CipherType { Identity = 4, } -#[derive(Clone, Copy, Serialize_repr, Deserialize_repr, Debug, JsonSchema)] +#[derive(Clone, Copy, Serialize_repr, Deserialize_repr, Debug, JsonSchema, PartialEq)] #[repr(u8)] #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] pub enum CipherRepromptType { @@ -56,7 +68,7 @@ pub struct Cipher { pub notes: Option, pub r#type: CipherType, - pub login: Option, + pub login: Option, pub identity: Option, pub card: Option, pub secure_note: Option, @@ -86,13 +98,14 @@ pub struct CipherView { pub folder_id: Option, pub collection_ids: Vec, + /// Temporary, required to support re-encrypting existing items. pub key: Option, pub name: String, pub notes: Option, pub r#type: CipherType, - pub login: Option, + pub login: Option, pub identity: Option, pub card: Option, pub secure_note: Option, @@ -113,7 +126,20 @@ pub struct CipherView { pub revision_date: DateTime, } -#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[derive(Serialize, Deserialize, Debug, JsonSchema, PartialEq)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] +pub enum CipherListViewType { + Login { + has_fido2: bool, + totp: Option, + }, + SecureNote, + Card, + Identity, +} + +#[derive(Serialize, Deserialize, Debug, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] pub struct CipherListView { @@ -122,10 +148,13 @@ pub struct CipherListView { pub folder_id: Option, pub collection_ids: Vec, + /// Temporary, required to support calculating TOTP from CipherListView. + pub key: Option, + pub name: String, pub sub_title: String, - pub r#type: CipherType, + pub r#type: CipherListViewType, pub favorite: bool, pub reprompt: CipherRepromptType, @@ -140,6 +169,25 @@ pub struct CipherListView { pub revision_date: DateTime, } +impl CipherListView { + pub(crate) fn get_totp_key( + self, + enc: &dyn KeyContainer, + ) -> Result, CryptoError> { + let key = self.locate_key(enc, &None)?; + let cipher_key = Cipher::get_cipher_key(key, &self.key)?; + let key = cipher_key.as_ref().unwrap_or(key); + + let totp = if let CipherListViewType::Login { totp, .. } = self.r#type { + totp.decrypt_with_key(key)? + } else { + None + }; + + Ok(totp) + } +} + impl KeyEncryptable for CipherView { fn encrypt_with_key(mut self, key: &SymmetricCryptoKey) -> Result { let ciphers_key = Cipher::get_cipher_key(key, &self.key)?; @@ -342,13 +390,14 @@ fn build_subtitle_identity(first_name: Option, last_name: Option } impl CipherView { - pub fn generate_cipher_key(&mut self, key: &SymmetricCryptoKey) -> Result<()> { + pub fn generate_cipher_key(&mut self, key: &SymmetricCryptoKey) -> Result<(), CryptoError> { let old_ciphers_key = Cipher::get_cipher_key(key, &self.key)?; let old_key = old_ciphers_key.as_ref().unwrap_or(key); let new_key = SymmetricCryptoKey::generate(rand::thread_rng()); self.reencrypt_attachment_keys(old_key, &new_key)?; + self.reencrypt_fido2_credentials(old_key, &new_key)?; self.key = Some(new_key.to_vec().encrypt_with_key(key)?); Ok(()) @@ -372,7 +421,7 @@ impl CipherView { &mut self, old_key: &SymmetricCryptoKey, new_key: &SymmetricCryptoKey, - ) -> Result<()> { + ) -> Result<(), CryptoError> { if let Some(attachments) = &mut self.attachments { for attachment in attachments { if let Some(attachment_key) = &mut attachment.key { @@ -384,22 +433,51 @@ impl CipherView { Ok(()) } + pub fn decrypt_fido2_credentials( + &self, + enc: &dyn KeyContainer, + ) -> Result, CipherError> { + let key = self.locate_key(enc, &None)?; + let cipher_key = Cipher::get_cipher_key(key, &self.key)?; + + let key = cipher_key.as_ref().unwrap_or(key); + + Ok(self + .login + .as_ref() + .and_then(|l| l.fido2_credentials.as_ref()) + .map(|f| f.decrypt_with_key(key)) + .transpose()? + .unwrap_or_default()) + } + + fn reencrypt_fido2_credentials( + &mut self, + old_key: &SymmetricCryptoKey, + new_key: &SymmetricCryptoKey, + ) -> Result<(), CryptoError> { + if let Some(login) = self.login.as_mut() { + if let Some(fido2_credentials) = &mut login.fido2_credentials { + let dec_fido2_credentials: Vec = + fido2_credentials.decrypt_with_key(old_key)?; + *fido2_credentials = dec_fido2_credentials.encrypt_with_key(new_key)?; + } + } + Ok(()) + } + pub fn move_to_organization( &mut self, enc: &dyn KeyContainer, organization_id: Uuid, - ) -> Result<()> { - let old_key = enc - .get_key(&self.organization_id) - .ok_or(Error::VaultLocked)?; + ) -> Result<(), CipherError> { + let old_key = enc.get_key(&self.organization_id)?; - let new_key = enc - .get_key(&Some(organization_id)) - .ok_or(Error::VaultLocked)?; + let new_key = enc.get_key(&Some(organization_id))?; // If any attachment is missing a key we can't reencrypt the attachment keys if self.attachments.iter().flatten().any(|a| a.key.is_none()) { - return Err("This cipher contains attachments without keys. Those attachments will need to be reuploaded to complete the operation".into()); + return Err(CipherError::AttachmentsWithoutKeys); } // If the cipher has a key, we need to re-encrypt it with the new organization key @@ -409,21 +487,19 @@ impl CipherView { } else { // If the cipher does not have a key, we need to reencrypt all attachment keys self.reencrypt_attachment_keys(old_key, new_key)?; + self.reencrypt_fido2_credentials(old_key, new_key)?; } self.organization_id = Some(organization_id); Ok(()) } - #[cfg(feature = "uniffi")] - pub(crate) fn set_new_fido2_credentials( + pub fn set_new_fido2_credentials( &mut self, enc: &dyn KeyContainer, creds: Vec, - ) -> Result<()> { - let key = enc - .get_key(&self.organization_id) - .ok_or(Error::VaultLocked)?; + ) -> Result<(), CipherError> { + let key = enc.get_key(&self.organization_id)?; let ciphers_key = Cipher::get_cipher_key(key, &self.key)?; let ciphers_key = ciphers_key.as_ref().unwrap_or(key); @@ -434,14 +510,11 @@ impl CipherView { Ok(()) } - #[cfg(feature = "uniffi")] - pub(crate) fn get_fido2_credentials( + pub fn get_fido2_credentials( &self, enc: &dyn KeyContainer, - ) -> Result> { - let key = enc - .get_key(&self.organization_id) - .ok_or(Error::VaultLocked)?; + ) -> Result, CipherError> { + let key = enc.get_key(&self.organization_id)?; let ciphers_key = Cipher::get_cipher_key(key, &self.key)?; let ciphers_key = ciphers_key.as_ref().unwrap_or(key); @@ -463,9 +536,24 @@ impl KeyDecryptable for Cipher { organization_id: self.organization_id, folder_id: self.folder_id, collection_ids: self.collection_ids.clone(), + key: self.key.clone(), name: self.name.decrypt_with_key(key).ok().unwrap_or_default(), sub_title: self.get_decrypted_subtitle(key).ok().unwrap_or_default(), - r#type: self.r#type, + r#type: match self.r#type { + CipherType::Login => { + let login = self + .login + .as_ref() + .ok_or(CryptoError::MissingField("login"))?; + CipherListViewType::Login { + has_fido2: login.fido2_credentials.is_some(), + totp: login.totp.clone(), + } + } + CipherType::SecureNote => CipherListViewType::SecureNote, + CipherType::Card => CipherListViewType::Card, + CipherType::Identity => CipherListViewType::Identity, + }, favorite: self.favorite, reprompt: self.reprompt, edit: self.edit, @@ -487,7 +575,7 @@ impl LocateKey for Cipher { &self, enc: &'a dyn KeyContainer, _: &Option, - ) -> Option<&'a SymmetricCryptoKey> { + ) -> Result<&'a SymmetricCryptoKey, CryptoError> { enc.get_key(&self.organization_id) } } @@ -496,15 +584,24 @@ impl LocateKey for CipherView { &self, enc: &'a dyn KeyContainer, _: &Option, - ) -> Option<&'a SymmetricCryptoKey> { + ) -> Result<&'a SymmetricCryptoKey, CryptoError> { + enc.get_key(&self.organization_id) + } +} +impl LocateKey for CipherListView { + fn locate_key<'a>( + &self, + enc: &'a dyn KeyContainer, + _: &Option, + ) -> Result<&'a SymmetricCryptoKey, CryptoError> { enc.get_key(&self.organization_id) } } impl TryFrom for Cipher { - type Error = Error; + type Error = VaultParseError; - fn try_from(cipher: CipherDetailsResponseModel) -> Result { + fn try_from(cipher: CipherDetailsResponseModel) -> Result { Ok(Self { id: cipher.id, organization_id: cipher.organization_id, @@ -574,11 +671,12 @@ mod tests { use attachment::AttachmentView; use super::*; + use crate::Fido2Credential; fn generate_cipher() -> CipherView { CipherView { r#type: CipherType::Login, - login: Some(login::LoginView { + login: Some(LoginView { username: Some("test_username".to_string()), password: Some("test_password".to_string()), password_revision_date: None, @@ -612,6 +710,91 @@ mod tests { } } + fn generate_fido2(key: &SymmetricCryptoKey) -> Fido2Credential { + Fido2Credential { + credential_id: "123".to_string().encrypt_with_key(key).unwrap(), + key_type: "public-key".to_string().encrypt_with_key(key).unwrap(), + key_algorithm: "ECDSA".to_string().encrypt_with_key(key).unwrap(), + key_curve: "P-256".to_string().encrypt_with_key(key).unwrap(), + key_value: "123".to_string().encrypt_with_key(key).unwrap(), + rp_id: "123".to_string().encrypt_with_key(key).unwrap(), + user_handle: None, + user_name: None, + counter: "123".to_string().encrypt_with_key(key).unwrap(), + rp_name: None, + user_display_name: None, + discoverable: "true".to_string().encrypt_with_key(key).unwrap(), + creation_date: "2024-06-07T14:12:36.150Z".parse().unwrap(), + } + } + + #[test] + fn test_decrypt_cipher_list_view() { + let key: SymmetricCryptoKey = "w2LO+nwV4oxwswVYCxlOfRUseXfvU03VzvKQHrqeklPgiMZrspUe6sOBToCnDn9Ay0tuCBn8ykVVRb7PWhub2Q==".to_string().try_into().unwrap(); + + let cipher = Cipher { + id: Some("090c19ea-a61a-4df6-8963-262b97bc6266".parse().unwrap()), + organization_id: None, + folder_id: None, + collection_ids: vec![], + key: None, + name: "2.d3rzo0P8rxV9Hs1m1BmAjw==|JOwna6i0zs+K7ZghwrZRuw==|SJqKreLag1ID+g6H1OdmQr0T5zTrVWKzD6hGy3fDqB0=".parse().unwrap(), + notes: None, + r#type: CipherType::Login, + login: Some(Login { + username: Some("2.EBNGgnaMHeO/kYnI3A0jiA==|9YXlrgABP71ebZ5umurCJQ==|GDk5jxiqTYaU7e2AStCFGX+a1kgCIk8j0NEli7Jn0L4=".parse().unwrap()), + password: Some("2.M7ZJ7EuFDXCq66gDTIyRIg==|B1V+jroo6+m/dpHx6g8DxA==|PIXPBCwyJ1ady36a7jbcLg346pm/7N/06W4UZxc1TUo=".parse().unwrap()), + password_revision_date: None, + uris: None, + totp: Some("2.hqdioUAc81FsKQmO1XuLQg==|oDRdsJrQjoFu9NrFVy8tcJBAFKBx95gHaXZnWdXbKpsxWnOr2sKipIG43pKKUFuq|3gKZMiboceIB5SLVOULKg2iuyu6xzos22dfJbvx0EHk=".parse().unwrap()), + autofill_on_page_load: None, + fido2_credentials: Some(vec![generate_fido2(&key)]), + }), + identity: None, + card: None, + secure_note: None, + favorite: false, + reprompt: CipherRepromptType::None, + organization_use_totp: false, + edit: true, + view_password: true, + local_data: None, + attachments: None, + fields: None, + password_history: None, + creation_date: "2024-01-30T17:55:36.150Z".parse().unwrap(), + deleted_date: None, + revision_date: "2024-01-30T17:55:36.150Z".parse().unwrap(), + }; + + let view: CipherListView = cipher.decrypt_with_key(&key).unwrap(); + + assert_eq!( + view, + CipherListView { + id: cipher.id, + organization_id: cipher.organization_id, + folder_id: cipher.folder_id, + collection_ids: cipher.collection_ids, + key: cipher.key, + name: "My test login".to_string(), + sub_title: "test_username".to_string(), + r#type: CipherListViewType::Login { + has_fido2: true, + totp: cipher.login.as_ref().unwrap().totp.clone() + }, + favorite: cipher.favorite, + reprompt: cipher.reprompt, + edit: cipher.edit, + view_password: cipher.view_password, + attachments: 0, + creation_date: cipher.creation_date, + deleted_date: cipher.deleted_date, + revision_date: cipher.revision_date + } + ) + } + #[test] fn test_generate_cipher_key() { let key = SymmetricCryptoKey::generate(rand::thread_rng()); @@ -672,8 +855,13 @@ mod tests { struct MockKeyContainer(HashMap, SymmetricCryptoKey>); impl KeyContainer for MockKeyContainer { - fn get_key<'a>(&'a self, org_id: &Option) -> Option<&'a SymmetricCryptoKey> { - self.0.get(org_id) + fn get_key<'a>( + &'a self, + org_id: &Option, + ) -> Result<&'a SymmetricCryptoKey, CryptoError> { + self.0 + .get(org_id) + .ok_or(CryptoError::MissingKey(org_id.unwrap_or_default())) } } @@ -776,6 +964,8 @@ mod tests { key: Some(attachment_key_enc), }; cipher.attachments = Some(vec![attachment]); + let cred = generate_fido2(enc.get_key(&None).unwrap()); + cipher.login.as_mut().unwrap().fido2_credentials = Some(vec![cred]); cipher.move_to_organization(&enc, org).unwrap(); @@ -789,6 +979,18 @@ mod tests { .unwrap(); let new_attachment_key_dec: SymmetricCryptoKey = new_attachment_key_dec.try_into().unwrap(); assert_eq!(new_attachment_key_dec.to_vec(), attachment_key.to_vec()); + + let cred2: Fido2CredentialFullView = cipher + .login + .unwrap() + .fido2_credentials + .unwrap() + .first() + .unwrap() + .decrypt_with_key(enc.get_key(&Some(org)).unwrap()) + .unwrap(); + + assert_eq!(cred2.credential_id, "123"); } #[test] @@ -826,6 +1028,9 @@ mod tests { }; cipher.attachments = Some(vec![attachment]); + let cred = generate_fido2(&cipher_key); + cipher.login.as_mut().unwrap().fido2_credentials = Some(vec![cred.clone()]); + cipher.move_to_organization(&enc, org).unwrap(); // Check that the cipher key has been re-encrypted with the org key, @@ -849,6 +1054,20 @@ mod tests { .to_string(), attachment_key_enc.to_string() ); + + let cred2: Fido2Credential = cipher + .login + .unwrap() + .fido2_credentials + .unwrap() + .first() + .unwrap() + .clone(); + + assert_eq!( + cred2.credential_id.to_string(), + cred.credential_id.to_string() + ); } #[test] diff --git a/crates/bitwarden/src/vault/cipher/field.rs b/crates/bitwarden-vault/src/cipher/field.rs similarity index 90% rename from crates/bitwarden/src/vault/cipher/field.rs rename to crates/bitwarden-vault/src/cipher/field.rs index 8a7b1d5f5..6c826d4ba 100644 --- a/crates/bitwarden/src/vault/cipher/field.rs +++ b/crates/bitwarden-vault/src/cipher/field.rs @@ -1,4 +1,5 @@ use bitwarden_api_api::models::CipherFieldModel; +use bitwarden_core::require; use bitwarden_crypto::{ CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, }; @@ -7,7 +8,7 @@ use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use super::linked_id::LinkedIdType; -use crate::error::{require, Error, Result}; +use crate::VaultParseError; #[derive(Clone, Copy, Serialize_repr, Deserialize_repr, Debug, JsonSchema)] #[repr(u8)] @@ -34,11 +35,11 @@ pub struct Field { #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] pub struct FieldView { - pub(crate) name: Option, - pub(crate) value: Option, - pub(crate) r#type: FieldType, + pub name: Option, + pub value: Option, + pub r#type: FieldType, - pub(crate) linked_id: Option, + pub linked_id: Option, } impl KeyEncryptable for FieldView { @@ -64,9 +65,9 @@ impl KeyDecryptable for Field { } impl TryFrom for Field { - type Error = Error; + type Error = VaultParseError; - fn try_from(model: CipherFieldModel) -> Result { + fn try_from(model: CipherFieldModel) -> Result { Ok(Self { name: EncString::try_from_optional(model.name)?, value: EncString::try_from_optional(model.value)?, diff --git a/crates/bitwarden/src/vault/cipher/identity.rs b/crates/bitwarden-vault/src/cipher/identity.rs similarity index 97% rename from crates/bitwarden/src/vault/cipher/identity.rs rename to crates/bitwarden-vault/src/cipher/identity.rs index 5103793b0..f01274d77 100644 --- a/crates/bitwarden/src/vault/cipher/identity.rs +++ b/crates/bitwarden-vault/src/cipher/identity.rs @@ -5,7 +5,7 @@ use bitwarden_crypto::{ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::error::{Error, Result}; +use crate::VaultParseError; #[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -106,9 +106,9 @@ impl KeyDecryptable for Identity { } impl TryFrom for Identity { - type Error = Error; + type Error = VaultParseError; - fn try_from(identity: CipherIdentityModel) -> Result { + fn try_from(identity: CipherIdentityModel) -> Result { Ok(Self { title: EncString::try_from_optional(identity.title)?, first_name: EncString::try_from_optional(identity.first_name)?, diff --git a/crates/bitwarden/src/vault/cipher/linked_id.rs b/crates/bitwarden-vault/src/cipher/linked_id.rs similarity index 96% rename from crates/bitwarden/src/vault/cipher/linked_id.rs rename to crates/bitwarden-vault/src/cipher/linked_id.rs index 373fe9f96..9e7cd91cc 100644 --- a/crates/bitwarden/src/vault/cipher/linked_id.rs +++ b/crates/bitwarden-vault/src/cipher/linked_id.rs @@ -1,3 +1,4 @@ +use bitwarden_core::MissingFieldError; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; @@ -10,7 +11,6 @@ pub enum LinkedIdType { Identity(IdentityLinkedIdType), } -use crate::error::{Error, Result}; #[cfg(feature = "uniffi")] use crate::UniffiCustomTypeConverter; #[cfg(feature = "uniffi")] @@ -81,9 +81,9 @@ pub enum IdentityLinkedIdType { } impl TryFrom for LinkedIdType { - type Error = Error; + type Error = MissingFieldError; - fn try_from(value: u32) -> Result { + fn try_from(value: u32) -> Result { match value { 100 => Ok(LinkedIdType::Login(LoginLinkedIdType::Username)), 101 => Ok(LinkedIdType::Login(LoginLinkedIdType::Password)), @@ -112,7 +112,7 @@ impl TryFrom for LinkedIdType { 416 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::FirstName)), 417 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::LastName)), 418 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::FullName)), - _ => Err(Error::MissingFields("LinkedIdType")), + _ => Err(MissingFieldError("LinkedIdType")), } } } diff --git a/crates/bitwarden/src/vault/cipher/local_data.rs b/crates/bitwarden-vault/src/cipher/local_data.rs similarity index 100% rename from crates/bitwarden/src/vault/cipher/local_data.rs rename to crates/bitwarden-vault/src/cipher/local_data.rs diff --git a/crates/bitwarden/src/vault/cipher/login.rs b/crates/bitwarden-vault/src/cipher/login.rs similarity index 90% rename from crates/bitwarden/src/vault/cipher/login.rs rename to crates/bitwarden-vault/src/cipher/login.rs index 85ab98ff6..4b476b075 100644 --- a/crates/bitwarden/src/vault/cipher/login.rs +++ b/crates/bitwarden-vault/src/cipher/login.rs @@ -1,5 +1,6 @@ use base64::{engine::general_purpose::STANDARD, Engine}; use bitwarden_api_api::models::{CipherLoginModel, CipherLoginUriModel}; +use bitwarden_core::require; use bitwarden_crypto::{ CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, }; @@ -8,7 +9,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; -use crate::error::{require, Error, Result}; +use crate::VaultParseError; #[derive(Clone, Copy, Serialize_repr, Deserialize_repr, Debug, JsonSchema)] #[repr(u8)] @@ -100,7 +101,7 @@ pub struct Fido2CredentialView { // so we keep it encrypted until we need it pub key_value: EncString, pub rp_id: String, - pub user_handle: Option>, + pub user_handle: Option, pub user_name: Option, pub counter: String, pub rp_name: Option, @@ -113,14 +114,14 @@ pub struct Fido2CredentialView { // Only meant to be used internally and not exposed to the outside world #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(crate) struct Fido2CredentialFullView { +pub struct Fido2CredentialFullView { pub credential_id: String, pub key_type: String, pub key_algorithm: String, pub key_curve: String, - pub key_value: Vec, + pub key_value: String, pub rp_id: String, - pub user_handle: Option>, + pub user_handle: Option, pub user_name: Option, pub counter: String, pub rp_name: Option, @@ -141,12 +142,11 @@ pub struct Fido2CredentialNewView { pub key_algorithm: String, pub key_curve: String, pub rp_id: String, - pub user_handle: Option>, + pub user_handle: Option, pub user_name: Option, pub counter: String, pub rp_name: Option, pub user_display_name: Option, - pub discoverable: String, pub creation_date: DateTime, } @@ -163,30 +163,29 @@ impl From for Fido2CredentialNewView { counter: value.counter, rp_name: value.rp_name, user_display_name: value.user_display_name, - discoverable: value.discoverable, creation_date: value.creation_date, } } } -impl KeyEncryptable for Fido2CredentialFullView { - fn encrypt_with_key( - self, - key: &SymmetricCryptoKey, - ) -> Result { - Ok(Fido2CredentialView { - credential_id: self.credential_id, - key_type: self.key_type, - key_algorithm: self.key_algorithm, - key_curve: self.key_curve, +impl KeyEncryptable for Fido2CredentialFullView { + fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { + Ok(Fido2Credential { + credential_id: self.credential_id.encrypt_with_key(key)?, + key_type: self.key_type.encrypt_with_key(key)?, + key_algorithm: self.key_algorithm.encrypt_with_key(key)?, + key_curve: self.key_curve.encrypt_with_key(key)?, key_value: self.key_value.encrypt_with_key(key)?, - rp_id: self.rp_id, - user_handle: self.user_handle, - user_name: self.user_name, - counter: self.counter, - rp_name: self.rp_name, - user_display_name: self.user_display_name, - discoverable: self.discoverable, + rp_id: self.rp_id.encrypt_with_key(key)?, + user_handle: self + .user_handle + .map(|h| h.encrypt_with_key(key)) + .transpose()?, + user_name: self.user_name.encrypt_with_key(key)?, + counter: self.counter.encrypt_with_key(key)?, + rp_name: self.rp_name.encrypt_with_key(key)?, + user_display_name: self.user_display_name.encrypt_with_key(key)?, + discoverable: self.discoverable.encrypt_with_key(key)?, creation_date: self.creation_date, }) } @@ -266,7 +265,7 @@ pub struct LoginView { pub autofill_on_page_load: Option, // TODO: Remove this once the SDK supports state - pub fido2_credentials: Option>, + pub fido2_credentials: Option>, } impl KeyEncryptable for LoginUriView { @@ -288,7 +287,7 @@ impl KeyEncryptable for LoginView { uris: self.uris.encrypt_with_key(key)?, totp: self.totp.encrypt_with_key(key)?, autofill_on_page_load: self.autofill_on_page_load, - fido2_credentials: self.fido2_credentials.encrypt_with_key(key)?, + fido2_credentials: self.fido2_credentials, }) } } @@ -312,7 +311,7 @@ impl KeyDecryptable for Login { uris: self.uris.decrypt_with_key(key).ok().flatten(), totp: self.totp.decrypt_with_key(key).ok().flatten(), autofill_on_page_load: self.autofill_on_page_load, - fido2_credentials: self.fido2_credentials.decrypt_with_key(key).ok().flatten(), + fido2_credentials: self.fido2_credentials.clone(), }) } } @@ -367,9 +366,9 @@ impl KeyDecryptable for Fido2Credential } impl TryFrom for Login { - type Error = Error; + type Error = VaultParseError; - fn try_from(login: CipherLoginModel) -> Result { + fn try_from(login: CipherLoginModel) -> Result { Ok(Self { username: EncString::try_from_optional(login.username)?, password: EncString::try_from_optional(login.password)?, @@ -392,9 +391,9 @@ impl TryFrom for Login { } impl TryFrom for LoginUri { - type Error = Error; + type Error = VaultParseError; - fn try_from(uri: CipherLoginUriModel) -> Result { + fn try_from(uri: CipherLoginUriModel) -> Result { Ok(Self { uri: EncString::try_from_optional(uri.uri)?, r#match: uri.r#match.map(|m| m.into()), @@ -417,9 +416,11 @@ impl From for UriMatchType { } impl TryFrom for Fido2Credential { - type Error = Error; + type Error = VaultParseError; - fn try_from(value: bitwarden_api_api::models::CipherFido2CredentialModel) -> Result { + fn try_from( + value: bitwarden_api_api::models::CipherFido2CredentialModel, + ) -> Result { Ok(Self { credential_id: require!(value.credential_id).parse()?, key_type: require!(value.key_type).parse()?, diff --git a/crates/bitwarden/src/vault/cipher/mod.rs b/crates/bitwarden-vault/src/cipher/mod.rs similarity index 64% rename from crates/bitwarden/src/vault/cipher/mod.rs rename to crates/bitwarden-vault/src/cipher/mod.rs index 91e5bc7eb..67513d524 100644 --- a/crates/bitwarden/src/vault/cipher/mod.rs +++ b/crates/bitwarden-vault/src/cipher/mod.rs @@ -12,9 +12,10 @@ pub(crate) mod secure_note; pub use attachment::{ Attachment, AttachmentEncryptResult, AttachmentFile, AttachmentFileView, AttachmentView, }; -pub use cipher::{Cipher, CipherListView, CipherRepromptType, CipherType, CipherView}; +pub use cipher::{Cipher, CipherError, CipherListView, CipherRepromptType, CipherType, CipherView}; pub use field::FieldView; -#[cfg(feature = "uniffi")] -pub(crate) use login::Fido2CredentialFullView; -pub use login::{Fido2Credential, Fido2CredentialNewView, Fido2CredentialView}; +pub use login::{ + Fido2Credential, Fido2CredentialFullView, Fido2CredentialNewView, Fido2CredentialView, Login, + LoginUriView, LoginView, +}; pub use secure_note::SecureNoteType; diff --git a/crates/bitwarden/src/vault/cipher/secure_note.rs b/crates/bitwarden-vault/src/cipher/secure_note.rs similarity index 89% rename from crates/bitwarden/src/vault/cipher/secure_note.rs rename to crates/bitwarden-vault/src/cipher/secure_note.rs index be4fc6689..2563160bb 100644 --- a/crates/bitwarden/src/vault/cipher/secure_note.rs +++ b/crates/bitwarden-vault/src/cipher/secure_note.rs @@ -1,10 +1,11 @@ use bitwarden_api_api::models::CipherSecureNoteModel; +use bitwarden_core::require; use bitwarden_crypto::{CryptoError, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; -use crate::error::{require, Error, Result}; +use crate::VaultParseError; #[derive(Clone, Copy, Serialize_repr, Deserialize_repr, Debug, JsonSchema)] #[repr(u8)] @@ -24,7 +25,7 @@ pub struct SecureNote { #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] pub struct SecureNoteView { - pub(crate) r#type: SecureNoteType, + pub r#type: SecureNoteType, } impl KeyEncryptable for SecureNoteView { @@ -44,9 +45,9 @@ impl KeyDecryptable for SecureNote { } impl TryFrom for SecureNote { - type Error = Error; + type Error = VaultParseError; - fn try_from(model: CipherSecureNoteModel) -> Result { + fn try_from(model: CipherSecureNoteModel) -> Result { Ok(Self { r#type: require!(model.r#type).into(), }) diff --git a/crates/bitwarden-vault/src/client_totp.rs b/crates/bitwarden-vault/src/client_totp.rs new file mode 100644 index 000000000..6425b72eb --- /dev/null +++ b/crates/bitwarden-vault/src/client_totp.rs @@ -0,0 +1,32 @@ +use chrono::{DateTime, Utc}; + +use crate::{ + generate_totp, generate_totp_cipher_view, CipherListView, ClientVault, TotpError, TotpResponse, +}; + +impl<'a> ClientVault<'a> { + /// Generate a TOTP code from a provided key. + /// + /// Key can be either: + /// - A base32 encoded string + /// - OTP Auth URI + /// - Steam URI + pub fn generate_totp( + &'a self, + key: String, + time: Option>, + ) -> Result { + generate_totp(key, time) + } + + /// Generate a TOTP code from a provided cipher list view. + pub fn generate_totp_cipher_view( + &'a self, + view: CipherListView, + time: Option>, + ) -> Result { + let enc = self.client.internal.get_encryption_settings()?; + + generate_totp_cipher_view(&enc, view, time) + } +} diff --git a/crates/bitwarden-vault/src/client_vault.rs b/crates/bitwarden-vault/src/client_vault.rs new file mode 100644 index 000000000..67f6792b9 --- /dev/null +++ b/crates/bitwarden-vault/src/client_vault.rs @@ -0,0 +1,30 @@ +use bitwarden_core::Client; + +use crate::{ + sync::{sync, SyncError}, + SyncRequest, SyncResponse, +}; + +pub struct ClientVault<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientVault<'a> { + pub fn new(client: &'a Client) -> Self { + Self { client } + } + + pub async fn sync(&self, input: &SyncRequest) -> Result { + sync(self.client, input).await + } +} + +pub trait ClientVaultExt<'a> { + fn vault(&'a self) -> ClientVault<'a>; +} + +impl<'a> ClientVaultExt<'a> for Client { + fn vault(&'a self) -> ClientVault<'a> { + ClientVault::new(self) + } +} diff --git a/crates/bitwarden/src/vault/collection.rs b/crates/bitwarden-vault/src/collection.rs similarity index 92% rename from crates/bitwarden/src/vault/collection.rs rename to crates/bitwarden-vault/src/collection.rs index cfa17b946..5e0fcbdc5 100644 --- a/crates/bitwarden/src/vault/collection.rs +++ b/crates/bitwarden-vault/src/collection.rs @@ -1,4 +1,5 @@ use bitwarden_api_api::models::CollectionDetailsResponseModel; +use bitwarden_core::require; use bitwarden_crypto::{ CryptoError, EncString, KeyContainer, KeyDecryptable, LocateKey, SymmetricCryptoKey, }; @@ -6,7 +7,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::error::{require, Error, Result}; +use crate::VaultParseError; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -41,7 +42,7 @@ impl LocateKey for Collection { &self, enc: &'a dyn KeyContainer, _: &Option, - ) -> Option<&'a SymmetricCryptoKey> { + ) -> Result<&'a SymmetricCryptoKey, CryptoError> { enc.get_key(&Some(self.organization_id)) } } @@ -61,9 +62,9 @@ impl KeyDecryptable for Collection { } impl TryFrom for Collection { - type Error = Error; + type Error = VaultParseError; - fn try_from(collection: CollectionDetailsResponseModel) -> Result { + fn try_from(collection: CollectionDetailsResponseModel) -> Result { Ok(Collection { id: collection.id, organization_id: require!(collection.organization_id), diff --git a/crates/bitwarden/src/vault/domain.rs b/crates/bitwarden-vault/src/domain.rs similarity index 70% rename from crates/bitwarden/src/vault/domain.rs rename to crates/bitwarden-vault/src/domain.rs index 482cb1f59..dbdfcaddf 100644 --- a/crates/bitwarden/src/vault/domain.rs +++ b/crates/bitwarden-vault/src/domain.rs @@ -1,7 +1,8 @@ +use bitwarden_core::require; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::error::{require, Error, Result}; +use crate::VaultParseError; #[derive(Serialize, Deserialize, Debug, JsonSchema)] pub struct GlobalDomains { @@ -11,9 +12,11 @@ pub struct GlobalDomains { } impl TryFrom for GlobalDomains { - type Error = Error; + type Error = VaultParseError; - fn try_from(global_domains: bitwarden_api_api::models::GlobalDomains) -> Result { + fn try_from( + global_domains: bitwarden_api_api::models::GlobalDomains, + ) -> Result { Ok(Self { r#type: require!(global_domains.r#type), domains: require!(global_domains.domains), diff --git a/crates/bitwarden-vault/src/error.rs b/crates/bitwarden-vault/src/error.rs new file mode 100644 index 000000000..123337678 --- /dev/null +++ b/crates/bitwarden-vault/src/error.rs @@ -0,0 +1,11 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum VaultParseError { + #[error(transparent)] + Chrono(#[from] chrono::ParseError), + #[error(transparent)] + Crypto(#[from] bitwarden_crypto::CryptoError), + #[error(transparent)] + MissingFieldError(#[from] bitwarden_core::MissingFieldError), +} diff --git a/crates/bitwarden/src/vault/folder.rs b/crates/bitwarden-vault/src/folder.rs similarity index 80% rename from crates/bitwarden/src/vault/folder.rs rename to crates/bitwarden-vault/src/folder.rs index 09bf8868e..f57b8ee15 100644 --- a/crates/bitwarden/src/vault/folder.rs +++ b/crates/bitwarden-vault/src/folder.rs @@ -1,4 +1,5 @@ use bitwarden_api_api::models::FolderResponseModel; +use bitwarden_core::require; use bitwarden_crypto::{ CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, }; @@ -6,12 +7,15 @@ use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; +#[cfg(feature = "wasm")] +use {tsify_next::Tsify, wasm_bindgen::prelude::*}; -use crate::error::{require, Error, Result}; +use crate::VaultParseError; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] pub struct Folder { id: Option, name: EncString, @@ -21,6 +25,7 @@ pub struct Folder { #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] pub struct FolderView { pub id: Option, pub name: String, @@ -48,9 +53,9 @@ impl KeyDecryptable for Folder { } impl TryFrom for Folder { - type Error = Error; + type Error = VaultParseError; - fn try_from(folder: FolderResponseModel) -> Result { + fn try_from(folder: FolderResponseModel) -> Result { Ok(Folder { id: folder.id, name: require!(EncString::try_from_optional(folder.name)?), diff --git a/crates/bitwarden-vault/src/lib.rs b/crates/bitwarden-vault/src/lib.rs new file mode 100644 index 000000000..70524bb20 --- /dev/null +++ b/crates/bitwarden-vault/src/lib.rs @@ -0,0 +1,25 @@ +#[cfg(feature = "uniffi")] +uniffi::setup_scaffolding!(); +#[cfg(feature = "uniffi")] +mod uniffi_support; + +mod cipher; +pub use cipher::*; +mod collection; +pub use collection::{Collection, CollectionView}; +mod folder; +pub use folder::{Folder, FolderView}; +mod password_history; +pub use password_history::{PasswordHistory, PasswordHistoryView}; +mod domain; +pub use domain::GlobalDomains; +mod totp; +pub use totp::{generate_totp, generate_totp_cipher_view, TotpError, TotpResponse}; +mod error; +pub use error::VaultParseError; +mod client_vault; +pub use client_vault::{ClientVault, ClientVaultExt}; +mod client_totp; +mod mobile; +mod sync; +pub use sync::{SyncRequest, SyncResponse}; diff --git a/crates/bitwarden/src/mobile/vault/client_attachments.rs b/crates/bitwarden-vault/src/mobile/client_attachments.rs similarity index 68% rename from crates/bitwarden/src/mobile/vault/client_attachments.rs rename to crates/bitwarden-vault/src/mobile/client_attachments.rs index 20dcf0c13..a98a7d1e7 100644 --- a/crates/bitwarden/src/mobile/vault/client_attachments.rs +++ b/crates/bitwarden-vault/src/mobile/client_attachments.rs @@ -1,14 +1,11 @@ use std::path::Path; +use bitwarden_core::{Client, Error}; use bitwarden_crypto::{EncString, KeyDecryptable, KeyEncryptable, LocateKey}; use crate::{ - error::{Error, Result}, - vault::{ - Attachment, AttachmentEncryptResult, AttachmentFile, AttachmentFileView, AttachmentView, - Cipher, ClientVault, - }, - Client, + Attachment, AttachmentEncryptResult, AttachmentFile, AttachmentFileView, AttachmentView, + Cipher, ClientVault, }; pub struct ClientAttachments<'a> { @@ -16,14 +13,14 @@ pub struct ClientAttachments<'a> { } impl<'a> ClientAttachments<'a> { - pub async fn encrypt_buffer( + pub fn encrypt_buffer( &self, cipher: Cipher, attachment: AttachmentView, buffer: &[u8], - ) -> Result { - let enc = self.client.get_encryption_settings()?; - let key = cipher.locate_key(enc, &None).ok_or(Error::VaultLocked)?; + ) -> Result { + let enc = self.client.internal.get_encryption_settings()?; + let key = cipher.locate_key(&enc, &None)?; Ok(AttachmentFileView { cipher, @@ -32,30 +29,30 @@ impl<'a> ClientAttachments<'a> { } .encrypt_with_key(key)?) } - pub async fn encrypt_file( + pub fn encrypt_file( &self, cipher: Cipher, attachment: AttachmentView, decrypted_file_path: &Path, encrypted_file_path: &Path, - ) -> Result { + ) -> Result { let data = std::fs::read(decrypted_file_path)?; let AttachmentEncryptResult { attachment, contents, - } = self.encrypt_buffer(cipher, attachment, &data).await?; + } = self.encrypt_buffer(cipher, attachment, &data)?; std::fs::write(encrypted_file_path, contents)?; Ok(attachment) } - pub async fn decrypt_buffer( + pub fn decrypt_buffer( &self, cipher: Cipher, attachment: Attachment, encrypted_buffer: &[u8], - ) -> Result> { - let enc = self.client.get_encryption_settings()?; - let key = cipher.locate_key(enc, &None).ok_or(Error::VaultLocked)?; + ) -> Result, Error> { + let enc = self.client.internal.get_encryption_settings()?; + let key = cipher.locate_key(&enc, &None)?; AttachmentFile { cipher, @@ -65,15 +62,15 @@ impl<'a> ClientAttachments<'a> { .decrypt_with_key(key) .map_err(Error::Crypto) } - pub async fn decrypt_file( + pub fn decrypt_file( &self, cipher: Cipher, attachment: Attachment, encrypted_file_path: &Path, decrypted_file_path: &Path, - ) -> Result<()> { + ) -> Result<(), Error> { let data = std::fs::read(encrypted_file_path)?; - let decrypted = self.decrypt_buffer(cipher, attachment, &data).await?; + let decrypted = self.decrypt_buffer(cipher, attachment, &data)?; std::fs::write(decrypted_file_path, decrypted)?; Ok(()) } diff --git a/crates/bitwarden/src/mobile/vault/client_ciphers.rs b/crates/bitwarden-vault/src/mobile/client_ciphers.rs similarity index 77% rename from crates/bitwarden/src/mobile/vault/client_ciphers.rs rename to crates/bitwarden-vault/src/mobile/client_ciphers.rs index 0127179da..345f04df1 100644 --- a/crates/bitwarden/src/mobile/vault/client_ciphers.rs +++ b/crates/bitwarden-vault/src/mobile/client_ciphers.rs @@ -1,55 +1,52 @@ -use bitwarden_crypto::{CryptoError, KeyDecryptable, KeyEncryptable, LocateKey}; +use bitwarden_core::{Client, Error}; +use bitwarden_crypto::{KeyDecryptable, KeyEncryptable, LocateKey}; use uuid::Uuid; -use crate::{ - error::{Error, Result}, - vault::{Cipher, CipherListView, CipherView, ClientVault}, - Client, -}; +use crate::{Cipher, CipherError, CipherListView, CipherView, ClientVault}; pub struct ClientCiphers<'a> { pub(crate) client: &'a Client, } impl<'a> ClientCiphers<'a> { - pub async fn encrypt(&self, mut cipher_view: CipherView) -> Result { - let enc = self.client.get_encryption_settings()?; + pub fn encrypt(&self, mut cipher_view: CipherView) -> Result { + let enc = self.client.internal.get_encryption_settings()?; // TODO: Once this flag is removed, the key generation logic should // be moved directly into the KeyEncryptable implementation - if cipher_view.key.is_none() && self.client.get_flags().enable_cipher_key_encryption { - let key = cipher_view - .locate_key(enc, &None) - .ok_or(Error::VaultLocked)?; + if cipher_view.key.is_none() + && self + .client + .internal + .get_flags() + .enable_cipher_key_encryption + { + let key = cipher_view.locate_key(&enc, &None)?; cipher_view.generate_cipher_key(key)?; } - let key = cipher_view - .locate_key(enc, &None) - .ok_or(Error::VaultLocked)?; + let key = cipher_view.locate_key(&enc, &None)?; let cipher = cipher_view.encrypt_with_key(key)?; Ok(cipher) } - pub async fn decrypt(&self, cipher: Cipher) -> Result { - let enc = self.client.get_encryption_settings()?; - let key = cipher - .locate_key(enc, &None) - .ok_or(CryptoError::MissingKey)?; + pub fn decrypt(&self, cipher: Cipher) -> Result { + let enc = self.client.internal.get_encryption_settings()?; + let key = cipher.locate_key(&enc, &None)?; let cipher_view = cipher.decrypt_with_key(key)?; Ok(cipher_view) } - pub async fn decrypt_list(&self, ciphers: Vec) -> Result> { - let enc = self.client.get_encryption_settings()?; + pub fn decrypt_list(&self, ciphers: Vec) -> Result, Error> { + let enc = self.client.internal.get_encryption_settings()?; - let cipher_views: Result> = ciphers + let cipher_views: Result, _> = ciphers .iter() - .map(|c| -> Result { - let key = c.locate_key(enc, &None).ok_or(CryptoError::MissingKey)?; + .map(|c| -> Result { + let key = c.locate_key(&enc, &None)?; Ok(c.decrypt_with_key(key)?) }) .collect(); @@ -57,13 +54,27 @@ impl<'a> ClientCiphers<'a> { cipher_views } - pub async fn move_to_organization( + #[cfg(feature = "uniffi")] + pub fn decrypt_fido2_credentials( + &self, + cipher_view: CipherView, + ) -> Result, Error> { + let enc = self.client.internal.get_encryption_settings()?; + + let credentials = cipher_view + .decrypt_fido2_credentials(&enc) + .map_err(|e| e.to_string())?; + + Ok(credentials) + } + + pub fn move_to_organization( &self, mut cipher_view: CipherView, organization_id: Uuid, - ) -> Result { - let enc = self.client.get_encryption_settings()?; - cipher_view.move_to_organization(enc, organization_id)?; + ) -> Result { + let enc = self.client.internal.get_encryption_settings()?; + cipher_view.move_to_organization(&enc, organization_id)?; Ok(cipher_view) } } @@ -79,15 +90,14 @@ impl<'a> ClientVault<'a> { #[cfg(test)] mod tests { + use bitwarden_core::client::test_accounts::test_bitwarden_com_account; + use super::*; - use crate::{ - client::test_accounts::test_bitwarden_com_account, - vault::{login::Login, Attachment, CipherRepromptType, CipherType}, - }; + use crate::{Attachment, CipherRepromptType, CipherType, ClientVaultExt, Login}; #[tokio::test] async fn test_decrypt_list() { - let mut client = Client::init_test_account(test_bitwarden_com_account()).await; + let client = Client::init_test_account(test_bitwarden_com_account()).await; let dec = client .vault() @@ -121,7 +131,7 @@ mod tests { deleted_date: None, revision_date: "2024-05-31T09:35:55.12Z".parse().unwrap(), }]) - .await + .unwrap(); assert_eq!(dec[0].name, "Test item"); @@ -188,53 +198,39 @@ mod tests { #[tokio::test] async fn test_move_user_cipher_with_attachment_without_key_to_org_fails() { - let mut client = Client::init_test_account(test_bitwarden_com_account()).await; + let client = Client::init_test_account(test_bitwarden_com_account()).await; let mut cipher = test_cipher(); cipher.attachments = Some(vec![test_attachment_legacy()]); - let view = client - .vault() - .ciphers() - .decrypt(cipher.clone()) - .await - .unwrap(); + let view = client.vault().ciphers().decrypt(cipher.clone()).unwrap(); // Move cipher to organization - let res = client - .vault() - .ciphers() - .move_to_organization( - view, - "1bc9ac1e-f5aa-45f2-94bf-b181009709b8".parse().unwrap(), - ) - .await; + let res = client.vault().ciphers().move_to_organization( + view, + "1bc9ac1e-f5aa-45f2-94bf-b181009709b8".parse().unwrap(), + ); assert!(res.is_err()); } #[tokio::test] async fn test_encrypt_cipher_with_legacy_attachment_without_key() { - let mut client = Client::init_test_account(test_bitwarden_com_account()).await; + let client = Client::init_test_account(test_bitwarden_com_account()).await; let mut cipher = test_cipher(); let attachment = test_attachment_legacy(); cipher.attachments = Some(vec![attachment.clone()]); - let view = client - .vault() - .ciphers() - .decrypt(cipher.clone()) - .await - .unwrap(); + let view = client.vault().ciphers().decrypt(cipher.clone()).unwrap(); assert!(cipher.key.is_none()); // Assert the cipher has a key, and the attachment is still readable - let new_cipher = client.vault().ciphers().encrypt(view).await.unwrap(); + let new_cipher = client.vault().ciphers().encrypt(view).unwrap(); assert!(new_cipher.key.is_some()); - let view = client.vault().ciphers().decrypt(new_cipher).await.unwrap(); + let view = client.vault().ciphers().decrypt(new_cipher).unwrap(); let attachments = view.clone().attachments.unwrap(); let attachment_view = attachments.first().unwrap().clone(); assert!(attachment_view.key.is_none()); @@ -252,7 +248,6 @@ mod tests { .vault() .attachments() .decrypt_buffer(cipher, attachment, buf.as_slice()) - .await .unwrap(); assert_eq!(content, b"Hello"); @@ -260,26 +255,21 @@ mod tests { #[tokio::test] async fn test_encrypt_cipher_with_v1_attachment_without_key() { - let mut client = Client::init_test_account(test_bitwarden_com_account()).await; + let client = Client::init_test_account(test_bitwarden_com_account()).await; let mut cipher = test_cipher(); let attachment = test_attachment_v2(); cipher.attachments = Some(vec![attachment.clone()]); - let view = client - .vault() - .ciphers() - .decrypt(cipher.clone()) - .await - .unwrap(); + let view = client.vault().ciphers().decrypt(cipher.clone()).unwrap(); assert!(cipher.key.is_none()); // Assert the cipher has a key, and the attachment is still readable - let new_cipher = client.vault().ciphers().encrypt(view).await.unwrap(); + let new_cipher = client.vault().ciphers().encrypt(view).unwrap(); assert!(new_cipher.key.is_some()); - let view = client.vault().ciphers().decrypt(new_cipher).await.unwrap(); + let view = client.vault().ciphers().decrypt(new_cipher).unwrap(); let attachments = view.clone().attachments.unwrap(); let attachment_view = attachments.first().unwrap().clone(); assert!(attachment_view.key.is_some()); @@ -303,7 +293,6 @@ mod tests { .vault() .attachments() .decrypt_buffer(cipher, attachment, buf.as_slice()) - .await .unwrap(); assert_eq!(content, b"Hello"); @@ -316,9 +305,8 @@ mod tests { view, "1bc9ac1e-f5aa-45f2-94bf-b181009709b8".parse().unwrap(), ) - .await .unwrap(); - let new_cipher = client.vault().ciphers().encrypt(new_view).await.unwrap(); + let new_cipher = client.vault().ciphers().encrypt(new_view).unwrap(); let attachment = new_cipher .clone() @@ -338,7 +326,6 @@ mod tests { .vault() .attachments() .decrypt_buffer(new_cipher, attachment, buf.as_slice()) - .await .unwrap(); assert_eq!(content, b"Hello"); diff --git a/crates/bitwarden/src/mobile/vault/client_collection.rs b/crates/bitwarden-vault/src/mobile/client_collection.rs similarity index 61% rename from crates/bitwarden/src/mobile/vault/client_collection.rs rename to crates/bitwarden-vault/src/mobile/client_collection.rs index 878db238f..a3d6ee612 100644 --- a/crates/bitwarden/src/mobile/vault/client_collection.rs +++ b/crates/bitwarden-vault/src/mobile/client_collection.rs @@ -1,34 +1,29 @@ -use bitwarden_crypto::{CryptoError, KeyDecryptable, LocateKey}; +use bitwarden_core::{Client, Error}; +use bitwarden_crypto::{KeyDecryptable, LocateKey}; -use crate::{ - error::Result, - vault::{ClientVault, Collection, CollectionView}, - Client, -}; +use crate::{ClientVault, Collection, CollectionView}; pub struct ClientCollections<'a> { pub(crate) client: &'a Client, } impl<'a> ClientCollections<'a> { - pub async fn decrypt(&self, collection: Collection) -> Result { - let enc = self.client.get_encryption_settings()?; - let key = collection - .locate_key(enc, &None) - .ok_or(CryptoError::MissingKey)?; + pub fn decrypt(&self, collection: Collection) -> Result { + let enc = self.client.internal.get_encryption_settings()?; + let key = collection.locate_key(&enc, &None)?; let view = collection.decrypt_with_key(key)?; Ok(view) } - pub async fn decrypt_list(&self, collections: Vec) -> Result> { - let enc = self.client.get_encryption_settings()?; + pub fn decrypt_list(&self, collections: Vec) -> Result, Error> { + let enc = self.client.internal.get_encryption_settings()?; - let views: Result> = collections + let views: Result, _> = collections .iter() - .map(|c| -> Result { - let key = c.locate_key(enc, &None).ok_or(CryptoError::MissingKey)?; + .map(|c| -> Result { + let key = c.locate_key(&enc, &None)?; Ok(c.decrypt_with_key(key)?) }) .collect(); @@ -47,11 +42,14 @@ impl<'a> ClientVault<'a> { #[cfg(test)] mod tests { - use crate::{client::test_accounts::test_bitwarden_com_account, vault::Collection, Client}; + use bitwarden_core::client::test_accounts::test_bitwarden_com_account; + + use super::*; + use crate::ClientVaultExt; #[tokio::test] async fn test_decrypt_list() { - let mut client = Client::init_test_account(test_bitwarden_com_account()).await; + let client = Client::init_test_account(test_bitwarden_com_account()).await; let dec = client.vault().collections().decrypt_list(vec![Collection { id: Some("66c5ca57-0868-4c7e-902f-b181009709c0".parse().unwrap()), @@ -60,14 +58,14 @@ mod tests { external_id: None, hide_passwords: false, read_only: false, - }]).await.unwrap(); + }]).unwrap(); assert_eq!(dec[0].name, "Default collection"); } #[tokio::test] async fn test_decrypt() { - let mut client = Client::init_test_account(test_bitwarden_com_account()).await; + let client = Client::init_test_account(test_bitwarden_com_account()).await; let dec = client.vault().collections().decrypt(Collection { id: Some("66c5ca57-0868-4c7e-902f-b181009709c0".parse().unwrap()), @@ -76,7 +74,7 @@ mod tests { external_id: None, hide_passwords: false, read_only: false, - }).await.unwrap(); + }).unwrap(); assert_eq!(dec.name, "Default collection"); } diff --git a/crates/bitwarden-vault/src/mobile/client_folders.rs b/crates/bitwarden-vault/src/mobile/client_folders.rs new file mode 100644 index 000000000..d8ebb76ed --- /dev/null +++ b/crates/bitwarden-vault/src/mobile/client_folders.rs @@ -0,0 +1,45 @@ +use bitwarden_core::{Client, Error}; +use bitwarden_crypto::{KeyDecryptable, KeyEncryptable}; + +use crate::{ClientVault, Folder, FolderView}; + +pub struct ClientFolders<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientFolders<'a> { + pub fn encrypt(&self, folder_view: FolderView) -> Result { + let enc = self.client.internal.get_encryption_settings()?; + let key = enc.get_key(&None)?; + + let folder = folder_view.encrypt_with_key(key)?; + + Ok(folder) + } + + pub fn decrypt(&self, folder: Folder) -> Result { + let enc = self.client.internal.get_encryption_settings()?; + let key = enc.get_key(&None)?; + + let folder_view = folder.decrypt_with_key(key)?; + + Ok(folder_view) + } + + pub fn decrypt_list(&self, folders: Vec) -> Result, Error> { + let enc = self.client.internal.get_encryption_settings()?; + let key = enc.get_key(&None)?; + + let views = folders.decrypt_with_key(key)?; + + Ok(views) + } +} + +impl<'a> ClientVault<'a> { + pub fn folders(&'a self) -> ClientFolders<'a> { + ClientFolders { + client: self.client, + } + } +} diff --git a/crates/bitwarden-vault/src/mobile/client_password_history.rs b/crates/bitwarden-vault/src/mobile/client_password_history.rs new file mode 100644 index 000000000..98d989dbd --- /dev/null +++ b/crates/bitwarden-vault/src/mobile/client_password_history.rs @@ -0,0 +1,39 @@ +use bitwarden_core::{Client, Error}; +use bitwarden_crypto::{KeyDecryptable, KeyEncryptable}; + +use crate::{ClientVault, PasswordHistory, PasswordHistoryView}; + +pub struct ClientPasswordHistory<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientPasswordHistory<'a> { + pub fn encrypt(&self, history_view: PasswordHistoryView) -> Result { + let enc = self.client.internal.get_encryption_settings()?; + let key = enc.get_key(&None)?; + + let history = history_view.encrypt_with_key(key)?; + + Ok(history) + } + + pub fn decrypt_list( + &self, + history: Vec, + ) -> Result, Error> { + let enc = self.client.internal.get_encryption_settings()?; + let key = enc.get_key(&None)?; + + let history_view = history.decrypt_with_key(key)?; + + Ok(history_view) + } +} + +impl<'a> ClientVault<'a> { + pub fn password_history(&'a self) -> ClientPasswordHistory<'a> { + ClientPasswordHistory { + client: self.client, + } + } +} diff --git a/crates/bitwarden-vault/src/mobile/mod.rs b/crates/bitwarden-vault/src/mobile/mod.rs new file mode 100644 index 000000000..59e28b6e8 --- /dev/null +++ b/crates/bitwarden-vault/src/mobile/mod.rs @@ -0,0 +1,5 @@ +mod client_attachments; +mod client_ciphers; +mod client_collection; +mod client_folders; +mod client_password_history; diff --git a/crates/bitwarden/src/vault/password_history.rs b/crates/bitwarden-vault/src/password_history.rs similarity index 95% rename from crates/bitwarden/src/vault/password_history.rs rename to crates/bitwarden-vault/src/password_history.rs index fcd67db69..5e2075e3b 100644 --- a/crates/bitwarden/src/vault/password_history.rs +++ b/crates/bitwarden-vault/src/password_history.rs @@ -6,7 +6,7 @@ use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::error::{Error, Result}; +use crate::VaultParseError; #[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -46,9 +46,9 @@ impl KeyDecryptable for PasswordHistory } impl TryFrom for PasswordHistory { - type Error = Error; + type Error = VaultParseError; - fn try_from(model: CipherPasswordHistoryModel) -> Result { + 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/sync.rs b/crates/bitwarden-vault/src/sync.rs similarity index 73% rename from crates/bitwarden/src/vault/sync.rs rename to crates/bitwarden-vault/src/sync.rs index bd9d38104..be4c3b169 100644 --- a/crates/bitwarden/src/vault/sync.rs +++ b/crates/bitwarden-vault/src/sync.rs @@ -1,17 +1,27 @@ use bitwarden_api_api::models::{ DomainsResponseModel, ProfileOrganizationResponseModel, ProfileResponseModel, SyncResponseModel, }; +use bitwarden_core::{ + client::encryption_settings::EncryptionSettings, require, Client, Error, MissingFieldError, +}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use thiserror::Error; use uuid::Uuid; -use super::domain::GlobalDomains; -use crate::{ - admin_console::Policy, - client::{encryption_settings::EncryptionSettings, Client}, - error::{require, Error, Result}, - vault::{Cipher, Collection, Folder}, -}; +use crate::{Cipher, Collection, Folder, GlobalDomains, VaultParseError}; + +#[derive(Debug, Error)] +pub enum SyncError { + #[error(transparent)] + Core(#[from] bitwarden_core::Error), + + #[error(transparent)] + MissingFieldError(#[from] MissingFieldError), + + #[error(transparent)] + VaultParse(#[from] VaultParseError), +} #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -20,10 +30,11 @@ pub struct SyncRequest { pub exclude_subdomains: Option, } -pub(crate) async fn sync(client: &mut Client, input: &SyncRequest) -> Result { - let config = client.get_api_configurations().await; - let sync = - bitwarden_api_api::apis::sync_api::sync_get(&config.api, input.exclude_subdomains).await?; +pub(crate) async fn sync(client: &Client, input: &SyncRequest) -> Result { + let config = client.internal.get_api_configurations().await; + let sync = bitwarden_api_api::apis::sync_api::sync_get(&config.api, input.exclude_subdomains) + .await + .map_err(|e| SyncError::Core(e.into()))?; let org_keys: Vec<_> = require!(sync.profile.as_ref()) .organizations @@ -33,9 +44,12 @@ pub(crate) async fn sync(client: &mut Client, input: &SyncRequest) -> Result, pub domains: Option, - pub policies: Vec, - pub sends: Vec, + //pub policies: Vec, + //pub sends: Vec, } impl SyncResponse { pub(crate) fn process_response( response: SyncResponseModel, enc: &EncryptionSettings, - ) -> Result { + ) -> Result { let profile = require!(response.profile); let ciphers = require!(response.ciphers); @@ -101,8 +115,8 @@ impl SyncResponse { collections: try_into_iter(require!(response.collections))?, ciphers: try_into_iter(ciphers)?, domains: response.domains.map(|d| (*d).try_into()).transpose()?, - policies: try_into_iter(require!(response.policies))?, - sends: try_into_iter(require!(response.sends))?, + //policies: try_into_iter(require!(response.policies))?, + //sends: try_into_iter(require!(response.sends))?, }) } } @@ -110,7 +124,7 @@ impl SyncResponse { impl ProfileOrganizationResponse { fn process_response( response: ProfileOrganizationResponseModel, - ) -> Result { + ) -> Result { Ok(ProfileOrganizationResponse { id: require!(response.id), }) @@ -121,7 +135,7 @@ impl ProfileResponse { fn process_response( response: ProfileResponseModel, _enc: &EncryptionSettings, - ) -> Result { + ) -> Result { Ok(ProfileResponse { id: require!(response.id), name: require!(response.name), @@ -139,8 +153,9 @@ impl ProfileResponse { } impl TryFrom for DomainResponse { - type Error = Error; - fn try_from(value: DomainsResponseModel) -> Result { + type Error = SyncError; + + fn try_from(value: DomainsResponseModel) -> Result { Ok(Self { equivalent_domains: value.equivalent_domains.unwrap_or_default(), global_equivalent_domains: value @@ -148,7 +163,7 @@ impl TryFrom for DomainResponse { .unwrap_or_default() .into_iter() .map(|s| s.try_into()) - .collect::>>()?, + .collect::, _>>()?, }) } } diff --git a/crates/bitwarden/src/vault/totp.rs b/crates/bitwarden-vault/src/totp.rs similarity index 74% rename from crates/bitwarden/src/vault/totp.rs rename to crates/bitwarden-vault/src/totp.rs index 605fbe19c..8aee3e694 100644 --- a/crates/bitwarden/src/vault/totp.rs +++ b/crates/bitwarden-vault/src/totp.rs @@ -1,12 +1,15 @@ use std::{collections::HashMap, str::FromStr}; +use bitwarden_core::VaultLocked; +use bitwarden_crypto::{CryptoError, KeyContainer}; use chrono::{DateTime, Utc}; use hmac::{Hmac, Mac}; use reqwest::Url; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use thiserror::Error; -use crate::error::{Error, Result}; +use crate::CipherListView; type HmacSha1 = Hmac; type HmacSha256 = Hmac; @@ -19,6 +22,19 @@ const DEFAULT_ALGORITHM: Algorithm = Algorithm::Sha1; const DEFAULT_DIGITS: u32 = 6; const DEFAULT_PERIOD: u32 = 30; +#[derive(Debug, Error)] +pub enum TotpError { + #[error("Invalid otpauth")] + InvalidOtpauth, + #[error("Missing secret")] + MissingSecret, + + #[error(transparent)] + CryptoError(#[from] CryptoError), + #[error(transparent)] + VaultLocked(#[from] VaultLocked), +} + #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] @@ -43,7 +59,7 @@ pub struct TotpResponse { /// 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 { +pub fn generate_totp(key: String, time: Option>) -> Result { let params: Totp = key.parse()?; let time = time.unwrap_or_else(Utc::now); @@ -56,6 +72,19 @@ pub(crate) fn generate_totp(key: String, time: Option>) -> Result< }) } +/// Generate a OATH or RFC 6238 TOTP code from a provided CipherListView. +/// +/// See [generate_totp] for more information. +pub fn generate_totp_cipher_view( + enc: &dyn KeyContainer, + view: CipherListView, + time: Option>, +) -> Result { + let key = view.get_totp_key(enc)?.ok_or(TotpError::MissingSecret)?; + + generate_totp(key, time) +} + #[derive(Clone, Copy, Debug)] enum Algorithm { Sha1, @@ -119,7 +148,7 @@ impl Totp { } impl FromStr for Totp { - type Err = Error; + type Err = TotpError; /// Parses the provided key and returns the corresponding `Totp`. /// @@ -127,9 +156,9 @@ impl FromStr for Totp { /// - A base32 encoded string /// - OTP Auth URI /// - Steam URI - fn from_str(key: &str) -> Result { + fn from_str(key: &str) -> Result { let params = if key.starts_with("otpauth://") { - let url = Url::parse(key).map_err(|_| "Unable to parse URL")?; + let url = Url::parse(key).map_err(|_| TotpError::InvalidOtpauth)?; let parts: HashMap<_, _> = url.query_pairs().collect(); Totp { @@ -156,7 +185,7 @@ impl FromStr for Totp { &parts .get("secret") .map(|v| v.to_string()) - .ok_or("Missing secret in otpauth URI")?, + .ok_or(TotpError::MissingSecret)?, ), } } else if let Some(secret) = key.strip_prefix("steam://") { @@ -230,9 +259,12 @@ fn decode_b32(s: &str) -> Vec { #[cfg(test)] mod tests { + use bitwarden_crypto::{CryptoError, SymmetricCryptoKey}; use chrono::Utc; + use uuid::Uuid; use super::*; + use crate::{cipher::cipher::CipherListViewType, CipherRepromptType}; #[test] fn test_decode_b32() { @@ -302,4 +334,48 @@ mod tests { assert_eq!(response.code, "730364".to_string()); assert_eq!(response.period, 60); } + + #[test] + fn test_generate_totp_cipher_view() { + let view = CipherListView { + id: Some("090c19ea-a61a-4df6-8963-262b97bc6266".parse().unwrap()), + organization_id: None, + folder_id: None, + collection_ids: vec![], + key: None, + name: "My test login".to_string(), + sub_title: "test_username".to_string(), + r#type: CipherListViewType::Login { + has_fido2: true, + totp: Some("2.hqdioUAc81FsKQmO1XuLQg==|oDRdsJrQjoFu9NrFVy8tcJBAFKBx95gHaXZnWdXbKpsxWnOr2sKipIG43pKKUFuq|3gKZMiboceIB5SLVOULKg2iuyu6xzos22dfJbvx0EHk=".parse().unwrap()), + }, + favorite: false, + reprompt: CipherRepromptType::None, + edit: true, + view_password: true, + attachments: 0, + creation_date: "2024-01-30T17:55:36.150Z".parse().unwrap(), + deleted_date: None, + revision_date: "2024-01-30T17:55:36.150Z".parse().unwrap(), + }; + + struct MockKeyContainer(SymmetricCryptoKey); + impl KeyContainer for MockKeyContainer { + fn get_key<'a>( + &'a self, + _: &Option, + ) -> Result<&'a SymmetricCryptoKey, CryptoError> { + Ok(&self.0) + } + } + + let enc = MockKeyContainer("w2LO+nwV4oxwswVYCxlOfRUseXfvU03VzvKQHrqeklPgiMZrspUe6sOBToCnDn9Ay0tuCBn8ykVVRb7PWhub2Q==".to_string().try_into().unwrap()); + let time = DateTime::parse_from_rfc3339("2023-01-01T00:00:00.000Z") + .unwrap() + .with_timezone(&Utc); + + let response = generate_totp_cipher_view(&enc, view, Some(time)).unwrap(); + assert_eq!(response.code, "559388".to_string()); + assert_eq!(response.period, 30); + } } diff --git a/crates/bitwarden-vault/src/uniffi_support.rs b/crates/bitwarden-vault/src/uniffi_support.rs new file mode 100644 index 000000000..932f0de7a --- /dev/null +++ b/crates/bitwarden-vault/src/uniffi_support.rs @@ -0,0 +1,8 @@ +use bitwarden_crypto::EncString; +use uuid::Uuid; + +uniffi::ffi_converter_forward!(EncString, bitwarden_crypto::UniFfiTag, crate::UniFfiTag); + +type DateTime = chrono::DateTime; +uniffi::ffi_converter_forward!(DateTime, bitwarden_core::UniFfiTag, crate::UniFfiTag); +uniffi::ffi_converter_forward!(Uuid, bitwarden_core::UniFfiTag, crate::UniFfiTag); diff --git a/crates/bitwarden-vault/uniffi.toml b/crates/bitwarden-vault/uniffi.toml new file mode 100644 index 000000000..8d4fbe384 --- /dev/null +++ b/crates/bitwarden-vault/uniffi.toml @@ -0,0 +1,9 @@ +[bindings.kotlin] +package_name = "com.bitwarden.vault" +generate_immutable_records = true +android = true + +[bindings.swift] +ffi_module_name = "BitwardenVaultFFI" +module_name = "BitwardenVault" +generate_immutable_records = true diff --git a/crates/bitwarden-wasm-internal/Cargo.toml b/crates/bitwarden-wasm-internal/Cargo.toml new file mode 100644 index 000000000..33f8128e7 --- /dev/null +++ b/crates/bitwarden-wasm-internal/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "bitwarden-wasm-internal" +version = "0.1.0" +publish = false + +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +homepage.workspace = true +repository.workspace = true +license-file.workspace = true +keywords.workspace = true + +[lib] +crate-type = ["cdylib"] + +[dependencies] +bitwarden-core = { workspace = true, features = ["wasm", "internal"] } +bitwarden-crypto = { workspace = true, features = ["wasm"] } +bitwarden-vault = { workspace = true, features = ["wasm"] } +console_error_panic_hook = "0.1.7" +console_log = { version = "1.0.0", features = ["color"] } +js-sys = "0.3.68" +log = "0.4.20" +serde_json = ">=1.0.96, <2.0" +# When upgrading wasm-bindgen, make sure to update the version in the workflows! +wasm-bindgen = { version = "=0.2.95", features = ["serde-serialize"] } +wasm-bindgen-futures = "0.4.41" + +[lints] +workspace = true diff --git a/crates/bitwarden-wasm-internal/README.md b/crates/bitwarden-wasm-internal/README.md new file mode 100644 index 000000000..4db9847f5 --- /dev/null +++ b/crates/bitwarden-wasm-internal/README.md @@ -0,0 +1,25 @@ +# bitwarden-wasm-internal + +**Note:** This is only for internal use. Bitwarden will not provide any support for this crate. + +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-internal/build.sh b/crates/bitwarden-wasm-internal/build.sh new file mode 100755 index 000000000..109037e89 --- /dev/null +++ b/crates/bitwarden-wasm-internal/build.sh @@ -0,0 +1,25 @@ +# Move to the root of the repository +cd "$(dirname "$0")" +cd ../../ + +if [ "$1" != "-r" ]; then + # Dev + cargo build -p bitwarden-wasm-internal --target wasm32-unknown-unknown + wasm-bindgen --target bundler --out-dir languages/js/sdk-internal ./target/wasm32-unknown-unknown/debug/bitwarden_wasm_internal.wasm + wasm-bindgen --target nodejs --out-dir languages/js/sdk-internal/node ./target/wasm32-unknown-unknown/debug/bitwarden_wasm_internal.wasm +else + # Release + cargo build -p bitwarden-wasm-internal --target wasm32-unknown-unknown --release + wasm-bindgen --target bundler --out-dir languages/js/sdk-internal ./target/wasm32-unknown-unknown/release/bitwarden_wasm_internal.wasm + wasm-bindgen --target nodejs --out-dir languages/js/sdk-internal/node ./target/wasm32-unknown-unknown/release/bitwarden_wasm_internal.wasm +fi + +# Format +npx prettier --write ./languages/js/sdk-internal + +# Optimize size +wasm-opt -Os ./languages/js/sdk-internal/bitwarden_wasm_internal_bg.wasm -o ./languages/js/sdk-internal/bitwarden_wasm_internal_bg.wasm + +# Transpile to JS +wasm2js ./languages/js/sdk-internal/bitwarden_wasm_internal_bg.wasm -o ./languages/js/sdk-internal/bitwarden_wasm_internal_bg.wasm.js +npx terser ./languages/js/sdk-internal/bitwarden_wasm_internal_bg.wasm.js -o ./languages/js/sdk-internal/bitwarden_wasm_internal_bg.wasm.js diff --git a/crates/bitwarden-wasm-internal/src/client.rs b/crates/bitwarden-wasm-internal/src/client.rs new file mode 100644 index 000000000..ac7779b2f --- /dev/null +++ b/crates/bitwarden-wasm-internal/src/client.rs @@ -0,0 +1,67 @@ +extern crate console_error_panic_hook; +use std::rc::Rc; + +use bitwarden_core::{Client, ClientSettings}; +use log::{set_max_level, Level}; +use wasm_bindgen::prelude::*; + +use crate::{vault::ClientVault, ClientCrypto}; + +#[wasm_bindgen] +pub enum LogLevel { + Trace, + Debug, + Info, + Warn, + Error, +} + +fn convert_level(level: LogLevel) -> Level { + match level { + LogLevel::Trace => Level::Trace, + LogLevel::Debug => Level::Debug, + LogLevel::Info => Level::Info, + LogLevel::Warn => Level::Warn, + LogLevel::Error => Level::Error, + } +} + +// Rc<...> is to avoid needing to take ownership of the Client during our async run_command +// function https://github.com/rustwasm/wasm-bindgen/issues/2195#issuecomment-799588401 +#[wasm_bindgen] +pub struct BitwardenClient(pub(crate) Rc); + +#[wasm_bindgen] +impl BitwardenClient { + #[wasm_bindgen(constructor)] + pub fn new(settings: Option, log_level: Option) -> Self { + console_error_panic_hook::set_once(); + let log_level = convert_level(log_level.unwrap_or(LogLevel::Info)); + if let Err(_e) = console_log::init_with_level(log_level) { + set_max_level(log_level.to_level_filter()) + } + + Self(Rc::new(Client::new(settings))) + } + + /// Test method, echoes back the input + pub fn echo(&self, msg: String) -> String { + msg + } + + /// Test method, calls http endpoint + pub async fn http_get(&self, url: String) -> Result { + let client = self.0.internal.get_http_client(); + let res = client.get(&url).send().await.map_err(|e| e.to_string())?; + + res.text().await.map_err(|e| e.to_string()) + } + + pub fn crypto(&self) -> ClientCrypto { + ClientCrypto::new(self.0.clone()) + } + + pub fn vault(&self) -> ClientVault { + ClientVault::new(self.0.clone()) + } +} diff --git a/crates/bitwarden-wasm-internal/src/crypto.rs b/crates/bitwarden-wasm-internal/src/crypto.rs new file mode 100644 index 000000000..f1b581964 --- /dev/null +++ b/crates/bitwarden-wasm-internal/src/crypto.rs @@ -0,0 +1,33 @@ +use std::rc::Rc; + +use bitwarden_core::{ + mobile::crypto::{InitOrgCryptoRequest, InitUserCryptoRequest}, + Client, +}; +use wasm_bindgen::prelude::*; + +use crate::error::Result; + +#[wasm_bindgen] +pub struct ClientCrypto(Rc); + +impl ClientCrypto { + pub fn new(client: Rc) -> Self { + Self(client) + } +} + +#[wasm_bindgen] +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.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.crypto().initialize_org_crypto(req).await?) + } +} diff --git a/crates/bitwarden-wasm-internal/src/custom_types.rs b/crates/bitwarden-wasm-internal/src/custom_types.rs new file mode 100644 index 000000000..8d20bf85d --- /dev/null +++ b/crates/bitwarden-wasm-internal/src/custom_types.rs @@ -0,0 +1,22 @@ +/// This file contains custom TypeScript for types defined by external crates. +/// Everything in the string below is appended to the generated TypeScript definition file. +#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)] +const TS_CUSTOM_TYPES: &'static str = r#" +export type Uuid = string; + +/** + * RFC3339 compliant date-time string. + * @typeParam T - Not used in JavaScript. + */ +export type DateTime = string; + +/** + * UTC date-time string. Not used in JavaScript. + */ +export type Utc = unknown; + +/** + * An integer that is known not to equal zero. + */ +export type NonZeroU32 = number; +"#; diff --git a/crates/bitwarden-wasm-internal/src/error.js b/crates/bitwarden-wasm-internal/src/error.js new file mode 100644 index 000000000..cd3d3a346 --- /dev/null +++ b/crates/bitwarden-wasm-internal/src/error.js @@ -0,0 +1,13 @@ +/** + * Error thrown by the WASM module. + * @param {string} message - Error message. + * @extends Error + */ +class WasmError extends Error { + constructor(message) { + super(message); + this.name = "WasmError"; + } +} + +exports.WasmError = WasmError; diff --git a/crates/bitwarden-wasm-internal/src/error.rs b/crates/bitwarden-wasm-internal/src/error.rs new file mode 100644 index 000000000..2750d2629 --- /dev/null +++ b/crates/bitwarden-wasm-internal/src/error.rs @@ -0,0 +1,27 @@ +use wasm_bindgen::prelude::*; + +// Importing an error class defined in JavaScript instead of defining it in Rust +// allows us to extend the `Error` class. It also provides much better console output. +#[wasm_bindgen(module = "/src/error.js")] +extern "C" { + type WasmError; + + #[wasm_bindgen(constructor)] + fn new(message: String) -> WasmError; +} + +pub type Result = std::result::Result; + +pub struct GenericError(pub String); + +impl From for GenericError { + fn from(error: T) -> Self { + GenericError(error.to_string()) + } +} + +impl From for JsValue { + fn from(error: GenericError) -> Self { + WasmError::new(error.0).into() + } +} diff --git a/crates/bitwarden-wasm-internal/src/lib.rs b/crates/bitwarden-wasm-internal/src/lib.rs new file mode 100644 index 000000000..6367ff317 --- /dev/null +++ b/crates/bitwarden-wasm-internal/src/lib.rs @@ -0,0 +1,9 @@ +mod client; +mod crypto; +mod custom_types; +mod error; +mod vault; + +pub use client::BitwardenClient; +pub use crypto::ClientCrypto; +pub use vault::{folders::ClientFolders, ClientVault}; diff --git a/crates/bitwarden-wasm-internal/src/vault/folders.rs b/crates/bitwarden-wasm-internal/src/vault/folders.rs new file mode 100644 index 000000000..652892694 --- /dev/null +++ b/crates/bitwarden-wasm-internal/src/vault/folders.rs @@ -0,0 +1,24 @@ +use std::rc::Rc; + +use bitwarden_core::Client; +use bitwarden_vault::{ClientVaultExt, Folder, FolderView}; +use wasm_bindgen::prelude::*; + +use crate::error::Result; + +#[wasm_bindgen] +pub struct ClientFolders(Rc); + +impl ClientFolders { + pub fn new(client: Rc) -> Self { + Self(client) + } +} + +#[wasm_bindgen] +impl ClientFolders { + /// Decrypt folder + pub fn decrypt(&self, folder: Folder) -> Result { + Ok(self.0.vault().folders().decrypt(folder)?) + } +} diff --git a/crates/bitwarden-wasm-internal/src/vault/mod.rs b/crates/bitwarden-wasm-internal/src/vault/mod.rs new file mode 100644 index 000000000..11db0e130 --- /dev/null +++ b/crates/bitwarden-wasm-internal/src/vault/mod.rs @@ -0,0 +1,24 @@ +pub mod folders; + +use std::rc::Rc; + +use bitwarden_core::Client; +use wasm_bindgen::prelude::*; + +use crate::ClientFolders; + +#[wasm_bindgen] +pub struct ClientVault(Rc); + +impl ClientVault { + pub fn new(client: Rc) -> Self { + Self(client) + } +} + +#[wasm_bindgen] +impl ClientVault { + pub fn folders(&self) -> ClientFolders { + ClientFolders::new(self.0.clone()) + } +} diff --git a/crates/bitwarden-wasm/Cargo.toml b/crates/bitwarden-wasm/Cargo.toml index a4ba8b6ae..b57a0192e 100644 --- a/crates/bitwarden-wasm/Cargo.toml +++ b/crates/bitwarden-wasm/Cargo.toml @@ -19,20 +19,26 @@ argon2 = { version = ">=0.5.0, <0.6", features = [ "alloc", "zeroize", ], default-features = false } -bitwarden-json = { path = "../bitwarden-json", features = [ - "secrets", - "internal", -] } +bitwarden-json = { path = "../bitwarden-json", features = ["secrets"] } console_error_panic_hook = "0.1.7" console_log = { version = "1.0.0", features = ["color"] } js-sys = "0.3.68" log = "0.4.20" serde = { version = "1.0.196", features = ["derive"] } -wasm-bindgen = { version = "0.2.91", features = ["serde-serialize"] } +# When upgrading wasm-bindgen, make sure to update the version in the workflows! +wasm-bindgen = { version = "=0.2.95", features = ["serde-serialize"] } wasm-bindgen-futures = "0.4.41" [dev-dependencies] wasm-bindgen-test = "0.3.41" +[target.'cfg(target_arch = "wasm32")'.dependencies] +chrono = { version = ">=0.4.26, <0.5", features = [ + "clock", + "serde", + "std", + "wasmbind", +], default-features = false } + [lints] workspace = true diff --git a/crates/bitwarden-wasm/build.sh b/crates/bitwarden-wasm/build.sh index ae32c775d..d49b12de3 100755 --- a/crates/bitwarden-wasm/build.sh +++ b/crates/bitwarden-wasm/build.sh @@ -4,12 +4,12 @@ cd ../../ if [ "$1" != "-r" ]; then # Dev - cargo build -p bitwarden -p bitwarden-wasm --target wasm32-unknown-unknown --features wasm-bindgen + cargo build -p bitwarden-wasm --target wasm32-unknown-unknown 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 + cargo build -p bitwarden-wasm --target wasm32-unknown-unknown --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 diff --git a/crates/bitwarden-wasm/src/client.rs b/crates/bitwarden-wasm/src/client.rs index bca8c2383..e130705c1 100644 --- a/crates/bitwarden-wasm/src/client.rs +++ b/crates/bitwarden-wasm/src/client.rs @@ -4,7 +4,7 @@ use std::rc::Rc; use argon2::{Algorithm, Argon2, Params, Version}; use bitwarden_json::client::Client as JsonClient; use js_sys::Promise; -use log::Level; +use log::{set_max_level, Level}; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::future_to_promise; @@ -37,10 +37,9 @@ impl BitwardenClient { #[wasm_bindgen(constructor)] pub fn new(settings_input: Option, log_level: Option) -> Self { console_error_panic_hook::set_once(); - if let Err(e) = - console_log::init_with_level(convert_level(log_level.unwrap_or(LogLevel::Info))) - { - panic!("failed to initialize logger: {:?}", e); + let log_level = convert_level(log_level.unwrap_or(LogLevel::Info)); + if let Err(_e) = console_log::init_with_level(log_level) { + set_max_level(log_level.to_level_filter()) } Self(Rc::new(bitwarden_json::client::Client::new(settings_input))) diff --git a/crates/bitwarden/CHANGELOG.md b/crates/bitwarden/CHANGELOG.md index 1d901f56f..5d3eefa95 100644 --- a/crates/bitwarden/CHANGELOG.md +++ b/crates/bitwarden/CHANGELOG.md @@ -10,10 +10,15 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Added - Support for secrets sync (#678) +- Password generator (#986) ### Changed - `ClientSettings` and `DeviceType` is now exported in the root module (#805) +- Secrets Manager now requires `bitwarden::secrets_manager::ClientSecretsExt` and + `bitwarden::secrets_manager::ClientProjectsExt` to be imported in order to access `secrets()` and + `projects` on the client (#798) +- Updated MSRV `1.75.0` (#980) ### Removed diff --git a/crates/bitwarden/Cargo.toml b/crates/bitwarden/Cargo.toml index 8bfafc462..3626f9d11 100644 --- a/crates/bitwarden/Cargo.toml +++ b/crates/bitwarden/Cargo.toml @@ -16,90 +16,24 @@ license-file.workspace = true [features] default = ["secrets"] -internal = [ - "dep:bitwarden-exporters", - "dep:bitwarden-generators", -] # Internal testing methods no-memory-hardening = [ - "bitwarden-crypto/no-memory-hardening", + "bitwarden-core/no-memory-hardening", ] # Disable memory hardening features -uniffi = [ - "bitwarden-crypto/uniffi", - "bitwarden-generators/uniffi", - "dep:uniffi", - "dep:passkey", - "dep:coset", - "dep:p256", -] # Uniffi bindings -secrets = [] # Secrets manager API -wasm-bindgen = ["chrono/wasmbind"] +secrets = [ + "bitwarden-core/secrets", + "dep:bitwarden-sm", + "dep:bitwarden-generators", +] # Secrets manager API +wasm = ["bitwarden-core/wasm"] # WASM support [dependencies] -async-trait = ">=0.1.80, <0.2" -base64 = ">=0.21.2, <0.23" -bitwarden-api-api = { workspace = true } -bitwarden-api-identity = { workspace = true } -bitwarden-crypto = { workspace = true } -bitwarden-exporters = { workspace = true, optional = true } +bitwarden-core = { workspace = true } bitwarden-generators = { workspace = true, optional = true } -chrono = { version = ">=0.4.26, <0.5", features = [ - "clock", - "serde", - "std", -], default-features = false } -coset = { version = "0.3.7", 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, <0.3", features = ["js"] } -hmac = ">=0.12.1, <0.13" -log = ">=0.4.18, <0.5" -p256 = { version = ">=0.13.2, <0.14", optional = true } -passkey = { git = "https://github.com/bitwarden/passkey-rs", rev = "c48c2ddfd6b884b2d754432576c66cb2b1985a3a", optional = true } -rand = ">=0.8.5, <0.9" -reqwest = { version = ">=0.12, <0.13", features = [ - "http2", - "json", -], default-features = false } -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.14" -serde_repr = ">=0.1.12, <0.2" -sha1 = ">=0.10.5, <0.11" -sha2 = ">=0.10.6, <0.11" -thiserror = ">=1.0.40, <2.0" -uniffi = { version = "=0.27.2", optional = true, features = ["tokio"] } -uuid = { version = ">=1.3.3, <2.0", features = ["serde"] } -zeroize = { version = ">=1.7.0, <2.0", features = ["derive", "aarch64"] } -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 -# 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 = ">=0.12, <0.13", features = [ - "rustls-tls-manual-roots", -], default-features = false } -rustls-platform-verifier = "0.2.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 = ">=0.12, <0.13", features = [ - "rustls-tls-webpki-roots", -], default-features = false } - -# This is a workaround to fix a bug with version 2.11.0 that added some symbols that are not available on iOS -# The bug is fixed already but the fix is not released yet. https://github.com/kornelski/rust-security-framework/pull/204 -[target.'cfg(target_os = "ios")'.dependencies] -security-framework = { version = "=2.10" } +bitwarden-sm = { workspace = true, optional = true } +thiserror = { workspace = true } [dev-dependencies] -bitwarden-crypto = { workspace = true, features = ["test"] } -rand_chacha = "0.3.1" -tokio = { version = "1.36.0", features = ["rt", "macros"] } -wiremock = "0.6.0" -zeroize = { version = ">=1.7.0, <2.0", features = ["derive", "aarch64"] } +uuid = { workspace = true } [lints] workspace = true diff --git a/crates/bitwarden/README.md b/crates/bitwarden/README.md index 47e1afe76..010a64627 100644 --- a/crates/bitwarden/README.md +++ b/crates/bitwarden/README.md @@ -13,17 +13,16 @@ bitwarden = { "*", features = ["secrets"] } ## Minimum Supported Rust Version -Rust **1.71** or higher. +Rust **1.75** or higher. ## Example ```rust use bitwarden::{ auth::login::AccessTokenLoginRequest, - client::client_settings::{ClientSettings, DeviceType}, error::Result, - secrets_manager::secrets::SecretIdentifiersRequest, - Client, + secrets_manager::{secrets::SecretIdentifiersRequest, ClientSecretsExt}, + Client, ClientSettings, DeviceType, }; use uuid::Uuid; diff --git a/crates/bitwarden/src/client/client.rs b/crates/bitwarden/src/client/client.rs deleted file mode 100644 index b30680654..000000000 --- a/crates/bitwarden/src/client/client.rs +++ /dev/null @@ -1,299 +0,0 @@ -use std::path::PathBuf; - -#[cfg(feature = "internal")] -pub use bitwarden_crypto::Kdf; -use bitwarden_crypto::SymmetricCryptoKey; -#[cfg(feature = "internal")] -use bitwarden_crypto::{AsymmetricEncString, EncString, MasterKey}; -use chrono::Utc; -use reqwest::header::{self, HeaderValue}; -use uuid::Uuid; - -#[cfg(feature = "internal")] -use crate::client::flags::Flags; -use crate::{ - auth::AccessToken, - client::{ - client_settings::{ClientSettings, DeviceType}, - encryption_settings::EncryptionSettings, - }, - error::{Error, Result}, -}; - -#[derive(Debug)] -pub(crate) struct ApiConfigurations { - pub identity: bitwarden_api_identity::apis::configuration::Configuration, - pub api: bitwarden_api_api::apis::configuration::Configuration, - /// Reqwest client useable for external integrations like email forwarders, HIBP. - #[allow(unused)] - pub external_client: reqwest::Client, - pub device_type: DeviceType, -} - -#[derive(Debug)] -pub(crate) enum LoginMethod { - #[cfg(feature = "internal")] - User(UserLoginMethod), - // TODO: Organizations supports api key - // Organization(OrganizationLoginMethod), - ServiceAccount(ServiceAccountLoginMethod), -} - -#[derive(Debug)] -#[cfg(feature = "internal")] -pub(crate) enum UserLoginMethod { - Username { - client_id: String, - email: String, - kdf: Kdf, - }, - ApiKey { - client_id: String, - client_secret: String, - - email: String, - kdf: Kdf, - }, -} - -#[derive(Debug)] -pub(crate) enum ServiceAccountLoginMethod { - AccessToken { - access_token: AccessToken, - organization_id: Uuid, - state_file: Option, - }, -} - -/// The main struct to interact with the Bitwarden SDK. -#[derive(Debug)] -pub struct Client { - token: Option, - pub(crate) refresh_token: Option, - pub(crate) token_expires_on: Option, - pub(crate) login_method: Option, - - #[cfg(feature = "internal")] - flags: Flags, - - /// Use Client::get_api_configurations() to access this. - /// It should only be used directly in renew_token - #[doc(hidden)] - pub(crate) __api_configurations: ApiConfigurations, - - encryption_settings: Option, -} - -impl Client { - pub fn new(settings_input: Option) -> Self { - let settings = settings_input.unwrap_or_default(); - - fn new_client_builder() -> reqwest::ClientBuilder { - #[allow(unused_mut)] - let mut client_builder = reqwest::Client::builder(); - - #[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))] - { - client_builder = - client_builder.use_preconfigured_tls(rustls_platform_verifier::tls_config()); - } - - client_builder - } - - let external_client = new_client_builder().build().expect("Build should not fail"); - - let mut headers = header::HeaderMap::new(); - headers.append( - "Device-Type", - HeaderValue::from_str(&(settings.device_type as u8).to_string()) - .expect("All numbers are valid ASCII"), - ); - let client_builder = new_client_builder().default_headers(headers); - - let client = client_builder.build().expect("Build should not fail"); - - let identity = bitwarden_api_identity::apis::configuration::Configuration { - base_path: settings.identity_url, - user_agent: Some(settings.user_agent.clone()), - client: client.clone(), - basic_auth: None, - oauth_access_token: None, - bearer_access_token: None, - api_key: None, - }; - - let api = bitwarden_api_api::apis::configuration::Configuration { - base_path: settings.api_url, - user_agent: Some(settings.user_agent), - client, - basic_auth: None, - oauth_access_token: None, - bearer_access_token: None, - api_key: None, - }; - - Self { - token: None, - refresh_token: None, - token_expires_on: None, - login_method: None, - #[cfg(feature = "internal")] - flags: Flags::default(), - __api_configurations: ApiConfigurations { - identity, - api, - external_client, - device_type: settings.device_type, - }, - encryption_settings: None, - } - } - - #[cfg(feature = "internal")] - pub fn load_flags(&mut self, flags: std::collections::HashMap) { - self.flags = Flags::load_from_map(flags); - } - - #[cfg(feature = "internal")] - pub(crate) fn get_flags(&self) -> &Flags { - &self.flags - } - - pub(crate) async fn get_api_configurations(&mut self) -> &ApiConfigurations { - // At the moment we ignore the error result from the token renewal, if it fails, - // the token will end up expiring and the next operation is going to fail anyway. - self.auth().renew_token().await.ok(); - &self.__api_configurations - } - - #[cfg(feature = "internal")] - pub(crate) fn get_http_client(&self) -> &reqwest::Client { - &self.__api_configurations.external_client - } - - #[cfg(feature = "internal")] - pub(crate) fn get_login_method(&self) -> &Option { - &self.login_method - } - - pub fn get_access_token_organization(&self) -> Option { - match self.login_method { - Some(LoginMethod::ServiceAccount(ServiceAccountLoginMethod::AccessToken { - organization_id, - .. - })) => Some(organization_id), - _ => None, - } - } - - pub(crate) fn get_encryption_settings(&self) -> Result<&EncryptionSettings> { - self.encryption_settings.as_ref().ok_or(Error::VaultLocked) - } - - pub(crate) fn set_login_method(&mut self, login_method: LoginMethod) { - use log::debug; - - debug! {"setting login method: {:#?}", login_method} - self.login_method = Some(login_method); - } - - pub(crate) fn set_tokens( - &mut self, - token: String, - refresh_token: Option, - expires_in: u64, - ) { - self.token = Some(token.clone()); - self.refresh_token = refresh_token; - 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); - } - - #[cfg(feature = "internal")] - pub fn is_authed(&self) -> bool { - self.token.is_some() || self.login_method.is_some() - } - - #[cfg(feature = "internal")] - pub(crate) fn initialize_user_crypto_master_key( - &mut self, - master_key: MasterKey, - user_key: EncString, - private_key: EncString, - ) -> Result<&EncryptionSettings> { - Ok(self.encryption_settings.insert(EncryptionSettings::new( - master_key, - user_key, - private_key, - )?)) - } - - #[cfg(feature = "internal")] - pub(crate) fn initialize_user_crypto_decrypted_key( - &mut self, - user_key: SymmetricCryptoKey, - private_key: EncString, - ) -> Result<&EncryptionSettings> { - Ok(self - .encryption_settings - .insert(EncryptionSettings::new_decrypted_key( - user_key, - private_key, - )?)) - } - - #[cfg(feature = "internal")] - pub(crate) fn initialize_user_crypto_pin( - &mut self, - pin_key: MasterKey, - pin_protected_user_key: EncString, - private_key: EncString, - ) -> Result<&EncryptionSettings> { - 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, - ) -> &EncryptionSettings { - self.encryption_settings - .insert(EncryptionSettings::new_single_key(key)) - } - - #[cfg(feature = "internal")] - pub(crate) fn initialize_org_crypto( - &mut self, - org_keys: Vec<(Uuid, AsymmetricEncString)>, - ) -> Result<&EncryptionSettings> { - let enc = self - .encryption_settings - .as_mut() - .ok_or(Error::VaultLocked)?; - - enc.set_org_keys(org_keys)?; - Ok(&*enc) - } -} - -#[cfg(test)] -mod tests { - #[test] - fn test_reqwest_rustls_platform_verifier_are_compatible() { - // rustls-platform-verifier is generating a rustls::ClientConfig, - // which reqwest accepts as a &dyn Any and then downcasts it to a - // rustls::ClientConfig. - - // This means that if the rustls version of the two crates don't match, - // the downcast will fail and we will get a runtime error. - - // This tests is added to ensure that it doesn't happen. - - let _ = reqwest::ClientBuilder::new() - .use_preconfigured_tls(rustls_platform_verifier::tls_config()) - .build() - .unwrap(); - } -} diff --git a/crates/bitwarden/src/client/mod.rs b/crates/bitwarden/src/client/mod.rs deleted file mode 100644 index 1568152e4..000000000 --- a/crates/bitwarden/src/client/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -//! Bitwarden SDK Client - -pub(crate) use client::*; -#[allow(clippy::module_inception)] -mod client; -pub mod client_settings; -pub(crate) mod encryption_settings; - -#[cfg(feature = "internal")] -mod flags; - -pub use client::Client; -pub use client_settings::{ClientSettings, DeviceType}; - -#[cfg(feature = "internal")] -#[cfg(test)] -pub(crate) mod test_accounts; diff --git a/crates/bitwarden/src/error.rs b/crates/bitwarden/src/error.rs index bc06a7591..163ccc208 100644 --- a/crates/bitwarden/src/error.rs +++ b/crates/bitwarden/src/error.rs @@ -1,126 +1,13 @@ //! Errors that can occur when using this SDK -use std::{borrow::Cow, fmt::Debug}; +use std::fmt::Debug; -use bitwarden_api_api::apis::Error as ApiError; -use bitwarden_api_identity::apis::Error as IdentityError; -#[cfg(feature = "internal")] -use bitwarden_exporters::ExportError; -#[cfg(feature = "internal")] -use bitwarden_generators::{PassphraseError, PasswordError, UsernameError}; -#[cfg(feature = "uniffi")] -use passkey::client::WebauthnError; -use reqwest::StatusCode; use thiserror::Error; #[derive(Debug, Error)] pub enum Error { - #[error("The client is not authenticated or the session has expired")] - NotAuthenticated, - - #[error("The client vault is locked and needs to be unlocked before use")] - VaultLocked, - - #[error("Access token is not in a valid format: {0}")] - AccessTokenInvalid(#[from] AccessTokenInvalidError), - - #[error("The response received was invalid and could not be processed")] - InvalidResponse, - #[error("The response received was missing some of the required fields: {0}")] - MissingFields(&'static str), - - #[error("Cryptography error, {0}")] - Crypto(#[from] bitwarden_crypto::CryptoError), - - #[error("Error parsing Identity response: {0}")] - IdentityFail(crate::auth::api::response::IdentityTokenFailResponse), - #[error(transparent)] - Reqwest(#[from] reqwest::Error), - #[error(transparent)] - Serde(#[from] serde_json::Error), - #[error(transparent)] - Io(#[from] std::io::Error), - #[error(transparent)] - InvalidBase64(#[from] base64::DecodeError), - #[error(transparent)] - Chrono(#[from] chrono::ParseError), - - #[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, - - // Generators - #[cfg(feature = "internal")] - #[error(transparent)] - UsernameError(#[from] UsernameError), - #[cfg(feature = "internal")] - #[error(transparent)] - PassphraseError(#[from] PassphraseError), - #[cfg(feature = "internal")] - #[error(transparent)] - PasswordError(#[from] PasswordError), - - #[cfg(feature = "internal")] - #[error(transparent)] - ExportError(#[from] ExportError), - - #[cfg(feature = "uniffi")] - #[error("Webauthn error: {0:?}")] - WebauthnError(WebauthnError), - - #[cfg(feature = "uniffi")] - #[error("Uniffi callback error: {0}")] - UniffiCallbackError(#[from] uniffi::UnexpectedUniFFICallbackError), - - #[cfg(feature = "uniffi")] - #[error("Fido2 Callback error: {0:?}")] - Fido2CallbackError(#[from] crate::platform::fido2::Fido2CallbackError), - - #[error("Internal error: {0}")] - Internal(Cow<'static, str>), -} - -#[cfg(feature = "uniffi")] -impl From for Error { - fn from(e: WebauthnError) -> Self { - Self::WebauthnError(e) - } -} - -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)] -pub enum AccessTokenInvalidError { - #[error("Doesn't contain a decryption key")] - NoKey, - #[error("Has the wrong number of parts")] - WrongParts, - #[error("Is the wrong version")] - WrongVersion, - #[error("Has an invalid identifier")] - InvalidUuid, - - #[error("Error decoding base64: {0}")] - InvalidBase64(#[from] base64::DecodeError), - - #[error("Invalid base64 length: expected {expected}, got {got}")] - InvalidBase64Length { expected: usize, got: usize }, + Core(#[from] bitwarden_core::Error), } // Ensure that the error messages implement Send and Sync @@ -134,38 +21,4 @@ const _: () = { } }; -macro_rules! impl_bitwarden_error { - ($name:ident) => { - impl From<$name> for Error { - fn from(e: $name) -> Self { - match e { - $name::Reqwest(e) => Self::Reqwest(e), - $name::ResponseError(e) => Self::ResponseContent { - status: e.status, - message: e.content, - }, - $name::Serde(e) => Self::Serde(e), - $name::Io(e) => Self::Io(e), - } - } - } - }; -} -impl_bitwarden_error!(ApiError); -impl_bitwarden_error!(IdentityError); - -/// This macro is used to require that a value is present or return an error otherwise. -/// It is equivalent to using `val.ok_or(Error::MissingFields)?`, but easier to use and -/// with a more descriptive error message. -/// Note that this macro will return early from the function if the value is not present. -macro_rules! require { - ($val:expr) => { - match $val { - Some(val) => val, - None => return Err($crate::error::Error::MissingFields(stringify!($val))), - } - }; -} -pub(crate) use require; - pub type Result = std::result::Result; diff --git a/crates/bitwarden/src/lib.rs b/crates/bitwarden/src/lib.rs index b5506485f..77a063f52 100644 --- a/crates/bitwarden/src/lib.rs +++ b/crates/bitwarden/src/lib.rs @@ -16,8 +16,10 @@ //! //! ```rust //! use bitwarden::{ -//! auth::login::AccessTokenLoginRequest, error::Result, -//! secrets_manager::secrets::SecretIdentifiersRequest, Client, ClientSettings, DeviceType, +//! auth::login::AccessTokenLoginRequest, +//! error::Result, +//! secrets_manager::{secrets::SecretIdentifiersRequest, ClientSecretsExt}, +//! Client, ClientSettings, DeviceType, //! }; //! use uuid::Uuid; //! @@ -56,35 +58,15 @@ #[doc = include_str!("../README.md")] mod readme {} -#[cfg(feature = "uniffi")] -uniffi::setup_scaffolding!(); - -#[cfg(feature = "internal")] -pub mod admin_console; -pub mod auth; -pub mod client; +pub use bitwarden_core::*; pub mod error; -#[cfg(feature = "internal")] -pub mod mobile; -#[cfg(feature = "internal")] -pub mod platform; -#[cfg(feature = "secrets")] -pub mod secrets_manager; -#[cfg(feature = "internal")] -pub mod tool; -#[cfg(feature = "uniffi")] -pub(crate) mod uniffi_support; -mod util; -#[cfg(feature = "internal")] -pub mod vault; - -pub use client::{Client, ClientSettings, DeviceType}; -#[cfg(feature = "internal")] +#[cfg(feature = "secrets")] pub mod generators { - pub use bitwarden_generators::{ - PassphraseGeneratorRequest, PasswordGeneratorRequest, UsernameGeneratorRequest, - }; + pub use bitwarden_generators::{ClientGeneratorExt, PasswordError, PasswordGeneratorRequest}; } -pub use bitwarden_crypto::ZeroizingAllocator; +#[cfg(feature = "secrets")] +pub mod secrets_manager { + pub use bitwarden_sm::*; +} diff --git a/crates/bitwarden/src/mobile/client_crypto.rs b/crates/bitwarden/src/mobile/client_crypto.rs deleted file mode 100644 index 6ef65975d..000000000 --- a/crates/bitwarden/src/mobile/client_crypto.rs +++ /dev/null @@ -1,66 +0,0 @@ -#[cfg(feature = "internal")] -use bitwarden_crypto::{AsymmetricEncString, EncString}; - -use crate::Client; -#[cfg(feature = "internal")] -use crate::{ - error::Result, - mobile::crypto::{ - derive_pin_key, derive_pin_user_key, enroll_admin_password_reset, get_user_encryption_key, - initialize_org_crypto, initialize_user_crypto, update_password, DerivePinKeyResponse, - InitOrgCryptoRequest, InitUserCryptoRequest, UpdatePasswordResponse, - }, -}; - -pub struct ClientCrypto<'a> { - pub(crate) client: &'a mut crate::Client, -} - -impl<'a> ClientCrypto<'a> { - #[cfg(feature = "internal")] - 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 - } - - #[cfg(feature = "internal")] - pub async fn get_user_encryption_key(&mut self) -> Result { - get_user_encryption_key(self.client).await - } - - #[cfg(feature = "internal")] - pub async fn update_password( - &mut self, - new_password: String, - ) -> Result { - update_password(self.client, new_password) - } - - #[cfg(feature = "internal")] - pub async fn derive_pin_key(&mut self, pin: String) -> Result { - derive_pin_key(self.client, pin) - } - - #[cfg(feature = "internal")] - pub async fn derive_pin_user_key(&mut self, encrypted_pin: EncString) -> Result { - derive_pin_user_key(self.client, encrypted_pin) - } - - #[cfg(feature = "internal")] - pub fn enroll_admin_password_reset( - &mut self, - public_key: String, - ) -> Result { - enroll_admin_password_reset(self.client, public_key) - } -} - -impl<'a> Client { - pub fn crypto(&'a mut self) -> ClientCrypto<'a> { - ClientCrypto { client: self } - } -} diff --git a/crates/bitwarden/src/mobile/tool/client_sends.rs b/crates/bitwarden/src/mobile/tool/client_sends.rs deleted file mode 100644 index f98027974..000000000 --- a/crates/bitwarden/src/mobile/tool/client_sends.rs +++ /dev/null @@ -1,93 +0,0 @@ -use std::path::Path; - -use bitwarden_crypto::{EncString, KeyDecryptable, KeyEncryptable}; - -use crate::{ - error::{Error, Result}, - tool::{Send, SendListView, SendView}, - Client, -}; - -pub struct ClientSends<'a> { - pub(crate) client: &'a Client, -} - -impl<'a> ClientSends<'a> { - pub async fn decrypt(&self, send: Send) -> Result { - let enc = self.client.get_encryption_settings()?; - let key = enc.get_key(&None).ok_or(Error::VaultLocked)?; - - let send_view = send.decrypt_with_key(key)?; - - Ok(send_view) - } - - pub async fn decrypt_list(&self, sends: Vec) -> Result> { - let enc = self.client.get_encryption_settings()?; - let key = enc.get_key(&None).ok_or(Error::VaultLocked)?; - - let send_views = sends.decrypt_with_key(key)?; - - Ok(send_views) - } - - pub async fn decrypt_file( - &self, - send: Send, - encrypted_file_path: &Path, - decrypted_file_path: &Path, - ) -> Result<()> { - let data = std::fs::read(encrypted_file_path)?; - let decrypted = self.decrypt_buffer(send, &data).await?; - std::fs::write(decrypted_file_path, decrypted)?; - Ok(()) - } - - pub async fn decrypt_buffer(&self, send: Send, encrypted_buffer: &[u8]) -> Result> { - let enc = self.client.get_encryption_settings()?; - let key = enc.get_key(&None).ok_or(Error::VaultLocked)?; - let key = Send::get_key(&send.key, key)?; - - let buf = EncString::from_buffer(encrypted_buffer)?; - Ok(buf.decrypt_with_key(&key)?) - } - - pub async fn encrypt(&self, send_view: SendView) -> Result { - let enc = self.client.get_encryption_settings()?; - let key = enc.get_key(&None).ok_or(Error::VaultLocked)?; - - let send = send_view.encrypt_with_key(key)?; - - Ok(send) - } - - pub async fn encrypt_file( - &self, - send: Send, - decrypted_file_path: &Path, - encrypted_file_path: &Path, - ) -> Result<()> { - let data = std::fs::read(decrypted_file_path)?; - let encrypted = self.encrypt_buffer(send, &data).await?; - std::fs::write(encrypted_file_path, encrypted)?; - Ok(()) - } - - pub async fn encrypt_buffer(&self, send: Send, buffer: &[u8]) -> Result> { - let key = self - .client - .get_encryption_settings()? - .get_key(&None) - .ok_or(Error::VaultLocked)?; - let key = Send::get_key(&send.key, key)?; - - let enc = buffer.encrypt_with_key(&key)?; - Ok(enc.to_buffer()?) - } -} - -impl<'a> Client { - pub fn sends(&'a mut self) -> ClientSends<'a> { - ClientSends { client: self } - } -} diff --git a/crates/bitwarden/src/mobile/tool/mod.rs b/crates/bitwarden/src/mobile/tool/mod.rs deleted file mode 100644 index 729697715..000000000 --- a/crates/bitwarden/src/mobile/tool/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod client_sends; -pub use client_sends::ClientSends; diff --git a/crates/bitwarden/src/mobile/vault/client_folders.rs b/crates/bitwarden/src/mobile/vault/client_folders.rs deleted file mode 100644 index 9d396d9e1..000000000 --- a/crates/bitwarden/src/mobile/vault/client_folders.rs +++ /dev/null @@ -1,48 +0,0 @@ -use bitwarden_crypto::{CryptoError, KeyDecryptable, KeyEncryptable}; - -use crate::{ - error::Result, - vault::{ClientVault, Folder, FolderView}, - Client, -}; - -pub struct ClientFolders<'a> { - pub(crate) client: &'a Client, -} - -impl<'a> ClientFolders<'a> { - pub async fn encrypt(&self, folder_view: FolderView) -> Result { - let enc = self.client.get_encryption_settings()?; - let key = enc.get_key(&None).ok_or(CryptoError::MissingKey)?; - - let folder = folder_view.encrypt_with_key(key)?; - - Ok(folder) - } - - pub async fn decrypt(&self, folder: Folder) -> Result { - let enc = self.client.get_encryption_settings()?; - let key = enc.get_key(&None).ok_or(CryptoError::MissingKey)?; - - let folder_view = folder.decrypt_with_key(key)?; - - Ok(folder_view) - } - - pub async fn decrypt_list(&self, folders: Vec) -> Result> { - let enc = self.client.get_encryption_settings()?; - let key = enc.get_key(&None).ok_or(CryptoError::MissingKey)?; - - let views = folders.decrypt_with_key(key)?; - - Ok(views) - } -} - -impl<'a> ClientVault<'a> { - pub fn folders(&'a self) -> ClientFolders<'a> { - ClientFolders { - client: self.client, - } - } -} diff --git a/crates/bitwarden/src/mobile/vault/client_password_history.rs b/crates/bitwarden/src/mobile/vault/client_password_history.rs deleted file mode 100644 index 734969127..000000000 --- a/crates/bitwarden/src/mobile/vault/client_password_history.rs +++ /dev/null @@ -1,42 +0,0 @@ -use bitwarden_crypto::{CryptoError, KeyDecryptable, KeyEncryptable}; - -use crate::{ - error::Result, - vault::{ClientVault, PasswordHistory, PasswordHistoryView}, - Client, -}; - -pub struct ClientPasswordHistory<'a> { - pub(crate) client: &'a Client, -} - -impl<'a> ClientPasswordHistory<'a> { - pub async fn encrypt(&self, history_view: PasswordHistoryView) -> Result { - let enc = self.client.get_encryption_settings()?; - let key = enc.get_key(&None).ok_or(CryptoError::MissingKey)?; - - let history = history_view.encrypt_with_key(key)?; - - Ok(history) - } - - pub async fn decrypt_list( - &self, - history: Vec, - ) -> Result> { - let enc = self.client.get_encryption_settings()?; - let key = enc.get_key(&None).ok_or(CryptoError::MissingKey)?; - - let history_view = history.decrypt_with_key(key)?; - - Ok(history_view) - } -} - -impl<'a> ClientVault<'a> { - pub fn password_history(&'a self) -> ClientPasswordHistory<'a> { - ClientPasswordHistory { - client: self.client, - } - } -} diff --git a/crates/bitwarden/src/mobile/vault/client_totp.rs b/crates/bitwarden/src/mobile/vault/client_totp.rs deleted file mode 100644 index 00a7ef6aa..000000000 --- a/crates/bitwarden/src/mobile/vault/client_totp.rs +++ /dev/null @@ -1,22 +0,0 @@ -use chrono::{DateTime, Utc}; - -use crate::{ - error::Result, - vault::{generate_totp, ClientVault, TotpResponse}, -}; - -impl<'a> ClientVault<'a> { - /// Generate a TOTP code from a provided key. - /// - /// Key can be either: - /// - A base32 encoded string - /// - OTP Auth URI - /// - Steam URI - pub fn generate_totp( - &'a self, - key: String, - time: Option>, - ) -> Result { - generate_totp(key, time) - } -} diff --git a/crates/bitwarden/src/mobile/vault/mod.rs b/crates/bitwarden/src/mobile/vault/mod.rs deleted file mode 100644 index 18b96eb50..000000000 --- a/crates/bitwarden/src/mobile/vault/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -mod client_attachments; -mod client_ciphers; -mod client_collection; -mod client_folders; -mod client_password_history; -mod client_totp; - -pub use client_attachments::ClientAttachments; -pub use client_ciphers::ClientCiphers; -pub use client_collection::ClientCollections; -pub use client_folders::ClientFolders; -pub use client_password_history::ClientPasswordHistory; diff --git a/crates/bitwarden/src/platform/fido2/crypto.rs b/crates/bitwarden/src/platform/fido2/crypto.rs deleted file mode 100644 index a7fb5cff1..000000000 --- a/crates/bitwarden/src/platform/fido2/crypto.rs +++ /dev/null @@ -1,37 +0,0 @@ -use coset::{ - iana::{self}, - CoseKey, -}; -use p256::{pkcs8::EncodePrivateKey, SecretKey}; -use passkey::authenticator::{private_key_from_cose_key, CoseKeyPair}; - -use crate::error::{Error, Result}; - -pub fn cose_key_to_pkcs8(cose_key: &CoseKey) -> Result> { - // cose_key. - let secret_key = private_key_from_cose_key(cose_key).map_err(|error| { - log::error!("Failed to extract private key from cose_key: {:?}", error); - Error::Internal("Failed to extract private key from cose_key".into()) - })?; - - let vec = secret_key - .to_pkcs8_der() - .map_err(|error| { - log::error!("Failed to convert P256 private key to PKC8: {:?}", error); - Error::Internal("Failed to convert P256 private key to PKC8".into()) - })? - .as_bytes() - .to_vec(); - - Ok(vec) -} - -pub fn pkcs8_to_cose_key(secret_key: &[u8]) -> Result { - let secret_key = SecretKey::from_slice(secret_key).map_err(|error| { - log::error!("Failed to extract private key from secret_key: {:?}", error); - Error::Internal("Failed to extract private key from secret_key".into()) - })?; - - let cose_key_pair = CoseKeyPair::from_secret_key(&secret_key, iana::Algorithm::ES256); - Ok(cose_key_pair.private) -} diff --git a/crates/bitwarden/src/platform/fido2/mod.rs b/crates/bitwarden/src/platform/fido2/mod.rs deleted file mode 100644 index 099ce4fa3..000000000 --- a/crates/bitwarden/src/platform/fido2/mod.rs +++ /dev/null @@ -1,276 +0,0 @@ -use std::sync::Mutex; - -use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; -use bitwarden_crypto::KeyContainer; -use passkey::types::{ctap2::Aaguid, Passkey}; - -mod authenticator; -mod client; -mod crypto; -mod traits; -mod types; - -pub use authenticator::Fido2Authenticator; -pub use client::Fido2Client; -pub use passkey::authenticator::UIHint; -pub use traits::{ - CheckUserOptions, CheckUserResult, Fido2CallbackError, Fido2CredentialStore, - Fido2UserInterface, Verification, -}; -pub use types::{ - AuthenticatorAssertionResponse, AuthenticatorAttestationResponse, ClientData, - GetAssertionRequest, GetAssertionResult, MakeCredentialRequest, MakeCredentialResult, Options, - PublicKeyCredentialAuthenticatorAssertionResponse, - PublicKeyCredentialAuthenticatorAttestationResponse, PublicKeyCredentialRpEntity, - PublicKeyCredentialUserEntity, -}; - -use self::crypto::{cose_key_to_pkcs8, pkcs8_to_cose_key}; -use crate::{ - error::{Error, Result}, - vault::{CipherView, Fido2CredentialFullView, Fido2CredentialNewView, Fido2CredentialView}, - Client, -}; - -// This is the AAGUID for the Bitwarden Passkey provider (d548826e-79b4-db40-a3d8-11116f7e8349) -// It is used for the Relaying Parties to identify the authenticator during registration -const AAGUID: Aaguid = Aaguid([ - 0xd5, 0x48, 0x82, 0x6e, 0x79, 0xb4, 0xdb, 0x40, 0xa3, 0xd8, 0x11, 0x11, 0x6f, 0x7e, 0x83, 0x49, -]); - -pub struct ClientFido2<'a> { - #[allow(dead_code)] - pub(crate) client: &'a mut Client, -} - -impl<'a> ClientFido2<'a> { - pub fn create_authenticator( - &'a mut self, - - user_interface: &'a dyn Fido2UserInterface, - credential_store: &'a dyn Fido2CredentialStore, - ) -> Result> { - Ok(Fido2Authenticator { - client: self.client, - user_interface, - credential_store, - selected_credential: Mutex::new(None), - requested_uv: Mutex::new(None), - }) - } - - pub fn create_client( - &'a mut self, - - user_interface: &'a dyn Fido2UserInterface, - credential_store: &'a dyn Fido2CredentialStore, - ) -> Result> { - Ok(Fido2Client { - authenticator: self.create_authenticator(user_interface, credential_store)?, - }) - } -} - -#[allow(dead_code)] -#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] -pub struct SelectedCredential { - cipher: CipherView, - credential: Fido2CredentialView, -} - -// This container is needed so we can properly implement the TryFrom trait for Passkey -// Otherwise we need to decrypt the Fido2 credentials every time we create a CipherView -#[derive(Clone)] -pub(crate) struct CipherViewContainer { - cipher: CipherView, - fido2_credentials: Vec, -} - -impl CipherViewContainer { - fn new(cipher: CipherView, enc: &dyn KeyContainer) -> Result { - let fido2_credentials = cipher.get_fido2_credentials(enc)?; - Ok(Self { - cipher, - fido2_credentials, - }) - } -} - -impl TryFrom for Passkey { - type Error = crate::error::Error; - - fn try_from(value: CipherViewContainer) -> Result { - let cred = value - .fido2_credentials - .first() - .ok_or(Error::Internal("No Fido2 credentials found".into()))?; - - cred.clone().try_into() - } -} - -impl TryFrom for Passkey { - type Error = crate::error::Error; - - fn try_from(value: Fido2CredentialFullView) -> Result { - let counter: u32 = value.counter.parse().expect("Invalid counter"); - let counter = (counter != 0).then_some(counter); - - let key = pkcs8_to_cose_key(&value.key_value)?; - - Ok(Self { - key, - credential_id: string_to_guid_bytes(&value.credential_id)?.into(), - rp_id: value.rp_id.clone(), - user_handle: value.user_handle.map(|u| u.into()), - counter, - }) - } -} - -impl Fido2CredentialView { - pub(crate) fn fill_with_credential(&self, value: Passkey) -> Result { - let cred_id: Vec = value.credential_id.into(); - - Ok(Fido2CredentialFullView { - credential_id: guid_bytes_to_string(&cred_id)?, - key_type: "public-key".to_owned(), - key_algorithm: "ECDSA".to_owned(), - key_curve: "P-256".to_owned(), - key_value: cose_key_to_pkcs8(&value.key)?, - rp_id: value.rp_id, - rp_name: self.rp_name.clone(), - user_handle: Some(cred_id), - - counter: value.counter.unwrap_or(0).to_string(), - user_name: self.user_name.clone(), - user_display_name: self.user_display_name.clone(), - discoverable: "true".to_owned(), - creation_date: chrono::offset::Utc::now(), - }) - } -} - -impl Fido2CredentialNewView { - pub(crate) fn try_from_credential( - user: &passkey::types::ctap2::make_credential::PublicKeyCredentialUserEntity, - rp: &passkey::types::ctap2::make_credential::PublicKeyCredentialRpEntity, - ) -> Result { - let cred_id: Vec = vec![0; 16]; - - Ok(Fido2CredentialNewView { - credential_id: guid_bytes_to_string(&cred_id)?, - key_type: "public-key".to_owned(), - key_algorithm: "ECDSA".to_owned(), - key_curve: "P-256".to_owned(), - rp_id: rp.id.clone(), - rp_name: rp.name.clone(), - user_handle: Some(cred_id), - - counter: 0.to_string(), - user_name: user.name.clone(), - user_display_name: user.display_name.clone(), - discoverable: "true".to_owned(), - creation_date: chrono::offset::Utc::now(), - }) - } -} - -impl Fido2CredentialFullView { - pub(crate) fn try_from_credential( - value: Passkey, - user: passkey::types::ctap2::make_credential::PublicKeyCredentialUserEntity, - rp: passkey::types::ctap2::make_credential::PublicKeyCredentialRpEntity, - ) -> Result { - let cred_id: Vec = value.credential_id.into(); - - Ok(Fido2CredentialFullView { - credential_id: guid_bytes_to_string(&cred_id)?, - key_type: "public-key".to_owned(), - key_algorithm: "ECDSA".to_owned(), - key_curve: "P-256".to_owned(), - key_value: cose_key_to_pkcs8(&value.key)?, - rp_id: value.rp_id, - rp_name: rp.name, - user_handle: Some(cred_id), - - counter: value.counter.unwrap_or(0).to_string(), - user_name: user.name, - user_display_name: user.display_name, - discoverable: "true".to_owned(), - creation_date: chrono::offset::Utc::now(), - }) - } -} - -pub fn guid_bytes_to_string(source: &[u8]) -> Result { - if source.len() != 16 { - return Err(Error::Internal("Input should be a 16 byte array".into())); - } - Ok(uuid::Uuid::from_bytes(source.try_into().expect("Invalid length")).to_string()) -} - -pub fn string_to_guid_bytes(source: &str) -> Result> { - if source.starts_with("b64.") { - let bytes = URL_SAFE_NO_PAD.decode(source.trim_start_matches("b64."))?; - Ok(bytes) - } else { - let Ok(uuid) = uuid::Uuid::try_parse(source) else { - return Err(Error::Internal("Input should be a valid GUID".into())); - }; - Ok(uuid.as_bytes().to_vec()) - } -} - -// Some utilities to convert back and forth between enums and strings -fn get_enum_from_string_name(s: &str) -> Result { - let serialized = format!(r#""{}""#, s); - let deserialized: T = serde_json::from_str(&serialized)?; - Ok(deserialized) -} - -fn get_string_name_from_enum(s: impl serde::Serialize) -> Result { - let serialized = serde_json::to_string(&s)?; - let deserialized: String = serde_json::from_str(&serialized)?; - Ok(deserialized) -} - -#[cfg(test)] -mod tests { - use passkey::types::webauthn::AuthenticatorAttachment; - - use super::{get_enum_from_string_name, get_string_name_from_enum}; - - #[test] - fn test_enum_string_conversion_works_as_expected() { - assert_eq!( - get_string_name_from_enum(AuthenticatorAttachment::CrossPlatform).unwrap(), - "cross-platform" - ); - - assert_eq!( - get_enum_from_string_name::("cross-platform").unwrap(), - AuthenticatorAttachment::CrossPlatform - ); - } - - #[test] - fn string_to_guid_with_uuid_works() { - let uuid = "d548826e-79b4-db40-a3d8-11116f7e8349"; - let bytes = super::string_to_guid_bytes(uuid).unwrap(); - assert_eq!( - bytes, - vec![213, 72, 130, 110, 121, 180, 219, 64, 163, 216, 17, 17, 111, 126, 131, 73] - ); - } - - #[test] - fn string_to_guid_with_b64_works() { - let b64 = "b64.1UiCbnm020Cj2BERb36DSQ"; - let bytes = super::string_to_guid_bytes(b64).unwrap(); - assert_eq!( - bytes, - vec![213, 72, 130, 110, 121, 180, 219, 64, 163, 216, 17, 17, 111, 126, 131, 73] - ); - } -} diff --git a/crates/bitwarden/src/secrets_manager/client_projects.rs b/crates/bitwarden/src/secrets_manager/client_projects.rs deleted file mode 100644 index acd4adfae..000000000 --- a/crates/bitwarden/src/secrets_manager/client_projects.rs +++ /dev/null @@ -1,41 +0,0 @@ -use crate::{ - error::Result, - secrets_manager::projects::{ - create_project, delete_projects, get_project, list_projects, update_project, - ProjectCreateRequest, ProjectGetRequest, ProjectPutRequest, ProjectResponse, - ProjectsDeleteRequest, ProjectsDeleteResponse, ProjectsListRequest, ProjectsResponse, - }, - Client, -}; - -pub struct ClientProjects<'a> { - pub(crate) client: &'a mut crate::Client, -} - -impl<'a> ClientProjects<'a> { - pub async fn get(&mut self, input: &ProjectGetRequest) -> Result { - get_project(self.client, input).await - } - - pub async fn create(&mut self, input: &ProjectCreateRequest) -> Result { - create_project(self.client, input).await - } - - pub async fn list(&mut self, input: &ProjectsListRequest) -> Result { - list_projects(self.client, input).await - } - - pub async fn update(&mut self, input: &ProjectPutRequest) -> Result { - update_project(self.client, input).await - } - - pub async fn delete(&mut self, input: ProjectsDeleteRequest) -> Result { - delete_projects(self.client, input).await - } -} - -impl<'a> Client { - pub fn projects(&'a mut self) -> ClientProjects<'a> { - ClientProjects { client: self } - } -} diff --git a/crates/bitwarden/src/secrets_manager/client_secrets.rs b/crates/bitwarden/src/secrets_manager/client_secrets.rs deleted file mode 100644 index 6247c0856..000000000 --- a/crates/bitwarden/src/secrets_manager/client_secrets.rs +++ /dev/null @@ -1,62 +0,0 @@ -use crate::{ - error::Result, - secrets_manager::secrets::{ - create_secret, delete_secrets, get_secret, get_secrets_by_ids, list_secrets, - list_secrets_by_project, sync_secrets, update_secret, SecretCreateRequest, - SecretGetRequest, SecretIdentifiersByProjectRequest, SecretIdentifiersRequest, - SecretIdentifiersResponse, SecretPutRequest, SecretResponse, SecretsDeleteRequest, - SecretsDeleteResponse, SecretsGetRequest, SecretsResponse, SecretsSyncRequest, - SecretsSyncResponse, - }, - Client, -}; - -pub struct ClientSecrets<'a> { - pub(crate) client: &'a mut crate::Client, -} - -impl<'a> ClientSecrets<'a> { - pub async fn get(&mut self, input: &SecretGetRequest) -> Result { - get_secret(self.client, input).await - } - - pub async fn get_by_ids(&mut self, input: SecretsGetRequest) -> Result { - get_secrets_by_ids(self.client, input).await - } - - pub async fn create(&mut self, input: &SecretCreateRequest) -> Result { - create_secret(self.client, input).await - } - - pub async fn list( - &mut self, - input: &SecretIdentifiersRequest, - ) -> Result { - list_secrets(self.client, input).await - } - - pub async fn list_by_project( - &mut self, - input: &SecretIdentifiersByProjectRequest, - ) -> Result { - list_secrets_by_project(self.client, input).await - } - - pub async fn update(&mut self, input: &SecretPutRequest) -> Result { - update_secret(self.client, input).await - } - - pub async fn delete(&mut self, input: SecretsDeleteRequest) -> Result { - delete_secrets(self.client, input).await - } - - pub async fn sync(&mut self, input: &SecretsSyncRequest) -> Result { - sync_secrets(self.client, input).await - } -} - -impl<'a> Client { - pub fn secrets(&'a mut self) -> ClientSecrets<'a> { - ClientSecrets { client: self } - } -} diff --git a/crates/bitwarden/src/secrets_manager/mod.rs b/crates/bitwarden/src/secrets_manager/mod.rs deleted file mode 100644 index 181edf6b6..000000000 --- a/crates/bitwarden/src/secrets_manager/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod projects; -pub mod secrets; -pub mod state; - -mod client_projects; -mod client_secrets; - -pub use client_projects::ClientProjects; -pub use client_secrets::ClientSecrets; diff --git a/crates/bitwarden/src/secrets_manager/projects/create.rs b/crates/bitwarden/src/secrets_manager/projects/create.rs deleted file mode 100644 index ab3b7bd62..000000000 --- a/crates/bitwarden/src/secrets_manager/projects/create.rs +++ /dev/null @@ -1,46 +0,0 @@ -use bitwarden_api_api::models::ProjectCreateRequestModel; -use bitwarden_crypto::KeyEncryptable; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use uuid::Uuid; - -use super::ProjectResponse; -use crate::{ - client::Client, - error::{Error, Result}, -}; - -#[derive(Serialize, Deserialize, Debug, JsonSchema)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct ProjectCreateRequest { - /// Organization where the project will be created - pub organization_id: Uuid, - - pub name: String, -} - -pub(crate) async fn create_project( - client: &mut Client, - input: &ProjectCreateRequest, -) -> Result { - let key = client - .get_encryption_settings()? - .get_key(&Some(input.organization_id)) - .ok_or(Error::VaultLocked)?; - - let project = Some(ProjectCreateRequestModel { - name: input.name.clone().encrypt_with_key(key)?.to_string(), - }); - - let config = client.get_api_configurations().await; - let res = bitwarden_api_api::apis::projects_api::organizations_organization_id_projects_post( - &config.api, - input.organization_id, - project, - ) - .await?; - - let enc = client.get_encryption_settings()?; - - ProjectResponse::process_response(res, enc) -} diff --git a/crates/bitwarden/src/secrets_manager/projects/update.rs b/crates/bitwarden/src/secrets_manager/projects/update.rs deleted file mode 100644 index e00609ff4..000000000 --- a/crates/bitwarden/src/secrets_manager/projects/update.rs +++ /dev/null @@ -1,45 +0,0 @@ -use bitwarden_api_api::models::ProjectUpdateRequestModel; -use bitwarden_crypto::KeyEncryptable; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use uuid::Uuid; - -use super::ProjectResponse; -use crate::{ - client::Client, - error::{Error, Result}, -}; - -#[derive(Serialize, Deserialize, Debug, JsonSchema)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct ProjectPutRequest { - /// ID of the project to modify - pub id: Uuid, - /// Organization ID of the project to modify - pub organization_id: Uuid, - - pub name: String, -} - -pub(crate) async fn update_project( - client: &mut Client, - input: &ProjectPutRequest, -) -> Result { - let key = client - .get_encryption_settings()? - .get_key(&Some(input.organization_id)) - .ok_or(Error::VaultLocked)?; - - let project = Some(ProjectUpdateRequestModel { - name: input.name.clone().encrypt_with_key(key)?.to_string(), - }); - - let config = client.get_api_configurations().await; - let res = - bitwarden_api_api::apis::projects_api::projects_id_put(&config.api, input.id, project) - .await?; - - let enc = client.get_encryption_settings()?; - - ProjectResponse::process_response(res, enc) -} diff --git a/crates/bitwarden/src/secrets_manager/secrets/create.rs b/crates/bitwarden/src/secrets_manager/secrets/create.rs deleted file mode 100644 index 4f84223dc..000000000 --- a/crates/bitwarden/src/secrets_manager/secrets/create.rs +++ /dev/null @@ -1,54 +0,0 @@ -use bitwarden_api_api::models::SecretCreateRequestModel; -use bitwarden_crypto::KeyEncryptable; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use uuid::Uuid; - -use super::SecretResponse; -use crate::{ - error::{Error, Result}, - Client, -}; - -#[derive(Serialize, Deserialize, Debug, JsonSchema)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct SecretCreateRequest { - /// Organization where the secret will be created - pub organization_id: Uuid, - - pub key: String, - pub value: String, - pub note: String, - - /// IDs of the projects that this secret will belong to - pub project_ids: Option>, -} - -pub(crate) async fn create_secret( - client: &mut Client, - input: &SecretCreateRequest, -) -> Result { - let key = client - .get_encryption_settings()? - .get_key(&Some(input.organization_id)) - .ok_or(Error::VaultLocked)?; - - let secret = Some(SecretCreateRequestModel { - key: input.key.clone().encrypt_with_key(key)?.to_string(), - value: input.value.clone().encrypt_with_key(key)?.to_string(), - note: input.note.clone().encrypt_with_key(key)?.to_string(), - project_ids: input.project_ids.clone(), - }); - - let config = client.get_api_configurations().await; - let res = bitwarden_api_api::apis::secrets_api::organizations_organization_id_secrets_post( - &config.api, - input.organization_id, - secret, - ) - .await?; - - let enc = client.get_encryption_settings()?; - - SecretResponse::process_response(res, enc) -} diff --git a/crates/bitwarden/src/secrets_manager/secrets/update.rs b/crates/bitwarden/src/secrets_manager/secrets/update.rs deleted file mode 100644 index f9e54f810..000000000 --- a/crates/bitwarden/src/secrets_manager/secrets/update.rs +++ /dev/null @@ -1,50 +0,0 @@ -use bitwarden_api_api::models::SecretUpdateRequestModel; -use bitwarden_crypto::KeyEncryptable; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use uuid::Uuid; - -use super::SecretResponse; -use crate::{ - client::Client, - error::{Error, Result}, -}; - -#[derive(Serialize, Deserialize, Debug, JsonSchema)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct SecretPutRequest { - /// ID of the secret to modify - pub id: Uuid, - /// Organization ID of the secret to modify - pub organization_id: Uuid, - - pub key: String, - pub value: String, - pub note: String, - pub project_ids: Option>, -} - -pub(crate) async fn update_secret( - client: &mut Client, - input: &SecretPutRequest, -) -> Result { - let key = client - .get_encryption_settings()? - .get_key(&Some(input.organization_id)) - .ok_or(Error::VaultLocked)?; - - let secret = Some(SecretUpdateRequestModel { - key: input.key.clone().encrypt_with_key(key)?.to_string(), - value: input.value.clone().encrypt_with_key(key)?.to_string(), - note: input.note.clone().encrypt_with_key(key)?.to_string(), - project_ids: input.project_ids.clone(), - }); - - let config = client.get_api_configurations().await; - let res = - bitwarden_api_api::apis::secrets_api::secrets_id_put(&config.api, input.id, secret).await?; - - let enc = client.get_encryption_settings()?; - - SecretResponse::process_response(res, enc) -} diff --git a/crates/bitwarden/src/tool/exporters/client_exporter.rs b/crates/bitwarden/src/tool/exporters/client_exporter.rs deleted file mode 100644 index 05eb737f3..000000000 --- a/crates/bitwarden/src/tool/exporters/client_exporter.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::{ - error::Result, - tool::exporters::{export_organization_vault, export_vault, ExportFormat}, - vault::{Cipher, Collection, Folder}, - Client, -}; - -pub struct ClientExporters<'a> { - pub(crate) client: &'a crate::Client, -} - -impl<'a> ClientExporters<'a> { - /// **Draft:** Export the vault as a CSV, JSON, or encrypted JSON file. - pub async fn export_vault( - &self, - folders: Vec, - ciphers: Vec, - format: ExportFormat, - ) -> Result { - export_vault(self.client, folders, ciphers, format) - } - - pub async fn export_organization_vault( - &self, - collections: Vec, - ciphers: Vec, - format: ExportFormat, - ) -> Result { - export_organization_vault(collections, ciphers, format) - } -} - -impl<'a> Client { - pub fn exporters(&'a self) -> ClientExporters<'a> { - ClientExporters { client: self } - } -} diff --git a/crates/bitwarden/src/tool/mod.rs b/crates/bitwarden/src/tool/mod.rs deleted file mode 100644 index 0f4f34564..000000000 --- a/crates/bitwarden/src/tool/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod exporters; -pub use exporters::{ClientExporters, ExportFormat}; -mod client_generator; -pub use client_generator::ClientGenerator; - -mod send; -pub use send::{Send, SendListView, SendView}; diff --git a/crates/bitwarden/src/vault/client_vault.rs b/crates/bitwarden/src/vault/client_vault.rs deleted file mode 100644 index 550947ed5..000000000 --- a/crates/bitwarden/src/vault/client_vault.rs +++ /dev/null @@ -1,18 +0,0 @@ -use super::sync::{sync, SyncRequest, SyncResponse}; -use crate::{error::Result, Client}; - -pub struct ClientVault<'a> { - pub(crate) client: &'a mut crate::Client, -} - -impl<'a> ClientVault<'a> { - pub async fn sync(&mut self, input: &SyncRequest) -> Result { - sync(self.client, input).await - } -} - -impl<'a> Client { - pub fn vault(&'a mut self) -> ClientVault<'a> { - ClientVault { client: self } - } -} diff --git a/crates/bitwarden/src/vault/mod.rs b/crates/bitwarden/src/vault/mod.rs deleted file mode 100644 index 220337b6c..000000000 --- a/crates/bitwarden/src/vault/mod.rs +++ /dev/null @@ -1,30 +0,0 @@ -mod cipher; -pub use cipher::*; - -#[cfg(feature = "internal")] -mod client_vault; -#[cfg(feature = "internal")] -pub use client_vault::ClientVault; - -mod collection; -pub use collection::{Collection, CollectionView}; - -mod folder; -pub use folder::{Folder, FolderView}; - -mod password_history; -pub use password_history::{PasswordHistory, PasswordHistoryView}; - -#[cfg(feature = "internal")] -mod sync; -#[cfg(feature = "internal")] -pub use sync::{SyncRequest, SyncResponse}; -#[cfg(feature = "internal")] -mod domain; - -#[cfg(feature = "internal")] -mod totp; -#[cfg(feature = "internal")] -pub(crate) use totp::generate_totp; -#[cfg(feature = "internal")] -pub use totp::TotpResponse; diff --git a/crates/bw/Cargo.toml b/crates/bw/Cargo.toml index 7361f15b6..a2ba807c3 100644 --- a/crates/bw/Cargo.toml +++ b/crates/bw/Cargo.toml @@ -14,15 +14,17 @@ repository.workspace = true license-file.workspace = true [dependencies] -bitwarden = { workspace = true, features = ["internal"] } bitwarden-cli = { workspace = true } +bitwarden-core = { workspace = true } bitwarden-crypto = { workspace = true } +bitwarden-generators = { workspace = true } +bitwarden-vault = { workspace = true } clap = { version = "4.5.4", features = ["derive", "env"] } color-eyre = "0.6.3" env_logger = "0.11.1" inquire = "0.7.0" log = "0.4.20" -tokio = { version = "1.36.0", features = ["rt-multi-thread", "macros"] } +tokio = { workspace = true, features = ["rt-multi-thread"] } [dev-dependencies] tempfile = "3.10.0" diff --git a/crates/bw/src/auth/login.rs b/crates/bw/src/auth/login.rs index 52681703d..131d39f02 100644 --- a/crates/bw/src/auth/login.rs +++ b/crates/bw/src/auth/login.rs @@ -1,17 +1,17 @@ -use bitwarden::{ +use bitwarden_cli::text_prompt_when_none; +use bitwarden_core::{ auth::login::{ ApiKeyLoginRequest, PasswordLoginRequest, TwoFactorEmailRequest, TwoFactorProvider, TwoFactorRequest, }, - vault::SyncRequest, Client, }; -use bitwarden_cli::text_prompt_when_none; +use bitwarden_vault::{ClientVaultExt, SyncRequest}; use color_eyre::eyre::{bail, Result}; use inquire::{Password, Text}; use log::{debug, error, info}; -pub(crate) async fn login_password(mut client: Client, email: Option) -> Result<()> { +pub(crate) async fn login_password(client: Client, email: Option) -> Result<()> { let email = text_prompt_when_none("Email", email)?; let password = Password::new("Password").without_confirmation().prompt()?; @@ -93,7 +93,7 @@ pub(crate) async fn login_password(mut client: Client, email: Option) -> } pub(crate) async fn login_api_key( - mut client: Client, + client: Client, client_id: Option, client_secret: Option, ) -> Result<()> { @@ -117,7 +117,7 @@ pub(crate) async fn login_api_key( } pub(crate) async fn login_device( - mut client: Client, + client: Client, email: Option, device_identifier: Option, ) -> Result<()> { diff --git a/crates/bw/src/main.rs b/crates/bw/src/main.rs index 6674bda1e..06d5d0bd5 100644 --- a/crates/bw/src/main.rs +++ b/crates/bw/src/main.rs @@ -1,9 +1,8 @@ -use bitwarden::{ - auth::RegisterRequest, - client::client_settings::ClientSettings, - generators::{PassphraseGeneratorRequest, PasswordGeneratorRequest}, -}; use bitwarden_cli::{install_color_eyre, text_prompt_when_none, Color}; +use bitwarden_core::{auth::RegisterRequest, ClientSettings}; +use bitwarden_generators::{ + ClientGeneratorExt, PassphraseGeneratorRequest, PasswordGeneratorRequest, +}; use clap::{command, Args, CommandFactory, Parser, Subcommand}; use color_eyre::eyre::Result; use inquire::Password; @@ -157,7 +156,7 @@ async fn process_commands() -> Result<()> { identity_url: format!("{}/identity", server), ..Default::default() }); - let client = bitwarden::Client::new(settings); + let client = bitwarden_core::Client::new(settings); match args.command { // FIXME: Rust CLI will not support password login! @@ -188,7 +187,7 @@ async fn process_commands() -> Result<()> { identity_url: format!("{}/identity", server), ..Default::default() }); - let mut client = bitwarden::Client::new(settings); + let client = bitwarden_core::Client::new(settings); let email = text_prompt_when_none("Email", email)?; let password = Password::new("Password").prompt()?; @@ -207,7 +206,7 @@ async fn process_commands() -> Result<()> { } // Not login, assuming we have a config - let client = bitwarden::Client::new(None); + let client = bitwarden_core::Client::new(None); // And finally we process all the commands which require authentication match command { @@ -217,30 +216,24 @@ async fn process_commands() -> Result<()> { Commands::Sync {} => todo!(), Commands::Generate { command } => match command { GeneratorCommands::Password(args) => { - let password = client - .generator() - .password(PasswordGeneratorRequest { - lowercase: args.lowercase, - uppercase: args.uppercase, - numbers: args.numbers, - special: args.special, - length: args.length, - ..Default::default() - }) - .await?; + let password = client.generator().password(PasswordGeneratorRequest { + lowercase: args.lowercase, + uppercase: args.uppercase, + numbers: args.numbers, + special: args.special, + length: args.length, + ..Default::default() + })?; println!("{}", password); } 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?; + let passphrase = client.generator().passphrase(PassphraseGeneratorRequest { + num_words: args.words, + word_separator: args.separator.to_string(), + capitalize: args.capitalize, + include_number: args.include_number, + })?; println!("{}", passphrase); } diff --git a/crates/bws/CHANGELOG.md b/crates/bws/CHANGELOG.md index 25cec1164..2690c14f2 100644 --- a/crates/bws/CHANGELOG.md +++ b/crates/bws/CHANGELOG.md @@ -7,6 +7,18 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added + +- The ability to edit unassigned secrets with direct permissions. (#906) + +### Changed + +- Updated MSRV `1.75.0` (#980) + +### Removed + +- The deprecated `action type` commands are now removed. Please use `type action` instead. (#836) + ## [0.5.0] - 2024-04-26 ### Added diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 6686b5e1e..08e3941f2 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bws" -version = "0.5.0" +version = "1.0.0" description = """ Bitwarden Secrets Manager CLI """ @@ -30,6 +30,7 @@ color-eyre = "0.6.3" comfy-table = "7.1.1" directories = "5.0.1" env_logger = "0.11.1" +itertools = "0.13.0" log = "0.4.20" regex = { version = "1.10.3", features = [ "std", @@ -40,9 +41,10 @@ serde_json = "1.0.113" serde_yaml = "0.9" supports-color = "3.0.0" thiserror = "1.0.57" -tokio = { version = "1.36.0", features = ["rt-multi-thread", "macros"] } +tokio = { workspace = true, features = ["rt-multi-thread"] } toml = "0.8.10" uuid = { version = "1.7.0", features = ["serde"] } +which = "6.0.1" [build-dependencies] bitwarden-cli = { workspace = true } diff --git a/crates/bws/Dockerfile b/crates/bws/Dockerfile index ccf9865c9..07f9f3a79 100644 --- a/crates/bws/Dockerfile +++ b/crates/bws/Dockerfile @@ -1,7 +1,7 @@ ############################################### # Build stage # ############################################### -FROM --platform=$BUILDPLATFORM rust:1.76 AS build +FROM --platform=$BUILDPLATFORM rust:1.81 AS build # Docker buildx supplies the value for this arg ARG TARGETPLATFORM @@ -24,8 +24,8 @@ RUN mkdir /lib64-bws RUN ldd /app/target/release/bws | tr -s '[:blank:]' '\n' | grep '^/lib' | xargs -I % cp % /lib-bws RUN ldd /app/target/release/bws | tr -s '[:blank:]' '\n' | grep '^/lib64' | xargs -I % cp % /lib64-bws -# Make a HOME directory for the app stage -RUN mkdir -p /home/app +# Make a user and HOME directory for the app stage +RUN useradd -m app ############################################### # App stage # @@ -35,11 +35,15 @@ FROM scratch ARG TARGETPLATFORM LABEL com.bitwarden.product="bitwarden" -# Set a HOME directory +# Set a HOME directory and copy the user file COPY --from=build /home/app /home/app +COPY --from=build /etc/passwd /etc/passwd ENV HOME=/home/app WORKDIR /home/app +# Switch to the app user +USER app + # Copy built project from the build stage COPY --from=build /app/target/release/bws /bin/bws diff --git a/crates/bws/README.md b/crates/bws/README.md index ace5210f1..524a168ad 100644 --- a/crates/bws/README.md +++ b/crates/bws/README.md @@ -6,11 +6,29 @@ and might be missing some functionality. ## Install +We offer three ways to install bws: + +### Cargo (crates.io) + +Download bws via `cargo` from [crates.io](https://crates.io): + ```bash -cargo install bws +cargo install bws --locked ``` -Or download a pre-built binary from the [Releases](https://github.com/bitwarden/sdk/releases) page. +### Install Script (from GitHub Releases) + +Linux/macOS: `curl https://bws.bitwarden.com/install | sh` + +Windows: `iwr https://bws.bitwarden.com/install | iex` + +An optional `-u/--uninstall` flag can be passed to the POSIX script to uninstall the CLI. The +PowerShell version accepts an equivalent `-Uninstall` flag. The uninstallation process will remove +the `bws` binary and the configuration directory (`~/.bws`). + +### GitHub Releases (Manual) + +Download a pre-built binary from the [Releases](https://github.com/bitwarden/sdk/releases) page. ## Usage diff --git a/crates/bws/scripts/install.ps1 b/crates/bws/scripts/install.ps1 new file mode 100755 index 000000000..daa5cf9d1 --- /dev/null +++ b/crates/bws/scripts/install.ps1 @@ -0,0 +1,108 @@ +param ( + [switch]$Uninstall +) + +$ErrorActionPreference = "Stop" + +$defaultBwsVersion = "1.0.0" +$bwsVersion = if ($env:bwsVersion) { $env:bwsVersion } else { $defaultBwsVersion } +$installDir = [Environment]::GetFolderPath([Environment+SpecialFolder]::LocalApplicationData) | Join-Path -ChildPath "Programs" | Join-Path -ChildPath "Bitwarden" + +# https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-processor#properties +$processorArch = (Get-CimInstance -ClassName Win32_Processor).Architecture +if ($processorArch -eq 9) { + $arch = "x86_64" +} elseif ($processorArch -eq 12) { + $arch = "aarch64" +} else { + throw "Unsupported architecture: $processorArch" +} + +function Test-BwsInstallation { + $existingBws = Get-Command bws -ErrorAction SilentlyContinue + if ($null -ne $existingBws) { + $userInput = Read-Host "bws is already installed at $($existingBws.Source). Do you want to overwrite it? (Y/N)" + if ($userInput -ne "Y") { + Write-Host "Installation cancelled by user." + exit + } + } +} + +function Invoke-BwsDownload { + Write-Host "Detected architecture: $arch" + + $bwsUrl = "https://github.com/bitwarden/sdk/releases/download/bws-v$bwsVersion/bws-$arch-pc-windows-msvc-$bwsVersion.zip" + Write-Host "Downloading bws from: $bwsUrl" + $outputPath = Join-Path $env:TEMP "bws.zip" + Invoke-WebRequest -Uri $bwsUrl -OutFile $outputPath + return $outputPath +} + +function Test-Checksum { + param($zipPath) + Write-Host "Validating checksum..." + + $checksumUrl = "https://github.com/bitwarden/sdk/releases/download/bws-v$bwsVersion/bws-sha256-checksums-$bwsVersion.txt" + $checksumFile = Join-Path $env:TEMP "bws-checksums.txt" + Invoke-WebRequest -Uri $checksumUrl -OutFile $checksumFile + + $expectedChecksum = (Get-Content $checksumFile | Where-Object { $_ -match "bws-$arch-pc-windows-msvc-$bwsVersion.zip" }).Split(" ")[0] + $actualChecksum = (Get-FileHash -Algorithm SHA256 -Path $zipPath).Hash + + if ($actualChecksum -ne $expectedChecksum) { + throw "Checksum validation failed. Expected: $expectedChecksum, Actual: $actualChecksum" + } else { + Write-Host "Checksum validation successful." + } +} + +function Install-Bws { + param($zipPath) + Write-Host "Installing bws..." + New-Item -ItemType Directory -Force -Path $installDir | Out-Null + Expand-Archive -Force $zipPath -DestinationPath $installDir + Write-Host "bws installed to $installDir" + setx PATH "$env:PATH;$installDir" + Write-Host "$installDir has been added to your PATH" + Write-Host "Please restart your shell to use bws" +} + +function Test-Bws { + Write-Host "Checking bws..." + $bwsPath = Join-Path $installDir "bws.exe" + if (Test-Path $bwsPath) { + Write-Host "bws is installed at $bwsPath" + } else { + throw "bws is not installed" + } +} + +function Remove-Bws { + Write-Host "Uninstalling bws..." + + if (Test-Path $installDir) { + Remove-Item -Path $installDir -Recurse -Force + Write-Host "bws uninstalled from $installDir" + } else { + Write-Host "bws installation directory not found at $installDir. Skipping removal." + } + + $configDir = "$env:USERPROFILE\.bws" + if (Test-Path $configDir -PathType Container) { + Remove-Item -Path $configDir -Recurse -Force + Write-Host "bws config directory removed from $configDir" + } else { + Write-Host "bws config directory not found at $configDir. Skipping removal." + } +} + +if ($Uninstall) { + Remove-Bws +} else { + Test-BwsInstallation + $zipPath = Invoke-BwsDownload + Test-Checksum -zipPath $zipPath + Install-Bws -zipPath $zipPath + Test-Bws +} diff --git a/crates/bws/scripts/install.sh b/crates/bws/scripts/install.sh new file mode 100755 index 000000000..6cd7fe01f --- /dev/null +++ b/crates/bws/scripts/install.sh @@ -0,0 +1,180 @@ +#!/bin/sh + +################################################## +# An installer for the bws command line utility. # +################################################## + +DEFAULT_BWS_VERSION="1.0.0" +BWS_VERSION="${BWS_VERSION:-$DEFAULT_BWS_VERSION}" + +main() { + case "$1" in + -u | --uninstall) + uninstall_bws + ;; + *) + check_required + platform_detect + arch_detect + download_bws + validate_checksum + install_bws + ;; + esac +} + +error() { + echo "$1" >&2 + echo "Exiting..." >&2 + exit 1 +} + +check_required() { + if ! command -v curl >/dev/null && ! command -v wget >/dev/null; then + error "curl or wget is required to download bws." + fi + + if ! command -v unzip >/dev/null; then + error "unzip is required to install bws." + fi +} + +can_sudo() { + if command -v sudo >/dev/null; then + echo "Attempting to install bws with sudo. Please enter your password if prompted." + if sudo -v 2>/dev/null; then + echo "sudo is available and we have the necessary permissions." + echo "Installing bws to /usr/local/bin..." + return 0 + else + echo "sudo is available, but we failed to authenticate." + return 1 + fi + else + echo "sudo is not available." + return 1 + fi +} + +platform_detect() { + if [ "$(uname -s)" = "Linux" ]; then + PLATFORM="unknown-linux-gnu" + elif [ "$(uname -s)" = "Darwin" ]; then + PLATFORM="apple-darwin" + else + error "Unsupported platform: $(uname -s)" + fi +} + +arch_detect() { + if [ "$(uname -m)" = "x86_64" ]; then + ARCH="x86_64" + elif [ "$(uname -m)" = "aarch64" ]; then # Linux uname output + ARCH="aarch64" + elif [ "$(uname -m)" = "arm64" ]; then # Darwin uname output + ARCH="aarch64" + else + error "Unsupported architecture: $(uname -m)" + fi +} + +checksum() { + if command -v sha256sum >/dev/null; then + sha256sum "$1" + else + shasum -a 256 "$1" + fi +} + +downloader() { + if command -v curl >/dev/null; then + curl -L -o "$2" "$1" + else + wget -O "$2" "$1" + fi +} + +extract() { + unzip -o "$1" -d "$2" +} + +download_bws() { + bws_url="https://github.com/bitwarden/sdk/releases/download/bws-v${BWS_VERSION}/bws-${ARCH}-${PLATFORM}-${BWS_VERSION}.zip" + echo "Downloading bws from: $bws_url" + tmp_dir="$(mktemp -d)" + downloader "$bws_url" "$tmp_dir/bws.zip" +} + +validate_checksum() { + checksum_url="https://github.com/bitwarden/sdk/releases/download/bws-v${BWS_VERSION}/bws-sha256-checksums-${BWS_VERSION}.txt" + echo "Downloading checksum file from: $checksum_url" + checksum_file="$tmp_dir/bws-checksums.txt" + downloader "$checksum_url" "$checksum_file" + + expected_checksum="$(grep "bws-${ARCH}-${PLATFORM}-${BWS_VERSION}.zip" "$checksum_file" | awk '{print $1}')" + actual_checksum="$(checksum "$tmp_dir/bws.zip" | awk '{print $1}')" + + if [ "$actual_checksum" != "$expected_checksum" ]; then + error "Checksum validation failed. Expected: $expected_checksum, Actual: $actual_checksum" + else + echo "Checksum validation successful." + fi +} + +install_bws() { + echo "Installing bws..." + extract "$tmp_dir/bws.zip" "$tmp_dir" + chmod +x "$tmp_dir/bws" + + if can_sudo; then + sudo install -m 755 "$tmp_dir/bws" /usr/local/bin/bws + + if ! command -v bws >/dev/null; then + error "Installation failed. bws was not found in /usr/local/bin" + fi + + echo "bws installed to /usr/local/bin/bws" + else + echo "Installing to your \$HOME directory..." + user_bin_dir="${HOME}/.local/bin" + mkdir -p "${user_bin_dir}" + install -m 755 "$tmp_dir/bws" "${user_bin_dir}/bws" + + if ! command -v "${user_bin_dir}/bws" >/dev/null; then + error "Installation failed. bws was not found in ${user_bin_dir}" + fi + + echo "bws installed at ${user_bin_dir}/bws" + echo "Please add ${user_bin_dir} to your PATH by adding the following line to your ~/.profile or shell rc file:" + echo "export PATH=\"\$PATH:${user_bin_dir}\"" + fi + + rm -rf "$tmp_dir" +} + +uninstall_bws() { + if command -v bws >/dev/null; then + echo "Uninstalling bws..." + if can_sudo; then + sudo rm "$(command -v bws)" + else + rm "$(command -v bws)" + fi + + # Safely remove the configuration directory + if [ -n "$HOME" ]; then + echo "Removing bws configuration directory at ${HOME}/.bws" + echo "If you use another directory for your configuration, you may want to remove it manually." + rm -rf "${HOME}/.bws" + else + echo "HOME environment variable is not set. Cannot safely remove .bws directory." + fi + + echo "bws uninstalled successfully." + else + echo "bws is not installed." + fi + exit 0 +} + +main "$@" diff --git a/crates/bws/src/cli.rs b/crates/bws/src/cli.rs index 48d2f528e..9c81e8bfd 100644 --- a/crates/bws/src/cli.rs +++ b/crates/bws/src/cli.rs @@ -9,9 +9,10 @@ pub(crate) const ACCESS_TOKEN_KEY_VAR_NAME: &str = "BWS_ACCESS_TOKEN"; pub(crate) const CONFIG_FILE_KEY_VAR_NAME: &str = "BWS_CONFIG_FILE"; pub(crate) const PROFILE_KEY_VAR_NAME: &str = "BWS_PROFILE"; pub(crate) const SERVER_URL_KEY_VAR_NAME: &str = "BWS_SERVER_URL"; +pub(crate) const UUIDS_AS_KEYNAMES_VAR_NAME: &str = "BWS_UUIDS_AS_KEYNAMES"; pub(crate) const DEFAULT_CONFIG_FILENAME: &str = "config"; -pub(crate) const DEFAULT_CONFIG_DIRECTORY: &str = ".bws"; +pub(crate) const DEFAULT_CONFIG_DIRECTORY: &str = ".config/bws"; #[allow(non_camel_case_types)] #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)] @@ -19,7 +20,8 @@ pub(crate) enum ProfileKey { server_base, server_api, server_identity, - state_file_dir, + state_dir, + state_opt_out, } #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)] @@ -89,30 +91,26 @@ pub(crate) enum Commands { #[command(subcommand)] cmd: SecretCommand, }, - #[command(long_about = "Create a single item (deprecated)", hide(true))] - Create { - #[command(subcommand)] - cmd: CreateCommand, - }, - #[command(long_about = "Delete one or more items (deprecated)", hide(true))] - Delete { - #[command(subcommand)] - cmd: DeleteCommand, - }, - #[command(long_about = "Edit a single item (deprecated)", hide(true))] - Edit { - #[command(subcommand)] - cmd: EditCommand, - }, - #[command(long_about = "Retrieve a single item (deprecated)", hide(true))] - Get { - #[command(subcommand)] - cmd: GetCommand, - }, - #[command(long_about = "List items (deprecated)", hide(true))] - List { - #[command(subcommand)] - cmd: ListCommand, + #[command(long_about = "Run a command with secrets injected")] + Run { + #[arg(help = "The command to run")] + command: Vec, + #[arg(long, help = "The shell to use")] + shell: Option, + #[arg( + long, + help = "Don't inherit environment variables from the current shell" + )] + no_inherit_env: bool, + #[arg(long, help = "The ID of the project to use")] + project_id: Option, + #[arg( + long, + global = true, + env = UUIDS_AS_KEYNAMES_VAR_NAME, + help = "Use the secret UUID (in its POSIX form) instead of the key name for the environment variable" + )] + uuids_as_keynames: bool, }, } @@ -169,60 +167,3 @@ pub(crate) enum ProjectCommand { }, List, } - -#[derive(Subcommand, Debug)] -pub(crate) enum ListCommand { - Projects, - Secrets { project_id: Option }, -} - -#[derive(Subcommand, Debug)] -pub(crate) enum GetCommand { - Project { project_id: Uuid }, - Secret { secret_id: Uuid }, -} - -#[derive(Subcommand, Debug)] -pub(crate) enum CreateCommand { - Project { - name: String, - }, - Secret { - key: String, - value: String, - - #[arg(long, help = "An optional note to add to the secret")] - note: Option, - - #[arg(long, help = "The ID of the project this secret will be added to")] - project_id: Uuid, - }, -} - -#[derive(Subcommand, Debug)] -pub(crate) enum EditCommand { - #[clap(group = ArgGroup::new("edit_field").required(true).multiple(true))] - Project { - project_id: Uuid, - #[arg(long, group = "edit_field")] - name: String, - }, - #[clap(group = ArgGroup::new("edit_field").required(true).multiple(true))] - Secret { - secret_id: Uuid, - #[arg(long, group = "edit_field")] - key: Option, - #[arg(long, group = "edit_field")] - value: Option, - #[arg(long, group = "edit_field")] - note: Option, - #[arg(long, group = "edit_field")] - project_id: Option, - }, -} - -#[derive(Subcommand, Debug)] -pub(crate) enum DeleteCommand { - Project { project_ids: Vec }, - Secret { secret_ids: Vec }, -} diff --git a/crates/bws/src/command/mod.rs b/crates/bws/src/command/mod.rs new file mode 100644 index 000000000..98287e452 --- /dev/null +++ b/crates/bws/src/command/mod.rs @@ -0,0 +1,67 @@ +pub(crate) mod project; +pub(crate) mod run; +pub(crate) mod secret; + +use std::{path::PathBuf, str::FromStr}; + +use bitwarden::auth::AccessToken; +use clap::CommandFactory; +use clap_complete::Shell; +use color_eyre::eyre::{bail, Result}; + +use crate::{config, util, Cli, ProfileKey}; + +pub(crate) fn completions(shell: Option) -> Result<()> { + let Some(shell) = shell.or_else(Shell::from_env) else { + bail!("Couldn't autodetect a valid shell. Run `bws completions --help` for more info."); + }; + + let mut cmd = Cli::command(); + let name = cmd.get_name().to_string(); + clap_complete::generate(shell, &mut cmd, name, &mut std::io::stdout()); + + Ok(()) +} + +pub(crate) fn config( + name: Option, + value: Option, + delete: bool, + profile: Option, + access_token: Option, + config_file: Option, +) -> Result<()> { + let profile = if let Some(profile) = profile { + profile + } else if let Some(access_token) = access_token { + AccessToken::from_str(&access_token)? + .access_token_id + .to_string() + } else { + String::from("default") + }; + + if delete { + config::delete_profile(config_file.as_deref(), profile)?; + println!("Profile deleted successfully!"); + } else { + let (name, value) = match (name, value) { + (None, None) => bail!("Missing `name` and `value`"), + (None, Some(_)) => bail!("Missing `value`"), + (Some(_), None) => bail!("Missing `name`"), + (Some(ProfileKey::state_opt_out), Some(value)) => { + if util::string_to_bool(value.as_str()).is_err() { + bail!("Profile key \"state_opt_out\" must be \"true\" or \"false\""); + } else { + (ProfileKey::state_opt_out, value) + } + } + (Some(name), Some(value)) => (name, value), + }; + + config::update_profile(config_file.as_deref(), profile, name, value)?; + println!("Profile updated successfully!"); + }; + + Ok(()) +} diff --git a/crates/bws/src/command/project.rs b/crates/bws/src/command/project.rs new file mode 100644 index 000000000..3b521e2cb --- /dev/null +++ b/crates/bws/src/command/project.rs @@ -0,0 +1,141 @@ +use bitwarden::{ + secrets_manager::{ + projects::{ + ProjectCreateRequest, ProjectGetRequest, ProjectPutRequest, ProjectsDeleteRequest, + ProjectsListRequest, + }, + ClientProjectsExt, + }, + Client, +}; +use color_eyre::eyre::{bail, Result}; +use uuid::Uuid; + +use crate::{ + render::{serialize_response, OutputSettings}, + ProjectCommand, +}; + +pub(crate) async fn process_command( + command: ProjectCommand, + client: Client, + organization_id: Uuid, + output_settings: OutputSettings, +) -> Result<()> { + match command { + ProjectCommand::List => list(client, organization_id, output_settings).await, + ProjectCommand::Get { project_id } => get(client, project_id, output_settings).await, + ProjectCommand::Create { name } => { + create(client, organization_id, name, output_settings).await + } + ProjectCommand::Edit { project_id, name } => { + edit(client, organization_id, project_id, name, output_settings).await + } + ProjectCommand::Delete { project_ids } => delete(client, project_ids).await, + } +} + +pub(crate) async fn list( + client: Client, + organization_id: Uuid, + output_settings: OutputSettings, +) -> Result<()> { + let projects = client + .projects() + .list(&ProjectsListRequest { organization_id }) + .await? + .data; + serialize_response(projects, output_settings); + + Ok(()) +} + +pub(crate) async fn get( + client: Client, + project_id: Uuid, + output_settings: OutputSettings, +) -> Result<()> { + let project = client + .projects() + .get(&ProjectGetRequest { id: project_id }) + .await?; + serialize_response(project, output_settings); + + Ok(()) +} + +pub(crate) async fn create( + client: Client, + organization_id: Uuid, + name: String, + output_settings: OutputSettings, +) -> Result<()> { + let project = client + .projects() + .create(&ProjectCreateRequest { + organization_id, + name, + }) + .await?; + serialize_response(project, output_settings); + + Ok(()) +} + +pub(crate) async fn edit( + client: Client, + organization_id: Uuid, + project_id: Uuid, + name: String, + output_settings: OutputSettings, +) -> Result<()> { + let project = client + .projects() + .update(&ProjectPutRequest { + id: project_id, + organization_id, + name, + }) + .await?; + serialize_response(project, output_settings); + + Ok(()) +} + +pub(crate) async fn delete(client: Client, project_ids: Vec) -> Result<()> { + let count = project_ids.len(); + + let result = client + .projects() + .delete(ProjectsDeleteRequest { ids: project_ids }) + .await?; + + let projects_failed: Vec<(Uuid, String)> = result + .data + .into_iter() + .filter_map(|r| r.error.map(|e| (r.id, e))) + .collect(); + let deleted_projects = count - projects_failed.len(); + + match deleted_projects { + 2.. => println!("{} projects deleted successfully.", deleted_projects), + 1 => println!("{} project deleted successfully.", deleted_projects), + _ => (), + } + + match projects_failed.len() { + 2.. => eprintln!("{} projects had errors:", projects_failed.len()), + 1 => eprintln!("{} project had an error:", projects_failed.len()), + _ => (), + } + + for project in &projects_failed { + eprintln!("{}: {}", project.0, project.1); + } + + if !projects_failed.is_empty() { + bail!("Errors when attempting to delete projects."); + } + + Ok(()) +} diff --git a/crates/bws/src/command/run.rs b/crates/bws/src/command/run.rs new file mode 100644 index 000000000..6548778eb --- /dev/null +++ b/crates/bws/src/command/run.rs @@ -0,0 +1,149 @@ +use std::{ + collections::HashMap, + io::{IsTerminal, Read}, + process, +}; + +use bitwarden::{ + secrets_manager::{ + secrets::{SecretIdentifiersByProjectRequest, SecretIdentifiersRequest, SecretsGetRequest}, + ClientSecretsExt, + }, + Client, +}; +use color_eyre::eyre::{bail, Result}; +use itertools::Itertools; +use uuid::Uuid; +use which::which; + +use crate::{ + util::{is_valid_posix_name, uuid_to_posix}, + ACCESS_TOKEN_KEY_VAR_NAME, +}; + +// Essential environment variables that should be preserved even when `--no-inherit-env` is used +const WINDOWS_ESSENTIAL_VARS: &[&str] = &["SystemRoot", "ComSpec", "windir"]; + +pub(crate) async fn run( + client: Client, + organization_id: Uuid, + project_id: Option, + uuids_as_keynames: bool, + no_inherit_env: bool, + shell: Option, + command: Vec, +) -> Result { + let is_windows = std::env::consts::OS == "windows"; + + let shell = shell.unwrap_or_else(|| { + if is_windows { + "powershell".to_string() + } else { + "sh".to_string() + } + }); + + if which(&shell).is_err() { + bail!("Shell '{}' not found", shell); + } + + let user_command = if command.is_empty() { + if std::io::stdin().is_terminal() { + bail!("No command provided"); + } + + let mut buffer = String::new(); + std::io::stdin().read_to_string(&mut buffer)?; + buffer + } else { + command.join(" ") + }; + + let res = if let Some(project_id) = project_id { + client + .secrets() + .list_by_project(&SecretIdentifiersByProjectRequest { project_id }) + .await? + } else { + client + .secrets() + .list(&SecretIdentifiersRequest { organization_id }) + .await? + }; + + let secret_ids = res.data.into_iter().map(|e| e.id).collect(); + let secrets = client + .secrets() + .get_by_ids(SecretsGetRequest { ids: secret_ids }) + .await? + .data; + + if !uuids_as_keynames { + if let Some(duplicate) = secrets.iter().map(|s| &s.key).duplicates().next() { + bail!("Multiple secrets with name: '{}'. Use --uuids-as-keynames or use unique names for secrets", duplicate); + } + } + + let environment: HashMap = secrets + .into_iter() + .map(|s| { + if uuids_as_keynames { + (uuid_to_posix(&s.id), s.value) + } else { + (s.key, s.value) + } + }) + .inspect(|(k, _)| { + if !is_valid_posix_name(k) { + eprintln!( + "Warning: secret '{}' does not have a POSIX-compliant name", + k + ); + } + }) + .collect(); + + let mut command = process::Command::new(shell); + command + .arg("-c") + .arg(&user_command) + .stdout(process::Stdio::inherit()) + .stderr(process::Stdio::inherit()); + + if no_inherit_env { + let path = std::env::var("PATH").unwrap_or_else(|_| match is_windows { + true => "C:\\Windows;C:\\Windows\\System32".to_string(), + false => "/bin:/usr/bin".to_string(), + }); + + command.env_clear(); + + // Preserve essential PowerShell environment variables on Windows + if is_windows { + for &var in WINDOWS_ESSENTIAL_VARS { + if let Ok(value) = std::env::var(var) { + command.env(var, value); + } + } + } + + command.env("PATH", path); // PATH is always necessary + command.envs(environment); + } else { + command.env_remove(ACCESS_TOKEN_KEY_VAR_NAME); + command.envs(environment); + } + + // propagate the exit status from the child process + match command.spawn() { + Ok(mut child) => match child.wait() { + Ok(exit_status) => Ok(exit_status.code().unwrap_or(1)), + Err(e) => { + bail!("Failed to wait for process: {}", e) + } + }, + Err(e) => { + bail!("Failed to execute process: {}", e) + } + } +} diff --git a/crates/bws/src/command/secret.rs b/crates/bws/src/command/secret.rs new file mode 100644 index 000000000..0f1aa3981 --- /dev/null +++ b/crates/bws/src/command/secret.rs @@ -0,0 +1,221 @@ +use bitwarden::{ + secrets_manager::{ + secrets::{ + SecretCreateRequest, SecretGetRequest, SecretIdentifiersByProjectRequest, + SecretIdentifiersRequest, SecretPutRequest, SecretsDeleteRequest, SecretsGetRequest, + }, + ClientSecretsExt, + }, + Client, +}; +use color_eyre::eyre::{bail, Result}; +use uuid::Uuid; + +use crate::{ + render::{serialize_response, OutputSettings}, + SecretCommand, +}; + +#[derive(Debug)] +pub(crate) struct SecretCreateCommandModel { + pub(crate) key: String, + pub(crate) value: String, + pub(crate) note: Option, + pub(crate) project_id: Uuid, +} + +#[derive(Debug)] +pub(crate) struct SecretEditCommandModel { + pub(crate) id: Uuid, + pub(crate) key: Option, + pub(crate) value: Option, + pub(crate) note: Option, + pub(crate) project_id: Option, +} + +pub(crate) async fn process_command( + command: SecretCommand, + client: Client, + organization_id: Uuid, + output_settings: OutputSettings, +) -> Result<()> { + match command { + SecretCommand::List { project_id } => { + list(client, organization_id, project_id, output_settings).await + } + SecretCommand::Get { secret_id } => get(client, secret_id, output_settings).await, + SecretCommand::Create { + key, + value, + note, + project_id, + } => { + create( + client, + organization_id, + SecretCreateCommandModel { + key, + value, + note, + project_id, + }, + output_settings, + ) + .await + } + SecretCommand::Edit { + secret_id, + key, + value, + note, + project_id, + } => { + edit( + client, + organization_id, + SecretEditCommandModel { + id: secret_id, + key, + value, + note, + project_id, + }, + output_settings, + ) + .await + } + SecretCommand::Delete { secret_ids } => delete(client, secret_ids).await, + } +} + +pub(crate) async fn list( + client: Client, + organization_id: Uuid, + project_id: Option, + output_settings: OutputSettings, +) -> Result<()> { + let res = if let Some(project_id) = project_id { + client + .secrets() + .list_by_project(&SecretIdentifiersByProjectRequest { project_id }) + .await? + } else { + client + .secrets() + .list(&SecretIdentifiersRequest { organization_id }) + .await? + }; + + let secret_ids = res.data.into_iter().map(|e| e.id).collect(); + let secrets = client + .secrets() + .get_by_ids(SecretsGetRequest { ids: secret_ids }) + .await? + .data; + serialize_response(secrets, output_settings); + + Ok(()) +} + +pub(crate) async fn get( + client: Client, + secret_id: Uuid, + output_settings: OutputSettings, +) -> Result<()> { + let secret = client + .secrets() + .get(&SecretGetRequest { id: secret_id }) + .await?; + serialize_response(secret, output_settings); + + Ok(()) +} + +pub(crate) async fn create( + client: Client, + organization_id: Uuid, + secret: SecretCreateCommandModel, + output_settings: OutputSettings, +) -> Result<()> { + let secret = client + .secrets() + .create(&SecretCreateRequest { + organization_id, + key: secret.key, + value: secret.value, + note: secret.note.unwrap_or_default(), + project_ids: Some(vec![secret.project_id]), + }) + .await?; + serialize_response(secret, output_settings); + + Ok(()) +} + +pub(crate) async fn edit( + client: Client, + organization_id: Uuid, + secret: SecretEditCommandModel, + output_settings: OutputSettings, +) -> Result<()> { + let old_secret = client + .secrets() + .get(&SecretGetRequest { id: secret.id }) + .await?; + + let new_secret = client + .secrets() + .update(&SecretPutRequest { + id: secret.id, + organization_id, + key: secret.key.unwrap_or(old_secret.key), + value: secret.value.unwrap_or(old_secret.value), + note: secret.note.unwrap_or(old_secret.note), + project_ids: secret + .project_id + .or(old_secret.project_id) + .map(|id| vec![id]), + }) + .await?; + serialize_response(new_secret, output_settings); + + Ok(()) +} + +pub(crate) async fn delete(client: Client, secret_ids: Vec) -> Result<()> { + let count = secret_ids.len(); + + let result = client + .secrets() + .delete(SecretsDeleteRequest { ids: secret_ids }) + .await?; + + let secrets_failed: Vec<(Uuid, String)> = result + .data + .into_iter() + .filter_map(|r| r.error.map(|e| (r.id, e))) + .collect(); + let deleted_secrets = count - secrets_failed.len(); + + match deleted_secrets { + 2.. => println!("{} secrets deleted successfully.", deleted_secrets), + 1 => println!("{} secret deleted successfully.", deleted_secrets), + _ => (), + } + + match secrets_failed.len() { + 2.. => eprintln!("{} secrets had errors:", secrets_failed.len()), + 1 => eprintln!("{} secret had an error:", secrets_failed.len()), + _ => (), + } + + for secret in &secrets_failed { + eprintln!("{}: {}", secret.0, secret.1); + } + + if !secrets_failed.is_empty() { + bail!("Errors when attempting to delete secrets."); + } + + Ok(()) +} diff --git a/crates/bws/src/config.rs b/crates/bws/src/config.rs index 9756704f4..9fd91849e 100644 --- a/crates/bws/src/config.rs +++ b/crates/bws/src/config.rs @@ -20,7 +20,8 @@ pub(crate) struct Profile { pub server_base: Option, pub server_api: Option, pub server_identity: Option, - pub state_file_dir: Option, + pub state_dir: Option, + pub state_opt_out: Option, } impl ProfileKey { @@ -29,7 +30,8 @@ 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), + ProfileKey::state_dir => p.state_dir = Some(value), + ProfileKey::state_opt_out => p.state_opt_out = Some(value), } } } @@ -117,7 +119,8 @@ impl Profile { server_base: Some(url.to_string()), server_api: None, server_identity: None, - state_file_dir: None, + state_dir: None, + state_opt_out: None, }) } pub(crate) fn api_url(&self) -> Result { @@ -129,7 +132,7 @@ impl Profile { return Ok(format!("{base}/api")); } - bail!("Profile has no `server_base` or `server_api`") + bail!("Profile has no `server_base` or `server_api`"); } pub(crate) fn identity_url(&self) -> Result { @@ -141,7 +144,7 @@ impl Profile { return Ok(format!("{base}/identity")); } - bail!("Profile has no `server_base` or `server_identity`") + bail!("Profile has no `server_base` or `server_identity`"); } } diff --git a/crates/bws/src/main.rs b/crates/bws/src/main.rs index eb5c8304b..e77c8fd24 100644 --- a/crates/bws/src/main.rs +++ b/crates/bws/src/main.rs @@ -1,32 +1,24 @@ -use std::{path::PathBuf, process, str::FromStr}; +use std::{path::PathBuf, str::FromStr}; use bitwarden::{ auth::{login::AccessTokenLoginRequest, AccessToken}, - client::client_settings::ClientSettings, - secrets_manager::{ - projects::{ - ProjectCreateRequest, ProjectGetRequest, ProjectPutRequest, ProjectsDeleteRequest, - ProjectsListRequest, - }, - secrets::{ - SecretCreateRequest, SecretGetRequest, SecretIdentifiersByProjectRequest, - SecretIdentifiersRequest, SecretPutRequest, SecretsDeleteRequest, SecretsGetRequest, - }, - }, + ClientSettings, }; use bitwarden_cli::install_color_eyre; use clap::{CommandFactory, Parser}; -use clap_complete::Shell; use color_eyre::eyre::{bail, Result}; +use config::Profile; use log::error; -use uuid::Uuid; +use render::OutputSettings; mod cli; +mod command; mod config; mod render; mod state; +mod util; -use crate::{cli::*, render::serialize_response}; +use crate::cli::*; #[tokio::main(flavor = "current_thread")] async fn main() -> Result<()> { @@ -51,47 +43,21 @@ async fn process_commands() -> Result<()> { // These commands don't require authentication, so we process them first match command { Commands::Completions { shell } => { - let Some(shell) = shell.or_else(Shell::from_env) else { - eprintln!("Couldn't autodetect a valid shell. Run `bws completions --help` for more info."); - std::process::exit(1); - }; - - let mut cmd = Cli::command(); - let name = cmd.get_name().to_string(); - clap_complete::generate(shell, &mut cmd, name, &mut std::io::stdout()); - return Ok(()); + return command::completions(shell); } Commands::Config { name, value, delete, } => { - let profile = if let Some(profile) = cli.profile { - profile - } else if let Some(access_token) = cli.access_token { - AccessToken::from_str(&access_token)? - .access_token_id - .to_string() - } else { - String::from("default") - }; - - if delete { - config::delete_profile(cli.config_file.as_deref(), profile)?; - println!("Profile deleted successfully!"); - } else { - let (name, value) = match (name, value) { - (None, None) => bail!("Missing `name` and `value`"), - (None, Some(_)) => bail!("Missing `value`"), - (Some(_), None) => bail!("Missing `name`"), - (Some(name), Some(value)) => (name, value), - }; - - config::update_profile(cli.config_file.as_deref(), profile, name, value)?; - println!("Profile updated successfully!"); - }; - - return Ok(()); + return command::config( + name, + value, + delete, + cli.profile, + cli.access_token, + cli.config_file, + ); } _ => (), } @@ -120,23 +86,32 @@ 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 state_file = match get_state_opt_out(&profile) { + true => None, + false => match state::get_state_file( + profile.and_then(|p| p.state_dir).map(Into::into), + access_token_obj.access_token_id.to_string(), + ) { + Ok(state_file) => Some(state_file), + Err(e) => { + eprintln!("Warning: {}\nRetrieving the state file failed. Attempting to continue without using state. Please set \"state_dir\" in your config file to avoid authentication limits.", e); + None + } + }, + }; - let mut client = bitwarden::Client::new(settings); + let client = bitwarden::Client::new(settings); // Load session or return if no session exists let _ = client .auth() .login_access_token(&AccessTokenLoginRequest { access_token, - state_file: state_file_path, + state_file, }) .await?; - let organization_id = match client.get_access_token_organization() { + let organization_id = match client.internal.get_access_token_organization() { Some(id) => id, None => { error!("Access token isn't associated to an organization."); @@ -144,272 +119,44 @@ async fn process_commands() -> Result<()> { } }; + let output_settings = OutputSettings::new(cli.output, color); + // And finally we process all the commands which require authentication match command { - Commands::Project { - cmd: ProjectCommand::List, - } - | Commands::List { - cmd: ListCommand::Projects, - } => { - let projects = client - .projects() - .list(&ProjectsListRequest { organization_id }) - .await? - .data; - serialize_response(projects, cli.output, color); + Commands::Project { cmd } => { + command::project::process_command(cmd, client, organization_id, output_settings).await } - Commands::Project { - cmd: ProjectCommand::Get { project_id }, - } - | Commands::Get { - cmd: GetCommand::Project { project_id }, - } => { - let project = client - .projects() - .get(&ProjectGetRequest { id: project_id }) - .await?; - serialize_response(project, cli.output, color); + Commands::Secret { cmd } => { + command::secret::process_command(cmd, client, organization_id, output_settings).await } - Commands::Project { - cmd: ProjectCommand::Create { name }, - } - | Commands::Create { - cmd: CreateCommand::Project { name }, + Commands::Run { + command, + shell, + no_inherit_env, + project_id, + uuids_as_keynames, } => { - let project = client - .projects() - .create(&ProjectCreateRequest { - organization_id, - name, - }) - .await?; - serialize_response(project, cli.output, color); - } + let exit_code = command::run::run( + client, + organization_id, + project_id, + uuids_as_keynames, + no_inherit_env, + shell, + command, + ) + .await?; - Commands::Project { - cmd: ProjectCommand::Edit { project_id, name }, - } - | Commands::Edit { - cmd: EditCommand::Project { project_id, name }, - } => { - let project = client - .projects() - .update(&ProjectPutRequest { - id: project_id, - organization_id, - name, - }) - .await?; - serialize_response(project, cli.output, color); - } - - Commands::Project { - cmd: ProjectCommand::Delete { project_ids }, - } - | Commands::Delete { - cmd: DeleteCommand::Project { project_ids }, - } => { - let count = project_ids.len(); - - let result = client - .projects() - .delete(ProjectsDeleteRequest { ids: project_ids }) - .await?; - - let projects_failed: Vec<(Uuid, String)> = result - .data - .into_iter() - .filter_map(|r| r.error.map(|e| (r.id, e))) - .collect(); - let deleted_projects = count - projects_failed.len(); - - if deleted_projects > 1 { - println!("{} projects deleted successfully.", deleted_projects); - } else if deleted_projects == 1 { - println!("{} project deleted successfully.", deleted_projects); - } - - if projects_failed.len() > 1 { - eprintln!("{} projects had errors:", projects_failed.len()); - } else if projects_failed.len() == 1 { - eprintln!("{} project had an error:", projects_failed.len()); - } - - for project in &projects_failed { - eprintln!("{}: {}", project.0, project.1); - } - - if !projects_failed.is_empty() { - process::exit(1); - } - } - - Commands::Secret { - cmd: SecretCommand::List { project_id }, - } - | Commands::List { - cmd: ListCommand::Secrets { project_id }, - } => { - let res = if let Some(project_id) = project_id { - client - .secrets() - .list_by_project(&SecretIdentifiersByProjectRequest { project_id }) - .await? - } else { - client - .secrets() - .list(&SecretIdentifiersRequest { organization_id }) - .await? - }; - - let secret_ids = res.data.into_iter().map(|e| e.id).collect(); - let secrets = client - .secrets() - .get_by_ids(SecretsGetRequest { ids: secret_ids }) - .await? - .data; - serialize_response(secrets, cli.output, color); - } - - Commands::Secret { - cmd: SecretCommand::Get { secret_id }, - } - | Commands::Get { - cmd: GetCommand::Secret { secret_id }, - } => { - let secret = client - .secrets() - .get(&SecretGetRequest { id: secret_id }) - .await?; - serialize_response(secret, cli.output, color); - } - - Commands::Secret { - cmd: - SecretCommand::Create { - key, - value, - note, - project_id, - }, - } - | Commands::Create { - cmd: - CreateCommand::Secret { - key, - value, - note, - project_id, - }, - } => { - let secret = client - .secrets() - .create(&SecretCreateRequest { - organization_id, - key, - value, - note: note.unwrap_or_default(), - project_ids: Some(vec![project_id]), - }) - .await?; - serialize_response(secret, cli.output, color); - } - - Commands::Secret { - cmd: - SecretCommand::Edit { - secret_id, - key, - value, - note, - project_id, - }, - } - | Commands::Edit { - cmd: - EditCommand::Secret { - secret_id, - key, - value, - note, - project_id, - }, - } => { - let old_secret = client - .secrets() - .get(&SecretGetRequest { id: secret_id }) - .await?; - - let secret = client - .secrets() - .update(&SecretPutRequest { - id: secret_id, - organization_id, - key: key.unwrap_or(old_secret.key), - value: value.unwrap_or(old_secret.value), - note: note.unwrap_or(old_secret.note), - project_ids: match project_id { - Some(id) => Some(vec![id]), - None => match old_secret.project_id { - Some(id) => Some(vec![id]), - None => bail!("Editing a secret requires a project_id."), - }, - }, - }) - .await?; - serialize_response(secret, cli.output, color); - } - - Commands::Secret { - cmd: SecretCommand::Delete { secret_ids }, - } - | Commands::Delete { - cmd: DeleteCommand::Secret { secret_ids }, - } => { - let count = secret_ids.len(); - - let result = client - .secrets() - .delete(SecretsDeleteRequest { ids: secret_ids }) - .await?; - - let secrets_failed: Vec<(Uuid, String)> = result - .data - .into_iter() - .filter_map(|r| r.error.map(|e| (r.id, e))) - .collect(); - let deleted_secrets = count - secrets_failed.len(); - - if deleted_secrets > 1 { - println!("{} secrets deleted successfully.", deleted_secrets); - } else if deleted_secrets == 1 { - println!("{} secret deleted successfully.", deleted_secrets); - } - - if secrets_failed.len() > 1 { - eprintln!("{} secrets had errors:", secrets_failed.len()); - } else if secrets_failed.len() == 1 { - eprintln!("{} secret had an error:", secrets_failed.len()); - } - - for secret in &secrets_failed { - eprintln!("{}: {}", secret.0, secret.1); - } - - if !secrets_failed.is_empty() { - process::exit(1); - } + // exit with the exit code from the child process + std::process::exit(exit_code); } Commands::Config { .. } | Commands::Completions { .. } => { unreachable!() } } - - Ok(()) } fn get_config_profile( @@ -436,3 +183,13 @@ fn get_config_profile( }; Ok(profile) } + +fn get_state_opt_out(profile: &Option) -> bool { + if let Some(profile) = profile { + if let Some(state_opt_out) = &profile.state_opt_out { + return util::string_to_bool(state_opt_out).unwrap_or(false); + } + } + + false +} diff --git a/crates/bws/src/render.rs b/crates/bws/src/render.rs index 219e72b68..bf0c26f6c 100644 --- a/crates/bws/src/render.rs +++ b/crates/bws/src/render.rs @@ -4,38 +4,45 @@ use chrono::{DateTime, Utc}; use comfy_table::Table; use serde::Serialize; -use crate::cli::Output; +use crate::{cli::Output, util::is_valid_posix_name}; const ASCII_HEADER_ONLY: &str = " -- "; +pub(crate) struct OutputSettings { + pub(crate) output: Output, + pub(crate) color: Color, +} + +impl OutputSettings { + pub(crate) fn new(output: Output, color: Color) -> Self { + OutputSettings { output, color } + } +} + pub(crate) fn serialize_response, const N: usize>( data: T, - output: Output, - color: Color, + output_settings: OutputSettings, ) { - match output { + match output_settings.output { Output::JSON => { let mut text = serde_json::to_string_pretty(&data).expect("Serialize should be infallible"); // Yaml/table/tsv serializations add a newline at the end, so we do the same here for // consistency text.push('\n'); - pretty_print("json", &text, color); + pretty_print("json", &text, output_settings.color); } Output::YAML => { let text = serde_yaml::to_string(&data).expect("Serialize should be infallible"); - pretty_print("yaml", &text, color); + pretty_print("yaml", &text, output_settings.color); } Output::Env => { - let valid_key_regex = - regex::Regex::new("^[a-zA-Z_][a-zA-Z0-9_]*$").expect("regex is valid"); - let mut commented_out = false; let mut text: Vec = data .get_values() .into_iter() .map(|row| { - if valid_key_regex.is_match(&row[1]) { + if is_valid_posix_name(&row[1]) { format!("{}=\"{}\"", row[1], row[2]) } else { commented_out = true; @@ -50,7 +57,11 @@ pub(crate) fn serialize_response, const N: usiz )); } - pretty_print("sh", &format!("{}\n", text.join("\n")), color); + pretty_print( + "sh", + &format!("{}\n", text.join("\n")), + output_settings.color, + ); } Output::Table => { let mut table = Table::new(); diff --git a/crates/bws/src/state.rs b/crates/bws/src/state.rs index 9c90da81f..b5756a056 100644 --- a/crates/bws/src/state.rs +++ b/crates/bws/src/state.rs @@ -1,20 +1,32 @@ use std::path::PathBuf; -use color_eyre::eyre::Result; +use color_eyre::eyre::{bail, Result}; +use directories::BaseDirs; -pub(crate) fn get_state_file_path( - state_file_dir: Option, - access_token_id: String, -) -> Result> { - if let Some(mut state_file_path) = state_file_dir { - state_file_path.push(access_token_id); +use crate::DEFAULT_CONFIG_DIRECTORY; + +pub(crate) const DEFAULT_STATE_DIRECTORY: &str = "state"; - if let Some(parent_folder) = state_file_path.parent() { - std::fs::create_dir_all(parent_folder)?; +pub(crate) fn get_state_file( + state_dir: Option, + access_token_id: String, +) -> Result { + let mut state_dir = match state_dir { + Some(state_dir) => state_dir, + None => { + if let Some(base_dirs) = BaseDirs::new() { + base_dirs + .home_dir() + .join(DEFAULT_CONFIG_DIRECTORY) + .join(DEFAULT_STATE_DIRECTORY) + } else { + bail!("A valid home directory doesn't exist"); + } } + }; - return Ok(Some(state_file_path)); - } + std::fs::create_dir_all(&state_dir)?; + state_dir.push(access_token_id); - Ok(None) + Ok(state_dir) } diff --git a/crates/bws/src/util.rs b/crates/bws/src/util.rs new file mode 100644 index 000000000..a86f0f569 --- /dev/null +++ b/crates/bws/src/util.rs @@ -0,0 +1,93 @@ +use regex::Regex; +use uuid::Uuid; + +const VALID_POSIX_NAME_REGEX: &str = "^[a-zA-Z_][a-zA-Z0-9_]*$"; +const STRING_TO_BOOL_ERROR_MESSAGE: &str = "Could not convert string to bool"; + +pub(crate) fn is_valid_posix_name(input_text: &str) -> bool { + Regex::new(VALID_POSIX_NAME_REGEX) + .expect("VALID_POSIX_NAME_REGEX to be a valid regex") + .is_match(input_text) +} + +pub(crate) fn string_to_bool(value: &str) -> Result { + match value.trim().to_lowercase().as_str() { + "true" | "1" => Ok(true), + "false" | "0" => Ok(false), + _ => Err(STRING_TO_BOOL_ERROR_MESSAGE), + } +} + +/// Converts a UUID to a POSIX-compliant environment variable name. +/// +/// POSIX environment variable names must start with a letter or an underscore +/// and can only contain letters, numbers, and underscores. +pub(crate) fn uuid_to_posix(uuid: &Uuid) -> String { + format!("_{}", uuid.to_string().replace('-', "_")) +} + +mod tests { + #[allow(unused_imports)] + use super::*; + + #[test] + fn test_is_valid_posix_name_true() { + assert!(is_valid_posix_name("a_valid_name")); + assert!(is_valid_posix_name("another_valid_name")); + assert!(is_valid_posix_name("_another_valid_name")); + assert!(is_valid_posix_name("ANOTHER_ONE")); + assert!(is_valid_posix_name( + "abcdefghijklmnopqrstuvwxyz__ABCDEFGHIJKLMNOPQRSTUVWXYZ__0123456789" + )); + } + + #[test] + fn test_is_valid_posix_name_false() { + assert!(!is_valid_posix_name("")); + assert!(!is_valid_posix_name("1a")); + assert!(!is_valid_posix_name("a bad name")); + assert!(!is_valid_posix_name("another-bad-name")); + assert!(!is_valid_posix_name("a\nbad\nname")); + } + + #[test] + fn test_uuid_to_posix_success() { + assert_eq!( + "_759130d0_29dd_48bd_831a_e3bdbafeeb6e", + uuid_to_posix( + &uuid::Uuid::parse_str("759130d0-29dd-48bd-831a-e3bdbafeeb6e").expect("valid uuid") + ) + ); + assert!(is_valid_posix_name(&uuid_to_posix(&uuid::Uuid::new_v4()))); + } + + #[test] + fn test_string_to_bool_true_true() { + let result = string_to_bool("true"); + assert_eq!(result, Ok(true)); + } + + #[test] + fn test_string_to_bool_one_true() { + let result = string_to_bool("1"); + assert_eq!(result, Ok(true)); + } + + #[test] + fn test_string_to_bool_false_false() { + let result = string_to_bool("false"); + assert_eq!(result, Ok(false)); + } + + #[test] + fn test_string_to_bool_zero_false() { + let result = string_to_bool("0"); + assert_eq!(result, Ok(false)); + } + + #[test] + fn test_string_to_bool_bad_string_errors() { + let result = string_to_bool("hello world"); + assert_eq!(result, Err(STRING_TO_BOOL_ERROR_MESSAGE)); + } +} diff --git a/crates/memory-testing/Dockerfile b/crates/memory-testing/Dockerfile index 3804f59e0..3df22466d 100644 --- a/crates/memory-testing/Dockerfile +++ b/crates/memory-testing/Dockerfile @@ -1,7 +1,7 @@ ############################################### # Build stage # ############################################### -FROM rust:1.76 AS build +FROM rust:1.81 AS build WORKDIR /app diff --git a/crates/memory-testing/cases.json b/crates/memory-testing/cases.json index cdb718b21..5223d3671 100644 --- a/crates/memory-testing/cases.json +++ b/crates/memory-testing/cases.json @@ -72,7 +72,7 @@ "kdf": { "argon2id": { "iterations": 3, - "memory": 4, + "memory": 16, "parallelism": 1 } } @@ -80,16 +80,15 @@ "memory_lookups": [ { "name": "Key", - "hex": "3bc0520a0abff0097d521ce0ee5e5b1cee301939a84742623c0c1697d7a4bd46", - "allowed_count": 3 + "hex": "59079cd7134409c6882c2701de8357a3d8aabb2dad2da19eea5f1b8081dfb51c" }, { "name": "Hash B64", - "string": "lHkprdORlICVJ4Umwi94Uz/nATK6Y7If7e+iFoabzh0=" + "string": "P1ZT6T80zOfEqXj/kPbtON3yszf7xLNGCxWjdO2xfjU=" }, { "name": "Hash bytes", - "hex": "947929add391948095278526c22f78533fe70132ba63b21fedefa216869bce1d" + "hex": "3f5653e93f34cce7c4a978ff90f6ed38ddf2b337fbc4b3460b15a374edb17e35" } ] }, diff --git a/crates/memory-testing/src/bin/capture-dumps.rs b/crates/memory-testing/src/bin/capture-dumps.rs index 1fe4eead1..5dddd7622 100644 --- a/crates/memory-testing/src/bin/capture-dumps.rs +++ b/crates/memory-testing/src/bin/capture-dumps.rs @@ -37,6 +37,11 @@ fn wait_dump_and_continue( loop { let mut buf = [0u8; 1024]; let read = stdout.read(&mut buf).unwrap(); + + if read == 0 { + panic!("Process exited unexpectedly"); + } + let buf_str = std::str::from_utf8(&buf[..read]).unwrap(); if buf_str.contains("Waiting for dump...") { break; diff --git a/crates/memory-testing/src/main.rs b/crates/memory-testing/src/main.rs index 056ab582b..133a786ee 100644 --- a/crates/memory-testing/src/main.rs +++ b/crates/memory-testing/src/main.rs @@ -39,7 +39,7 @@ fn main() { email, kdf, } => { - let key = MasterKey::derive(password.as_bytes(), email.as_bytes(), &kdf).unwrap(); + let key = MasterKey::derive(&password, &email, &kdf).unwrap(); let hash = key .derive_master_key_hash( password.as_bytes(), diff --git a/crates/sdk-schemas/Cargo.toml b/crates/sdk-schemas/Cargo.toml index 055878b13..13a664873 100644 --- a/crates/sdk-schemas/Cargo.toml +++ b/crates/sdk-schemas/Cargo.toml @@ -12,17 +12,12 @@ license-file.workspace = true keywords.workspace = true [features] -internal = [ - "bitwarden/internal", - "bitwarden-json/internal", - "bitwarden-uniffi/docs", -] [dependencies] anyhow = "1.0.82" bitwarden = { workspace = true } bitwarden-json = { path = "../bitwarden-json" } -bitwarden-uniffi = { path = "../bitwarden-uniffi" } -itertools = "0.12.1" -schemars = { version = "0.8.16", features = ["preserve_order"] } +bitwarden-uniffi = { path = "../bitwarden-uniffi", optional = true } +itertools = "0.13.0" +schemars = { workspace = true, features = ["preserve_order"] } serde_json = "1.0.113" diff --git a/crates/sdk-schemas/src/main.rs b/crates/sdk-schemas/src/main.rs index 8a4a67592..2db3ce5fe 100644 --- a/crates/sdk-schemas/src/main.rs +++ b/crates/sdk-schemas/src/main.rs @@ -91,7 +91,7 @@ use bitwarden_json::response::Response; #[derive(JsonSchema)] struct SchemaTypes { // Input types for new Client - client_settings: bitwarden::client::client_settings::ClientSettings, + client_settings: bitwarden::ClientSettings, // Input types for Client::run_command input_command: bitwarden_json::command::Command, @@ -99,7 +99,7 @@ struct SchemaTypes { // Output types for Client::run_command api_key_login: Response, password_login: Response, - access_token_login: Response, + login_access_token: Response, secret_identifiers: Response, secret: Response, secrets: Response, @@ -108,20 +108,11 @@ struct SchemaTypes { project: Response, projects: Response, projects_delete: Response, - - #[cfg(feature = "internal")] - fingerprint: Response, - #[cfg(feature = "internal")] - sync: Response, - #[cfg(feature = "internal")] - user_api_key: Response, + password: Response, } fn main() -> Result<()> { write_schema_for!("schema_types", SchemaTypes); - #[cfg(feature = "internal")] - write_schema_for!(bitwarden_uniffi::docs::DocRef); - Ok(()) } diff --git a/crates/uniffi-bindgen/Cargo.toml b/crates/uniffi-bindgen/Cargo.toml index a3203ea4a..b5401afb9 100644 --- a/crates/uniffi-bindgen/Cargo.toml +++ b/crates/uniffi-bindgen/Cargo.toml @@ -17,4 +17,4 @@ name = "uniffi-bindgen" path = "uniffi-bindgen.rs" [dependencies] -uniffi = { version = "=0.27.2", features = ["cli"] } +uniffi = { workspace = true, features = ["cli"] } diff --git a/languages/cpp/CMakeBuild.md b/languages/cpp/CMakeBuild.md index 4c7c29814..b75da5c17 100644 --- a/languages/cpp/CMakeBuild.md +++ b/languages/cpp/CMakeBuild.md @@ -1,33 +1,61 @@ -# CMAKE build +# CMake Build -## INTRODUCTION +## 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`. +Cmake is used to build the C++ Bitwarden client library. Output should be placed in the build directory. +The output contains two dynamic libraries: -## PREREQUISITES +- The C++ client `BitwardenClient` +- The Bitwarden library used by the C++ client `bitwarden_c`. + +See how to use these libraries in the [example use guide](./examples/ExampleUse.md) + +## Prerequisites - Cmake installed, minimum version 3.15 - `schemas.hpp` generated into `include` directory - installed `nlohmann-json` library - installed `boost` library -## BUILD commands +## 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: -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: +```bash +mkdir -p build +cd build +cmake .. -DNLOHMANN=/path/to/include/nlohmann -DBOOST=/path/to/include/boost -DTARGET=relative/path/to/libbitwarden_c +cmake --build . +``` -$ mkdir build -$ cd build -$ cmake .. -DNLOHMANN=/path/to/include/nlohmann -DBOOST=/path/to/include/boost -DTARGET=relative/path/to/libbitwarden_c -$ cmake --build . +## IDE Support +You may need to manually set the CMake `TARGET` variable for your IDE. For CLion, add the following to the CMake options +settings: +```bash +# macOS example +-DTARGET=../../target/release/libbitwarden_c.dylib +``` ## Example -macOS: +### macOS + +#### Install Prerequisites + +```bash +brew install cmake +brew install boost +brew install nlohmann-json +``` -$ mkdir build -$ cd build -$ cmake .. -DNLOHMANN=/opt/hombrew/include -DBOOST=/opt/homebrew/include -DTARGET=../../target/release/libbitwarden_c.dylib -$ cmake --build . +#### Build +```bash +mkdir -p build +cd build +cmake .. -DNLOHMANN=/opt/homebrew/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 index e513a32ed..e6ad7f4f3 100644 --- a/languages/cpp/CMakeLists.txt +++ b/languages/cpp/CMakeLists.txt @@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.15) project(BitwardenClient) set(CMAKE_CXX_STANDARD 20) +set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) # Set placeholders to be passed from command line set(NLOHMANN_JSON_INCLUDE_DIR_PLACEHOLDER ${NLOHMANN}) diff --git a/languages/cpp/ExampleUse.md b/languages/cpp/ExampleUse.md deleted file mode 100644 index 579cd0e67..000000000 --- a/languages/cpp/ExampleUse.md +++ /dev/null @@ -1,70 +0,0 @@ -# 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 index 23b59ac76..fb714a20e 100644 --- a/languages/cpp/README.md +++ b/languages/cpp/README.md @@ -22,9 +22,10 @@ bitwardenSettings.set_identity_url(""); ```c++ std::string accessToken = ""; +std::string stateFile = ""; // Optional - argument in BitwardenClient BitwardenClient bitwardenClient = BitwardenClient(bitwardenSettings); -bitwardenClient.accessTokenLogin(accessToken); +bitwardenClient.loginAccessToken(accessToken, stateFile); ``` ### Create new project @@ -32,6 +33,7 @@ bitwardenClient.accessTokenLogin(accessToken); ```c++ boost::uuids::uuid organizationUuid = boost::uuids::string_generator()(""); ProjectResponse projectResponseCreate = bitwardenClient.createProject(organizationUuid, "TestProject"); +boost::uuids::uuid projectId = boost::uuids::string_generator()(projectResponseCreate.get_id()); ``` ### List all projects @@ -43,21 +45,19 @@ ProjectsResponse projectResponseList = bitwardenClient.listProjects(organization ### 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"); +ProjectResponse projectResponseUpdate = bitwardenClient.updateProject(organizationUuid, projectId, "TestProjectUpdated"); ``` ### Delete projects ```c++ -SecretsDeleteResponse secretsDeleteResponse = bitwardenClient.deleteSecrets({secretId}); +ProjectsDeleteResponse projectsDeleteResponse = bitwardenClient.deleteProjects({projectId}); ``` ### Add new secret @@ -66,7 +66,8 @@ SecretsDeleteResponse secretsDeleteResponse = bitwardenClient.deleteSecrets({sec std::string key = "key"; std::string value = "value"; std::string note = "note"; -SecretResponse secretResponseCreate = bitwardenClient.createSecret(key, value, note, organizationUuid, {projectId}); +SecretResponse secretResponseCreate = bitwardenClient.createSecret(organizationUuid, key, value, note, {projectId}); +boost::uuids::uuid secretId = boost::uuids::string_generator()(secretResponseCreate.get_id()); ``` ### List secrets @@ -77,14 +78,28 @@ SecretIdentifiersResponse secretIdentifiersResponse = bitwardenClient.listSecret ### Get secret details -``` -boost::uuids::uuid secretId = boost::uuids::string_generator()(secretResponseCreate.get_id()); +```c++ SecretResponse secretResponseGet = bitwardenClient.getSecret(secretId); ``` +### Get multiple secrets by ids + +```c++ +std::vector secretIds = {secretId, secretId2}; +SecretsResponse secretsResponseGet = bitwardenClient.getSecrets(secretIds); +``` + ### Update secret + +```c++ +SecretResponse secretResponseUpdate = bitwardenClient.updateSecret(organizationUuid, secretId, "key2", "value2", "note2", {projectId}); +``` + +### Sync secrets + ```c++ -SecretResponse secretResponseUpdate = bitwardenClient.updateSecret(secretId, "key2", "value2", "note2", organizationUuid, {projectId}); +std::chrono::system_clock::time_point lastSyncedDate = std::chrono::system_clock::now(); +SecretsSyncResponse secretsSyncResponse = bitwardenClient.sync(orgnizationUuid, lastSyncedDate); ``` # Delete secrets diff --git a/languages/cpp/examples/ExampleUse.md b/languages/cpp/examples/ExampleUse.md new file mode 100644 index 000000000..f5ec6f6ff --- /dev/null +++ b/languages/cpp/examples/ExampleUse.md @@ -0,0 +1,108 @@ +# Examples + +## Prerequisites + +### Bitwarden Libraries + +Have the two Bitwarden libraries at the same path: + +- `BitwardenClient` +- `bitwarden_c` + +For each OS the library files will be the following: + +- macOS: `libBitwardenClient.dylib` and `libbitwarden_c.dylib` +- Linux: `libBitwardenClient.so` and `libbitwarden_c.so` +- Windows: `BitwardenClient.dll` and `bitwarden_c.dll` + +Follow the [cmake build guide](../CMakeBuild.md) to create the libraries locally. + +### Include Directory + +`include` directory contains: + +- `BitwardenLibrary.h` +- `BitwardenClient.h` +- `BitwardenSettings.h` +- `CommandRunner.h` +- `Projects.h` +- `Secrets.h` +- `schemas.hpp` + +### Other Libraries + +- `nlohmann-json` () +- `boost` () + +### Compiling + +Use g++/clang++ for compiling. + +Example of the folder structure (macOS): + +```text +--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` +``` + +Set the environment variable path for the Bitwarden libraries. + +For macOS: + +```bash +export DYLD_LIBRARY_PATH=/path/to/your/library:$DYLD_LIBRARY_PATH +``` + +For Linux: + +```bash +export LD_LIBRARY_PATH=/path/to/your/library:$LD_LIBRARY_PATH +``` + +For Windows: + +```shell + set "PATH=%PATH%;C:\path\to\your\library" +``` + +Set environment variables used in `Wrapper.cpp`: + +```bash +export ACCESS_TOKEN=<"access-token"> +export ORGANIZATION_ID=<"organization-id"> +export API_URL=http://localhost:4000 +export IDENTITY_URL=http://localhost:33656 +``` + +Compile: + +```bash +cd examples +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, + +for macOS nlohmann and boost libraries installed with homebrew the following can be used: + +```bash +-I/opt/homebrew/include +``` + +The result is `MyBitwardenApp` in the `examples` directory, and can be ran from the `examples` directory: + +```bash +./MyBitwardenApp +``` diff --git a/languages/cpp/examples/Wrapper.cpp b/languages/cpp/examples/Wrapper.cpp index e94980698..7790adfb0 100644 --- a/languages/cpp/examples/Wrapper.cpp +++ b/languages/cpp/examples/Wrapper.cpp @@ -1,14 +1,18 @@ #include "BitwardenClient.h" #include #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"); + 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"); + // Use optional state file for authentication + const char *stateFile = std::getenv("STATE_FILE"); + + 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; @@ -18,57 +22,110 @@ int main() { std::string accessToken = accessTokenEnv; std::string organizationId = organizationIdEnv; - // Configuring the URLS is optional, remove them to use the default values + // Configuring the URLS is optional; if unset, use bitwarden.com BitwardenSettings bitwardenSettings; - bitwardenSettings.set_api_url(apiUrl); - bitwardenSettings.set_identity_url(identityUrl); + if (apiUrl != nullptr && identityUrl != nullptr) { + bitwardenSettings.set_api_url(apiUrl); + bitwardenSettings.set_identity_url(identityUrl); + } else { + std::cerr << "Info: API_URL and IDENTITY_URL are not set, using default values..." << std::endl; + } // Create a Bitwarden client instance - BitwardenClient bitwardenClient = BitwardenClient(bitwardenSettings); - // // Access token login - bitwardenClient.accessTokenLogin(accessToken); - // Organization ID - boost::uuids::uuid organizationUuid = boost::uuids::string_generator()(organizationId); - - // // Create a new project + BitwardenClient bitwardenClient(bitwardenSettings); + + // Access token login + if (stateFile != nullptr) { + bitwardenClient.loginAccessToken(accessToken, stateFile); + } else { + bitwardenClient.loginAccessToken(accessToken); + } + + // Convert organization ID to UUID + boost::uuids::uuid organizationUuid = boost::uuids::string_generator()(organizationId); + + // Create a new project + std::cout << "Projects:\n"; ProjectResponse projectResponseCreate = bitwardenClient.createProject(organizationUuid, "NewTestProject"); boost::uuids::uuid projectId = boost::uuids::string_generator()(projectResponseCreate.get_id()); - + + std::cout << "\tcreateProject: '" << projectResponseCreate.get_name() << "'\n\n"; + // List projects ProjectsResponse projectResponseList = bitwardenClient.listProjects(organizationUuid); + std::cout << "\tlistProjects:\n"; + for (const ProjectResponse& project : projectResponseList.get_data()) { + std::cout << "\t\tID: '" << project.get_id() << "', Name: '" << project.get_name() << "'\n"; + } + std::cout << '\n'; // Get project details ProjectResponse projectResponseGet = bitwardenClient.getProject(projectId); + std::cout << "\tgetProject:\n\t\tID: '" << projectResponseGet.get_id() << "', Name: '" << projectResponseGet.get_name() << "'\n\n"; // Update project - ProjectResponse ProjectResponseUpdate = bitwardenClient.updateProject(projectId, organizationUuid, "NewTestProject2"); + ProjectResponse projectResponseUpdate = bitwardenClient.updateProject(organizationUuid, projectId, "NewTestProject2"); + std::cout << "\tupdateProject: '" << projectResponseUpdate.get_name() << "'\n\n"; // Secrets std::string key = "key"; std::string value = "value"; std::string note = "note"; + // Sync secrets + std::cout << "Secrets:\n"; + std::cout << "\tSyncing secrets...\n"; + SecretsSyncResponse secretsSyncResponse = bitwardenClient.sync(organizationUuid, {}); + std::chrono::system_clock::time_point lastSyncedDate = std::chrono::system_clock::now(); + std::cout << "\t\tSync has changes: '" << (secretsSyncResponse.get_has_changes() ? "true" : "false") << "'\n\n"; + + std::cout << "\tSyncing again to ensure no changes since last sync...\n"; + secretsSyncResponse = bitwardenClient.sync(organizationUuid, lastSyncedDate); + std::cout << "\t\tSync has changes: '" << (secretsSyncResponse.get_has_changes() ? "true" : "false") << "'\n\n"; + // Create a new secret - SecretResponse secretResponseCreate = bitwardenClient.createSecret(key, value, note, organizationUuid, {projectId}); + SecretResponse secretResponseCreate = bitwardenClient.createSecret(organizationUuid, key, value, note, {projectId}); boost::uuids::uuid secretId = boost::uuids::string_generator()(secretResponseCreate.get_id()); + std::cout << "\tcreateSecret: '" << secretResponseCreate.get_key() << "'\n\n"; + // List secrets SecretIdentifiersResponse secretIdentifiersResponse = bitwardenClient.listSecrets(organizationUuid); + std::cout << "\tlistSecrets:\n"; + for (const SecretIdentifierResponse& secretIdentifier : secretIdentifiersResponse.get_data()) { + std::cout << "\t\tID: '" << secretIdentifier.get_id() << "'\n"; + } + std::cout << '\n'; // Get secret details SecretResponse secretResponseGet = bitwardenClient.getSecret(secretId); + std::cout << "\tgetSecret: '" << secretResponseGet.get_key() << "'\n\n"; + + // Get secrets by IDs + std::cout << "\tgetSecretsByIds:\n"; + SecretsResponse secretsResponseGetByIds = bitwardenClient.getSecretsByIds({secretId}); + for (const SecretResponse& secret : secretsResponseGetByIds.get_data()) { + std::cout << "\t\tID: '" << secret.get_id() << "', Key: '" << secret.get_key() << "'\n"; + } + std::cout << '\n'; // Update secret - key = "key2"; - value = "value2"; - note = "note2"; - SecretResponse responseForSecretResponseUpdate = bitwardenClient.updateSecret(secretId, key, value, note, organizationUuid, {projectId}); + key = "updated-key"; + value = "updated-value"; + note = "updated-note"; + SecretResponse responseForSecretResponseUpdate = bitwardenClient.updateSecret( + organizationUuid, secretId, key, value, note, {projectId}); + + std::cout << "\tupdateSecret: '" << responseForSecretResponseUpdate.get_key() << "'\n\n"; // Delete secrets + std::cout << "Deleting projects and secrets...\n"; SecretsDeleteResponse secretsDeleteResponse = bitwardenClient.deleteSecrets({secretId}); + std::cout << "\tdeleteSecrets: '" << secretsDeleteResponse.get_data()[0].get_id() << "'\n\n"; // Delete projects ProjectsDeleteResponse projectsDeleteResponse = bitwardenClient.deleteProjects({projectId}); + std::cout << "\tdeleteProjects: '" << projectsDeleteResponse.get_data()[0].get_id() << "'\n\n"; return 0; } diff --git a/languages/cpp/include/BitwardenClient.h b/languages/cpp/include/BitwardenClient.h index a5cf72475..2910c49b7 100644 --- a/languages/cpp/include/BitwardenClient.h +++ b/languages/cpp/include/BitwardenClient.h @@ -9,20 +9,22 @@ class BitwardenClient { public: - BitwardenClient(const BitwardenSettings& bitwardenSettings = BitwardenSettings()); + explicit BitwardenClient(const BitwardenSettings& bitwardenSettings = BitwardenSettings()); ~BitwardenClient(); - - void accessTokenLogin(const std::string& accessToken); + + void loginAccessToken(const std::string& accessToken, const std::string& stateFile = ""); 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); + ProjectResponse updateProject(const boost::uuids::uuid& organizationId, const boost::uuids::uuid& id, 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); + SecretsResponse getSecretsByIds(const std::vector& ids); + SecretResponse createSecret(const boost::uuids::uuid& organizationId, const std::string& key, const std::string& value, const std::string& note, const std::vector& projectIds); + SecretResponse updateSecret(const boost::uuids::uuid& organizationId, const boost::uuids::uuid& id, const std::string& key, const std::string& value, const std::string& note, const std::vector& projectIds); SecretsDeleteResponse deleteSecrets(const std::vector& ids); SecretIdentifiersResponse listSecrets(const boost::uuids::uuid& organizationId); + SecretsSyncResponse sync(const boost::uuids::uuid &organizationId, const std::chrono::system_clock::time_point &lastSyncedDate); private: BitwardenLibrary* library; diff --git a/languages/cpp/include/Projects.h b/languages/cpp/include/Projects.h index 9bef19b9c..27511c327 100644 --- a/languages/cpp/include/Projects.h +++ b/languages/cpp/include/Projects.h @@ -10,7 +10,7 @@ class Projects { 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); + ProjectResponse update(const boost::uuids::uuid& organizationId, const boost::uuids::uuid& id, const std::string& name); ProjectsDeleteResponse deleteProjects(const std::vector& ids); ProjectsResponse list(const boost::uuids::uuid& organizationId); diff --git a/languages/cpp/include/Secrets.h b/languages/cpp/include/Secrets.h index 024ec3692..5c5a3275c 100644 --- a/languages/cpp/include/Secrets.h +++ b/languages/cpp/include/Secrets.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include #include #include "CommandRunner.h" @@ -9,10 +11,12 @@ class Secrets { 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); + SecretsResponse getByIds(const std::vector &ids); + SecretResponse create(const boost::uuids::uuid& organizationId, const std::string& key, const std::string& value, const std::string& note, const std::vector& projectIds); + SecretResponse update(const boost::uuids::uuid& organizationId, const boost::uuids::uuid& id, const std::string& key, const std::string& value, const std::string& note, const std::vector& projectIds); SecretsDeleteResponse deleteSecrets(const std::vector& ids); SecretIdentifiersResponse list(const boost::uuids::uuid& organizationId); + SecretsSyncResponse sync(const boost::uuids::uuid& organizationId, const boost::optional& lastSyncedDate); private: CommandRunner* commandRunner; diff --git a/languages/cpp/src/BitwardenClient.cpp b/languages/cpp/src/BitwardenClient.cpp index fef9ea267..2d11977d4 100644 --- a/languages/cpp/src/BitwardenClient.cpp +++ b/languages/cpp/src/BitwardenClient.cpp @@ -50,11 +50,12 @@ BitwardenClient::~BitwardenClient() { } } -void BitwardenClient::accessTokenLogin(const std::string& accessToken) { +void BitwardenClient::loginAccessToken(const std::string& accessToken, const std::string& stateFile) { Command command; AccessTokenLoginRequest accessTokenLoginRequest; accessTokenLoginRequest.set_access_token(accessToken); - command.set_access_token_login(accessTokenLoginRequest); + accessTokenLoginRequest.set_state_file(stateFile); + command.set_login_access_token(accessTokenLoginRequest); auto deserializer = [](const char* response) -> ResponseForApiKeyLoginResponse { nlohmann::json jsonResponse = nlohmann::json::parse(response); @@ -84,11 +85,11 @@ ProjectResponse BitwardenClient::createProject(const boost::uuids::uuid& organiz return projects.create(organizationId, name); } -ProjectResponse BitwardenClient::updateProject(const boost::uuids::uuid& id, const boost::uuids::uuid& organizationId, const std::string& name){ +ProjectResponse BitwardenClient::updateProject(const boost::uuids::uuid& organizationId, const boost::uuids::uuid& id, const std::string& name){ if (!isClientOpen) { throw std::runtime_error("Client is not open."); } - return projects.update(id, organizationId, name); + return projects.update(organizationId, id, name); } ProjectsDeleteResponse BitwardenClient::deleteProjects(const std::vector& ids) { @@ -114,18 +115,25 @@ SecretResponse BitwardenClient::getSecret(const boost::uuids::uuid& id){ 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){ +SecretsResponse BitwardenClient::getSecretsByIds(const std::vector& ids){ if (!isClientOpen) { throw std::runtime_error("Client is not open."); } - return secrets.create(key, value, note, organizationId, projectIds); + return secrets.getByIds(ids); } -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){ +SecretResponse BitwardenClient::createSecret(const boost::uuids::uuid& organizationId, const std::string& key, const std::string& value, const std::string& note, const std::vector& projectIds){ if (!isClientOpen) { throw std::runtime_error("Client is not open."); } - return secrets.update(id, key, value, note, organizationId, projectIds); + return secrets.create(organizationId, key, value, note, projectIds); +} + +SecretResponse BitwardenClient::updateSecret(const boost::uuids::uuid& organizationId, const boost::uuids::uuid& id, const std::string& key, const std::string& value, const std::string& note, const std::vector& projectIds){ + if (!isClientOpen) { + throw std::runtime_error("Client is not open."); + } + return secrets.update(organizationId, id, key, value, note, projectIds); } SecretsDeleteResponse BitwardenClient::deleteSecrets(const std::vector& ids) { @@ -143,3 +151,10 @@ SecretIdentifiersResponse BitwardenClient::listSecrets(const boost::uuids::uuid return secrets.list(organizationId); } + +SecretsSyncResponse BitwardenClient::sync(const boost::uuids::uuid &organizationId, const std::chrono::system_clock::time_point &lastSyncedDate) { + if (!isClientOpen) { + throw std::runtime_error("Client is not open."); + } + return secrets.sync(organizationId, lastSyncedDate); +} diff --git a/languages/cpp/src/Projects.cpp b/languages/cpp/src/Projects.cpp index d0aa6ed49..b2fa1c688 100644 --- a/languages/cpp/src/Projects.cpp +++ b/languages/cpp/src/Projects.cpp @@ -67,7 +67,7 @@ ProjectResponse Projects::create(const boost::uuids::uuid& organizationId, const } } -ProjectResponse Projects::update(const boost::uuids::uuid& id, const boost::uuids::uuid& organizationId, const std::string& name) { +ProjectResponse Projects::update(const boost::uuids::uuid& organizationId, const boost::uuids::uuid& id, const std::string& name) { Command command; ProjectsCommand projectsCommand; ProjectPutRequest projectPutRequest; diff --git a/languages/cpp/src/Secrets.cpp b/languages/cpp/src/Secrets.cpp index e153ea7f1..1fff9441f 100644 --- a/languages/cpp/src/Secrets.cpp +++ b/languages/cpp/src/Secrets.cpp @@ -1,8 +1,8 @@ #include "Secrets.h" #include #include -#include -#include +#include +#include Secrets::Secrets(CommandRunner* commandRunner) : commandRunner(commandRunner) {} @@ -13,6 +13,13 @@ auto secretsDeserializer = [](const std::string& response) -> ResponseForSecretR return secretResponse; }; +auto secretsByIdsDeserializer = [](const std::string& response) -> ResponseForSecretsResponse { + nlohmann::json jsonResponse = nlohmann::json::parse(response); + ResponseForSecretsResponse secretsResponse; + Bitwarden::Sdk::from_json(jsonResponse, secretsResponse); + return secretsResponse; +}; + auto deleteSecretsDeserializer = [](const std::string& response) -> ResponseForSecretsDeleteResponse { nlohmann::json jsonResponse = nlohmann::json::parse(response); ResponseForSecretsDeleteResponse deleteSecretsResponse; @@ -27,6 +34,13 @@ auto secretListDeserializer = [](const std::string& response) -> ResponseForSecr return listResponse; }; +auto secretsSyncDeserializer = [](const std::string& response) -> ResponseForSecretsSyncResponse { + nlohmann::json jsonResponse = nlohmann::json::parse(response); + ResponseForSecretsSyncResponse syncResponse; + Bitwarden::Sdk::from_json(jsonResponse, syncResponse); + return syncResponse; +}; + SecretResponse Secrets::get(const boost::uuids::uuid& id) { Command command; SecretsCommand secretsCommand; @@ -46,7 +60,29 @@ SecretResponse Secrets::get(const boost::uuids::uuid& id) { } } -SecretResponse Secrets::create(const std::string& key, const std::string& value, const std::string& note, const boost::uuids::uuid& organizationId, const std::vector& projectIds) { +SecretsResponse Secrets::getByIds(const std::vector& ids) { + Command command; + SecretsCommand secretsCommand; + SecretsGetRequest secretsGetRequest; + + std::vector idsStr; + for (const auto& id : ids) { + idsStr.push_back(boost::uuids::to_string(id)); + } + secretsGetRequest.set_ids(idsStr); + + secretsCommand.set_get_by_ids(secretsGetRequest); + command.set_secrets(secretsCommand); + + try { + return commandRunner->runCommand(command, secretsByIdsDeserializer); + } catch (const std::exception& ex) { + std::cerr << "Error in getSecretsByIds: " << ex.what() << std::endl; + throw ex; + } +} + +SecretResponse Secrets::create(const boost::uuids::uuid& organizationId, const std::string& key, const std::string& value, const std::string& note, const std::vector& projectIds) { Command command; SecretsCommand secretsCommand; SecretCreateRequest secretCreateRequest; @@ -75,7 +111,7 @@ SecretResponse Secrets::create(const std::string& key, const std::string& value, } } -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) { +SecretResponse Secrets::update(const boost::uuids::uuid& organizationId, const boost::uuids::uuid& id, const std::string& key, const std::string& value, const std::string& note, const std::vector& projectIds) { Command command; SecretsCommand secretsCommand; SecretPutRequest secretPutRequest; @@ -147,3 +183,48 @@ SecretIdentifiersResponse Secrets::list(const boost::uuids::uuid& organizationId throw ex; } } + +SecretsSyncResponse Secrets::sync(const boost::uuids::uuid& organizationId, const boost::optional& lastSyncedDate) { + Command command; + SecretsCommand secretsCommand; + SecretsSyncRequest secretsSyncRequest; + + std::string orgIdStr = boost::uuids::to_string(organizationId); + secretsSyncRequest.set_organization_id(orgIdStr); + + if (lastSyncedDate.has_value()) { + auto timePoint = lastSyncedDate.value(); + + // Get time as time_t and milliseconds + auto timeT = std::chrono::system_clock::to_time_t(timePoint); + auto milliseconds = std::chrono::duration_cast(timePoint.time_since_epoch()) % 1000; + + // Convert to a tm struct + std::tm tm = *std::gmtime(&timeT); + + // Create a string stream to format the date and time + std::stringstream dateStream; + dateStream << std::put_time(&tm, "%Y-%m-%dT%H:%M:%S"); + + // Add milliseconds + dateStream << '.' << std::setw(3) << std::setfill('0') << milliseconds.count() << 'Z'; + + // Convert to string + std::string dateStr = dateStream.str(); + + // Set the last synced date + secretsSyncRequest.set_last_synced_date(dateStr); + } else { + secretsSyncRequest.set_last_synced_date(boost::none); + } + + secretsCommand.set_sync(secretsSyncRequest); + command.set_secrets(secretsCommand); + + try { + return commandRunner->runCommand(command, secretsSyncDeserializer); + } catch (const std::exception& ex) { + std::cerr << "Error in syncSecrets: " << ex.what() << std::endl; + throw ex; + } +} diff --git a/languages/cpp/vcpkg.json b/languages/cpp/vcpkg.json index 06a7b968c..8e5b968b9 100644 --- a/languages/cpp/vcpkg.json +++ b/languages/cpp/vcpkg.json @@ -1,10 +1,11 @@ { - "name": "bitwarden-sdk-secrets", - "version": "0.1.0", - "homepage": "https://github.com/bitwarden/sdk/tree/languages/cpp", - "description": "Bitwarden Secrets Manager SDK for C++", - "dependencies": [ - "boost", - "nlohmann-json" - ] + "name": "bitwarden-sdk-secrets", + "version": "0.1.0", + "homepage": "https://github.com/bitwarden/sdk/tree/languages/cpp", + "description": "Bitwarden Secrets Manager SDK for C++", + "dependencies": [ + "boost-uuid", + "boost-optional", + "nlohmann-json" + ] } diff --git a/languages/csharp/Bitwarden.Sdk.Samples/Program.cs b/languages/csharp/Bitwarden.Sdk.Samples/Program.cs index ee6834979..e80d12017 100644 --- a/languages/csharp/Bitwarden.Sdk.Samples/Program.cs +++ b/languages/csharp/Bitwarden.Sdk.Samples/Program.cs @@ -1,30 +1,73 @@ using Bitwarden.Sdk; -// Configure secrets +// Get environment variables +var identityUrl = Environment.GetEnvironmentVariable("IDENTITY_URL")!; +var apiUrl = Environment.GetEnvironmentVariable("API_URL")!; +var organizationId = Guid.Parse(Environment.GetEnvironmentVariable("ORGANIZATION_ID")!); var accessToken = Environment.GetEnvironmentVariable("ACCESS_TOKEN")!; -var organizationIdString = Environment.GetEnvironmentVariable("ORGANIZATION_ID")!; -var organizationId = Guid.Parse(organizationIdString); +var stateFile = Environment.GetEnvironmentVariable("STATE_FILE")!; -// Create SDK Client -using var bitwardenClient = new BitwardenClient(); +// Create the SDK Client +using var bitwardenClient = new BitwardenClient(new BitwardenSettings +{ + ApiUrl = apiUrl, + IdentityUrl = identityUrl +}); // Authenticate -bitwardenClient.AccessTokenLogin(accessToken); +bitwardenClient.Auth.LoginAccessToken(accessToken, stateFile); -// Project operations +// Projects List +var projectsList = bitwardenClient.Projects.List(organizationId).Data; +Console.WriteLine("A list of all projects:"); +foreach (ProjectResponse pr in projectsList) +{ + Console.WriteLine(" Project: " + pr.Name); +} + +Console.Write("Press enter to continue..."); +Console.ReadLine(); + +// Projects Create, Update, & Get +Console.WriteLine("Creating and updating a project"); var projectResponse = bitwardenClient.Projects.Create(organizationId, "NewTestProject"); -var projectsResponse = bitwardenClient.Projects.List(organizationId); -var projectId = projectResponse.Id; -projectResponse = bitwardenClient.Projects.Get(projectId); -projectResponse = bitwardenClient.Projects.Update(projectId, organizationId, "NewTestProject2"); - -// Secret operations -var secretResponse = - bitwardenClient.Secrets.Create("key", "value", "note", organizationId, new[] { projectId }); -var secretId = secretResponse.Id; -var secretIdentifiersResponse = bitwardenClient.Secrets.List(organizationId); -secretResponse = bitwardenClient.Secrets.Get(secretId); -secretResponse = bitwardenClient.Secrets - .Update(secretId, "key2", "value2", "note2", organizationId, new[] { projectId }); -bitwardenClient.Secrets.Delete(new[] { secretId }); -bitwardenClient.Projects.Delete(new[] { projectId }); +projectResponse = bitwardenClient.Projects.Update(organizationId, projectResponse.Id, "NewTestProject Renamed"); +projectResponse = bitwardenClient.Projects.Get(projectResponse.Id); +Console.WriteLine("Here is the project we created and updated:"); +Console.WriteLine(projectResponse.Name); + +Console.Write("Press enter to continue..."); +Console.ReadLine(); + +// Secrets list +var secretsList = bitwardenClient.Secrets.List(organizationId).Data; +Console.WriteLine("A list of all secrets:"); +foreach (SecretIdentifierResponse sr in secretsList) +{ + Console.WriteLine(" Secret: " + sr.Key); +} + +Console.Write("Press enter to continue..."); +Console.ReadLine(); + +// Secrets Create, Update, Get +Console.WriteLine("Creating and updating a secret"); +var secretResponse = bitwardenClient.Secrets.Create(organizationId, "New Secret", "the secret value", "the secret note", new[] { projectResponse.Id }); +secretResponse = bitwardenClient.Secrets.Update(organizationId, secretResponse.Id, "New Secret Name", "the secret value", "the secret note", new[] { projectResponse.Id }); +secretResponse = bitwardenClient.Secrets.Get(secretResponse.Id); +Console.WriteLine("Here is the secret we created and updated:"); +Console.WriteLine(secretResponse.Key); + +Console.Write("Press enter to continue..."); +Console.ReadLine(); + +// Secrets GetByIds +var secretsResponse = bitwardenClient.Secrets.GetByIds(new[] { secretResponse.Id }); + +// Secrets Sync +var syncResponse = bitwardenClient.Secrets.Sync(organizationId, null); + +// Secrets & Projects Delete +Console.WriteLine("Deleting our secret and project"); +bitwardenClient.Secrets.Delete(new[] { secretResponse.Id }); +bitwardenClient.Projects.Delete(new[] { projectResponse.Id }); diff --git a/languages/csharp/Bitwarden.Sdk/AuthClient.cs b/languages/csharp/Bitwarden.Sdk/AuthClient.cs new file mode 100644 index 000000000..e801f2aee --- /dev/null +++ b/languages/csharp/Bitwarden.Sdk/AuthClient.cs @@ -0,0 +1,21 @@ +namespace Bitwarden.Sdk; + +public class AuthClient +{ + private readonly CommandRunner _commandRunner; + + internal AuthClient(CommandRunner commandRunner) + { + _commandRunner = commandRunner; + } + + public void LoginAccessToken(string accessToken, string stateFile = "") + { + var command = new Command { LoginAccessToken = new AccessTokenLoginRequest { AccessToken = accessToken, StateFile = stateFile } }; + var response = _commandRunner.RunCommand(command); + if (response is not { Success: true }) + { + throw new BitwardenAuthException(response != null ? response.ErrorMessage : "Login failed"); + } + } +} diff --git a/languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj b/languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj index 35770eedf..a7c82e4b5 100644 --- a/languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj +++ b/languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj @@ -25,7 +25,7 @@ - + diff --git a/languages/csharp/Bitwarden.Sdk/BitwardenClient.cs b/languages/csharp/Bitwarden.Sdk/BitwardenClient.cs index cb352d84f..2f10e0cf9 100644 --- a/languages/csharp/Bitwarden.Sdk/BitwardenClient.cs +++ b/languages/csharp/Bitwarden.Sdk/BitwardenClient.cs @@ -18,21 +18,14 @@ public BitwardenClient(BitwardenSettings? settings = null) _commandRunner = new CommandRunner(_handle); Projects = new ProjectsClient(_commandRunner); Secrets = new SecretsClient(_commandRunner); - } - - public void AccessTokenLogin(string accessToken) - { - var command = new Command { AccessTokenLogin = new AccessTokenLoginRequest { AccessToken = accessToken } }; - var response = _commandRunner.RunCommand(command); - if (response is not { Success: true }) - { - throw new BitwardenAuthException(response != null ? response.ErrorMessage : "Login failed"); - } + Auth = new AuthClient(_commandRunner); } public ProjectsClient Projects { get; } public SecretsClient Secrets { get; } + public AuthClient Auth { get; set; } + public void Dispose() => _handle.Dispose(); } diff --git a/languages/csharp/Bitwarden.Sdk/ProjectsClient.cs b/languages/csharp/Bitwarden.Sdk/ProjectsClient.cs index 54649b989..47a419364 100644 --- a/languages/csharp/Bitwarden.Sdk/ProjectsClient.cs +++ b/languages/csharp/Bitwarden.Sdk/ProjectsClient.cs @@ -41,7 +41,7 @@ public ProjectResponse Create(Guid organizationId, string name) throw new BitwardenException(result != null ? result.ErrorMessage : "Project create failed"); } - public ProjectResponse Update(Guid id, Guid organizationId, string name) + public ProjectResponse Update(Guid organizationId, Guid id, string name) { var command = new Command { diff --git a/languages/csharp/Bitwarden.Sdk/SecretsClient.cs b/languages/csharp/Bitwarden.Sdk/SecretsClient.cs index fafdca2ab..5dd77fc6b 100644 --- a/languages/csharp/Bitwarden.Sdk/SecretsClient.cs +++ b/languages/csharp/Bitwarden.Sdk/SecretsClient.cs @@ -22,8 +22,20 @@ public SecretResponse Get(Guid id) throw new BitwardenException(result != null ? result.ErrorMessage : "Secret not found"); } - public SecretResponse Create(string key, string value, string note, Guid organizationId, - Guid[] projectIds) + public SecretsResponse GetByIds(Guid[] ids) + { + var command = new Command { Secrets = new SecretsCommand { GetByIds = new SecretsGetRequest { Ids = ids } } }; + var result = _commandRunner.RunCommand(command); + + if (result is { Success: true }) + { + return result.Data; + } + + throw new BitwardenException(result != null ? result.ErrorMessage : "Secret not found"); + } + + public SecretResponse Create(Guid organizationId, string key, string value, string note, Guid[] projectIds) { var command = new Command { @@ -50,8 +62,7 @@ public SecretResponse Create(string key, string value, string note, Guid organiz throw new BitwardenException(result != null ? result.ErrorMessage : "Secret create failed"); } - public SecretResponse Update(Guid id, string key, string value, string note, Guid organizationId, - Guid[] projectIds) + public SecretResponse Update(Guid organizationId, Guid id, string key, string value, string note, Guid[] projectIds) { var command = new Command { @@ -107,4 +118,28 @@ public SecretIdentifiersResponse List(Guid organizationId) throw new BitwardenException(result != null ? result.ErrorMessage : "No secrets for given organization"); } + + public SecretsSyncResponse Sync(Guid organizationId, DateTimeOffset? lastSyncedDate) + { + var command = new Command + { + Secrets = new SecretsCommand + { + Sync = new SecretsSyncRequest + { + OrganizationId = organizationId, + LastSyncedDate = lastSyncedDate + } + } + }; + + var result = _commandRunner.RunCommand(command); + + if (result is { Success: true }) + { + return result.Data; + } + + throw new BitwardenException(result != null ? result.ErrorMessage : "Secret update failed"); + } } diff --git a/languages/csharp/README.md b/languages/csharp/README.md index dea2d1d22..ece863fa3 100644 --- a/languages/csharp/README.md +++ b/languages/csharp/README.md @@ -12,8 +12,15 @@ Review the help documentation on [Access Tokens] ```csharp const string accessToken = ""; -using var bitwardenClient = new BitwardenClient(); -bitwardenClient.AccessTokenLogin(accessToken); +const string stateFile = ""; + +using var bitwardenClient = new BitwardenClient(new BitwardenSettings +{ + ApiUrl = apiUrl, + IdentityUrl = identityUrl +}); + +bitwardenClient.LoginAccessToken(accessToken, stateFile); ``` ### Create new project @@ -34,7 +41,7 @@ var response = bitwardenClient.Projects.List(organizationId); ```csharp var projectId = projectResponse.Id; projectResponse = bitwardenClient.Projects.Get(projectId); -projectResponse = bitwardenClient.Projects.Update(projectId, organizationId, "TestProjectUpdated"); +projectResponse = bitwardenClient.Projects.Update(organizationId, projectId, "TestProjectUpdated"); ``` ### Add new secret @@ -43,14 +50,20 @@ projectResponse = bitwardenClient.Projects.Update(projectId, organizationId, "Te var key = "key"; var value = "value"; var note = "note"; -var secretResponse = bitwardenClient.Secrets.Create(key, value, note, organizationId, new[] { projectId }); -var secretId = secretResponse.Id; +var secretResponse = bitwardenClient.Secrets.Create(organizationId, key, value, note, new[] { projectId }); ``` ### Update secret ```csharp -secretResponse = bitwardenClient.Secrets - .Update(secretId, "key2", "value2", "note2", organizationId, new[] { projectId }); +var secretId = secretResponse.Id; +secretResponse = bitwardenClient.Secrets.Get(secretId); +secretResponse = bitwardenClient.Secrets.Update(organizationId, secretId, "key2", "value2", "note2", new[] { projectId }); +``` + +### Secret GetByIds + +```csharp +var secretsResponse = bitwardenClient.Secrets.GetByIds(new[] { secretResponse.Id }); ``` ### List secrets @@ -59,6 +72,12 @@ secretResponse = bitwardenClient.Secrets var secretIdentifiersResponse = bitwardenClient.Secrets.List(organizationId); ``` +### Sync secrets + +```csharp +var syncResponse = bitwardenClient.Secrets.Sync(organizationId, null); +``` + # Delete secret or project ```csharp diff --git a/languages/csharp/global.json b/languages/csharp/global.json index 10b65be86..391ba3c2a 100644 --- a/languages/csharp/global.json +++ b/languages/csharp/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "6.0.100", + "version": "8.0.100", "rollForward": "latestFeature" } } diff --git a/languages/go/.version b/languages/go/.version index 6da28dde7..e6d5cb833 100644 --- a/languages/go/.version +++ b/languages/go/.version @@ -1 +1 @@ -0.1.1 \ No newline at end of file +1.0.2 \ No newline at end of file diff --git a/languages/go/INSTRUCTIONS.md b/languages/go/INSTRUCTIONS.md new file mode 100644 index 000000000..1c9702476 --- /dev/null +++ b/languages/go/INSTRUCTIONS.md @@ -0,0 +1,150 @@ +# Instructions + +This guide is for developers who want to use the Bitwarden Go SDK module in their own Go projects. Please see the main [README](./README.md) and [example.go](./example/example.go) file for examples. + +## Supported Targets + +The Bitwarden Go SDK module utilizes FFI calls to the Bitwarden Rust SDK via [cgo](https://pkg.go.dev/cmd/cgo). The module supports the following statically linked targets: + +- Linux `x86-64` & `arm64` +- macOS `x86-64` & `arm64` +- Windows `x86-64` + +## Linux + +### Prerequisites + +- [Go](https://go.dev/dl) +- A C toolchain + +We recommend the [MUSL toolchain](https://musl.libc.org). You can install this on most debian based systems with: + +```shell +sudo apt install musl-tools +``` + +### Set Go Environment Info + +#### Enable cgo + +```shell +go env -w CGO_ENABLED=1 +``` + +#### Set the C compiler + +```shell +go env -w CC=musl-gcc +``` + +#### Verify +```shell +go env +``` + +### Install the Bitwarden Go SDK + +#### Adding the Module + +```shell +go get github.com/bitwarden/sdk-go +``` + +#### Build + +```shell +go build -ldflags '-linkmode external -extldflags "-static -Wl,-unresolved-symbols=ignore-all"' +``` + +## macOS + +### Prerequisites + +- [Go](https://go.dev/dl) +- A C toolchain + +[Clang](https://clang.llvm.org/get_started.html) is the default C and C++ toolchain on Mac OS. The easiest way to ensure you have the toolchain is to install the Xcode Command Line tools. + +You can install Clang with: + + +```shell +xcode-select --install +``` + +### Set Go Environment Info + +#### Enable cgo + +```shell +go env -w CGO_ENABLED=1 +``` + +#### Set the C & C++ compilers + +```shell +go env -w CC=clang CXX=clang++ +``` + +#### Verify +```shell +go env +``` + +### Install the Bitwarden Go SDK + +#### Adding the Module + +```shell +go get github.com/bitwarden/sdk-go +``` + +#### Build + +```shell +go build +``` + +## Windows + +### Prerequisites + +- [Go](https://go.dev/dl) +- [GCC](https://gcc.gnu.org) + +Go [documentation](https://go.dev/wiki/cgo) recommends the mingw-w64 gcc compiler. + +We recommend following the Visual Studio Code [guide](https://code.visualstudio.com/docs/cpp/config-mingw#_installing-the-mingww64-toolchain) for installing the mingw-w64 toolchain. + +### Set Go Environment Info + +#### Enable cgo + +```shell +go env -w CGO_ENABLED=1 +``` + +#### Set the C & C++ compilers + +```shell +go env -w CC=gcc CXX=g++ +``` + +#### Verify +```shell +go env +``` + +### Install the Bitwarden Go SDK + +#### Adding the Module + +```shell +go get github.com/bitwarden/sdk-go +``` + +#### Build + +```shell +go build +``` diff --git a/languages/go/README.md b/languages/go/README.md index 669c31aaa..8332f5dc0 100644 --- a/languages/go/README.md +++ b/languages/go/README.md @@ -10,7 +10,7 @@ managing projects and secrets, as well as a client interface to facilitate opera ## Installation -Download the SDK files and place them in your Go project directory. +Follow the installation instructions [here](./INSTRUCTIONS.md). ## Table of Contents @@ -36,13 +36,13 @@ bitwardenClient, _ := sdk.NewBitwardenClient(&apiURL, &identityURL) ### Login -To login using an access token. Define some `statePath` and pass it to use state, or pass `nil` +To login using an access token. Define some `stateFile` and pass it to use state, or pass `nil` instead to not use state. ```go -statePath := os.Getenv("STATE_PATH") +stateFile := os.Getenv("STATE_FILE") -err := bitwardenClient.AccessTokenLogin(accessToken, &statePath) +err := bitwardenClient.AccessTokenLogin(accessToken, &stateFile) ``` --- @@ -52,25 +52,31 @@ err := bitwardenClient.AccessTokenLogin(accessToken, &statePath) #### Create a Project ```go -project, err := client.Projects().Create("organization_id", "project_name") +project, err := bitwardenClient.Projects().Create("organization_id", "project_name") ``` #### List Projects ```go -projects, err := client.Projects().List("organization_id") +projects, err := bitwardenClient.Projects().List("organization_id") +``` + +#### Get a Project + +```go +project, err := bitwardenClient.Projects().Get("project_id") ``` #### Update a Project ```go -project, err := client.Projects().Update("project_id", "organization_id", "new_project_name") +project, err := bitwardenClient.Projects().Update("project_id", "organization_id", "new_project_name") ``` #### Delete Projects ```go -project, err := client.Projects().Delete([]string{"project_id_1", "project_id_2"}) +project, err := bitwardenClient.Projects().Delete([]string{"project_id_1", "project_id_2"}) ``` --- @@ -80,34 +86,46 @@ project, err := client.Projects().Delete([]string{"project_id_1", "project_id_2" #### Create a Secret ```go -secret, err := client.Secrets().Create("key", "value", "note", "organization_id", []string{"project_id"}) +secret, err := bitwardenClient.Secrets().Create("key", "value", "note", "organization_id", []string{"project_id"}) ``` #### List Secrets ```go -secrets, err := client.Secrets().List("organization_id") +secrets, err := bitwardenClient.Secrets().List("organization_id") +``` + +#### Get a Secret + +```go +secret, err := bitwardenClient.Secrets().Get("secret_id") +``` + +#### Get Multiple Secrets by IDs + +```go +secrets, err := bitwardenClient.Secrets().GetByIDS([]string{"secret_ids"}) ``` #### Update a Secret ```go -secret, err := client.Secrets().Update("secret_id", "new_key", "new_value", "new_note", "organization_id", []string{"project_id"}) +secret, err := bitwardenClient.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"}) +secret, err := bitwardenClient.Secrets().Delete([]string{"secret_id_1", "secret_id_2"}) ``` #### Secrets Sync ```go -secretsSync, err := client.Secrets().Sync("organization_id", nil) +secretsSync, err := bitwardenClient.Secrets().Sync("organization_id", nil) lastSyncedDate := time.Now() -secretsSync, err := client.Secrets().Sync("organization_id", lastSyncedDate) +secretsSync, err = bitwardenClient.Secrets().Sync("organization_id", lastSyncedDate) ``` --- diff --git a/languages/go/bitwarden_client.go b/languages/go/bitwarden_client.go index 435ccdd95..121eda0a5 100644 --- a/languages/go/bitwarden_client.go +++ b/languages/go/bitwarden_client.go @@ -7,9 +7,10 @@ import ( ) type BitwardenClientInterface interface { - AccessTokenLogin(accessToken string, statePath *string) error + AccessTokenLogin(accessToken string, stateFile *string) error Projects() ProjectsInterface Secrets() SecretsInterface + Generators() GeneratorsInterface Close() } @@ -19,6 +20,7 @@ type BitwardenClient struct { commandRunner CommandRunnerInterface projects ProjectsInterface secrets SecretsInterface + generators GeneratorsInterface } func NewBitwardenClient(apiURL *string, identityURL *string) (BitwardenClientInterface, error) { @@ -49,12 +51,13 @@ func NewBitwardenClient(apiURL *string, identityURL *string) (BitwardenClientInt commandRunner: runner, projects: NewProjects(runner), secrets: NewSecrets(runner), + generators: NewGenerators(runner), }, nil } -func (c *BitwardenClient) AccessTokenLogin(accessToken string, statePath *string) error { - req := AccessTokenLoginRequest{AccessToken: accessToken, StateFile: statePath} - command := Command{AccessTokenLogin: &req} +func (c *BitwardenClient) AccessTokenLogin(accessToken string, stateFile *string) error { + req := AccessTokenLoginRequest{AccessToken: accessToken, StateFile: stateFile} + command := Command{LoginAccessToken: &req} responseStr, err := c.commandRunner.RunCommand(command) if err != nil { @@ -73,6 +76,10 @@ func (c *BitwardenClient) Secrets() SecretsInterface { return c.secrets } +func (c *BitwardenClient) Generators() GeneratorsInterface { + return c.generators +} + func (c *BitwardenClient) Close() { c.lib.FreeMem(c.client) } diff --git a/languages/go/example/example.go b/languages/go/example/example.go index 517a4fbb6..06cfcd48c 100644 --- a/languages/go/example/example.go +++ b/languages/go/example/example.go @@ -21,15 +21,15 @@ func main() { organizationIDStr := os.Getenv("ORGANIZATION_ID") projectName := os.Getenv("PROJECT_NAME") - // Configuring the statePath is optional, pass nil + // Configuring the stateFile is optional, pass nil // in AccessTokenLogin() to not use state - statePath := os.Getenv("STATE_PATH") + stateFile := os.Getenv("STATE_FILE") if projectName == "" { projectName = "NewTestProject" // default value } - err := bitwardenClient.AccessTokenLogin(accessToken, &statePath) + err := bitwardenClient.AccessTokenLogin(accessToken, &stateFile) if err != nil { panic(err) } @@ -112,5 +112,26 @@ func main() { fmt.Println(string(jsonSecrets)) + // Generate a password which can be used as a secret value + request := sdk.PasswordGeneratorRequest{ + AvoidAmbiguous: true, + Length: 64, + Lowercase: true, + MinLowercase: new(int64), + MinNumber: new(int64), + MinSpecial: new(int64), + MinUppercase: new(int64), + Numbers: true, + Special: true, + Uppercase: true, + } + password, err := bitwardenClient.Generators().GeneratePassword(request) + + if err != nil { + panic(err) + } + + fmt.Println(*password) + defer bitwardenClient.Close() } diff --git a/languages/go/example/go.mod b/languages/go/example/go.mod index ee8a38d33..cb09b706c 100644 --- a/languages/go/example/go.mod +++ b/languages/go/example/go.mod @@ -2,7 +2,7 @@ module example replace github.com/bitwarden/sdk-go => ../ -go 1.20 +go 1.21 require ( github.com/bitwarden/sdk-go v0.0.0-00010101000000-000000000000 diff --git a/languages/go/generators.go b/languages/go/generators.go new file mode 100644 index 000000000..8d72c9aa5 --- /dev/null +++ b/languages/go/generators.go @@ -0,0 +1,35 @@ +package sdk + +type GeneratorsInterface interface { + GeneratePassword(request PasswordGeneratorRequest) (*string, error) +} + +type Generators struct { + CommandRunner CommandRunnerInterface +} + +func NewGenerators(commandRunner CommandRunnerInterface) *Generators { + return &Generators{CommandRunner: commandRunner} +} + +func (s *Generators) executeCommand(command Command, target interface{}) error { + responseStr, err := s.CommandRunner.RunCommand(command) + if err != nil { + return err + } + return checkSuccessAndError(responseStr, target) +} + +func (s *Generators) GeneratePassword(request PasswordGeneratorRequest) (*string, error) { + command := Command{ + Generators: &GeneratorsCommand{ + GeneratePassword: request, + }, + } + + var response string + if err := s.executeCommand(command, &response); err != nil { + return nil, err + } + return &response, nil +} diff --git a/languages/go/go.mod b/languages/go/go.mod index 1d6428e99..ae9f50111 100644 --- a/languages/go/go.mod +++ b/languages/go/go.mod @@ -1,3 +1,3 @@ module github.com/bitwarden/sdk-go -go 1.18 +go 1.21 diff --git a/languages/go/internal/cinterface/bitwarden_library.go b/languages/go/internal/cinterface/bitwarden_library.go index 3a63be61b..69d752716 100644 --- a/languages/go/internal/cinterface/bitwarden_library.go +++ b/languages/go/internal/cinterface/bitwarden_library.go @@ -11,7 +11,7 @@ import ( #cgo linux,arm64 LDFLAGS: -L ./lib/linux-arm64 #cgo darwin,amd64 LDFLAGS: -L ./lib/darwin-x64 -framework Security -framework SystemConfiguration #cgo darwin,arm64 LDFLAGS: -L ./lib/darwin-arm64 -framework Security -framework SystemConfiguration -#cgo windows,amd64 LDFLAGS: -L ./lib/windows-x64 -lbitwarden_c -ladvapi32 -lbcrypt -lcrypt32 -lcryptnet -lkernel32 -lncrypt -lntdll -luserenv -lws2_32 -lmsvcrt +#cgo windows,amd64 LDFLAGS: -L ./lib/windows-x64 -lbitwarden_c -ladvapi32 -lbcrypt -lcrypt32 -lcryptnet -lkernel32 -lncrypt -lntdll -luserenv -lws2_32 -lmsvcrt -loleaut32 -lruntimeobject #include typedef void* ClientPtr; extern char* run_command(const char *command, ClientPtr client); diff --git a/languages/go/secrets.go b/languages/go/secrets.go index f11106b9b..825a0f100 100644 --- a/languages/go/secrets.go +++ b/languages/go/secrets.go @@ -134,17 +134,11 @@ func (s *Secrets) Delete(ids []string) (*SecretsDeleteResponse, error) { } func (s *Secrets) Sync(organizationID string, lastSyncedDate *time.Time) (*SecretsSyncResponse, error) { - var lastSyncedDateString *string - if lastSyncedDate != nil { - tempRfc3339 := lastSyncedDate.UTC().Format(time.RFC3339) - lastSyncedDateString = &tempRfc3339 - } - command := Command{ Secrets: &SecretsCommand{ Sync: &SecretsSyncRequest{ OrganizationID: organizationID, - LastSyncedDate: lastSyncedDateString, + LastSyncedDate: lastSyncedDate, }, }, } diff --git a/languages/java/Example.java b/languages/java/Example.java deleted file mode 100644 index eacf23f8a..000000000 --- a/languages/java/Example.java +++ /dev/null @@ -1,33 +0,0 @@ -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/INSTALL.md b/languages/java/INSTALL.md new file mode 100644 index 000000000..73e474745 --- /dev/null +++ b/languages/java/INSTALL.md @@ -0,0 +1,52 @@ +# Java build + +## Introduction + +Gradle is used to build Java Bitwarden client library. + +The output of the build is placed in `build/libs` directory and should contain `BitwardenSDK.jar` file. + +## Prerequisites + +- JDK 17 installed. +- Bitwarden SDK native library build. See [SDK README.md](../../README.md) for instructions. + +## Build Commands + +```shell +./gradlew build +``` + +## Example + +### macOS + +#### Install Prerequisites + +Use brew to install JDK 17. + +```shell +brew install --cask temurin@17 +brew install jenv +export PATH="$HOME/.jenv/bin:$PATH" +eval "$(jenv init -)" +jenv add /Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home +jenv shell 17 +``` + +#### Build Commands + +```shell +./gradlew build +``` + +## Example SDK Usage Project + +```shell +export ACCESS_TOKEN="" +export ORGANIZATION_ID="" +export API_URL="https://api.bitwarden.com" +export IDENTITY_URL="https://identity.bitwarden.com" + +./gradlew :example:run +``` diff --git a/languages/java/README.md b/languages/java/README.md index 06f1ffec3..b4db4475e 100644 --- a/languages/java/README.md +++ b/languages/java/README.md @@ -12,11 +12,20 @@ Review the help documentation on [Access Tokens] ### Create new Bitwarden client ```java +import com.bitwarden.sdk.*; +import com.bitwarden.sdk.schema.*; + +import java.lang.System; +import java.util.UUID; +import java.time.OffsetDateTime; + +String stateFile = System.getenv("STATE_FILE"); + BitwardenSettings bitwardenSettings = new BitwardenSettings(); bitwardenSettings.setApiUrl("https://api.bitwarden.com"); bitwardenSettings.setIdentityUrl("https://identity.bitwarden.com"); BitwardenClient bitwardenClient = new BitwardenClient(bitwardenSettings); -bitwardenClient.accessTokenLogin(""); +bitwardenClient.auth().loginAccessToken("", stateFile); ``` ### Create new project @@ -24,6 +33,13 @@ bitwardenClient.accessTokenLogin(""); ```java UUID organizationId = UUID.fromString(""); var projectResponse = bitwardenClient.projects().create(organizationId, "TestProject"); +UUID projectId = projectResponse.getID(); +``` + +### Get project + +```java +var projectResponse = bitwardenClient.projects().get(projectId); ``` ### List all projects @@ -35,9 +51,7 @@ 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"); +var projectResponse = bitwardenClient.projects().update(organizationId, projectId, "TestProjectUpdated"); ``` ### Add new secret @@ -46,23 +60,44 @@ projectResponse = bitwardenClient.projects.update(projectId, organizationId, "Te String key = "key"; String value = "value"; String note = "note"; -var secretResponse = bitwardenClient.secrets().create(key, value, note, organizationId, new UUID[]{projectId}); +var secretResponse = bitwardenClient.secrets().create(organizationId, key, value, note, new UUID[]{projectId}); UUID secretId = secretResponse.getID(); ``` +### Get secret + +```java +var secretResponse = bitwardenClient.secrets().get(secretId); +``` + +### Get secrets by ids + +```java +SecretsResponse secretsByIds = bitwardenClient.secrets().getByIds(new UUID[]{secretId}); +for (SecretResponse sr : secretsByIds.getData()) { + System.out.println(sr.getKey()); +} +``` + ### Update secret ```java -bitwardenClient.secrets().update(secretId, key2, value2, note2, organizationId, new UUID[]{projectId}); +var secretResponse = bitwardenClient.secrets().update(organizationId, secretId, key2, value2, note2, new UUID[]{projectId}); ``` ### List secrets ```java -var secretIdentifiersResponse secretIdentifiersResponse = bitwardenClient.secrets().list(organizationId); +var secretIdentifiersResponse = bitwardenClient.secrets().list(organizationId); +``` + +### Secrets sync +```java +SecretsSyncResponse syncResponse = bitwardenClient.secrets().sync(organizationId, OffsetDateTime.now()); +System.out.println("Has changes: " + syncResponse.getHasChanges()); ``` -# Delete secret or project +### Delete secret or project ```java bitwardenClient.secrets().delete(new UUID[]{secretId}); @@ -70,4 +105,4 @@ 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 +[Bitwarden Secrets Manager]: https://bitwarden.com/products/secrets-manager/ diff --git a/languages/java/build.gradle b/languages/java/build.gradle index 8d91e2e6e..52337a447 100644 --- a/languages/java/build.gradle +++ b/languages/java/build.gradle @@ -36,7 +36,7 @@ repositories { def branchName = "git branch --show-current".execute().text.trim() if (branchName == "main" || branchName == "rc" || branchName == "hotfix-rc") { - version = "0.1.0" + version = "1.0.0" } else { // branchName-SNAPSHOT version = "${branchName.replaceAll('/', '-')}-SNAPSHOT" @@ -74,6 +74,7 @@ tasks.withType(JavaCompile) { tasks.withType(Javadoc) { options.encoding = 'UTF-8' + failOnError = false } java { @@ -81,18 +82,25 @@ java { withSourcesJar() } -// 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' -//} +jar { + // Copy native library to jar resources for local gradle build + if (System.getenv("GITHUB_TOKEN") == null) { + from('../../target/debug') { + include '*libbitwarden_c*.dylib' + into "darwin-x86-64" + } + from('../../target/debug') { + include '*libbitwarden_c*.dylib' + into "darwin-aarch64" + } + from('../../target/debug') { + include '*libbitwarden_c*.so' + into "linux-x86-64" + } + from('../../target/debug') { + include '*bitwarden_c*.dll' + into "win32-x86-64" + } + } +} + diff --git a/languages/java/example/Example.java b/languages/java/example/Example.java new file mode 100644 index 000000000..cc34c2a29 --- /dev/null +++ b/languages/java/example/Example.java @@ -0,0 +1,73 @@ +import java.lang.System; +import java.util.UUID; +import java.time.OffsetDateTime; + +import com.bitwarden.sdk.*; +import com.bitwarden.sdk.schema.*; + +class Example { + public static void main(String[] args) { + if (!System.getenv().containsKey("ACCESS_TOKEN") || !System.getenv().containsKey("ORGANIZATION_ID")) { + System.err.println("Missing environment variable ACCESS_TOKEN or ORGANIZATION_ID"); + System.exit(1); + } + + 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"); + String stateFile = System.getenv("STATE_FILE"); + + // Configuring the URLS is optional, remove them to use the default values + BitwardenSettings bitwardenSettings = new BitwardenSettings(); + bitwardenSettings.setApiUrl(apiUrl); + bitwardenSettings.setIdentityUrl(identityUrl); + + try (BitwardenClient client = new BitwardenClient(bitwardenSettings)) { + client.auth().loginAccessToken(accessToken, stateFile); + + ProjectResponse project = client.projects().create(organizationId, "Test Project from Java SDK"); + System.out.println("Project CREATE, id: " + project.getID()); + + project = client.projects().get(project.getID()); + System.out.println("Project GET, id: " + project.getID()); + + ProjectsResponse projects = client.projects().list(organizationId); + System.out.println("Projects LIST, count: " + projects.getData().length); + + client.projects().update(organizationId, project.getID(), "Updated Test Project"); + project = client.projects().get(project.getID()); + System.out.println("Project UPDATE, new name: " + project.getName()); + + SecretResponse secret = client.secrets().create(organizationId, "Secret Key", "Secret Value", "Secret Note", new UUID[]{project.getID()}); + System.out.println("Secret CREATE, id: " + secret.getID()); + + secret = client.secrets().get(secret.getID()); + System.out.println("Secret GET, id: " + secret.getID()); + + SecretIdentifiersResponse secrets = client.secrets().list(organizationId); + System.out.println("Secrets LIST, count: " + secrets.getData().length); + + client.secrets().update(organizationId, secret.getID(), "Updated Key", "Updated Value", "Updated Note", new UUID[]{project.getID()}); + secret = client.secrets().get(secret.getID()); + System.out.println("Secret UPDATE, new key: " + secret.getKey()); + + SecretsResponse secretsByIds = client.secrets().getByIds(new UUID[]{secret.getID()}); + System.out.println("Getting secrets by ids, here are the keys of the retrieved secrets..."); + for (SecretResponse sr : secretsByIds.getData()) { + System.out.println(" " + sr.getKey()); + } + + SecretsSyncResponse syncResponse = client.secrets().sync(organizationId, OffsetDateTime.now()); + System.out.println("Running a secrets sync request based on the current time..."); + System.out.println("Has changes: " + syncResponse.getHasChanges()); + + System.out.println("Deleting the created secret and project..."); + client.secrets().delete(new UUID[]{secret.getID()}); + client.projects().delete(new UUID[]{project.getID()}); + + System.out.println("Execution complete."); + } + } +} diff --git a/languages/java/example/build.gradle b/languages/java/example/build.gradle new file mode 100644 index 000000000..73025e50d --- /dev/null +++ b/languages/java/example/build.gradle @@ -0,0 +1,23 @@ +plugins { + id 'application' +} + +repositories { + mavenLocal() + mavenCentral() +} + +dependencies { + implementation rootProject +} + +application { + mainClass = 'Example' +} + +sourceSets { + main { + java.srcDirs += '.' + } +} + diff --git a/languages/java/settings.gradle b/languages/java/settings.gradle index 961795f50..bdf7a7ed8 100644 --- a/languages/java/settings.gradle +++ b/languages/java/settings.gradle @@ -3,3 +3,5 @@ */ rootProject.name = 'BitwardenSDK' + +include "example" diff --git a/languages/java/src/main/java/com/bitwarden/sdk/AuthClient.java b/languages/java/src/main/java/com/bitwarden/sdk/AuthClient.java new file mode 100644 index 000000000..b4c26eb75 --- /dev/null +++ b/languages/java/src/main/java/com/bitwarden/sdk/AuthClient.java @@ -0,0 +1,32 @@ +package com.bitwarden.sdk; + +import com.bitwarden.sdk.schema.*; + +import java.util.function.Function; + +public class AuthClient { + + private final CommandRunner commandRunner; + + AuthClient(CommandRunner commandRunner) { + this.commandRunner = commandRunner; + } + + public APIKeyLoginResponse loginAccessToken(String accessToken, String stateFile) { + Command command = new Command(); + AccessTokenLoginRequest accessTokenLoginRequest = new AccessTokenLoginRequest(); + accessTokenLoginRequest.setAccessToken(accessToken); + accessTokenLoginRequest.setStateFile(stateFile); + + command.setLoginAccessToken(accessTokenLoginRequest); + + ResponseForAPIKeyLoginResponse response = commandRunner.runCommand(command, + BitwardenClient.throwingFunctionWrapper(Converter::ResponseForAPIKeyLoginResponseFromJsonString)); + + if (response == null || !response.getSuccess()) { + throw new BitwardenClientException(response != null ? response.getErrorMessage() : "Login failed"); + } + + return response.getData(); + } +} diff --git a/languages/java/src/main/java/com/bitwarden/sdk/BitwardenClient.java b/languages/java/src/main/java/com/bitwarden/sdk/BitwardenClient.java index 9e1948184..bed29982d 100644 --- a/languages/java/src/main/java/com/bitwarden/sdk/BitwardenClient.java +++ b/languages/java/src/main/java/com/bitwarden/sdk/BitwardenClient.java @@ -21,6 +21,8 @@ public class BitwardenClient implements AutoCloseable { private final SecretsClient secrets; + private final AuthClient auth; + public BitwardenClient(BitwardenSettings bitwardenSettings) { ClientSettings clientSettings = new ClientSettings(); clientSettings.setAPIURL(bitwardenSettings.getApiUrl()); @@ -39,36 +41,20 @@ public BitwardenClient(BitwardenSettings bitwardenSettings) { commandRunner = new CommandRunner(library, client); projects = new ProjectsClient(commandRunner); secrets = new SecretsClient(commandRunner); + auth = new AuthClient(commandRunner); isClientOpen = true; } static Function throwingFunctionWrapper(ThrowingFunction throwingFunction) { - return i -> { try { return throwingFunction.accept(i); } catch (Exception ex) { - throw new BitwardenClientException("Response deserialization failed"); + throw new BitwardenClientException("Response failed", ex); } }; } - 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; } @@ -77,6 +63,10 @@ public SecretsClient secrets() { return secrets; } + public AuthClient auth() { + return auth; + } + @Override public void close() { if (isClientOpen) { diff --git a/languages/java/src/main/java/com/bitwarden/sdk/BitwardenClientException.java b/languages/java/src/main/java/com/bitwarden/sdk/BitwardenClientException.java index 7b6025be8..e1bc9d477 100644 --- a/languages/java/src/main/java/com/bitwarden/sdk/BitwardenClientException.java +++ b/languages/java/src/main/java/com/bitwarden/sdk/BitwardenClientException.java @@ -5,4 +5,8 @@ public class BitwardenClientException extends RuntimeException { public BitwardenClientException(String message) { super(message); } + + public BitwardenClientException(String message, Exception ex) { + super(message, ex); + } } diff --git a/languages/java/src/main/java/com/bitwarden/sdk/ProjectsClient.java b/languages/java/src/main/java/com/bitwarden/sdk/ProjectsClient.java index 73b87440c..eb96b8286 100644 --- a/languages/java/src/main/java/com/bitwarden/sdk/ProjectsClient.java +++ b/languages/java/src/main/java/com/bitwarden/sdk/ProjectsClient.java @@ -49,7 +49,7 @@ public ProjectResponse create(UUID organizationId, String name) { return response.getData(); } - public ProjectResponse update(UUID id, UUID organizationId, String name) { + public ProjectResponse update(UUID organizationId, UUID id, String name) { Command command = new Command(); ProjectsCommand projectsCommand = new ProjectsCommand(); ProjectPutRequest projectPutRequest = new ProjectPutRequest(); diff --git a/languages/java/src/main/java/com/bitwarden/sdk/SecretsClient.java b/languages/java/src/main/java/com/bitwarden/sdk/SecretsClient.java index f1a97fdc7..63324449f 100644 --- a/languages/java/src/main/java/com/bitwarden/sdk/SecretsClient.java +++ b/languages/java/src/main/java/com/bitwarden/sdk/SecretsClient.java @@ -3,6 +3,7 @@ import com.bitwarden.sdk.schema.*; import java.util.UUID; +import java.time.OffsetDateTime; public class SecretsClient { @@ -30,7 +31,7 @@ public SecretResponse get(UUID id) { return response.getData(); } - public SecretResponse create(String key, String value, String note, UUID organizationId, UUID[] projectIds) { + public SecretResponse create(UUID organizationId, String key, String value, String note, UUID[] projectIds) { Command command = new Command(); SecretsCommand secretsCommand = new SecretsCommand(); SecretCreateRequest secretCreateRequest = new SecretCreateRequest(); @@ -52,8 +53,7 @@ public SecretResponse create(String key, String value, String note, UUID organiz return response.getData(); } - public SecretResponse update(UUID id, String key, String value, String note, UUID organizationId, - UUID[] projectIds) { + public SecretResponse update(UUID organizationId, UUID id, String key, String value, String note, UUID[] projectIds) { Command command = new Command(); SecretsCommand secretsCommand = new SecretsCommand(); SecretPutRequest secretPutRequest = new SecretPutRequest(); @@ -112,4 +112,41 @@ public SecretIdentifiersResponse list(UUID organizationId) { return response.getData(); } + + public SecretsResponse getByIds(UUID[] ids) { + Command command = new Command(); + SecretsCommand secretsCommand = new SecretsCommand(); + SecretsGetRequest secretsGetRequest = new SecretsGetRequest(); + secretsGetRequest.setIDS(ids); + secretsCommand.setGetByIDS(secretsGetRequest); + command.setSecrets(secretsCommand); + + ResponseForSecretsResponse response = commandRunner.runCommand(command, + BitwardenClient.throwingFunctionWrapper(Converter::ResponseForSecretsResponseFromJsonString)); + + if (response == null || !response.getSuccess()) { + throw new BitwardenClientException(response != null ? response.getErrorMessage() : "Secret(s) not found"); + } + + return response.getData(); + } + + public SecretsSyncResponse sync(UUID organizationId, OffsetDateTime lastSyncedDate) { + Command command = new Command(); + SecretsCommand secretsCommand = new SecretsCommand(); + SecretsSyncRequest secretsSyncRequest = new SecretsSyncRequest(); + secretsSyncRequest.setOrganizationID(organizationId); + secretsSyncRequest.setLastSyncedDate(lastSyncedDate); + secretsCommand.setSync(secretsSyncRequest); + command.setSecrets(secretsCommand); + + ResponseForSecretsSyncResponse response = commandRunner.runCommand(command, + BitwardenClient.throwingFunctionWrapper(Converter::ResponseForSecretsSyncResponseFromJsonString)); + + if (response == null || !response.getSuccess()) { + throw new BitwardenClientException(response != null ? response.getErrorMessage() : "Secrets sync failed"); + } + + return response.getData(); + } } diff --git a/languages/js/example/package-lock.json b/languages/js/example/package-lock.json index d48fd43be..2b7016955 100644 --- a/languages/js/example/package-lock.json +++ b/languages/js/example/package-lock.json @@ -11,13 +11,15 @@ } }, "../sdk-client": { + "name": "@bitwarden/sdk-client", "devDependencies": { - "@types/node": "^18.15.11", - "rimraf": "^5.0.0", + "@types/node": "^20.0.0", + "rimraf": "^6.0.0", "typescript": "^5.0.3" } }, "../wasm": { + "name": "@bitwarden/sdk-wasm", "version": "0.1.0" }, "node_modules/@bitwarden/sdk-client": { diff --git a/languages/js/sdk-client/package-lock.json b/languages/js/sdk-client/package-lock.json index 70f0b3a6d..5911a3331 100644 --- a/languages/js/sdk-client/package-lock.json +++ b/languages/js/sdk-client/package-lock.json @@ -6,8 +6,8 @@ "": { "name": "@bitwarden/sdk-client", "devDependencies": { - "@types/node": "^18.15.11", - "rimraf": "^5.0.0", + "@types/node": "^20.0.0", + "rimraf": "^6.0.0", "typescript": "^5.0.3" } }, @@ -16,6 +16,7 @@ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, + "license": "ISC", "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -28,30 +29,22 @@ "node": ">=12" } }, - "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.19.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.33.tgz", - "integrity": "sha512-NR9+KrpSajr2qBVp/Yt5TU/rp+b5Mayi3+OlMlcg2cVCfRmcG5PWZ7S4+MG9PZ5gWBoc9Pd0BKSRViuBCRPu0A==", + "version": "20.16.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.11.tgz", + "integrity": "sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==", "dev": true, + "license": "MIT", "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.19.2" } }, "node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -64,6 +57,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -75,13 +69,15 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "dev": true, + "license": "MIT" }, "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, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -91,6 +87,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -102,13 +99,15 @@ "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 + "dev": true, + "license": "MIT" }, "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, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -122,19 +121,22 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "dev": true, + "license": "MIT" }, "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==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "dev": true, + "license": "ISC", "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" @@ -147,22 +149,24 @@ } }, "node_modules/glob": { - "version": "10.3.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.15.tgz", - "integrity": "sha512-0c6RlJt1TICLyvJYIApxb8GsXoai0KUP7AxKKAtsYXdgJR1mGEUa7DgwShbdk1nly0PYoZj01xd4hzbq3fsjpw==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", "dev": true, + "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.6", - "minimatch": "^9.0.1", - "minipass": "^7.0.4", - "path-scurry": "^1.11.0" + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -173,6 +177,7 @@ "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, + "license": "MIT", "engines": { "node": ">=8" } @@ -181,97 +186,110 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", + "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, "engines": { - "node": ">=14" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" } }, "node_modules/lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz", + "integrity": "sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==", "dev": true, + "license": "ISC", "engines": { - "node": "14 || >=16.14" + "node": "20 || >=22" } }, "node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minipass": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz", - "integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, + "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.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, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/rimraf": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.7.tgz", - "integrity": "sha512-nV6YcJo5wbLW77m+8KjH8aB/7/rxQy9SZ0HY5shnwULfS+9nmTtVXAJET5NdZmCzA4fPI/Hm1wo/Po/4mopOdg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", "dev": true, + "license": "ISC", "dependencies": { - "glob": "^10.3.7" + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" }, "bin": { "rimraf": "dist/esm/bin.mjs" }, "engines": { - "node": ">=14.18" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -282,6 +300,7 @@ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, + "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" }, @@ -294,6 +313,7 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -303,6 +323,7 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, + "license": "ISC", "engines": { "node": ">=14" }, @@ -315,6 +336,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, + "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -333,6 +355,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -347,6 +370,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -355,13 +379,15 @@ "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 + "dev": true, + "license": "MIT" }, "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, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -374,6 +400,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -390,6 +417,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -402,15 +430,17 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -420,16 +450,18 @@ } }, "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 + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" }, "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, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -445,6 +477,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -463,6 +496,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -480,6 +514,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -489,6 +524,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -503,13 +539,15 @@ "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 + "dev": true, + "license": "MIT" }, "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, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -524,6 +562,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, diff --git a/languages/js/sdk-client/package.json b/languages/js/sdk-client/package.json index 9e4998b4f..17269acc6 100644 --- a/languages/js/sdk-client/package.json +++ b/languages/js/sdk-client/package.json @@ -13,8 +13,8 @@ "clean": "rimraf dist" }, "devDependencies": { - "@types/node": "^18.15.11", - "rimraf": "^5.0.0", + "@types/node": "^20.0.0", + "rimraf": "^6.0.0", "typescript": "^5.0.3" } } diff --git a/languages/js/sdk-internal/.gitignore b/languages/js/sdk-internal/.gitignore new file mode 100644 index 000000000..99cddcd21 --- /dev/null +++ b/languages/js/sdk-internal/.gitignore @@ -0,0 +1,7 @@ +**/snippets/**/*.js +bitwarden_wasm_internal_bg.js +bitwarden_wasm_internal_bg.wasm +bitwarden_wasm_internal_bg.wasm.d.ts +bitwarden_wasm_internal_bg.wasm.js +bitwarden_wasm_internal.d.ts +bitwarden_wasm_internal.js diff --git a/languages/js/sdk-internal/LICENSE b/languages/js/sdk-internal/LICENSE new file mode 100644 index 000000000..e9d496ff7 --- /dev/null +++ b/languages/js/sdk-internal/LICENSE @@ -0,0 +1,295 @@ +BITWARDEN SOFTWARE DEVELOPMENT KIT LICENSE AGREEMENT +Version 1, 17 March 2023 + +1. Introduction + +1.1 The Bitwarden Software Development Kit (referred to in the License Agreement +as the "SDK" and available for download at the following URL +https://github.com/bitwarden/sdk) is licensed to you subject to the terms of +this License Agreement. The License Agreement forms a legally binding contract +between you and the Company in relation to your use of the SDK. + +1.2 "Bitwarden" means the Bitwarden software made available by the Company, +available for download at the following URL, as updated from time to time. + +1.3 A "Compatible Application" means any software program or service that (i) +connects to and interoperates with a current version of the Bitwarden server +products distributed by the Company; and (ii) complies with the Company’s +acceptable use policy available at the following URL: +https://bitwarden.com/terms/#acceptable_use. + +1.4 "Company" means Bitwarden Inc., organized under the laws of the State of +Delaware. + +2. Accepting this License Agreement + +2.1 In order to access or use the SDK, you must first agree to the License +Agreement. You may not access or use the SDK if you do not accept the License +Agreement. + +2.2 By clicking to accept and/or accessing or using the SDK, you hereby agree to +the terms of the License Agreement. + +2.3 You may not access or use the SDK and may not accept the License Agreement +if you are a person barred from receiving the SDK under the laws of the United +States or other countries, including the country in which you are resident or +from which you access or use the SDK. + +2.4 If you are agreeing to be bound by the License Agreement on behalf of your +employer or any other entity, you represent and warrant that you have full legal +authority to bind your employer or such other entity to the License Agreement. +If you do not have the requisite authority, you may not accept the License +Agreement or you may not access or use the SDK on behalf of your employer or +other entity. + +3. SDK License from Bitwarden + +3.1 Subject to the terms of this License Agreement, Bitwarden grants you a +limited, worldwide, royalty-free, non-assignable, non-exclusive, and +non-sublicensable license to use the SDK solely (a) to develop, test, and +demonstrate a Compatible Application; (b) to develop, test, and run a Compatible +Application for personal use by your family; or (c) to to develop, test, and run +a Compatible Application for the internal business operations of your +organization in connection with a paid license for a Bitwarden server product, +provided that in no case above may the Compatible Application be offered, +licensed, or sold to a third party. + +3.2 You agree that Bitwarden or third parties own all legal right, title and +interest in and to the SDK, including any Intellectual Property Rights that +subsist in the SDK. "Intellectual Property Rights" means any and all rights +under patent law, copyright law, trade secret law, trademark law, and any and +all other proprietary rights. Bitwarden reserves all rights not expressly +granted to you. + +3.3 You may not use this SDK to develop applications for use with software other +than Bitwarden (including non-compatible implementations of Bitwarden) or to +develop another SDK. + +3.4 You may not use the SDK for any purpose not expressly permitted by the +License Agreement. Except for contributions to Bitwarden pursuant to the +Contribution License Agreement available at this URL: +https://cla-assistant.io/bitwarden/clients, or to the extent required by +applicable third party licenses, you may not copy modify, adapt, redistribute, +decompile, reverse engineer, disassemble, or create derivative works of the SDK +or any part of the SDK. + +3.5 Use, reproduction, and distribution of a component of the SDK licensed under +an open source software license are governed solely by the terms of that open +source software license and not the License Agreement. + +3.6 You agree that the form and nature of the SDK that the Company provides may +change without prior notice to you and that future versions of the SDK may be +incompatible with applications developed on previous versions of the SDK. You +agree that the Company may stop (permanently or temporarily) providing the SDK +or any features within the SDK to you or to users generally at the Company’s +sole discretion, without prior notice to you. + +3.7 Nothing in the License Agreement gives you a right to use any of the +Company’s trade names, trademarks, service marks, logos, domain names, or other +distinctive brand features. + +3.8 You agree that you will not remove, obscure, or alter any proprietary rights +notices (including copyright and trademark notices) that may be affixed to or +contained within the SDK. + +4. Use of the SDK by You + +4.1 The Company agrees that it obtains no right, title, or interest from you (or +your licensors) under the License Agreement in or to any software applications +that you develop using the SDK, including any Intellectual Property Rights that +subsist in those applications. + +4.2 You agree to use the SDK and write applications only for purposes that are +permitted by (a) the License Agreement and (b) any applicable law, regulation or +generally accepted practices or guidelines in the relevant jurisdictions +(including any laws regarding the export of data or software to and from the +United States or other relevant countries). + +4.3 You agree that if you use the SDK to develop applications for other users, +you will protect the privacy and legal rights of those users. If the users +provide you with user names, passwords, or other login information or personal +information, you must make the users aware that the information will be +available to your application, and you must provide legally adequate privacy +notice and protection for those users. If your application stores personal or +sensitive information provided by users, it must do so securely. If the user +provides your application with Bitwarden Account information, your application +may only use that information to access the user's Bitwarden Account when, and +for the limited purposes for which, the user has given you permission to do so. + +4.4 You agree that you will not engage in any activity with the SDK, including +the development or distribution of an application, that interferes with, +disrupts, damages, or accesses in an unauthorized manner the servers, networks, +or other properties or services of any third party including, but not limited +to, the Company, or any mobile communications carrier or public cloud service. + +4.5 If you use the SDK to retrieve a user's data from Bitwarden, you acknowledge +and agree that you shall retrieve data only with the user's explicit consent and +only when, and for the limited purposes for which, the user has given you +permission to do so. + +4.6 You agree that you are solely responsible for, and that the Company has no +responsibility to you or to any third party for, any data, content, or resources +that you create, transmit or display through Bitwarden and/or applications for +Bitwarden, and for the consequences of your actions (including any loss or +damage which Bitwarden may suffer) by doing so. + +4.7 You agree that you are solely responsible for, and that the Company has no +responsibility to you or to any third party for, any breach of your obligations +under the License Agreement, any applicable third party contract or Terms of +Service, or any applicable law or regulation, and for the consequences +(including any loss or damage which the Company or any third party may suffer) +of any such breach. + +5. Third Party Applications + +5.1 If you use the SDK to integrate or run applications developed by a third +party or that access data, content or resources provided by a third party, you +agree that the Company is not responsible for those applications, data, content, +or resources. You understand that all data, content or resources which you may +access through such third party applications are the sole responsibility of the +person from which they originated and that the Company is not liable for any +loss or damage that you may experience as a result of the use or access of any +of those third party applications, data, content, or resources. + +5.2 You should be aware that the data, content, and resources presented to you +through such a third party application may be protected by intellectual property +rights which are owned by the providers (or by other persons or companies on +their behalf). You acknowledge that your use of such third party applications, +data, content, or resources may be subject to separate terms between you and the +relevant third party. In that case, the License Agreement does not affect your +legal relationship with these third parties. + +6. Use of Bitwarden Server + +You acknowledge and agree that the Bitwarden server products to which any +Compatible Application must connect is protected by intellectual property rights +which are owned by the Company and your use of the Bitwarden server products is +subject to additional terms not set forth in this License Agreement. + +7. Terminating this License Agreement + +7.1 The License Agreement will continue to apply until terminated by either you +or the Company as set out below. + +7.2 If you want to terminate the License Agreement, you may do so by ceasing +your use of the SDK and any relevant developer credentials. + +7.3 The Company may at any time, terminate the License Agreement with you if: + +(a) you have breached any provision of the License Agreement; or + +(b) the Company is required to do so by law; or + +(c) a third party with whom the Company offered certain parts of the SDK to you +has terminated its relationship with the Company or ceased to offer certain +parts of the SDK to either the Company or to you; or + +(d) the Company decides to no longer provide the SDK or certain parts of the SDK +to users in the country in which you are resident or from which you use the +service, or the provision of the SDK or certain SDK services to you by the +Company is, in the Company’'s sole discretion, no longer commercially viable or +technically practicable. + +7.4 When the License Agreement comes to an end, all of the legal rights, +obligations and liabilities that you and the Company have benefited from, been +subject to (or which have accrued over time whilst the License Agreement has +been in force) or which are expressed to continue indefinitely, shall be +unaffected by this cessation, and the provisions of paragraph 12.8 shall +continue to apply to such rights, obligations and liabilities indefinitely. + +8. NO SUPPORT + +The Company is not obligated under this License Agreement to provide you any +support services for the SDK. Any support provided is at the Company’s sole +discretion and provided on an "as is" basis and without warranty of any kind. + +9. DISCLAIMER OF WARRANTIES + +9.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT YOUR USE OF THE SDK IS AT YOUR SOLE +RISK AND THAT THE SDK IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTY OF +ANY KIND FROM Bitwarden. + +9.2 YOUR USE OF THE SDK AND ANY MATERIAL DOWNLOADED OR OTHERWISE OBTAINED +THROUGH THE USE OF THE SDK IS AT YOUR OWN DISCRETION AND RISK AND YOU ARE SOLELY +RESPONSIBLE FOR ANY DAMAGE TO YOUR COMPUTER SYSTEM OR OTHER DEVICE OR LOSS OF +DATA THAT RESULTS FROM SUCH USE. + +9.3 THE COMPANY FURTHER EXPRESSLY DISCLAIMS ALL WARRANTIES AND CONDITIONS OF ANY +KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE IMPLIED +WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE +AND NON-INFRINGEMENT. + +10. LIMITATION OF LIABILITY + +YOU EXPRESSLY UNDERSTAND AND AGREE THAT THE COMPANY, ITS SUBSIDIARIES AND +AFFILIATES, AND ITS LICENSORS SHALL NOT BE LIABLE TO YOU UNDER ANY THEORY OF +LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, +STATUTORY, OR EXEMPLARY DAMAGES THAT MAY BE INCURRED BY YOU, INCLUDING ANY LOSS +OF DATA, WHETHER OR NOT THE COMPANY OR ITS REPRESENTATIVES HAVE BEEN ADVISED OF +OR SHOULD HAVE BEEN AWARE OF THE POSSIBILITY OF ANY SUCH LOSSES ARISING. + +11. Indemnification + +To the maximum extent permitted by law, you agree to defend, indemnify and hold +harmless the Company, its affiliates and their respective directors, officers, +employees and agents from and against any and all claims, actions, suits or +proceedings, as well as any and all losses, liabilities, damages, costs and +expenses (including reasonable attorneys fees) arising out of or accruing from +(a) your use of the SDK, (b) any application you develop on the SDK that +infringes any copyright, trademark, trade secret, trade dress, patent or other +intellectual property right of any person or defames any person or violates +their rights of publicity or privacy, and (c) any non-compliance by you with the +License Agreement. + +12. General Legal Terms + +12.1 The Company may make changes to the License Agreement as it distributes new +versions of the SDK. When these changes are made, the Company will make a new +version of the License Agreement available on the website where the SDK is made +available. + +12.2 The License Agreement constitutes the whole legal agreement between you and +the Company and governs your use of the SDK (excluding any services or software +which the Company may provide to you under a separate written agreement), and +completely replaces any prior agreements between you and the Company in relation +to the SDK. + +12.3 You agree that if the Company does not exercise or enforce any legal right +or remedy which is contained in the License Agreement (or which the Company has +the benefit of under any applicable law), this will not be taken to be a formal +waiver of the Company's rights and that those rights or remedies will still be +available to the Company. + +12.4 If any court of law, having the jurisdiction to decide on this matter, +rules that any provision of the License Agreement is invalid, then that +provision will be removed from the License Agreement without affecting the rest +of the License Agreement. The remaining provisions of the License Agreement will +continue to be valid and enforceable. + +12.5 You acknowledge and agree that each member of the group of companies of +which the Company is the parent shall be third party beneficiaries to the +License Agreement and that such other companies shall be entitled to directly +enforce, and rely upon, any provision of the License Agreement that confers a +benefit on them or rights in favor of them. Other than this, no other person or +company shall be third party beneficiaries to the License Agreement. + +12.6 EXPORT RESTRICTIONS. THE SDK IS SUBJECT TO UNITED STATES EXPORT LAWS AND +REGULATIONS. YOU MUST COMPLY WITH ALL DOMESTIC AND INTERNATIONAL EXPORT LAWS AND +REGULATIONS THAT APPLY TO THE SDK. THESE LAWS INCLUDE RESTRICTIONS ON +DESTINATIONS, END USERS, AND END USE. + +12.7 The rights granted in the License Agreement may not be assigned or +transferred by either you or the Company without the prior written approval of +the other party, provided that the Company may assign this License Agreement +upon notice to you in connection with an acquisition, merger, sale of assets, or +similar corporate change in control for the Company or the Intellectual Property +Rights in the SDK. + +12.8 The License Agreement, and any dispute relating to or arising out of this +License Agreement, shall be governed by the laws of the State of California +without regard to its conflict of laws provisions. You and the Company agree to +submit to the exclusive jurisdiction of the courts located within the county of +Los Angeles, California to resolve any dispute or legal matter arising from the +License Agreement. Notwithstanding this, you agree that the Company shall be +allowed to apply for injunctive remedies, or any equivalent type of urgent legal +relief, in any forum or jurisdiction. diff --git a/languages/js/sdk-internal/README.md b/languages/js/sdk-internal/README.md new file mode 100644 index 000000000..9391aa071 --- /dev/null +++ b/languages/js/sdk-internal/README.md @@ -0,0 +1,3 @@ +# @bitwarden/sdk-internal + +**Note:** This is only for internal use. Bitwarden will not provide any support for this package. diff --git a/languages/js/sdk-internal/index.js b/languages/js/sdk-internal/index.js new file mode 100644 index 000000000..0525f7aa4 --- /dev/null +++ b/languages/js/sdk-internal/index.js @@ -0,0 +1,8 @@ +import { __wbg_set_wasm } from "./bitwarden_wasm_internal_bg.js"; + +// In order to support a fallback strategy for web we need to conditionally load the wasm file +export function init(wasm) { + __wbg_set_wasm(wasm); +} + +export * from "./bitwarden_wasm_internal_bg.js"; diff --git a/languages/js/sdk-internal/package.json b/languages/js/sdk-internal/package.json new file mode 100644 index 000000000..29e5c5a54 --- /dev/null +++ b/languages/js/sdk-internal/package.json @@ -0,0 +1,25 @@ +{ + "name": "@bitwarden/sdk-internal", + "version": "0.1.0", + "license": "SEE LICENSE IN LICENSE", + "files": [ + "bitwarden_wasm_internal_bg.js", + "bitwarden_wasm_internal_bg.wasm", + "bitwarden_wasm_internal_bg.wasm.d.ts", + "bitwarden_wasm_internal_bg.wasm.js", + "bitwarden_wasm_internal.d.ts", + "bitwarden_wasm_internal.js", + "index.js", + "node/bitwarden_wasm_internal_bg.wasm", + "node/bitwarden_wasm_internal_bg.wasm.d.ts", + "node/bitwarden_wasm_internal.d.ts", + "node/bitwarden_wasm_internal.js" + ], + "main": "node/bitwarden_wasm_internal.js", + "module": "index.js", + "types": "bitwarden_wasm_internal.d.ts", + "scripts": {}, + "sideEffects": [ + "./bitwarden_wasm_internal.js" + ] +} diff --git a/languages/kotlin/app/src/main/AndroidManifest.xml b/languages/kotlin/app/src/main/AndroidManifest.xml index 09e0fe67a..de89e684b 100644 --- a/languages/kotlin/app/src/main/AndroidManifest.xml +++ b/languages/kotlin/app/src/main/AndroidManifest.xml @@ -11,6 +11,7 @@ android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" + android:networkSecurityConfig="@xml/network_security_config" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.MyApplication" 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 f51d0309e..e421416d6 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 @@ -23,7 +23,7 @@ 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.vault.Folder import com.bitwarden.core.InitOrgCryptoRequest import com.bitwarden.core.InitUserCryptoMethod import com.bitwarden.core.InitUserCryptoRequest @@ -63,6 +63,11 @@ import javax.net.ssl.X509TrustManager * 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. + * + * Note that this SDK uses the system trust store to perform TLS certificate validation. If you want to use this SDK with + * self hosted servers using self-signed certificates, make sure to configure the Android Network Security Config correctly. + * We provide an example configuration in `languages/kotlin/app/src/main/res/xml/network_security_config.xml` + * https://developer.android.com/privacy-and-security/security-config */ const val SERVER_URL = "https://10.0.2.2:8080/" diff --git a/languages/kotlin/app/src/main/res/xml/network_security_config.xml b/languages/kotlin/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 000000000..a164210ed --- /dev/null +++ b/languages/kotlin/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + bitwarden.com + + + + + + + bitwarden.eu + + + + + + diff --git a/languages/kotlin/publish-local.sh b/languages/kotlin/publish-local.sh new file mode 100755 index 000000000..68e67455f --- /dev/null +++ b/languages/kotlin/publish-local.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -e + +cd "$(dirname "$0")" + +mkdir -p ./sdk/src/main/jniLibs/{arm64-v8a,armeabi-v7a,x86_64,x86} + +# Build arm64 for emulator +cross build -p bitwarden-uniffi --release --target=aarch64-linux-android +mv ../../target/aarch64-linux-android/release/libbitwarden_uniffi.so ./sdk/src/main/jniLibs/arm64-v8a/libbitwarden_uniffi.so + +# Generate latest bindings +./build-schemas.sh + +# Publish to local maven +./gradlew sdk:publishToMavenLocal -Pversion=LOCAL diff --git a/languages/kotlin/sdk/build.gradle b/languages/kotlin/sdk/build.gradle index 14f6f943c..2006e4673 100644 --- a/languages/kotlin/sdk/build.gradle +++ b/languages/kotlin/sdk/build.gradle @@ -12,8 +12,8 @@ android { minSdk 28 targetSdk 34 - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles "consumer-rules.pro" + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + consumerProguardFiles 'consumer-rules.pro' } buildTypes { @@ -31,7 +31,7 @@ android { } lint { - baseline = file("lint-baseline.xml") + baseline = file('lint-baseline.xml') } publishing { @@ -47,23 +47,25 @@ publishing { groupId = 'com.bitwarden' artifactId = 'sdk-android' - // Determine the version from the git history. - // - // PRs: use the branch name. - // Main: Grab it from `crates/bitwarden/Cargo.toml` + if (findProperty('version') == 'unspecified') { + // Determine the version from the git history. + // + // PRs: use the branch name. + // Main: Grab it from `crates/bitwarden/Cargo.toml` - def branchName = "git branch --show-current".execute().text.trim() + def branchName = 'git branch --show-current'.execute().text.trim() - if (branchName == "main") { - def content = ['grep', '-o', '^version = ".*"', '../../Cargo.toml'].execute().text.trim() - def match = ~/version = "(.*)"/ - def matcher = match.matcher(content) - matcher.find() + if (branchName == 'main') { + def content = ['grep', '-o', '^version = ".*"', '../../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" + version = "${matcher.group(1)}-SNAPSHOT" + } else { + // branchName-SNAPSHOT + version = "${branchName.replaceAll('/', '-')}-SNAPSHOT" + } } afterEvaluate { @@ -73,18 +75,38 @@ publishing { } repositories { maven { - name = "GitHubPackages" - url = "https://maven.pkg.github.com/bitwarden/sdk" + name = 'GitHubPackages' + url = 'https://maven.pkg.github.com/bitwarden/sdk' credentials { - username = System.getenv("GITHUB_ACTOR") - password = System.getenv("GITHUB_TOKEN") + username = System.getenv('GITHUB_ACTOR') + password = System.getenv('GITHUB_TOKEN') } } } } +// Find and include the classes.jar from platform-verifier. +// +// Based on the instructions from the readme in https://github.com/rustls/rustls-platform-verifier +// and issue details from https://github.com/rustls/rustls-platform-verifier/issues/115. +File findRustlsPlatformVerifierClassesJar() { + def dependencyText = providers.exec { + it.workingDir = new File('../../') + commandLine('cargo', 'metadata', '--format-version', '1', '--manifest-path', 'crates/bitwarden-uniffi/Cargo.toml') + }.standardOutput.asText.get() + + def dependencyJson = new groovy.json.JsonSlurper().parseText(dependencyText) + def manifestPath = file(dependencyJson.packages.find { it.name == "rustls-platform-verifier-android" }.manifest_path) + + def aar = fileTree(manifestPath.parentFile).matching { + include "maven/rustls/rustls-platform-verifier/*/rustls-platform-verifier-*.aar" + }.getSingleFile() + return zipTree(aar).matching { include 'classes.jar'}.getSingleFile() +} + dependencies { implementation 'net.java.dev.jna:jna:5.14.0@aar' + implementation files(findRustlsPlatformVerifierClassesJar()) implementation 'androidx.core:core-ktx:1.13.0' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3' diff --git a/languages/kotlin/settings.gradle b/languages/kotlin/settings.gradle index fd45e3140..fac8f3fae 100644 --- a/languages/kotlin/settings.gradle +++ b/languages/kotlin/settings.gradle @@ -12,6 +12,7 @@ dependencyResolutionManagement { mavenCentral() } } -rootProject.name = "My Application" + +rootProject.name = 'My Application' include ':app' include ':sdk' diff --git a/languages/php/.gitignore b/languages/php/.gitignore index b2a69e9a0..5d6ed424e 100644 --- a/languages/php/.gitignore +++ b/languages/php/.gitignore @@ -1,2 +1,4 @@ .DS_Store vendor +src/lib/ +src/Schemas/ diff --git a/languages/php/INSTALL.md b/languages/php/INSTALL.md new file mode 100644 index 000000000..299053389 --- /dev/null +++ b/languages/php/INSTALL.md @@ -0,0 +1,56 @@ +# PHP Installation + +## Introduction + +Composer is used to build the PHP Bitwarden client library. + +## Prerequisites + +- PHP >= 8.0 +- FFI extension enabled in PHP configuration +- Composer +- Bitwarden SDK native library. + - Expected in one of below locations, depending on the OS and architecture. + The `src` is relative path to the [src](./src) directory. + - Windows x86_64: `src\lib\windows-x64\bitwarden_c.dll` + - Linux x86_64: `src/lib/linux-x64/libbitwarden_c.so` + - macOS x86_64: `src/lib/macos-x64/libbitwarden_c.dylib` + - macOS aarch64: `src/lib/macos-arm64/libbitwarden_c.dylib` + - If you prefer to build the SDK yourself, see the [SDK README.md](../../README.md) for instructions. + +## Build Commands + +```shell +composer install +``` + +## Example + +### macOS + +#### Install Prerequisites + +Use brew Composer and PHP + +```shell +brew install php +brew install composer +``` + +#### Build Commands + +```shell +composer install +``` + +## Example SDK Usage Project + +```shell +export ACCESS_TOKEN="" +export STATE_FILE="" +export ORGANIZATION_ID="" +export API_URL="https://api.bitwarden.com" +export IDENTITY_URL="https://identity.bitwarden.com" + +php example.php +``` diff --git a/languages/php/README.md b/languages/php/README.md index 9e4a9385d..61991bd0e 100644 --- a/languages/php/README.md +++ b/languages/php/README.md @@ -1,100 +1,121 @@ # 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 - +See the [installation instructions](./INSTALL.md) ## 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. +### Create access token -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. +To interact with the client first you need to obtain the access token from Bitwarden. +Review the help documentation on [Access Tokens]. +### Create new Bitwarden client ```php -$access_token = ''; -$api_url = ""; -$identity_url = ""; +require_once 'vendor/autoload.php'; + +$access_token = ""; +$state_file = ""; +$organization_id = ""; +$api_url = "https://api.bitwarden.com"; +$identity_url = "https://identity.bitwarden.com"; + $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); +$bitwarden_client->auth->login_access_token($access_token, $state_file); ``` -After successful authorization you can interact with client to manage your projects and secrets. -```php -$organization_id = ""; +Initialize `BitwardenSettings` by passing `$api_url` and `$identity_url` or set to null to use the defaults. +The default for `api_url` is `https://api.bitwarden.com` and for `identity_url` is `https://identity.bitwarden.com`. -$bitwarden_client = new \Bitwarden\Sdk\BitwardenClient($bitwarden_settings); -$res = $bitwarden_client->access_token_login($access_token); +### Create new project -// create project -$name = "PHP project" -$res = $bitwarden_client->projects->create($name, $organization_id); +```php +$name = "PHP project"; +$res = $bitwarden_client->projects->create($organization_id, $name); $project_id = $res->id; +``` -// get project +### Get project + +```php $res = $bitwarden_client->projects->get($project_id); +``` + +### List all projects -// list projects +```php $res = $bitwarden_client->projects->list($organization_id); +``` -// update project -$name = "Updated PHP project" -$res = $bitwarden_client->projects->put($project_id, $name, $organization_id); +### Update project -// get secret -$res = $bitwarden_client->secrets->get($secret_id); +```php +$name = "Updated PHP project"; +$res = $bitwarden_client->projects->update($organization_id, $project_id, $name); +``` -// list secrets -$res = $bitwarden_client->secrets->list($organization_id); +### Delete project -// delete project +```php $res = $bitwarden_client->projects->delete([$project_id]); +``` +### Create new secret + +```php +$key = "Secret key"; +$note = "Secret note"; +$value = "Secret value"; +$res = $bitwarden_client->secrets->create($organization_id, $key, $value, $note, [$project_id]); +$secret_id = $res->id; ``` -Similarly, you interact with secrets: +### Get secret + ```php -$organization_id = ""; +$res = $bitwarden_client->secrets->get($secret_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 multiple secrets -// get secret -$res = $bitwarden_sdk->secrets->get($secret_id); +```php +$res = $bitwarden_client->secrets->get_by_ids([$secret_id]); +``` -// list secrets +### List all secrets + +```php $res = $bitwarden_client->secrets->list($organization_id); +``` + +### Update secret + +```php +$key = "Updated key"; +$note = "Updated note"; +$value = "Updated value"; +$res = $bitwarden_client->secrets->update($organization_id, $secret_id, $key, $value, $note, [$project_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); +### Sync secrets -// delete secret -$res = $bitwarden_sdk->secrets->delete([$secret_id]); +```php +$last_synced_date = "2024-09-01T00:00:00Z"; +$res = $bitwarden_client->secrets->sync($organization_id, $last_synced_date); +``` + +### Delete secret + +```php +$res = $bitwarden_client->secrets->delete([$secret_id]); ``` +[Access Tokens]: https://bitwarden.com/help/access-tokens/ [Bitwarden Secrets Manager]: https://bitwarden.com/products/secrets-manager/ diff --git a/languages/php/composer.json b/languages/php/composer.json index 85447e72a..6df44ed0b 100644 --- a/languages/php/composer.json +++ b/languages/php/composer.json @@ -4,16 +4,16 @@ "type": "library", "keywords": ["bitwarden","sdk","password-manager"], "homepage": "https://github.com/bitwarden/sdk", - "version": "0.1.0", + "version": "1.0.0", "require": { "php": "^8.0", - "swaggest/json-schema": "^0.12.42", "ext-ffi": "*" }, "autoload": { "psr-4": { "Bitwarden\\Sdk\\": "src/" - } + }, + "files": ["src/Schemas/Schemas.php"] }, "authors": [ { diff --git a/languages/php/composer.lock b/languages/php/composer.lock index fc6b42c4f..187511304 100644 --- a/languages/php/composer.lock +++ b/languages/php/composer.lock @@ -4,234 +4,8 @@ "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" - } - ], + "content-hash": "1769eb8cdcb42d17f993aa0ef123895b", + "packages": [], "packages-dev": [], "aliases": [], "minimum-stability": "stable", diff --git a/languages/php/example.php b/languages/php/example.php index 7eafcb96a..864a4ca23 100644 --- a/languages/php/example.php +++ b/languages/php/example.php @@ -3,45 +3,101 @@ require_once 'vendor/autoload.php'; $access_token = getenv('ACCESS_TOKEN'); +$state_file = getenv('STATE_FILE'); $organization_id = getenv('ORGANIZATION_ID'); // 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); +try { + $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); + $bitwarden_client = new \Bitwarden\Sdk\BitwardenClient($client_settings); -// create project -$res = $bitwarden_client->projects->create('php project', $organization_id); -$project_id = $res->id; + $bitwarden_client->auth->login_access_token($access_token, $state_file); -// get project -$res = $bitwarden_client->projects->get($project_id); + // create project + print("Projects:\n"); + $res = $bitwarden_client->projects->create($organization_id, 'php project'); + $project_id = $res->id; + print("\tcreate: '" . $project_id . "'\n\n"); -// list projects -$res = $bitwarden_client->projects->list($organization_id); + // get project + $res = $bitwarden_client->projects->get($project_id); + print("\tget: '" . $res->name . "'\n\n"); -// update project -$res = $bitwarden_client->projects->put($project_id, 'php test awesome', $organization_id); + // list projects + $res = $bitwarden_client->projects->list($organization_id); + print("\tlist:\n"); + foreach ($res->data as $project) { + print("\t\tID: '" . $project->id . "', Name: '" . $project->name . "'\n"); + } + print("\n"); -// create secret -$res = $bitwarden_client->secrets->create("New Key", "hello world", $organization_id, [$project_id], "123"); -$secret_id = $res->id; + // update project + $res = $bitwarden_client->projects->update($organization_id, $project_id, 'php test project'); + print("\tupdate: '" . $res->name . "'\n\n"); -// get secret -$res = $bitwarden_client->secrets->get($secret_id); + // sync secrets + print("Secrets:\n"); + print("\tSyncing secrets...\n"); + $res = $bitwarden_client->secrets->sync($organization_id, null); + $now = new DateTime(); + $now_string = $now->format('Y-m-d\TH:i:s.u\Z'); + print("\t\tSync has changes: " . ($res->hasChanges ? 'true' : 'false') . "\n\n"); -// list secrets -$res = $bitwarden_client->secrets->list($organization_id); + print("\tSyncing again to ensure no changes since last sync...\n"); + $res = $bitwarden_client->secrets->sync($organization_id, $now_string); + print("\t\tSync has changes: " . ($res->hasChanges ? 'true' : 'false') . "\n\n"); -// update secret -$res = $bitwarden_client->secrets->update($secret_id, "hello world 2", "hello", $organization_id, [$project_id], "123"); + // create secret + $res = $bitwarden_client->secrets->create($organization_id, "New Key", "New value", "New note", [$project_id]); + $secret_id = $res->id; + print("\tcreate: '" . $secret_id . "'\n\n"); -// delete secret -$res = $bitwarden_client->secrets->delete([$secret_id]); + // get secret + $res = $bitwarden_client->secrets->get($secret_id); + print("\tget: '" . $res->key . "'\n\n"); -// delete project -$res = $bitwarden_client->projects->delete([$project_id]); + // get multiple secrets by ids + $res = $bitwarden_client->secrets->get_by_ids([$secret_id]); + print("\tget_by_ids:\n"); + foreach ($res->data as $secret) { + print("\t\tID: '" . $secret->id . "', Key: '" . $secret->key . "'\n"); + } + print("\n"); + + // list secrets + $res = $bitwarden_client->secrets->list($organization_id); + print("\tlist:\n"); + foreach ($res->data as $secret) { + print("\t\tID: '" . $secret->id . "', Key: '" . $secret->key . "'\n"); + } + print("\n"); + + // update secret + $res = $bitwarden_client->secrets->update($organization_id, $secret_id, "Updated key", "Updated value", "Updated note", [$project_id]); + print("\tupdate: '" . $res->key . "'\n\n"); + + // delete secret + print("Cleaning up secrets and projects:\n"); + $res = $bitwarden_client->secrets->delete([$secret_id]); + print("\tdelete:\n"); + foreach ($res->data as $secret) { + print("\t\tdeleted secret: '" . $secret->id . "'\n"); + } + print("\n"); + + // delete project + $res = $bitwarden_client->projects->delete([$project_id]); + print("\tdelete:\n"); + foreach ($res->data as $project) { + print("\t\tdeleted project: '" . $project->id . "'\n"); + } + print("\n"); + +} catch (Exception $e) { + print("Error: " . $e->getMessage() . "\n"); + exit(1); +} diff --git a/languages/php/src/AuthClient.php b/languages/php/src/AuthClient.php new file mode 100644 index 000000000..449c76905 --- /dev/null +++ b/languages/php/src/AuthClient.php @@ -0,0 +1,35 @@ +commandRunner = $commandRunner; + } + + /** + * @throws Exception + */ + public function login_access_token(string $access_token, ?string $state_file): void + { + $access_token_request = new AccessTokenLoginRequest($access_token, $state_file); + $command = new Command(passwordLogin: null, apiKeyLogin: null, loginAccessToken: $access_token_request, + getUserApiKey: null, fingerprint: null, sync: null, secrets: null, projects: null, generators: null); + try { + $result = $this->commandRunner->run($command); + if (!isset($result->authenticated) || !$result->authenticated) { + throw new Exception("Unauthorized"); + } + } catch (Exception $exception) { + throw new Exception("Authorization error: " . $exception->getMessage()); + } + } +} diff --git a/languages/php/src/BitwardenClient.php b/languages/php/src/BitwardenClient.php index 79fccdf9c..c125b6aa7 100644 --- a/languages/php/src/BitwardenClient.php +++ b/languages/php/src/BitwardenClient.php @@ -2,59 +2,40 @@ namespace Bitwarden\Sdk; -use Bitwarden\Sdk\Schemas\AccessTokenLoginRequest; -use Bitwarden\Sdk\schemas\ClientSettings; -use Bitwarden\Sdk\Schemas\Command; -use FFI; -use Swaggest\JsonDiff\Exception; - +use Bitwarden\Sdk\Schemas\ClientSettings; +use Bitwarden\Sdk\Schemas\DeviceType; +use JsonException; class BitwardenClient { - private BitwardenLib $bitwarden_lib; - - private ClientSettings $clientSettings; - public ProjectsClient $projects; public SecretsClient $secrets; - private CommandRunner $commandRunner; + public AuthClient $auth; - private FFI\CData $handle; + private BitwardenLib $bitwarden_lib; + private ClientSettings $clientSettings; + + private CommandRunner $commandRunner; + + /** + * @throws JsonException + */ public function __construct(BitwardenSettings $bitwardenSettings) { - $this->clientSettings = new ClientSettings(); - $this->clientSettings->apiUrl = $bitwardenSettings->get_api_url(); - $this->clientSettings->identityUrl = $bitwardenSettings->get_identity_url(); - $this->clientSettings->userAgent = "Bitwarden PHP-SDK"; + $this->clientSettings = new ClientSettings(apiUrl: $bitwardenSettings->get_api_url(), + deviceType: DeviceType::$SDK, identityUrl: $bitwardenSettings->get_identity_url(), + userAgent: "Bitwarden PHP-SDK"); $this->bitwarden_lib = new BitwardenLib(); - $this->handle = $this->bitwarden_lib->init($this->clientSettings); + $this->bitwarden_lib->init($this->clientSettings); - $this->commandRunner = new CommandRunner($this->bitwarden_lib, $this->handle); + $this->commandRunner = new CommandRunner($this->bitwarden_lib); $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"); - } + $this->auth = new AuthClient($this->commandRunner); } public function __destruct() diff --git a/languages/php/src/BitwardenLib.php b/languages/php/src/BitwardenLib.php index 351728986..53be3299b 100644 --- a/languages/php/src/BitwardenLib.php +++ b/languages/php/src/BitwardenLib.php @@ -4,10 +4,11 @@ use Bitwarden\Sdk\Schemas\ClientSettings; use Bitwarden\Sdk\Schemas\Command; +use Exception; use FFI; -use Swaggest\JsonDiff\Exception; -use Swaggest\JsonSchema\JsonSchema; - +use JsonException; +use RuntimeException; +use stdClass; class BitwardenLib { @@ -15,36 +16,36 @@ class BitwardenLib public FFI\CData $handle; /** - * @throws \Exception + * @throws Exception */ public function __construct() { $lib_file = null; if (PHP_OS === 'WINNT') { - $lib_file = '/lib/windows-x64/bitwarden_c.dll'; - if (file_exists($lib_file) == false) { - $lib_file = __DIR__.'/../../../target/debug/bitwarden_c.dll'; + $lib_file = __DIR__ . '/lib/windows-x64/bitwarden_c.dll'; + if (!file_exists($lib_file)) { + $lib_file = __DIR__ . '/../../../target/debug/bitwarden_c.dll'; } } elseif (PHP_OS === 'Linux') { - $lib_file = '/lib/linux-x64/libbitwarden_c.so'; - if (file_exists($lib_file) == false) { - $lib_file = __DIR__.'/../../../target/debug/libbitwarden_c.so'; + $lib_file = __DIR__ . '/lib/linux-x64/libbitwarden_c.so'; + if (!file_exists($lib_file)) { + $lib_file = __DIR__ . '/../../../target/debug/libbitwarden_c.so'; } } elseif (PHP_OS === 'Darwin') { $architecture = trim(exec('uname -m')); if ($architecture === 'x86_64' || $architecture === 'amd64') { - $lib_file = __DIR__.'/lib/macos-x64/libbitwarden_c.dylib'; + $lib_file = __DIR__ . '/lib/macos-x64/libbitwarden_c.dylib'; } elseif ($architecture === 'arm64') { - $lib_file = __DIR__.'/lib/macos-arm64/libbitwarden_c.dylib'; + $lib_file = __DIR__ . '/lib/macos-arm64/libbitwarden_c.dylib'; } - if (file_exists($lib_file) == false) { - $lib_file = __DIR__.'/../../../target/debug/libbitwarden_c.dylib'; + if (!file_exists($lib_file)) { + $lib_file = __DIR__ . '/../../../target/debug/libbitwarden_c.dylib'; } } - if ($lib_file == null || is_file($lib_file) == false) { - throw new \Exception("Lib file not found"); + if ($lib_file == null || !is_file($lib_file)) { + throw new Exception("Lib file not found"); } $this->ffi = FFI::cdef(' @@ -55,20 +56,29 @@ public function __construct() ); } + /** + * @throws JsonException + * @throws Exception + */ public function init(ClientSettings $client_settings): FFI\CData { - $this->handle = $this->ffi->init(json_encode($client_settings->jsonSerialize())); + $encoded_json = $this::json_encode_sdk_format($client_settings->to()); + $this->handle = $this->ffi->init($encoded_json); return $this->handle; } - public function run_command(Command $command): \stdClass + /** + * @throws JsonException + * @throws Exception + */ + public function run_command(Command $command): stdClass { - $encoded_json = json_encode($command->jsonSerialize()); + $encoded_json = $this::json_encode_sdk_format($command->to()); 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()); + } catch (FFI\Exception $e) { + throw new RuntimeException('Error occurred during FFI operation: ' . $e->getMessage()); } } @@ -76,4 +86,27 @@ public function free_mem(): void { $this->ffi->free_mem($this->handle); } + + /** + * @throws JsonException + */ + private static function json_encode_sdk_format($object): string + { + $withoutNull = function ($a) use (&$withoutNull) { + if (is_object($a)) { + $a = array_filter((array)$a); + foreach ($a as $k => $v) { + $a[$k] = $withoutNull($v); + } + + return (object)$a; + } + + return $a; + }; + + $object_no_nulls = $withoutNull($object); + + return json_encode($object_no_nulls, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES); + } } diff --git a/languages/php/src/CommandRunner.php b/languages/php/src/CommandRunner.php index 9eec68b2d..532b9625e 100644 --- a/languages/php/src/CommandRunner.php +++ b/languages/php/src/CommandRunner.php @@ -2,36 +2,32 @@ namespace Bitwarden\Sdk; - use Bitwarden\Sdk\Schemas\Command; -use FFI; +use Exception; +use stdClass; class CommandRunner { - private FFI\CData $handle; - private BitwardenLib $bitwardenLib; - public function __construct(BitwardenLib $bitwardenLib, $handle) + public function __construct(BitwardenLib $bitwardenLib) { $this->bitwardenLib = $bitwardenLib; - $this->handle = $handle; } /** - * @throws \Exception + * @throws Exception */ - public function run(Command $command): \stdClass + public function run(Command $command): stdClass { $result = $this->bitwardenLib->run_command($command); - if ($result->success == true) { + if ($result->success) { return $result->data; } - if (isset($result->errorMessage)) - { - throw new \Exception($result->errorMessage); + if (isset($result->errorMessage)) { + throw new Exception($result->errorMessage); } - throw new \Exception("Unknown error occurred"); + throw new Exception("Unknown error occurred"); } } diff --git a/languages/php/src/ProjectsClient.php b/languages/php/src/ProjectsClient.php index 6b6f9fb6a..cca44f1e6 100644 --- a/languages/php/src/ProjectsClient.php +++ b/languages/php/src/ProjectsClient.php @@ -9,6 +9,8 @@ use Bitwarden\Sdk\Schemas\ProjectsCommand; use Bitwarden\Sdk\Schemas\ProjectsDeleteRequest; use Bitwarden\Sdk\Schemas\ProjectsListRequest; +use Exception; +use stdClass; class ProjectsClient { @@ -19,63 +21,74 @@ public function __construct(CommandRunner $commandRunner) $this->commandRunner = $commandRunner; } - public function get(string $project_id): \stdClass + /** + * @throws Exception + */ + public function get(string $project_id): stdClass { - $project_get_request = new ProjectGetRequest(); - $project_get_request->id = $project_id; + $project_get_request = new ProjectGetRequest($project_id); $project_get_request->validate(); - $project_command = new ProjectsCommand(); - $project_command->get = $project_get_request->jsonSerialize(); + $project_command = new ProjectsCommand(get: $project_get_request, create: null, list: null, update: null, + delete: null); return $this->run_project_command($project_command); } - public function list(string $organization_id): \stdClass + /** + * @throws Exception + */ + public function list(string $organization_id): stdClass { - $project_list_request = new ProjectsListRequest(); - $project_list_request->organizationId = $organization_id; + $project_list_request = new ProjectsListRequest($organization_id); $project_list_request->validate(); - $project_command = new ProjectsCommand(); - $project_command->list = $project_list_request->jsonSerialize(); + $project_command = new ProjectsCommand(get: null, create: null, list: $project_list_request, update: null, + delete: null); return $this->run_project_command($project_command); } - public function create(string $project_name, string $organization_id): \stdClass + /** + * @throws Exception + */ + public function create(string $organization_id, string $project_name): stdClass { - $project_create_request = new ProjectCreateRequest(); - $project_create_request->name = $project_name; - $project_create_request->organizationId = $organization_id; + $project_create_request = new ProjectCreateRequest(name: $project_name, organizationId: $organization_id); $project_create_request->validate(); - $project_command = new ProjectsCommand(); - $project_command->create = $project_create_request->jsonSerialize(); + $project_command = new ProjectsCommand(get: null, create: $project_create_request, list: null, update: null, + delete: null); return $this->run_project_command($project_command); } - public function put(string $project_id, string $project_name, string $organization_id): \stdClass + /** + * @throws Exception + */ + public function update(string $organization_id, string $project_id, string $project_name): 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 = new ProjectPutRequest(id: $project_id, name: $project_name, + organizationId: $organization_id); $project_put_request->validate(); - $project_command = new ProjectsCommand(); - $project_command->update = $project_put_request->jsonSerialize(); + $project_command = new ProjectsCommand(get: null, create: null, list: null, update: $project_put_request, + delete: null); return $this->run_project_command($project_command); } - public function delete(array $ids): \stdClass + /** + * @throws Exception + */ + public function delete(array $ids): stdClass { - $projects_delete_request = new ProjectsDeleteRequest(); - $projects_delete_request->ids = $ids; + $projects_delete_request = new ProjectsDeleteRequest($ids); $projects_delete_request->validate(); - $project_command = new ProjectsCommand(); - $project_command->delete = $projects_delete_request->jsonSerialize(); + $project_command = new ProjectsCommand(get: null, create: null, list: null, update: null, + delete: $projects_delete_request); return $this->run_project_command($project_command); } - public function run_project_command($projectCommand): \stdClass + /** + * @throws Exception + */ + public function run_project_command($projectCommand): stdClass { - $command = new Command(); - $command->projects = $projectCommand; + $command = new Command(passwordLogin: null, apiKeyLogin: null, loginAccessToken: null, getUserApiKey: null, + fingerprint: null, sync: null, secrets: null, projects: $projectCommand, generators: null); return $this->commandRunner->run($command); } } diff --git a/languages/php/src/SecretsClient.php b/languages/php/src/SecretsClient.php index d5c0b0cef..85bc334d5 100644 --- a/languages/php/src/SecretsClient.php +++ b/languages/php/src/SecretsClient.php @@ -10,6 +10,9 @@ use Bitwarden\Sdk\Schemas\SecretsCommand; use Bitwarden\Sdk\Schemas\SecretsDeleteRequest; use Bitwarden\Sdk\Schemas\SecretsGetRequest; +use Bitwarden\Sdk\Schemas\SecretsSyncRequest; +use Exception; +use stdClass; class SecretsClient { @@ -20,79 +23,103 @@ public function __construct(CommandRunner $commandRunner) $this->commandRunner = $commandRunner; } - public function get(string $secret_id): \stdClass + /** + * @throws Exception + */ + public function get(string $secret_id): stdClass { - $secret_get_request = new SecretGetRequest(); - $secret_get_request->id = $secret_id; + $secret_get_request = new SecretGetRequest($secret_id); $secret_get_request->validate(); - $secret_command = new SecretsCommand(); - $secret_command->get = $secret_get_request->jsonSerialize(); - return $this->run_secret_command($secret_command); + $secrets_command = new SecretsCommand(get: $secret_get_request, getByIds: null, create: null, list: null, + update: null, delete: null, sync: null); + return $this->run_secret_command($secrets_command); } - public function get_by_ids(array $secret_ids): \stdClass + /** + * @throws Exception + */ + 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 = new SecretsGetRequest($secret_ids); $project_get_by_ids_request->validate(); - $secrets_command = new SecretsCommand(); - $secrets_command->get_by_ids = $project_get_by_ids_request->jsonSerialize(); + $secrets_command = new SecretsCommand(get: null, getByIds: $project_get_by_ids_request, create: null, list: null, + update: null, delete: null, sync: null); return $this->run_secret_command($secrets_command); } - public function list(string $organization_id): \stdClass + /** + * @throws Exception + */ + public function list(string $organization_id): stdClass { - $secrets_list_request = new SecretIdentifiersRequest(); - $secrets_list_request->organizationId = $organization_id; + $secrets_list_request = new SecretIdentifiersRequest($organization_id); $secrets_list_request->validate(); - $secrets_command = new SecretsCommand(); - $secrets_command->list = $secrets_list_request->jsonSerialize(); + $secrets_command = new SecretsCommand(get: null, getByIds: null, create: null, list: $secrets_list_request, + update: null, delete: null, sync: null); return $this->run_secret_command($secrets_command); } - public function create(string $key, string $note, string $organization_id, array $project_ids, string $value): \stdClass + /** + * @throws Exception + */ + public function create(string $organization_id, string $key, string $value, string $note, array $project_ids): 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 = new SecretCreateRequest(key: $key, note: $note, organizationId: $organization_id, + projectIds: $project_ids, value: $value); $secrets_create_request->validate(); - $secrets_command = new SecretsCommand(); - $secrets_command->create = $secrets_create_request->jsonSerialize(); + $secrets_command = new SecretsCommand(get: null, getByIds: null, create: $secrets_create_request, list: null, + update: null, delete: null, sync: null); 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 + /** + * @throws Exception + */ + public function update(string $organization_id, string $id, string $key, string $value, string $note, array $project_ids): 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 = new SecretPutRequest(id: $id, key: $key, note: $note, organizationId: $organization_id, + projectIds: $project_ids, value: $value); $secrets_put_request->validate(); - $secrets_command = new SecretsCommand(); - $secrets_command->update = $secrets_put_request->jsonSerialize(); + $secrets_command = new SecretsCommand(get: null, getByIds: null, create: null, list: null, + update: $secrets_put_request, delete: null, sync: null); return $this->run_secret_command($secrets_command); } - public function delete(array $secrets_ids): \stdClass + /** + * @throws Exception + */ + public function delete(array $secrets_ids): stdClass { - $secrets_delete_request = new SecretsDeleteRequest(); - $secrets_delete_request->ids = $secrets_ids; + $secrets_delete_request = new SecretsDeleteRequest($secrets_ids); $secrets_delete_request->validate(); - $secrets_command = new SecretsCommand(); - $secrets_command->delete = $secrets_delete_request->jsonSerialize(); + $secrets_command = new SecretsCommand(get: null, getByIds: null, create: null, list: null, + update: null, delete: $secrets_delete_request, sync: null); + return $this->run_secret_command($secrets_command); + } + + /** + * @throws Exception + */ + public function sync(string $organization_id, ?string $last_synced_date): stdClass + { + if (empty($last_synced_date)) { + $last_synced_date = "1970-01-01T00:00:00.000Z"; + } + + $secrets_sync_request = new SecretsSyncRequest(lastSyncedDate: $last_synced_date, organizationId: $organization_id); + $secrets_sync_request->validate(); + $secrets_command = new SecretsCommand(get: null, getByIds: null, create: null, list: null, + update: null, delete: null, sync: $secrets_sync_request); return $this->run_secret_command($secrets_command); } - public function run_secret_command($secretsCommand): \stdClass + /** + * @throws Exception + */ + public function run_secret_command($secretsCommand): stdClass { - $command = new Command(); - $command->secrets = $secretsCommand; + $command = new Command(passwordLogin: null, apiKeyLogin: null, loginAccessToken: null, getUserApiKey: null, + fingerprint: null, sync: null, secrets: $secretsCommand, projects: null, generators: null); return $this->commandRunner->run($command); } } diff --git a/languages/php/src/schemas/AccessTokenLoginRequest.php b/languages/php/src/schemas/AccessTokenLoginRequest.php deleted file mode 100644 index a08805f92..000000000 --- a/languages/php/src/schemas/AccessTokenLoginRequest.php +++ /dev/null @@ -1,39 +0,0 @@ -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 deleted file mode 100644 index fd50354d4..000000000 --- a/languages/php/src/schemas/BitwardenClassStructure.php +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index c27cc3322..000000000 --- a/languages/php/src/schemas/ClientSettings.php +++ /dev/null @@ -1,133 +0,0 @@ -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 deleted file mode 100644 index cbd649c2f..000000000 --- a/languages/php/src/schemas/Command.php +++ /dev/null @@ -1,44 +0,0 @@ -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 deleted file mode 100644 index 6a4e0f082..000000000 --- a/languages/php/src/schemas/ProjectCreateRequest.php +++ /dev/null @@ -1,43 +0,0 @@ -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 deleted file mode 100644 index 972bf18ec..000000000 --- a/languages/php/src/schemas/ProjectGetRequest.php +++ /dev/null @@ -1,37 +0,0 @@ -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 deleted file mode 100644 index 96b9705e7..000000000 --- a/languages/php/src/schemas/ProjectPutRequest.php +++ /dev/null @@ -1,50 +0,0 @@ -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 deleted file mode 100644 index 22645db3c..000000000 --- a/languages/php/src/schemas/ProjectsCommand.php +++ /dev/null @@ -1,55 +0,0 @@ - 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 deleted file mode 100644 index 87a7cfad7..000000000 --- a/languages/php/src/schemas/ProjectsDeleteRequest.php +++ /dev/null @@ -1,39 +0,0 @@ -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 deleted file mode 100644 index cc1a9474f..000000000 --- a/languages/php/src/schemas/ProjectsListRequest.php +++ /dev/null @@ -1,38 +0,0 @@ -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 deleted file mode 100644 index d34b36e98..000000000 --- a/languages/php/src/schemas/SecretCreateRequest.php +++ /dev/null @@ -1,58 +0,0 @@ -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 deleted file mode 100644 index f31f7cad3..000000000 --- a/languages/php/src/schemas/SecretGetRequest.php +++ /dev/null @@ -1,38 +0,0 @@ -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 deleted file mode 100644 index b4e75b801..000000000 --- a/languages/php/src/schemas/SecretIdentifiersRequest.php +++ /dev/null @@ -1,38 +0,0 @@ -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 deleted file mode 100644 index d890a909d..000000000 --- a/languages/php/src/schemas/SecretPutRequest.php +++ /dev/null @@ -1,64 +0,0 @@ -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 deleted file mode 100644 index 95cfd1e15..000000000 --- a/languages/php/src/schemas/SecretVerificationRequest.php +++ /dev/null @@ -1,35 +0,0 @@ -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 deleted file mode 100644 index 1ed8c97c5..000000000 --- a/languages/php/src/schemas/SecretsCommand.php +++ /dev/null @@ -1,56 +0,0 @@ - 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 deleted file mode 100644 index 35138fcb1..000000000 --- a/languages/php/src/schemas/SecretsDeleteRequest.php +++ /dev/null @@ -1,39 +0,0 @@ -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 deleted file mode 100644 index 4758dabf4..000000000 --- a/languages/php/src/schemas/SecretsGetRequest.php +++ /dev/null @@ -1,39 +0,0 @@ -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'); - } -} diff --git a/languages/python/README.md b/languages/python/README.md index e5fe5ae70..29f3f97fb 100644 --- a/languages/python/README.md +++ b/languages/python/README.md @@ -2,17 +2,41 @@ ## Requirements - Python 3 +- Rust - `maturin` (install with `pip install maturin`) - `npm` ## Build -From the root of the repository: ```bash +npm install npm run schemas # generate schemas.py cd languages/python/ +``` + +You will need to build and run the script using a virtual environment. +This will be slightly different depending on the OS you are using: + +```bash +# --- Linux/macOS --- +python3 -m venv .venv +source .venv/bin/activate + +# --- Windows --- +python -m venv venv + +venv\Scripts\activate.bat # cmd.exe +venv\Scripts\Activate.ps1 # Powershell +``` + +## Run + +```bash maturin develop +python3 ./example.py + +deactivate # run this to close the virtual session ``` You can now import `BitwardenClient` in your Python code with: @@ -33,26 +57,3 @@ Set the `ORGANIZATION_ID` and `ACCESS_TOKEN` environment variables to your organ ```bash python3 ./example.py ``` - -# Using Virtual Environments - -If you would like to build & run the script within a virtual environment you can do the following. - -## Build - -```bash -npm run schemas # generate schemas.py - -cd languages/python/ -python3 -m venv .venv -maturin develop -``` - -## Run - -```bash -source .venv/bin/activate -python3 ./example.py - -deactivate # run this to close the virtual session -``` diff --git a/languages/python/bitwarden_sdk/__init__.py b/languages/python/bitwarden_sdk/__init__.py index b2aeffea1..f1712c77b 100644 --- a/languages/python/bitwarden_sdk/__init__.py +++ b/languages/python/bitwarden_sdk/__init__.py @@ -1,6 +1,6 @@ """The official Bitwarden client library for Python.""" -__version__ = "0.1.0" +__version__ = "1.0.0" from .bitwarden_client import * from .schemas import * diff --git a/languages/python/bitwarden_sdk/bitwarden_client.py b/languages/python/bitwarden_sdk/bitwarden_client.py index b5ea48c44..ac51e8dd9 100644 --- a/languages/python/bitwarden_sdk/bitwarden_client.py +++ b/languages/python/bitwarden_sdk/bitwarden_client.py @@ -2,7 +2,16 @@ from typing import Any, List, Optional from uuid import UUID import bitwarden_py -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 + +from .schemas import (ClientSettings, Command, ResponseForSecretIdentifiersResponse, ResponseForSecretResponse, + ResponseForSecretsResponse, ResponseForSecretsDeleteResponse, SecretCreateRequest, + SecretGetRequest, SecretsGetRequest, SecretIdentifiersRequest, SecretPutRequest, + SecretsCommand, SecretsDeleteRequest, SecretsSyncRequest, AccessTokenLoginRequest, + ResponseForSecretsSyncResponse, ResponseForAccessTokenLoginResponse, + ResponseForProjectResponse, ProjectsCommand, ProjectCreateRequest, ProjectGetRequest, + ProjectPutRequest, ProjectsListRequest, ResponseForProjectsResponse, + ResponseForProjectsDeleteResponse, ProjectsDeleteRequest) + class BitwardenClient: def __init__(self, settings: ClientSettings = None): @@ -12,11 +21,8 @@ def __init__(self, settings: ClientSettings = None): settings_json = json.dumps(settings.to_dict()) self.inner = bitwarden_py.BitwardenClient(settings_json) - def access_token_login(self, access_token: str, - state_file_path: str = None): - self._run_command( - Command(access_token_login=AccessTokenLoginRequest(access_token, state_file_path)) - ) + def auth(self): + return AuthClient(self) def secrets(self): return SecretsClient(self) @@ -30,9 +36,22 @@ def _run_command(self, command: Command) -> Any: if response["success"] == False: raise Exception(response["errorMessage"]) - + return response + +class AuthClient: + def __init__(self, client: BitwardenClient): + self.client = client + + def login_access_token(self, access_token: str, + state_file: str = None) -> ResponseForAccessTokenLoginResponse: + result = self.client._run_command( + Command(login_access_token=AccessTokenLoginRequest(access_token, state_file)) + ) + return ResponseForAccessTokenLoginResponse.from_dict(result) + + class SecretsClient: def __init__(self, client: BitwardenClient): self.client = client @@ -43,12 +62,24 @@ def get(self, id: str) -> ResponseForSecretResponse: ) return ResponseForSecretResponse.from_dict(result) - def create(self, key: str, - note: str, - organization_id: str, - value: str, - project_ids: Optional[List[UUID]] = None - ) -> ResponseForSecretResponse: + def get_by_ids(self, ids: List[UUID]) -> ResponseForSecretsResponse: + result = self.client._run_command( + Command(secrets=SecretsCommand( + get_by_ids=SecretsGetRequest(ids)) + )) + return ResponseForSecretsResponse.from_dict(result) + + def create( + self, + organization_id: UUID, + key: str, + value: str, + note: Optional[str], + project_ids: Optional[List[UUID]] = None, + ) -> ResponseForSecretResponse: + if note is None: + # secrets api does not accept empty notes + note = "" result = self.client._run_command( Command(secrets=SecretsCommand( create=SecretCreateRequest(key, note, organization_id, value, project_ids))) @@ -62,13 +93,18 @@ def list(self, organization_id: str) -> ResponseForSecretIdentifiersResponse: ) return ResponseForSecretIdentifiersResponse.from_dict(result) - def update(self, id: str, - key: str, - note: str, - organization_id: str, - value: str, - project_ids: Optional[List[UUID]] = None - ) -> ResponseForSecretResponse: + def update( + self, + organization_id: str, + id: str, + key: str, + value: str, + note: Optional[str], + project_ids: Optional[List[UUID]] = None, + ) -> ResponseForSecretResponse: + if note is None: + # secrets api does not accept empty notes + note = "" result = self.client._run_command( Command(secrets=SecretsCommand(update=SecretPutRequest( id, key, note, organization_id, value, project_ids))) @@ -81,6 +117,13 @@ def delete(self, ids: List[str]) -> ResponseForSecretsDeleteResponse: ) return ResponseForSecretsDeleteResponse.from_dict(result) + def sync(self, organization_id: str, last_synced_date: Optional[str]) -> ResponseForSecretsSyncResponse: + result = self.client._run_command( + Command(secrets=SecretsCommand(sync=SecretsSyncRequest(organization_id, last_synced_date))) + ) + return ResponseForSecretsSyncResponse.from_dict(result) + + class ProjectsClient: def __init__(self, client: BitwardenClient): self.client = client @@ -92,8 +135,8 @@ def get(self, id: str) -> ResponseForProjectResponse: return ResponseForProjectResponse.from_dict(result) def create(self, - name: str, organization_id: str, + name: str, ) -> ResponseForProjectResponse: result = self.client._run_command( Command(projects=ProjectsCommand( @@ -108,10 +151,12 @@ def list(self, organization_id: str) -> ResponseForProjectsResponse: ) return ResponseForProjectsResponse.from_dict(result) - def update(self, id: str, - name: str, - organization_id: str, - ) -> ResponseForProjectResponse: + def update( + self, + organization_id: str, + id: str, + name: str, + ) -> ResponseForProjectResponse: result = self.client._run_command( Command(projects=ProjectsCommand(update=ProjectPutRequest( id, name, organization_id))) diff --git a/languages/python/example.py b/languages/python/example.py index 16367a0c5..ee3690dd0 100755 --- a/languages/python/example.py +++ b/languages/python/example.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import logging import os +from datetime import datetime, timezone from bitwarden_sdk import BitwardenClient, DeviceType, client_settings_from_dict @@ -20,15 +21,19 @@ logging.basicConfig(level=logging.DEBUG) organization_id = os.getenv("ORGANIZATION_ID") +# Set the state file location +# Note: the path must exist, the file will be created & managed by the sdk +state_path = os.getenv("STATE_FILE") + # Attempt to authenticate with the Secrets Manager Access Token -client.access_token_login(os.getenv("ACCESS_TOKEN")) +client.auth().login_access_token(os.getenv("ACCESS_TOKEN"), state_path) # -- Example Project Commands -- -project = client.projects().create("ProjectName", organization_id) -project2 = client.projects().create("Project - Don't Delete Me!", organization_id) +project = client.projects().create(organization_id, "ProjectName") +project2 = client.projects().create(organization_id, "AnotherProject") updated_project = client.projects().update( - project.data.id, "Cool New Project Name", organization_id + organization_id, project.data.id, "Cool New Project Name" ) get_that_project = client.projects().get(project.data.id) @@ -39,31 +44,40 @@ # -- Example Secret Commands -- +if client.secrets().sync(organization_id, None).data.has_changes is True: + print("There are changes to sync") +else: + print("No changes to sync") + +last_synced_date = datetime.now(tz=timezone.utc) +print(client.secrets().sync(organization_id, last_synced_date)) + secret = client.secrets().create( + organization_id, "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, + "ANOTHER_SECRET", "Secret1234!", + None, [project2.data.id], ) secret_updated = client.secrets().update( + organization_id, 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) +secrets_retrieved = client.secrets().get_by_ids([secret.data.id, secret2.data.id]) -input("Press Enter to delete the secret...") -client.secrets().delete([secret.data.id]) +# cleanup +input("Press Enter to cleanup secrets and projects...") +client.secrets().delete([secret.id for secret in secrets_retrieved.data.data]) -print(client.secrets().list(organization_id)) +client.projects().delete([project2.data.id]) diff --git a/languages/python/pyproject.toml b/languages/python/pyproject.toml index 28bb22507..a4ab1f693 100644 --- a/languages/python/pyproject.toml +++ b/languages/python/pyproject.toml @@ -17,7 +17,7 @@ description = "A Bitwarden Client for python" name = "bitwarden_sdk" readme = "README.md" requires-python = ">=3.0" -version = "0.1.0" +version = "1.0.0" [tool.maturin] bindings = "pyo3" diff --git a/languages/ruby/README.md b/languages/ruby/README.md index a02b1b53e..790008b38 100644 --- a/languages/ruby/README.md +++ b/languages/ruby/README.md @@ -9,7 +9,7 @@ Requirements: Ruby >= 3.0 Install gem: `gem install bitwarden-sdk-secrets` -Import it: require 'bitwarden-sdk-secrets' +Import it: `require 'bitwarden-sdk-secrets'` ## Usage @@ -28,7 +28,7 @@ bitwarden_settings = BitwardenSDK::BitwardenSettings.new( # By passing these setting you can initialize BitwardenClient bw_client = BitwardenSDK::BitwardenClient.new(bitwarden_settings) -response = bw_client.access_token_login(token) +response = bw_client.auth.login_access_token(token, state_file) puts response ``` @@ -38,25 +38,25 @@ After successful authorization you can interact with client to manage your proje # CREATE project project_name = 'Test project 1' -response = bw_client.project_client.create_project(project_name, organization_id) +response = bw_client.projects.create(organization_id, project_name) puts response project_id = response['id'] # GET project -response = bw_client.project_client.get(project_id) +response = bw_client.projects.get(project_id) puts response # LIST projects -response = bw_client.project_client.list_projects(organization_id) +response = bw_client.projects.list(organization_id) puts response # UPDATE projects name = 'Updated test project 1' -response = bw_client.project_client.update_project(project_id, name, organization_id) +response = bw_client.projects.update(organization_id, project_id, name) puts response # DELETE project -response = bw_client.project_client.delete_projects([project_id]) +response = bw_client.projects.delete_projects([project_id]) puts response ``` @@ -67,30 +67,38 @@ Similarly, you interact with secrets: key = 'AWS-SES' note = 'Private account' value = '8t27.dfj;' -response = bw_client.secrets_client.create(key, note, organization_id, [project_id], value) +response = bw_client.secrets.create(organization_id, key, value, note, [project_id]) puts response secret_id = response['id'] # GET secret -response = bw_client.secrets_client.get(secret_id) +response = bw_client.secrets.get(secret_id) puts response # GET secret by ids -response = bw_client.secrets_client.get_by_ids([secret_id]) +response = bw_client.secrets.get_by_ids([secret_id]) puts response # LIST secrets -response = bw_client.secrets_client.list(organization_id) +response = bw_client.secrets.list(organization_id) +puts response + +# SYNC secrets +response = bw_client.secrets.sync(organization_id, nil) +last_synced_date = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%S.%6NZ') +puts response + +response = bw_client.secrets.sync(organization_id, last_synced_date) 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) +response = bw_client.secrets.update(organization_id, secret_id, key, value, note, [project_id]) puts response # DELETE secret -response = bw_client.secrets_client.delete_secret([secret_id]) +response = bw_client.secrets.delete([secret_id]) puts response ``` diff --git a/languages/ruby/bitwarden_sdk_secrets/lib/auth.rb b/languages/ruby/bitwarden_sdk_secrets/lib/auth.rb new file mode 100644 index 000000000..481f30257 --- /dev/null +++ b/languages/ruby/bitwarden_sdk_secrets/lib/auth.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true +require_relative 'bitwarden_error' + +module BitwardenSDKSecrets + class AuthClient + def initialize(command_runner) + @command_runner = command_runner + end + + def login_access_token(access_token, state_file = nil) + access_token_request = AccessTokenLoginRequest.new(access_token: access_token, state_file: state_file) + @command_runner.run(SelectiveCommand.new(login_access_token: access_token_request)) + nil + end + end +end diff --git a/languages/ruby/bitwarden_sdk_secrets/lib/bitwarden-sdk-secrets.rb b/languages/ruby/bitwarden_sdk_secrets/lib/bitwarden-sdk-secrets.rb index 15cd115d1..4cd1c2597 100644 --- a/languages/ruby/bitwarden_sdk_secrets/lib/bitwarden-sdk-secrets.rb +++ b/languages/ruby/bitwarden_sdk_secrets/lib/bitwarden-sdk-secrets.rb @@ -10,6 +10,7 @@ require_relative 'bitwarden_error' require_relative 'projects' require_relative 'secrets' +require_relative 'auth' module BitwardenSDKSecrets class BitwardenSettings @@ -26,7 +27,7 @@ def initialize(api_url, identity_url) end class BitwardenClient - attr_reader :bitwarden, :project_client, :secrets_client + attr_reader :bitwarden, :projects, :secrets, :auth def initialize(bitwarden_settings) client_settings = ClientSettings.new( @@ -39,14 +40,9 @@ def initialize(bitwarden_settings) @bitwarden = BitwardenLib @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) - end - - def access_token_login(access_token, state_file = nil) - access_token_request = AccessTokenLoginRequest.new(access_token: access_token, state_file: state_file) - @command_runner.run(SelectiveCommand.new(access_token_login: access_token_request)) - nil + @projects = ProjectsClient.new(@command_runner) + @secrets = SecretsClient.new(@command_runner) + @auth = AuthClient.new(@command_runner) end def free_mem diff --git a/languages/ruby/bitwarden_sdk_secrets/lib/extended_schemas/schemas.rb b/languages/ruby/bitwarden_sdk_secrets/lib/extended_schemas/schemas.rb index e2352237f..bcdfa5e5f 100644 --- a/languages/ruby/bitwarden_sdk_secrets/lib/extended_schemas/schemas.rb +++ b/languages/ruby/bitwarden_sdk_secrets/lib/extended_schemas/schemas.rb @@ -3,23 +3,25 @@ module BitwardenSDKSecrets 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 :login_access_token, 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) + attribute :generators, GeneratorsCommand.optional.default(nil) def to_dynamic { "passwordLogin" => password_login&.to_dynamic, "apiKeyLogin" => api_key_login&.to_dynamic, - "accessTokenLogin" => access_token_login&.to_dynamic, + "loginAccessToken" => login_access_token&.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, + "generators" => generators&.to_dynamic, }.compact end end @@ -49,6 +51,7 @@ class SelectiveSecretsCommand < SecretsCommand attribute :list, SecretIdentifiersRequest.optional.default(nil) attribute :update, SecretPutRequest.optional.default(nil) attribute :delete, SecretsDeleteRequest.optional.default(nil) + attribute :sync, SecretsSyncRequest.optional.default(nil) def to_dynamic { @@ -58,7 +61,18 @@ def to_dynamic "list" => list&.to_dynamic, "update" => update&.to_dynamic, "delete" => delete&.to_dynamic, + "sync" => sync&.to_dynamic, }.compact end end + + class SelectiveGeneratorsCommand < GeneratorsCommand + attribute :generate_password, PasswordGeneratorRequest.optional.default(nil) + + def to_dynamic + { + "generate_password" => generate_password&.to_dynamic, + }.compact + end + end end diff --git a/languages/ruby/bitwarden_sdk_secrets/lib/projects.rb b/languages/ruby/bitwarden_sdk_secrets/lib/projects.rb index 957a7d31d..bf4e903ca 100644 --- a/languages/ruby/bitwarden_sdk_secrets/lib/projects.rb +++ b/languages/ruby/bitwarden_sdk_secrets/lib/projects.rb @@ -8,7 +8,7 @@ def initialize(command_runner) @command_runner = command_runner end - def create_project(project_name, organization_id) + def create(organization_id, project_name) project_create_request = ProjectCreateRequest.new( project_create_request_name: project_name, organization_id: organization_id @@ -43,7 +43,7 @@ def get(project_id) error_response(projects_response) end - def list_projects(organization_id) + def list(organization_id) project_list_request = ProjectsListRequest.new(organization_id: organization_id) command = create_command(list: project_list_request) response = parse_response(command) @@ -58,7 +58,7 @@ def list_projects(organization_id) error_response(projects_response) end - def update_project(id, project_put_request_name, organization_id) + def update(organization_id, id, project_put_request_name) project_put_request = ProjectPutRequest.new( id: id, project_put_request_name: project_put_request_name, @@ -79,7 +79,7 @@ def update_project(id, project_put_request_name, organization_id) error_response(projects_response) end - def delete_projects(ids) + def delete(ids) project_delete_request = ProjectsDeleteRequest.new(ids: ids) command = create_command(delete: project_delete_request) response = parse_response(command) diff --git a/languages/ruby/bitwarden_sdk_secrets/lib/secrets.rb b/languages/ruby/bitwarden_sdk_secrets/lib/secrets.rb index 709d8f8b1..5dda98830 100644 --- a/languages/ruby/bitwarden_sdk_secrets/lib/secrets.rb +++ b/languages/ruby/bitwarden_sdk_secrets/lib/secrets.rb @@ -36,7 +36,23 @@ def get_by_ids(ids) error_response(secrets_response) end - def create(key, note, organization_id, project_ids, value) + def sync(organization_id, last_synced_date) + command = create_command( + sync: SecretsSyncRequest.new(organization_id: organization_id, last_synced_date: last_synced_date) + ) + response = run_command(command) + + secrets_response = ResponseForSecretsSyncResponse.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 create(organization_id, key, value, note, project_ids) command = create_command( create: SecretCreateRequest.new( key: key, note: note, organization_id: organization_id, project_ids: project_ids, value: value @@ -68,7 +84,7 @@ def list(organization_id) error_response(secrets_response) end - def update(id, key, note, organization_id, project_ids, value) + def update(organization_id, id, key, value, note, project_ids) command = create_command( update: SecretPutRequest.new( id: id, key: key, note: note, organization_id: organization_id, project_ids: project_ids, value: value @@ -86,7 +102,7 @@ def update(id, key, note, organization_id, project_ids, value) error_response(secrets_response) end - def delete_secret(ids) + def delete(ids) command = create_command(delete: SecretsDeleteRequest.new(ids: ids)) response = run_command(command) diff --git a/languages/ruby/bitwarden_sdk_secrets/lib/version.rb b/languages/ruby/bitwarden_sdk_secrets/lib/version.rb index 3103642a1..39b47b47b 100644 --- a/languages/ruby/bitwarden_sdk_secrets/lib/version.rb +++ b/languages/ruby/bitwarden_sdk_secrets/lib/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module BitwardenSDKSecrets - VERSION = '0.2.0' + VERSION = '1.0.0' end diff --git a/languages/ruby/bitwarden_sdk_secrets/sig/auth.rbs b/languages/ruby/bitwarden_sdk_secrets/sig/auth.rbs new file mode 100644 index 000000000..4e75cf4c4 --- /dev/null +++ b/languages/ruby/bitwarden_sdk_secrets/sig/auth.rbs @@ -0,0 +1,9 @@ +module BitwardenSDKSecrets + class AuthClient + @command_runner: untyped + + def initialize: (untyped command_runner) -> void + + def login_access_token: (untyped access_token, ?untyped? state_file) -> nil + end +end diff --git a/languages/ruby/bitwarden_sdk_secrets/sig/bitwarden-sdk-secrets.rbs b/languages/ruby/bitwarden_sdk_secrets/sig/bitwarden-sdk-secrets.rbs new file mode 100644 index 000000000..ffca59e61 --- /dev/null +++ b/languages/ruby/bitwarden_sdk_secrets/sig/bitwarden-sdk-secrets.rbs @@ -0,0 +1,39 @@ +module BitwardenSDKSecrets + class BitwardenSettings + @api_url: untyped + + @identity_url: untyped + + attr_accessor api_url: untyped + + attr_accessor identity_url: untyped + + def initialize: (untyped api_url, untyped identity_url) -> void + end + + class BitwardenClient + @bitwarden: untyped + + @handle: untyped + + @command_runner: untyped + + @projects: untyped + + @secrets: untyped + + @auth: untyped + + attr_reader bitwarden: untyped + + attr_reader projects: untyped + + attr_reader secrets: untyped + + attr_reader auth: untyped + + def initialize: (untyped bitwarden_settings) -> void + + def free_mem: () -> untyped + end +end diff --git a/languages/ruby/bitwarden_sdk_secrets/sig/bitwarden-sdk.rbs b/languages/ruby/bitwarden_sdk_secrets/sig/bitwarden-sdk.rbs deleted file mode 100644 index 3f6a73f6a..000000000 --- a/languages/ruby/bitwarden_sdk_secrets/sig/bitwarden-sdk.rbs +++ /dev/null @@ -1,13 +0,0 @@ -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_secrets/sig/bitwarden_error.rbs b/languages/ruby/bitwarden_sdk_secrets/sig/bitwarden_error.rbs new file mode 100644 index 000000000..99c60f48d --- /dev/null +++ b/languages/ruby/bitwarden_sdk_secrets/sig/bitwarden_error.rbs @@ -0,0 +1,5 @@ +module BitwardenSDKSecrets + class BitwardenError < StandardError + def initialize: (?::String message) -> void + end +end diff --git a/languages/ruby/bitwarden_sdk_secrets/sig/bitwarden_lib.rbs b/languages/ruby/bitwarden_sdk_secrets/sig/bitwarden_lib.rbs new file mode 100644 index 000000000..6038fbf03 --- /dev/null +++ b/languages/ruby/bitwarden_sdk_secrets/sig/bitwarden_lib.rbs @@ -0,0 +1,7 @@ +module BitwardenSDKSecrets + module BitwardenLib + extend FFI::Library + + def self.mac_with_intel?: () -> untyped + end +end diff --git a/languages/ruby/bitwarden_sdk_secrets/sig/bitwarden_settings.rbs b/languages/ruby/bitwarden_sdk_secrets/sig/bitwarden_settings.rbs deleted file mode 100644 index 154ee16e5..000000000 --- a/languages/ruby/bitwarden_sdk_secrets/sig/bitwarden_settings.rbs +++ /dev/null @@ -1,8 +0,0 @@ -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_secrets/sig/command_runner.rbs b/languages/ruby/bitwarden_sdk_secrets/sig/command_runner.rbs index 7a7a17dd9..483679076 100644 --- a/languages/ruby/bitwarden_sdk_secrets/sig/command_runner.rbs +++ b/languages/ruby/bitwarden_sdk_secrets/sig/command_runner.rbs @@ -1,4 +1,12 @@ -class CommandRunner - @bitwarden_sdk: Module - def run: -> String +module BitwardenSDKSecrets + class CommandRunner + @bitwarden_sdk: untyped + + @handle: untyped + + def initialize: (untyped bitwarden_sdk, untyped handle) -> void + + # @param [Dry-Struct] cmd + def run: (untyped cmd) -> untyped + end end diff --git a/languages/ruby/bitwarden_sdk_secrets/sig/projects.rbs b/languages/ruby/bitwarden_sdk_secrets/sig/projects.rbs new file mode 100644 index 000000000..e68df3d59 --- /dev/null +++ b/languages/ruby/bitwarden_sdk_secrets/sig/projects.rbs @@ -0,0 +1,25 @@ +module BitwardenSDKSecrets + class ProjectsClient + @command_runner: untyped + + def initialize: (untyped command_runner) -> void + + def create: (untyped organization_id, untyped project_name) -> untyped + + def get: (untyped project_id) -> untyped + + def list: (untyped organization_id) -> untyped + + def update: (untyped organization_id, untyped id, untyped project_put_request_name) -> untyped + + def delete: (untyped ids) -> untyped + + private + + def error_response: (untyped response) -> untyped + + def create_command: (untyped commands) -> untyped + + def parse_response: (untyped command) -> untyped + end +end diff --git a/languages/ruby/bitwarden_sdk_secrets/sig/projects_client.rbs b/languages/ruby/bitwarden_sdk_secrets/sig/projects_client.rbs deleted file mode 100644 index 00c9e578d..000000000 --- a/languages/ruby/bitwarden_sdk_secrets/sig/projects_client.rbs +++ /dev/null @@ -1,17 +0,0 @@ -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_secrets/sig/sdk.rbs b/languages/ruby/bitwarden_sdk_secrets/sig/sdk.rbs deleted file mode 100644 index 260fa1420..000000000 --- a/languages/ruby/bitwarden_sdk_secrets/sig/sdk.rbs +++ /dev/null @@ -1,3 +0,0 @@ -module BitwardenSDK - VERSION: String -end diff --git a/languages/ruby/bitwarden_sdk_secrets/sig/secrets.rbs b/languages/ruby/bitwarden_sdk_secrets/sig/secrets.rbs new file mode 100644 index 000000000..8590fd82b --- /dev/null +++ b/languages/ruby/bitwarden_sdk_secrets/sig/secrets.rbs @@ -0,0 +1,29 @@ +module BitwardenSDKSecrets + class SecretsClient + @command_runner: untyped + + def initialize: (untyped command_runner) -> void + + def get: (untyped id) -> untyped + + def get_by_ids: (untyped ids) -> untyped + + def sync: (untyped organization_id, untyped last_synced_date) -> untyped + + def create: (untyped organization_id, untyped key, untyped value, untyped note, untyped project_ids) -> untyped + + def list: (untyped organization_id) -> untyped + + def update: (untyped organization_id, untyped id, untyped key, untyped value, untyped note, untyped project_ids) -> untyped + + def delete: (untyped ids) -> untyped + + private + + def error_response: (untyped response) -> (untyped | nil | untyped) + + def create_command: (untyped commands) -> untyped + + def run_command: (untyped command) -> untyped + end +end diff --git a/languages/ruby/bitwarden_sdk_secrets/sig/secrets_client.rbs b/languages/ruby/bitwarden_sdk_secrets/sig/secrets_client.rbs deleted file mode 100644 index ccebcecd8..000000000 --- a/languages/ruby/bitwarden_sdk_secrets/sig/secrets_client.rbs +++ /dev/null @@ -1,18 +0,0 @@ -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/bitwarden_sdk_secrets/sig/version.rbs b/languages/ruby/bitwarden_sdk_secrets/sig/version.rbs new file mode 100644 index 000000000..869b679db --- /dev/null +++ b/languages/ruby/bitwarden_sdk_secrets/sig/version.rbs @@ -0,0 +1,3 @@ +module BitwardenSDKSecrets + VERSION: "0.2.0" +end diff --git a/languages/ruby/examples/example.rb b/languages/ruby/examples/example.rb index d1c7ce455..29a686708 100644 --- a/languages/ruby/examples/example.rb +++ b/languages/ruby/examples/example.rb @@ -3,7 +3,7 @@ token = ENV['ACCESS_TOKEN'] organization_id = ENV['ORGANIZATION_ID'] -state_path = ENV['STATE_PATH'] +state_file = ENV['STATE_FILE'] # Configuring the URLS is optional, set them to nil to use the default values api_url = ENV['API_URL'] @@ -12,58 +12,66 @@ bitwarden_settings = BitwardenSDKSecrets::BitwardenSettings.new(api_url, identity_url) bw_client = BitwardenSDKSecrets::BitwardenClient.new(bitwarden_settings) -response = bw_client.access_token_login(token, state_path) +response = bw_client.auth.login_access_token(token, state_file) puts response # CREATE project project_name = 'Test project 1' -response = bw_client.project_client.create_project(project_name, organization_id) +response = bw_client.projects.create(organization_id, project_name) puts response project_id = response['id'] # GET project -response = bw_client.project_client.get(project_id) +response = bw_client.projects.get(project_id) puts response # LIST projects -response = bw_client.project_client.list_projects(organization_id) +response = bw_client.projects.list(organization_id) puts response # UPDATE projects name = 'Updated test project 1' -response = bw_client.project_client.update_project(project_id, name, organization_id) +response = bw_client.projects.update(organization_id, project_id, name) 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) +response = bw_client.secrets.create(organization_id, key, value, note, [project_id]) puts response secret_id = response['id'] # GET secret -response = bw_client.secrets_client.get(secret_id) +response = bw_client.secrets.get(secret_id) puts response # GET secret by ids -response = bw_client.secrets_client.get_by_ids([secret_id]) +response = bw_client.secrets.get_by_ids([secret_id]) puts response # LIST secrets -response = bw_client.secrets_client.list(organization_id) +response = bw_client.secrets.list(organization_id) +puts response + +# SYNC secrets +response = bw_client.secrets.sync(organization_id, nil) +last_synced_date = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%S.%6NZ') +puts response + +response = bw_client.secrets.sync(organization_id, last_synced_date) 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) +response = bw_client.secrets.update(organization_id, secret_id, key, value, note, [project_id]) puts response # DELETE secret -response = bw_client.secrets_client.delete_secret([secret_id]) +response = bw_client.secrets.delete([secret_id]) puts response # DELETE project -response = bw_client.project_client.delete_projects([project_id]) +response = bw_client.projects.delete([project_id]) puts response diff --git a/languages/ruby/gen_ruby_typedefs.sh b/languages/ruby/gen_ruby_typedefs.sh new file mode 100755 index 000000000..acd2bcc3c --- /dev/null +++ b/languages/ruby/gen_ruby_typedefs.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# shellcheck disable=SC3044,SC3020 + +# bail if rbs is not installed +if ! command -v rbs &>/dev/null; then + echo "rbs could not be found" + exit +fi + +# use consistent repository root to avoid relative paths +REPO_ROOT="$(git rev-parse --show-toplevel)" +pushd "$REPO_ROOT"/languages/ruby || exit + +# delete existing typedefs +rm -rf bitwarden_sdk_secrets/sig/* +mkdir -p bitwarden_sdk_secrets/sig + +# generate typedefs +RUBY_LIB_FILES="$(find bitwarden_sdk_secrets/lib -name "*.rb")" + +for file in $RUBY_LIB_FILES; do + rbs prototype rb "$file" >bitwarden_sdk_secrets/sig/"$(basename "$file" .rb).rbs" + rm -f bitwarden_sdk_secrets/sig/schemas.rbs +done + +popd || exit diff --git a/languages/swift/iOS/App/ContentView.swift b/languages/swift/iOS/App/ContentView.swift index 2cda3e68b..251f0c5f6 100644 --- a/languages/swift/iOS/App/ContentView.swift +++ b/languages/swift/iOS/App/ContentView.swift @@ -400,7 +400,7 @@ class Fido2UserInterfaceImpl: Fido2UserInterface { abort() } - func checkUserAndPickCredentialForCreation(options: BitwardenSdk.CheckUserOptions, newCredential: BitwardenSdk.Fido2CredentialNewView) async throws -> BitwardenSdk.CipherViewWrapper { + func checkUserAndPickCredentialForCreation(options: BitwardenSdk.CheckUserOptions, newCredential: BitwardenSdk.Fido2CredentialNewView) async throws -> BitwardenSdk.CheckUserAndPickCredentialForCreationResult { abort() } @@ -414,6 +414,10 @@ class Fido2UserInterfaceImpl: Fido2UserInterface { } class Fido2CredentialStoreImpl: Fido2CredentialStore { + func allCredentials() async throws -> [BitwardenSdk.CipherView] { + abort() + } + func findCredentials(ids: [Data]?, ripId: String) async throws -> [BitwardenSdk.CipherView] { abort() } diff --git a/package-lock.json b/package-lock.json index 42d079262..bdf9b4859 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,20 +9,20 @@ "version": "0.0.0", "license": "SEE LICENSE IN LICENSE", "devDependencies": { - "@openapitools/openapi-generator-cli": "2.13.4", - "handlebars": "^4.7.8", - "prettier": "3.2.5", - "quicktype-core": "23.0.81", - "rimraf": "5.0.7", + "@openapitools/openapi-generator-cli": "2.14.0", + "prettier": "3.3.3", + "quicktype-core": "23.0.170", + "rimraf": "6.0.1", "ts-node": "10.9.2", - "typescript": "5.3.3" + "typescript": "5.5.4" } }, "node_modules/@babel/runtime": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz", - "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz", + "integrity": "sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==", "dev": true, + "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -35,6 +35,7 @@ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -43,16 +44,18 @@ } }, "node_modules/@glideapps/ts-necessities": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@glideapps/ts-necessities/-/ts-necessities-2.1.3.tgz", - "integrity": "sha512-q9U8v/n9qbkd2zDYjuX3qtlbl+OIyI9zF+zQhZjfYOE9VMDH7tfcUSJ9p0lXoY3lxmGFne09yi4iiNeQUwV7AA==", - "dev": true + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@glideapps/ts-necessities/-/ts-necessities-2.2.3.tgz", + "integrity": "sha512-gXi0awOZLHk3TbW55GZLCPP6O+y/b5X1pBXKBVckFONSwF1z1E5ND2BGJsghQFah+pW7pkkyFb2VhUQI2qhL5w==", + "dev": true, + "license": "MIT" }, "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, + "license": "ISC", "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -66,10 +69,11 @@ } }, "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -82,6 +86,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -93,13 +98,15 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@isaacs/cliui/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, + "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -117,6 +124,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -132,6 +140,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -149,21 +158,24 @@ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } }, "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 + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -174,15 +186,17 @@ "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/@nestjs/axios": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-3.0.2.tgz", - "integrity": "sha512-Z6GuOUdNQjP7FX+OuV2Ybyamse+/e0BFdTWBX5JxpBDKA+YkdLynDgG6HTF04zy6e9zPa19UX0WA2VDoehwhXQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-3.0.3.tgz", + "integrity": "sha512-h6TCn3yJwD6OKqqqfmtRS5Zo4E46Ip2n+gK1sqwzNBC+qxQ9xpCu+ODVRFur6V3alHSCSBxb3nNtt73VEdluyA==", "dev": true, + "license": "MIT", "peerDependencies": { "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", "axios": "^1.3.1", @@ -190,13 +204,14 @@ } }, "node_modules/@nestjs/common": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.3.0.tgz", - "integrity": "sha512-DGv34UHsZBxCM3H5QGE2XE/+oLJzz5+714JQjBhjD9VccFlQs3LRxo/epso4l7nJIiNlZkPyIUC8WzfU/5RTsQ==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.4.3.tgz", + "integrity": "sha512-4hbLd3XIJubHSylYd/1WSi4VQvG68KM/ECYpMDqA3k3J1/T17SAg40sDoq3ZoO5OZgU0xuNyjuISdOTjs11qVg==", "dev": true, + "license": "MIT", "dependencies": { "iterare": "1.2.1", - "tslib": "2.6.2", + "tslib": "2.7.0", "uid": "2.0.2" }, "funding": { @@ -206,7 +221,7 @@ "peerDependencies": { "class-transformer": "*", "class-validator": "*", - "reflect-metadata": "^0.1.12", + "reflect-metadata": "^0.1.12 || ^0.2.0", "rxjs": "^7.1.0" }, "peerDependenciesMeta": { @@ -219,17 +234,18 @@ } }, "node_modules/@nestjs/core": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.0.tgz", - "integrity": "sha512-N06P5ncknW/Pm8bj964WvLIZn2gNhHliCBoAO1LeBvNImYkecqKcrmLbY49Fa1rmMfEM3MuBHeDys3edeuYAOA==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.4.3.tgz", + "integrity": "sha512-6OQz+5C8mT8yRtfvE5pPCq+p6w5jDot+oQku1KzQ24ABn+lay1KGuJwcKZhdVNuselx+8xhdMxknZTA8wrGLIg==", "dev": true, "hasInstallScript": true, + "license": "MIT", "dependencies": { "@nuxtjs/opencollective": "0.3.2", "fast-safe-stringify": "2.1.1", "iterare": "1.2.1", - "path-to-regexp": "3.2.0", - "tslib": "2.6.2", + "path-to-regexp": "3.3.0", + "tslib": "2.7.0", "uid": "2.0.2" }, "funding": { @@ -241,7 +257,7 @@ "@nestjs/microservices": "^10.0.0", "@nestjs/platform-express": "^10.0.0", "@nestjs/websockets": "^10.0.0", - "reflect-metadata": "^0.1.12", + "reflect-metadata": "^0.1.12 || ^0.2.0", "rxjs": "^7.1.0" }, "peerDependenciesMeta": { @@ -261,6 +277,7 @@ "resolved": "https://registry.npmjs.org/@nuxtjs/opencollective/-/opencollective-0.3.2.tgz", "integrity": "sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.1.0", "consola": "^2.15.0", @@ -275,97 +292,88 @@ } }, "node_modules/@openapitools/openapi-generator-cli": { - "version": "2.13.4", - "resolved": "https://registry.npmjs.org/@openapitools/openapi-generator-cli/-/openapi-generator-cli-2.13.4.tgz", - "integrity": "sha512-4JKyrk55ohQK2FcuZbPdNvxdyXD14jjOIvE8hYjJ+E1cHbRbfXQXbYnjTODFE52Gx8eAxz8C9icuhDYDLn7nww==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@openapitools/openapi-generator-cli/-/openapi-generator-cli-2.14.0.tgz", + "integrity": "sha512-k+ioQLtXLXgNbhQbp1UOxtaUnnYTWwAPev88hP5qauFA+eq4NyeQGNojknFssXg2x0VT0TUGmU3PZ2DiQ70IVg==", "dev": true, "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "@nestjs/axios": "3.0.2", - "@nestjs/common": "10.3.0", - "@nestjs/core": "10.3.0", + "@nestjs/axios": "3.0.3", + "@nestjs/common": "10.4.3", + "@nestjs/core": "10.4.3", "@nuxtjs/opencollective": "0.3.2", - "axios": "1.6.8", + "axios": "1.7.7", "chalk": "4.1.2", "commander": "8.3.0", "compare-versions": "4.1.4", "concurrently": "6.5.1", "console.table": "0.10.0", "fs-extra": "10.1.0", - "glob": "7.2.3", - "https-proxy-agent": "7.0.4", + "glob": "9.3.5", + "https-proxy-agent": "7.0.5", "inquirer": "8.2.6", "lodash": "4.17.21", "reflect-metadata": "0.1.13", "rxjs": "7.8.1", - "tslib": "2.6.2" + "tslib": "2.7.0" }, "bin": { "openapi-generator-cli": "main.js" }, "engines": { - "node": ">=10.0.0" + "node": ">=16" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/openapi_generator" } }, - "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/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { - "version": "20.12.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.11.tgz", - "integrity": "sha512-vDg9PZ/zi+Nqp6boSOT7plNuthRugEKixDv5sFTIpkE89MmNtEArAShI4mxuX2+UrLEe9pxC1vm2cjm9YlWbJw==", + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.19.2" } }, - "node_modules/@types/urijs": { - "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": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", "dev": true, + "license": "MIT", "dependencies": { "event-target-shim": "^5.0.0" }, @@ -374,10 +382,11 @@ } }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -386,10 +395,14 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, "engines": { "node": ">=0.4.0" } @@ -399,6 +412,7 @@ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^4.3.4" }, @@ -411,6 +425,7 @@ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, + "license": "MIT", "dependencies": { "type-fest": "^0.21.3" }, @@ -426,6 +441,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -435,6 +451,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -449,19 +466,22 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/axios": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", - "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", "dev": true, + "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -472,7 +492,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/base64-js": { "version": "1.5.1", @@ -492,13 +513,15 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "dev": true, + "license": "MIT", "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -506,20 +529,21 @@ } }, "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==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/browser-or-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-2.1.1.tgz", - "integrity": "sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==", - "dev": true + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-3.0.0.tgz", + "integrity": "sha512-iczIdVJzGEYhP5DqQxYM9Hh7Ztpqqi+CXZpSmX8ALFs9ecXkQIeqRyM6TfxEfMVpwhl3dSuDvxdzzo9sUOIVBQ==", + "dev": true, + "license": "MIT" }, "node_modules/buffer": { "version": "5.7.1", @@ -540,6 +564,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -550,6 +575,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -565,13 +591,15 @@ "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dev": true, + "license": "MIT", "dependencies": { "restore-cursor": "^3.1.0" }, @@ -584,6 +612,7 @@ "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" }, @@ -596,6 +625,7 @@ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", "dev": true, + "license": "ISC", "engines": { "node": ">= 10" } @@ -605,6 +635,7 @@ "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, + "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -616,6 +647,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -633,6 +665,7 @@ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8" } @@ -641,13 +674,15 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/collection-utils/-/collection-utils-1.0.1.tgz", "integrity": "sha512-LA2YTIlR7biSpXkKYwwuzGjwL5rjWEZVOSnvdUc7gObvWe4WkjxOpfrdhoP7Hs09YWDVfg0Mal9BpAqLfVEzQg==", - "dev": true + "dev": true, + "license": "Apache-2.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, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -659,13 +694,15 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, + "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -678,6 +715,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "dev": true, + "license": "MIT", "engines": { "node": ">= 12" } @@ -686,19 +724,15 @@ "version": "4.1.4", "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-4.1.4.tgz", "integrity": "sha512-FemMreK9xNyL8gQevsdRMrvO4lFCkQP7qbuktn1q8ndcNk1+0mz7lgE7b/sNvbhVgY4w6tMN1FDp6aADjqw2rw==", - "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 + "dev": true, + "license": "MIT" }, "node_modules/concurrently": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-6.5.1.tgz", "integrity": "sha512-FlSwNpGjWQfRwPLXvJ/OgysbBxPkWpiVjy1042b0U7on7S7qwwMIILRj7WTN1mTgqa582bG6NFuScOoh6Zgdag==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.1.0", "date-fns": "^2.16.1", @@ -721,6 +755,7 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^1.9.0" }, @@ -733,6 +768,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -747,19 +783,22 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "dev": true, + "license": "0BSD" }, "node_modules/consola": { "version": "2.15.3", "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/console.table": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/console.table/-/console.table-0.10.0.tgz", "integrity": "sha512-dPyZofqggxuvSf7WXvNjuRfnsOk1YazkVP8FdxH4tcH2c37wc79/Yl6Bhr7Lsu00KMgy2ql/qCMuNu8xctZM8g==", "dev": true, + "license": "MIT", "dependencies": { "easy-table": "1.1.0" }, @@ -771,13 +810,15 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/cross-fetch": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", "dev": true, + "license": "MIT", "dependencies": { "node-fetch": "^2.6.12" } @@ -787,6 +828,7 @@ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -801,6 +843,7 @@ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/runtime": "^7.21.0" }, @@ -813,12 +856,13 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -834,6 +878,7 @@ "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", "dev": true, + "license": "MIT", "dependencies": { "clone": "^1.0.2" }, @@ -846,6 +891,7 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -855,6 +901,7 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } @@ -863,13 +910,15 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/easy-table": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.1.0.tgz", "integrity": "sha512-oq33hWOSSnl2Hoh00tZWaIPi1ievrD9aFG82/IgjlycAnW9hHx5PkJiXpxPsgEE+H7BsbVQXFVFST8TEXS6/pA==", "dev": true, + "license": "MIT", "optionalDependencies": { "wcwidth": ">=1.0.1" } @@ -878,13 +927,15 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -894,6 +945,7 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.0" } @@ -903,6 +955,7 @@ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -912,6 +965,7 @@ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.x" } @@ -921,6 +975,7 @@ "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", "dev": true, + "license": "MIT", "dependencies": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", @@ -934,13 +989,15 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", "dev": true, + "license": "MIT", "dependencies": { "escape-string-regexp": "^1.0.5" }, @@ -952,9 +1009,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", "dev": true, "funding": [ { @@ -962,6 +1019,7 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -972,10 +1030,11 @@ } }, "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==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "dev": true, + "license": "ISC", "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" @@ -992,6 +1051,7 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, + "license": "ISC", "engines": { "node": ">=14" }, @@ -1000,10 +1060,11 @@ } }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", "dev": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -1018,6 +1079,7 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -1031,78 +1093,105 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, + "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } }, "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", + "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", "dev": true, + "license": "ISC", "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" + "minimatch": "^8.0.2", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "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/glob/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "node_modules/glob/node_modules/minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/glob/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=0.4.7" + "node": ">=16 || 14 >=14.18" }, - "optionalDependencies": { - "uglify-js": "^3.1.4" + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" } }, + "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, + "license": "ISC" + }, "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, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/https-proxy-agent": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", - "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, + "license": "MIT", "dependencies": { "agent-base": "^7.0.2", "debug": "4" @@ -1116,6 +1205,7 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -1141,29 +1231,22 @@ "type": "consulting", "url": "https://feross.org/support" } - ] - }, - "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" - } + ], + "license": "BSD-3-Clause" }, "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 + "dev": true, + "license": "ISC" }, "node_modules/inquirer": { "version": "8.2.6", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", "dev": true, + "license": "MIT", "dependencies": { "ansi-escapes": "^4.2.1", "chalk": "^4.1.1", @@ -1190,6 +1273,7 @@ "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, + "license": "MIT", "engines": { "node": ">=8" } @@ -1199,6 +1283,7 @@ "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -1208,6 +1293,7 @@ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -1219,52 +1305,55 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/iterare": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz", "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==", "dev": true, + "license": "ISC", "engines": { "node": ">=6" } }, "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", + "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, "engines": { - "node": ">=14" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" } }, "node_modules/js-base64": { "version": "3.7.7", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, + "license": "MIT", "dependencies": { "universalify": "^2.0.0" }, @@ -1276,13 +1365,15 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" @@ -1295,25 +1386,28 @@ } }, "node_modules/lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz", + "integrity": "sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==", "dev": true, + "license": "ISC", "engines": { - "node": "14 || >=16.14" + "node": "20 || >=22" } }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "dev": true, + "license": "ISC" }, "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, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -1323,6 +1417,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -1335,63 +1430,57 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", + "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, + "node": ">=16 || 14 >=14.17" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minipass": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz", - "integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, + "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } }, "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 + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" }, "node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, - "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 + "dev": true, + "license": "ISC" }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dev": true, + "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -1407,20 +1496,12 @@ } } }, - "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, + "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" }, @@ -1436,6 +1517,7 @@ "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", "dev": true, + "license": "MIT", "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", @@ -1459,70 +1541,75 @@ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true - }, - "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" - } + "license": "(MIT AND Zlib)" }, "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, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-to-regexp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.2.0.tgz", - "integrity": "sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA==", - "dev": true + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "dev": true, + "license": "MIT" }, "node_modules/pluralize": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/prettier": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, + "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, @@ -1538,6 +1625,7 @@ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6.0" } @@ -1546,29 +1634,30 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/quicktype-core": { - "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==", + "version": "23.0.170", + "resolved": "https://registry.npmjs.org/quicktype-core/-/quicktype-core-23.0.170.tgz", + "integrity": "sha512-ZsjveG0yJUIijUx4yQshzyQ5EAXKbFSBTQJHnJ+KoSZVxcS+m3GcmDpzrdUIRYMhgLaF11ZGvLSYi5U0xcwemw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@glideapps/ts-necessities": "2.1.3", - "@types/urijs": "^1.19.19", - "browser-or-node": "^2.1.1", + "@glideapps/ts-necessities": "2.2.3", + "browser-or-node": "^3.0.0", "collection-utils": "^1.0.1", "cross-fetch": "^4.0.0", "is-url": "^1.2.4", - "js-base64": "^3.7.5", + "js-base64": "^3.7.7", "lodash": "^4.17.21", "pako": "^1.0.6", "pluralize": "^8.0.0", - "readable-stream": "4.4.2", + "readable-stream": "4.5.2", "unicode-properties": "^1.4.1", "urijs": "^1.19.1", "wordwrap": "^1.0.0", - "yaml": "^2.3.1" + "yaml": "^2.4.1" } }, "node_modules/quicktype-core/node_modules/buffer": { @@ -1590,16 +1679,18 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "node_modules/quicktype-core/node_modules/readable-stream": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz", - "integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, + "license": "MIT", "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", @@ -1616,6 +1707,7 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -1629,19 +1721,22 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", - "dev": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -1651,6 +1746,7 @@ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dev": true, + "license": "MIT", "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" @@ -1660,64 +1756,60 @@ } }, "node_modules/rimraf": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.7.tgz", - "integrity": "sha512-nV6YcJo5wbLW77m+8KjH8aB/7/rxQy9SZ0HY5shnwULfS+9nmTtVXAJET5NdZmCzA4fPI/Hm1wo/Po/4mopOdg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", "dev": true, + "license": "ISC", "dependencies": { - "glob": "^10.3.7" + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" }, "bin": { "rimraf": "dist/esm/bin.mjs" }, "engines": { - "node": ">=14.18" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rimraf/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/rimraf/node_modules/glob": { - "version": "10.3.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.15.tgz", - "integrity": "sha512-0c6RlJt1TICLyvJYIApxb8GsXoai0KUP7AxKKAtsYXdgJR1mGEUa7DgwShbdk1nly0PYoZj01xd4hzbq3fsjpw==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", "dev": true, + "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.6", - "minimatch": "^9.0.1", - "minipass": "^7.0.4", - "path-scurry": "^1.11.0" + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/rimraf/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -1728,6 +1820,7 @@ "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -1737,6 +1830,7 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" } @@ -1759,19 +1853,22 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "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 + "dev": true, + "license": "MIT" }, "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, + "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" }, @@ -1784,6 +1881,7 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -1792,21 +1890,13 @@ "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/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" - } + "license": "ISC" }, "node_modules/spawn-command": { - "version": "0.0.2-1", - "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", - "integrity": "sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==", + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", + "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", "dev": true }, "node_modules/string_decoder": { @@ -1814,6 +1904,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, + "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" } @@ -1823,6 +1914,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -1838,6 +1930,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -1852,6 +1945,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -1865,6 +1959,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -1877,6 +1972,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -1888,19 +1984,22 @@ "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/tiny-inflate": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, + "license": "MIT", "dependencies": { "os-tmpdir": "~1.0.2" }, @@ -1912,13 +2011,15 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true, + "license": "MIT", "bin": { "tree-kill": "cli.js" } @@ -1928,6 +2029,7 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, + "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -1967,16 +2069,18 @@ } }, "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 + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true, + "license": "0BSD" }, "node_modules/type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -1985,10 +2089,11 @@ } }, "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -1997,24 +2102,12 @@ "node": ">=14.17" } }, - "node_modules/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "dev": true, - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/uid": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==", "dev": true, + "license": "MIT", "dependencies": { "@lukeed/csprng": "^1.0.0" }, @@ -2023,10 +2116,11 @@ } }, "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==", + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/unicode-properties": { @@ -2034,6 +2128,7 @@ "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", "dev": true, + "license": "MIT", "dependencies": { "base64-js": "^1.3.0", "unicode-trie": "^2.0.0" @@ -2044,6 +2139,7 @@ "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", "dev": true, + "license": "MIT", "dependencies": { "pako": "^0.2.5", "tiny-inflate": "^1.0.0" @@ -2053,13 +2149,15 @@ "version": "0.2.9", "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 10.0.0" } @@ -2068,25 +2166,29 @@ "version": "1.19.11", "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.11.tgz", "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==", - "dev": true + "dev": true, + "license": "MIT" }, "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 + "dev": true, + "license": "MIT" }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", "dev": true, + "license": "MIT", "dependencies": { "defaults": "^1.0.3" } @@ -2095,13 +2197,15 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true + "dev": true, + "license": "BSD-2-Clause" }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "dev": true, + "license": "MIT", "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -2112,6 +2216,7 @@ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -2126,13 +2231,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -2148,6 +2255,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -2160,26 +2268,22 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "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/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } }, "node_modules/yaml": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz", - "integrity": "sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", + "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==", "dev": true, + "license": "ISC", "bin": { "yaml": "bin.mjs" }, @@ -2192,6 +2296,7 @@ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, + "license": "MIT", "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -2210,6 +2315,7 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } @@ -2219,6 +2325,7 @@ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } diff --git a/package.json b/package.json index dc256ea5d..fada308d2 100644 --- a/package.json +++ b/package.json @@ -20,12 +20,11 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "devDependencies": { - "@openapitools/openapi-generator-cli": "2.13.4", - "handlebars": "^4.7.8", - "prettier": "3.2.5", - "quicktype-core": "23.0.81", - "rimraf": "5.0.7", + "@openapitools/openapi-generator-cli": "2.14.0", + "prettier": "3.3.3", + "quicktype-core": "23.0.170", + "rimraf": "6.0.1", "ts-node": "10.9.2", - "typescript": "5.3.3" + "typescript": "5.5.4" } } diff --git a/support/docs/docs.ts b/support/docs/docs.ts deleted file mode 100644 index 067ff0827..000000000 --- a/support/docs/docs.ts +++ /dev/null @@ -1,139 +0,0 @@ -// Quick script that parses the rustdoc json output and generates a basic markdown documentation. -// -// Do note that this script follows no best practices and will not handle anything many edge cases. - -import fs from "fs"; -import path from "path"; -import Handlebars from "handlebars"; - -import { Input, InputType } from "./rustdoc"; - -const doc = JSON.parse(fs.readFileSync("./target/doc/bitwarden_uniffi.json", "utf8")); -const command = JSON.parse( - fs.readFileSync("./support/schemas/bitwarden_uniffi/DocRef.json", "utf8"), -); - -const template = Handlebars.compile( - fs.readFileSync(path.resolve(__dirname, "template.hbs"), "utf8"), -); - -// Modify this to include more root elements -const rootElements = [ - "Client", - "ClientAuth", - "ClientAttachments", - "ClientCiphers", - "ClientCollections", - "ClientCrypto", - "ClientExporters", - "ClientFolders", - "ClientGenerators", - "ClientPasswordHistory", - "ClientPlatform", - "ClientSends", - "ClientVault", -]; - -const localIndexArray = Object.values(doc.index).filter((entry: any) => entry.crate_id == 0); -const localIndex = localIndexArray.reduce((map: any, obj: any) => { - map[obj.id] = obj; - return map; -}, {}) as Record; - -let usedDefinitions: any[] = []; - -const out = rootElements.map((rootElement) => { - const root: any = localIndexArray.find((entry: any) => entry.name == rootElement); - const impls = root.inner.struct.impls; - - const elements = impls - .flatMap((e: any) => localIndex[e]) - .flatMap((e: any) => e.inner.impl.items) - .map((e: any) => localIndex[e]) - .filter((e: any) => e?.docs != null); - - return { - name: rootElement, - elements: elements.map((e: any) => { - return { - name: e.name, - docs: e.docs, - args: e.inner.function.decl.inputs.map((e: any) => map_input(e)), - output: map_type(e.inner.function.decl.output), - }; - }), - }; -}); - -function stripDef(str: string) { - return str.replace(/#\/definitions\//g, ""); -} - -Handlebars.registerHelper("stripDef", (str: string) => { - return stripDef(str); -}); - -// Add references -for (let i = 0; i < usedDefinitions.length; i++) { - const key = usedDefinitions[i]; - const cmd = command.definitions[key]; - if (cmd == null) { - continue; - } - - Object.entries(cmd.properties ?? {}).forEach((prop: any) => { - prop[1].allOf?.forEach((e: any) => { - usedDefinitions.push(stripDef(e["$ref"] as string)); - }); - }); -} - -const filteredDefinitions = [...new Set(usedDefinitions)] - .sort() - .map((key) => [key, command.definitions[key]]) - .filter((e) => e[1] != null) - .reduce((obj, cur) => ({ ...obj, [cur[0]]: cur[1] }), {}); - -console.log(template({ sections: out, commands: filteredDefinitions })); - -/// -/// Implementation details below. -/// - -// Format -function map_input(input: Input) { - return { - name: input[0], - type: map_type(input[1]), - }; -} - -function map_type(t: InputType) { - const args = t.resolved_path?.args; - const name = t.resolved_path?.name; - - let out = ""; - - if (name) { - usedDefinitions.push(name); - - if (command.definitions[name] != null) { - out += `[${name}](#${name.toLowerCase()})`; - } else { - out += name; - } - } - - if (args != null && args.angle_bracketed.args.length > 0) { - out += "<"; - out += args.angle_bracketed.args.map((t: any) => { - if (t.type.generic) { - return t.type.generic; - } else if (t.type.resolved_path) { - return t.type.resolved_path.name; - } - }); - out += ">"; - } - return out; -} diff --git a/support/docs/rustdoc.ts b/support/docs/rustdoc.ts deleted file mode 100644 index 2622b711e..000000000 --- a/support/docs/rustdoc.ts +++ /dev/null @@ -1,12 +0,0 @@ -export type Input = [string, InputType]; - -export type InputType = { - resolved_path?: { - name: string; - args: { - angle_bracketed: { - args: any[]; - }; - }; - }; -}; diff --git a/support/docs/template.hbs b/support/docs/template.hbs deleted file mode 100644 index bd78d138a..000000000 --- a/support/docs/template.hbs +++ /dev/null @@ -1,91 +0,0 @@ -# Bitwarden Mobile SDK - -Auto generated documentation for the Bitwarden Mobile SDK. For more information please refer to -the rust crates `bitwarden` and `bitwarden-uniffi`. For code samples check the -`languages/kotlin/app` and `languages/swift/app` directories. - -{{#each sections}} - -## {{name}} - -{{#each elements}} -### `{{name}}` -{{docs}} - -**Arguments**: -{{#each args}} -- {{name}}: {{{type}}} -{{/each}} - -**Output**: {{{output}}} - -{{/each}} -{{/each}} - -# References - -References are generated from the JSON schemas and should mostly match the kotlin and swift -implementations. - -{{#each commands}} - -## `{{@key}}` - -{{#if oneOf}} - - - - - - -{{#each oneOf}} -{{#each properties}} - - - - - -{{#if properties}} - - - -{{/if}} -{{/each}} -{{/each}} -
KeyTypeDescription
{{@key}}{{type}}
- - - - - - - {{#each properties}} - - - - - - {{/each}} -
KeyTypeDescription
{{@key}}{{type}}{{{description}}}
-
- -{{/if}} - -{{#unless oneOf}} - - - - - - -{{#each properties}} - - - - - -{{/each}} -
KeyTypeDescription
{{@key}}{{type}}{{description}}
-{{/unless}} - -{{/each}} diff --git a/support/scripts/schemas.ts b/support/scripts/schemas.ts index 757878334..873a9de7d 100644 --- a/support/scripts/schemas.ts +++ b/support/scripts/schemas.ts @@ -7,18 +7,6 @@ import { } from "quicktype-core"; import fs from "fs"; -import path from "path"; - -async function* walk(dir: string): AsyncIterable { - for await (const d of await fs.promises.opendir(dir)) { - const entry = path.join(dir, d.name); - if (d.isDirectory()) { - yield* walk(entry); - } else if (d.isFile()) { - yield entry; - } - } -} async function main() { const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore()); @@ -95,7 +83,7 @@ async function main() { lang: "go", rendererOptions: { package: "sdk", - "just-types-and-package": true, + "omit-empty": true, }, }); @@ -117,9 +105,30 @@ async function main() { java.forEach((file, path) => { writeToFile(javaDir + path, file.lines); }); + + const php = await quicktype({ + inputData, + lang: "php", + inferUuids: false, + inferDateTimes: false, + rendererOptions: { + "acronym-style": "camel", + "with-get": false, + }, + }); + + const phpDir = "./languages/php/src/Schemas/"; + if (!fs.existsSync(phpDir)) { + fs.mkdirSync(phpDir); + } + + php.lines.splice(1, 0, "namespace Bitwarden\\Sdk\\Schemas;", "use stdClass;", "use Exception;"); + + writeToFile("./languages/php/src/Schemas/Schemas.php", php.lines); } main(); + function writeToFile(filename: string, lines: string[]) { const output = fs.createWriteStream(filename); lines.forEach((line) => {